diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module b/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module index ef4badcb..73934cef 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module +++ b/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module @@ -33,7 +33,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { return array( 'title' => __('Repeater', __FILE__), // Module Title 'summary' => __('Maintains a collection of fields that are repeated for any number of times.', __FILE__), // Module Summary - 'version' => 105, + 'version' => 106, 'autoload' => true, 'installs' => 'InputfieldRepeater' ); @@ -496,7 +496,8 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { $inputfield = $this->wire('modules')->get($this->getInputfieldClass()); $inputfield->set('page', $page); $inputfield->set('field', $field); - $inputfield->set('repeaterMaxItems', (int) $field->get('repeaterMaxItems')); + $inputfield->set('repeaterMaxItems', (int) $field->get('repeaterMaxItems')); + $inputfield->set('repeaterMinItems', (int) $field->get('repeaterMinItems')); $inputfield->set('repeaterDepth', (int) $field->get('repeaterDepth')); $inputfield->set('repeaterReadyItems', 0); // ready items deprecated diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css index d0997bd3..24f2e7a6 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css @@ -6,10 +6,14 @@ white-space: nowrap; overflow: hidden; position: relative; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemLabel { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemLabel, + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls { + display: none; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemLabel { display: inline-block; padding-left: 0.25em; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls { + display: block; padding-right: 0.5em; padding-left: 0.5em; margin-top: 0.5em; @@ -20,19 +24,21 @@ display: block; white-space: nowrap; height: 100%; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterClone, - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterToggle, - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterTrash, - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .toggle-icon { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .InputfieldRepeaterClone, + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .InputfieldRepeaterToggle, + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .InputfieldRepeaterTrash, + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .toggle-icon { cursor: pointer; float: right; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterTrash { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .InputfieldRepeaterTrash { padding-right: 3px; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterToggle { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .InputfieldRepeaterToggle { margin-right: 1em; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterClone { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .InputfieldRepeaterClone { margin-right: 1em; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .toggle-icon { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .InputfieldRepeaterItemControls .pw-icon-disabled { + opacity: 0.3; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader.InputfieldRepeaterHeaderInit .toggle-icon { line-height: 1em; margin-right: 0.5em; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending).InputfieldStateCollapsed > .InputfieldHeader { diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js index 97034e6d..5fc1affb 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js @@ -26,6 +26,13 @@ function InputfieldRepeater($) { * */ var isReno = $('body').hasClass('AdminThemeReno'); + + /** + * Event timer for double clicks + * + */ + var doubleClickTimer = null; + /*** EVENTS ********************************************************************************************/ @@ -55,14 +62,17 @@ function InputfieldRepeater($) { * */ var eventDeleteClick = function(e) { - - var $header = $(this).closest('.InputfieldHeader'); + + var $this = $(this); + var $header = $this.closest('.InputfieldHeader'); var $item = $header.parent(); + if(isActionDisabled($this)) return false; + if($item.hasClass('InputfieldRepeaterNewItem')) { // delete new item (noAjaxAdd mode) var $numAddInput = $item.children('.InputfieldContent').children('.InputfieldRepeaterAddItem').children('input'); - $numAddInput.attr('value', parseInt($numAddInput.attr('value')-1)); // total number of new items to add, minus 1 + $numAddInput.attr('value', parseInt($numAddInput.attr('value') - 1)); // total number of new items to add, minus 1 $item.remove(); } else { @@ -87,20 +97,22 @@ function InputfieldRepeater($) { $header.find('.InputfieldRepeaterItemControls').css('background-color', $header.css('background-color')); } - checkMax($item.closest('.InputfieldRepeater')); + checkMinMax($item.closest('.InputfieldRepeater')); e.stopPropagation(); }; /** * Event handler for when the "delete" link is double clicked * - * @param e - * */ - var eventDeleteDblClick = function(e) { + var eventDeleteDblClick = function() { + + var $this = $(this); var $li = $(this).closest('li'); var undelete = $li.hasClass('InputfieldRepeaterDeletePending'); + if(isActionDisabled($this)) return false; + function selectAll() { $li.parent().children('li').each(function() { var $item = $(this); @@ -123,12 +135,13 @@ function InputfieldRepeater($) { /** * Event handler for when the "clone" repeater item action is clicked * - * @param e * @returns {boolean} * */ - var eventCloneClick = function(e) { - var $item = $(this).closest('.InputfieldRepeaterItem'); + var eventCloneClick = function() { + var $this = $(this); + if(isActionDisabled($this)) return false; + var $item = $this.closest('.InputfieldRepeaterItem'); ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.clone, function() { var itemID = $item.attr('data-page'); var $addLink = $item.closest('.InputfieldRepeater').children('.InputfieldContent') @@ -152,26 +165,29 @@ function InputfieldRepeater($) { var $item = $this.closest('.InputfieldRepeaterItem'); var $input = $item.find('.InputfieldRepeaterPublish'); - if($this.hasClass(toggleOn)) { - $this.removeClass(toggleOn).addClass(toggleOff); - $item.addClass('InputfieldRepeaterUnpublished InputfieldRepeaterOff'); - $input.val('-1'); - } else { - $this.removeClass(toggleOff).addClass(toggleOn); - $item.removeClass('InputfieldRepeaterUnpublished InputfieldRepeaterOff'); - $input.val('1'); - } - + if(doubleClickTimer) clearTimeout(doubleClickTimer); + doubleClickTimer = setTimeout(function() { + if(isActionDisabled($this)) return false; + if($this.hasClass(toggleOn)) { + $this.removeClass(toggleOn).addClass(toggleOff); + $item.addClass('InputfieldRepeaterUnpublished InputfieldRepeaterOff'); + $input.val('-1'); + } else { + $this.removeClass(toggleOff).addClass(toggleOn); + $item.removeClass('InputfieldRepeaterUnpublished InputfieldRepeaterOff'); + $input.val('1'); + } + checkMinMax($item.closest('.InputfieldRepeater')); + }, 250); + e.stopPropagation(); }; /** * Event handler for when a repeater item is about to be opened * - * @param e - * */ - var eventItemOpenReady = function(e) { + var eventItemOpenReady = function() { var $item = $(this); var $loaded = $item.find(".InputfieldRepeaterLoaded"); if(parseInt($loaded.val()) > 0) return; // item already loaded @@ -181,16 +197,18 @@ function InputfieldRepeater($) { /** * Event handler for when a repeater item is opened (primarily focused on ajax loaded items) * - * @param e - * */ - var eventItemOpened = function(e) { + var eventItemOpened = function() { + var $item = $(this); var $loaded = $item.find(".InputfieldRepeaterLoaded"); - + updateState($item); - if(parseInt($loaded.val()) > 0) return; // item already loaded + if(parseInt($loaded.val()) > 0) { + updateAccordion($item); + return; // item already loaded + } $loaded.val('1'); @@ -221,8 +239,10 @@ function InputfieldRepeater($) { initRepeater($(this)); }); + $content.slideDown('fast', function() { $spinner.removeClass('fa-spin fa-spinner').addClass('fa-arrows'); + updateAccordion($item); }); setTimeout(function() { $inputfields.find('.Inputfield').trigger('reloaded', ['InputfieldRepeaterItemEdit']); @@ -234,10 +254,8 @@ function InputfieldRepeater($) { /** * Event handler for when a repeater item is closed * - * @param e - * */ - var eventItemClosed = function(e) { + var eventItemClosed = function() { updateState($(this)); }; @@ -246,11 +264,10 @@ function InputfieldRepeater($) { * * Handles adding repeater items and initializing them * - * @param e * @returns {boolean} * */ - var eventAddLinkClick = function(e) { + var eventAddLinkClick = function() { var $addLink = $(this); var $inputfields = $addLink.parent('p').prev('ul.Inputfields'); var $inputfieldRepeater = $addLink.closest('.InputfieldRepeater'); @@ -278,10 +295,10 @@ function InputfieldRepeater($) { newItemTotal = $newItem.length; if(newItemTotal > 0) { if(newItemTotal > 1) $newItem = $newItem.slice(0, 1); - var $addItem = $newItem.clone(true) + var $addItem = $newItem.clone(true); addRepeaterItem($addItem); $numAddInput.attr('value', newItemTotal); - checkMax($inputfieldRepeater); + checkMinMax($inputfieldRepeater); } return false; } @@ -301,7 +318,7 @@ function InputfieldRepeater($) { } // determine which page IDs we don't accept for new items (because we already have them rendered) - var $unpublishedItems = $inputfields.find('.InputfieldRepeaterUnpublished'); + var $unpublishedItems = $inputfields.find('.InputfieldRepeaterUnpublished:not(.InputfieldRepeaterMinItem)'); if($unpublishedItems.length) { ajaxURL += '&repeater_not='; $unpublishedItems.each(function() { @@ -328,8 +345,9 @@ function InputfieldRepeater($) { scrollTop: $addItem.offset().top }, 500, 'swing'); updateState($addItem); - checkMax($inputfieldRepeater); - $nestedRepeaters = $addItem.find('.InputfieldRepeater'); + checkMinMax($inputfieldRepeater); + updateAccordion($addItem); + var $nestedRepeaters = $addItem.find('.InputfieldRepeater'); if($nestedRepeaters.length) { $nestedRepeaters.each(function() { initRepeater($(this)); @@ -348,9 +366,14 @@ function InputfieldRepeater($) { * */ var eventOpenAllClick = function(e) { + e.stopPropagation(); e.preventDefault(); + if(doubleClickTimer) clearTimeout(doubleClickTimer); + + if($(this).closest('.InputfieldRepeater').hasClass('InputfieldRepeaterAccordion')) return false; + var $repeater = $(this).closest('.InputfieldRepeater'); var $items = $repeater.children('.InputfieldContent').children('.Inputfields').children('.InputfieldRepeaterItem'); if(!$items.length) return false; @@ -372,6 +395,51 @@ function InputfieldRepeater($) { }; /*** GENERAL FUNCTIONS **********************************************************************************/ + + /** + * Returns whether or not the given icon action is disabled + * + * @param $this The '.fa-' icon that represents the action + * @returns {boolean} + * + */ + function isActionDisabled($this) { + if($this.hasClass('pw-icon-disabled')) { + ProcessWire.alert(ProcessWire.config.InputfieldRepeater.labels.disabledMinMax); + return true; + } + return false; + } + + function updateAccordion($item) { + + if(!$item.closest('.InputfieldRepeater').hasClass('InputfieldRepeaterAccordion')) return false; + + var itemID = $item.attr('id'); + var useScroll = false; + var $siblings = $item.parent().children('.InputfieldRepeaterItem'); + var itemHasPassed = false; + var hasOpen = false; + + $siblings.each(function() { + var $sibling = $(this); + if($sibling.attr('id') == itemID) { + itemHasPassed = true; + return; + } + if($sibling.hasClass('InputfieldStateCollapsed')) return; + if(!$sibling.is(':visible')) return; + if(!itemHasPassed) useScroll = true; + $sibling.children('.InputfieldHeader').find('.toggle-icon').trigger('click', [ { duration: 0 }]); + hasOpen = true; + }); + + if(useScroll && hasOpen) { + $('html, body').animate({scrollTop: $item.offset().top - 10}, 0); + } + + return true; + } /** * Given an InputfieldRepeaterItem update the label consistent with any present formatting sting @@ -379,28 +447,32 @@ function InputfieldRepeater($) { * Primarily adjusts item count(s) and allowed for {secondary} text appearance * * @param $item An .InputfieldRepeaterItem - * @param bool doIncrement Specify true to increment the item count value (like for new items) + * @param {boolean} doIncrement Specify true to increment the item count value (like for new items) * */ function adjustItemLabel($item, doIncrement) { - var $label = $item.children('label'); + var $label; + $label = $item.children('.InputfieldHeader').find('.InputfieldRepeaterItemLabel'); + if(typeof $label == "undefined") $label = $item.children('label'); var labelHTML = $label.html(); var _labelHTML = labelHTML; - if(doIncrement && labelHTML.indexOf('#') > -1) { - num = $item.siblings('.InputfieldRepeaterItem:visible').length + 1; - labelHTML = labelHTML.replace(/#[0-9]+/, '#' + num); - } + if(typeof labelHTML != "undefined") { + if(doIncrement && labelHTML.indexOf('#') > -1) { + var num = $item.siblings('.InputfieldRepeaterItem:visible').length + 1; + labelHTML = labelHTML.replace(/#[0-9]+/, '#' + num); + } - if(labelHTML.indexOf('{') > -1) { - // parts of the label wrapped in {brackets} get different appearance - labelHTML = labelHTML.replace(/\{/, ''); - labelHTML = labelHTML.replace(/}/, ''); - } + while(labelHTML.indexOf('}') > -1) { + // parts of the label wrapped in {brackets} get different appearance + labelHTML = labelHTML.replace(/\{/, ''); + labelHTML = labelHTML.replace(/}/, ''); + } - if(labelHTML != _labelHTML) { - $label.html(labelHTML); + if(labelHTML != _labelHTML) { + $label.html(labelHTML); + } } } @@ -557,6 +629,56 @@ function InputfieldRepeater($) { $inputfields.sortable(sortableOptions); } + /** + * Initialize the .InputfieldHeader for .InputfieldRepeaterItem elements + * + * @param $headers The .InputfieldHeader elements + * @param $inputfieldRepeater The parent .InputfieldRepeater + * @param {boolean} renderValueMode Whether or not this is value-only rendering mode + * + */ + function initHeaders($headers, $inputfieldRepeater, renderValueMode) { + + var $clone = $("").css('display', 'block'); + var $delete = $(""); + var $toggle = $(""); + var cfg = ProcessWire.config.InputfieldRepeater; + var allowClone = !$inputfieldRepeater.hasClass('InputfieldRepeaterNoAjaxAdd'); + + if(cfg) { + $toggle.attr('title', cfg.labels.toggle); + $delete.attr('title', cfg.labels.remove); + $clone.attr('title', cfg.labels.clone); + } + + $headers.each(function() { + var $t = $(this); + if($t.hasClass('InputfieldRepeaterHeaderInit')) return; + var icon = 'fa-arrows'; + var $item = $t.parent(); + if($item.hasClass('InputfieldRepeaterNewItem')) { + // noAjaxAdd mode + icon = 'fa-plus'; + $t.addClass('ui-priority-secondary'); + } + $t.addClass('ui-state-default InputfieldRepeaterHeaderInit'); + $t.prepend(""); + if(!renderValueMode) { + var $controls = $(""); + var $toggleControl = $toggle.clone(true) + .addClass($t.parent().hasClass('InputfieldRepeaterOff') ? 'fa-toggle-off' : 'fa-toggle-on'); + var $deleteControl = $delete.clone(true); + var $collapseControl = $t.find('.toggle-icon'); + $controls.prepend($collapseControl); + if(allowClone) $controls.prepend($clone.clone(true)); + $controls.prepend($toggleControl).prepend($deleteControl); + $t.prepend($controls); + $controls.css('background-color', $t.css('background-color')); + } + adjustItemLabel($item, false); + }); + } + /** * Initialize a repeater * @@ -578,58 +700,17 @@ function InputfieldRepeater($) { } if($inputfields.hasClass('InputfieldRepeaterInit')) return; + + var renderValueMode = $inputfields.closest('.InputfieldRenderValueMode').length > 0; $inputfields.addClass('InputfieldRepeaterInit'); - - var renderValueMode = $inputfields.closest('.InputfieldRenderValueMode').length > 0; - var $clone = $("").css('display', 'block'); - var $delete = $(""); - var $toggle = $(""); - var cfg = ProcessWire.config.InputfieldRepeater; - var allowClone = !$inputfieldRepeater.hasClass('InputfieldRepeaterNoAjaxAdd'); - - if(cfg) { - $toggle.attr('title', cfg.labels.toggle); - $delete.attr('title', cfg.labels.remove); - $clone.attr('title', cfg.labels.clone); - } - + $("input.InputfieldRepeaterDelete", $this).parents('.InputfieldCheckbox').hide(); - function initHeaders($headers) { - $headers.each(function() { - var $t = $(this); - if($t.hasClass('InputfieldRepeaterHeaderInit')) return; - var icon = 'fa-arrows'; - var $item = $t.parent(); - if($item.hasClass('InputfieldRepeaterNewItem')) { - // noAjaxAdd mode - icon = 'fa-plus'; - $t.addClass('ui-priority-secondary'); - } - $t.addClass('ui-state-default InputfieldRepeaterHeaderInit'); - $t.prepend("") - if(!renderValueMode) { - //if(allowClone) $t.prepend($clone.clone(true)); - var $controls = $(""); - var $toggleControl = $toggle.clone(true).addClass($t.parent().hasClass('InputfieldRepeaterOff') ? 'fa-toggle-off' : 'fa-toggle-on'); - var $deleteControl = $delete.clone(true); - var $collapseControl = $t.find('.toggle-icon'); - //$collapseControl.addClass('InputfieldRepeaterCollapse').removeClass('toggle-icon'); - $controls.prepend($collapseControl); - if(allowClone) $controls.prepend($clone.clone(true)); - $controls.prepend($toggleControl).prepend($deleteControl); - $t.prepend($controls); - $controls.css('background-color', $t.css('background-color')); - } - adjustItemLabel($item, false); - }); - } - if(isItem) { - initHeaders($this.children('.InputfieldHeader')); + initHeaders($this.children('.InputfieldHeader'), $inputfieldRepeater, renderValueMode); } else { - initHeaders($(".InputfieldRepeaterItem > .InputfieldHeader", $this)); + initHeaders($(".InputfieldRepeaterItem > .InputfieldHeader", $this), $inputfieldRepeater, renderValueMode); } if(renderValueMode) { @@ -662,28 +743,87 @@ function InputfieldRepeater($) { // check for maximum items if($inputfieldRepeater.hasClass('InputfieldRepeaterMax')) { - checkMax($inputfieldRepeater); + checkMinMax($inputfieldRepeater); } } /** * When "max items" setting is used, this toggles whether or not "add" links are visible * - * @todo Make this toggle the clone links as well * @param $inputfieldRepeater .InputfieldRepeater * */ - function checkMax($inputfieldRepeater) { - if(!$inputfieldRepeater.hasClass('InputfieldRepeaterMax')) return; + function checkMinMax($inputfieldRepeater) { + + if(!$inputfieldRepeater.hasClass('InputfieldRepeaterMax') + && !$inputfieldRepeater.hasClass('InputfieldRepeaterMin')) return; + var max = parseInt($inputfieldRepeater.attr('data-max')); - if(max <= 0) return; + var min = parseInt($inputfieldRepeater.attr('data-min')); + + if(max <= 0 && min <= 0) return; + var $content = $inputfieldRepeater.children('.InputfieldContent'); - var num = $content.children('.Inputfields').children('li:not(.InputfieldRepeaterDeletePending)').length; + var num = $content.children('.Inputfields') + .children('li:not(.InputfieldRepeaterDeletePending):not(.InputfieldRepeaterOff):visible').length; var $addItem = $content.children('.InputfieldRepeaterAddItem'); - if(num > max) { - $addItem.hide(); - } else if(!$addItem.is(":visible")) { - $addItem.show(); + var cloneChange = ''; + var trashChange = ''; + + if(max > 0) { + if(num >= max) { + $addItem.hide(); + cloneChange = 'hide'; + } else if(!$addItem.is(":visible")) { + $addItem.show(); + cloneChange = 'show'; + } + } + + if(min > 0) { + if(num <= min) { + trashChange = 'hide'; + $content.addClass('InputfieldRepeaterTrashHidden'); + } else if($content.hasClass('InputfieldRepeaterTrashHidden')) { + $content.removeClass('InputfieldRepeaterTrashHidden'); + trashChange = 'show'; + } + } + + if(cloneChange.length || trashChange.length) { + var $items = $content.children('.Inputfields').children('.InputfieldRepeaterItem'); + if(cloneChange.length) { + // update the visibility of clone actions + $items.each(function() { + var $clone = $(this).children('.InputfieldHeader').find('.InputfieldRepeaterClone'); + if(cloneChange === 'show') { + $clone.removeClass('pw-icon-disabled'); + } else { + $clone.addClass('pw-icon-disabled'); + } + }); + } + if(trashChange.length) { + // update visibility of trash actions + $items.each(function() { + var $header = $(this).children('.InputfieldHeader'); + var $trash = $header.find('.InputfieldRepeaterTrash'); + var $toggle = $header.find('.InputfieldRepeaterToggle.fa-toggle-on'); + if(trashChange === 'show') { + $trash.removeClass('pw-icon-disabled'); + $toggle.removeClass('pw-icon-disabled'); + } else { + $trash.addClass('pw-icon-disabled'); + $toggle.addClass('pw-icon-disabled'); + } + }); + if(trashChange == 'hide') { + $content.children('.Inputfields').children('li.InputfieldRepeaterDeletePending').each(function() { + var $trash = $(this).children('.InputfieldHeader').find('.InputfieldRepeaterTrash'); + $trash.removeClass('pw-icon-disabled'); + }); + } + } } } diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js index a5381ff6..7eadec19 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js @@ -1 +1 @@ -function InputfieldRepeater(i){var b=50;var t=i("body").hasClass("AdminThemeReno");var c=function(w,x){if(typeof x!="undefined"){if(x=="InputfieldRepeaterItemEdit"||x=="InputfieldRepeaterItemAdd"){w.stopPropagation();var v=i(this).find(".InputfieldRepeater");if(v.length){s(v)}return}}s(i(this))};var k=function(A){var y=i(this).closest(".InputfieldHeader");var x=y.parent();if(x.hasClass("InputfieldRepeaterNewItem")){var v=x.children(".InputfieldContent").children(".InputfieldRepeaterAddItem").children("input");v.attr("value",parseInt(v.attr("value")-1));x.remove()}else{var w=x.attr("data-page");var z=x.find("#delete_repeater"+w);if(z.is(":checked")){z.removeAttr("checked");y.removeClass("ui-state-error").addClass("ui-state-default");x.removeClass("InputfieldRepeaterDeletePending")}else{z.attr("checked","checked");y.removeClass("ui-state-default").addClass("ui-state-error");if(!x.hasClass("InputfieldStateCollapsed")){y.find(".toggle-icon").click()}x.addClass("InputfieldRepeaterDeletePending")}y.find(".InputfieldRepeaterItemControls").css("background-color",y.css("background-color"))}g(x.closest(".InputfieldRepeater"));A.stopPropagation()};var n=function(x){var y=i(this).closest("li");var v=y.hasClass("InputfieldRepeaterDeletePending");function w(){y.parent().children("li").each(function(){var z=i(this);var A=z.children(".InputfieldHeader").find(".InputfieldRepeaterTrash");if(z.hasClass("InputfieldRepeaterDeletePending")){if(v){A.click()}}else{if(!v){A.click()}}})}if(v){w()}else{ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.removeAll,w)}};var e=function(w){var v=i(this).closest(".InputfieldRepeaterItem");ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.clone,function(){var y=v.attr("data-page");var x=v.closest(".InputfieldRepeater").children(".InputfieldContent").children(".InputfieldRepeaterAddItem").find(".InputfieldRepeaterAddLink:eq(0)");x.attr("data-clone",y).click();i("html, body").animate({scrollTop:x.offset().top-100},250,"swing")});return false};var r=function(z){var y=i(this);var x=y.attr("data-on");var v=y.attr("data-off");var w=y.closest(".InputfieldRepeaterItem");var A=w.find(".InputfieldRepeaterPublish");if(y.hasClass(x)){y.removeClass(x).addClass(v);w.addClass("InputfieldRepeaterUnpublished InputfieldRepeaterOff");A.val("-1")}else{y.removeClass(v).addClass(x);w.removeClass("InputfieldRepeaterUnpublished InputfieldRepeaterOff");A.val("1")}z.stopPropagation()};var f=function(x){var v=i(this);var w=v.find(".InputfieldRepeaterLoaded");if(parseInt(w.val())>0){return}v.addClass("InputfieldRepeaterItemLoading")};var u=function(B){var D=i(this);var G=D.find(".InputfieldRepeaterLoaded");d(D);if(parseInt(G.val())>0){return}G.val("1");var w=D.find(".InputfieldContent").hide();var y=D.closest(".InputfieldRepeater");var C=y.attr("data-page");var x=parseInt(D.attr("data-page"));var F=y.attr("id");var E=F.replace("wrap_Inputfield_","");var v=ProcessWire.config.InputfieldRepeater.editorUrl+"?id="+C+"&field="+E+"&repeater_edit="+x;var z=D.find(".InputfieldRepeaterDrag");var A=G.closest(".Inputfields");z.removeClass("fa-arrows").addClass("fa-spin fa-spinner");F=F.replace(/_repeater\d+$/,"");i.get(v,function(J){var H=i(J).find("#"+F+" > .InputfieldContent > .Inputfields > .InputfieldRepeaterItem > .InputfieldContent > .Inputfields > .InputfieldWrapper > .Inputfields > .Inputfield");A.append(H);D.removeClass("InputfieldRepeaterItemLoading");InputfieldsInit(A);var I=H.filter(".InputfieldRepeater");if(I.length){I.each(function(){s(i(this))})}w.slideDown("fast",function(){z.removeClass("fa-spin fa-spinner").addClass("fa-arrows")});setTimeout(function(){A.find(".Inputfield").trigger("reloaded",["InputfieldRepeaterItemEdit"])},50)})};var j=function(v){d(i(this))};var m=function(F){var y=i(this);var E=y.parent("p").prev("ul.Inputfields");var w=y.closest(".InputfieldRepeater");var z=y.parent().children("input");var D=0;var K=y.attr("data-noajax").length==0;var x=y.attr("data-clone");function I(L){var M=L.attr("id")+"_";while(i("#"+M).length>0){M+="_"}L.attr("id",M);E.append(L);L.css("display","block");h(L,true);y.trigger("repeateradd",[L])}if(typeof x=="undefined"||!x){x=null}if(x){y.removeAttr("data-clone")}if(!K){var A=E.children(".InputfieldRepeaterNewItem");D=A.length;if(D>0){if(D>1){A=A.slice(0,1)}var C=A.clone(true);I(C);z.attr("value",D);g(w)}return false}var G=w.attr("data-page");var J=w.attr("id").replace("wrap_Inputfield_","");var B=y.parent().find(".InputfieldRepeaterSpinner");var v=ProcessWire.config.InputfieldRepeater.editorUrl+"?id="+G+"&field="+J;B.removeClass(B.attr("data-off")).addClass(B.attr("data-on"));if(x){v+="&repeater_clone="+x}else{v+="&repeater_add="+y.attr("data-type")}var H=E.find(".InputfieldRepeaterUnpublished");if(H.length){v+="&repeater_not=";H.each(function(){v+=i(this).attr("data-page")+","})}i.get(v,function(M){B.removeClass(B.attr("data-on")).addClass(B.attr("data-off"));var L=i(M).find(".InputfieldRepeaterItemRequested");if(!L.length){return}I(L);L.wrap("
");InputfieldsInit(L.parent());s(L);L.unwrap();L.find(".Inputfield").trigger("reloaded",["InputfieldRepeaterItemAdd"]);L.find(".InputfieldRepeaterSort").val(E.children().length);i("html, body").animate({scrollTop:L.offset().top},500,"swing");d(L);g(w);$nestedRepeaters=L.find(".InputfieldRepeater");if($nestedRepeaters.length){$nestedRepeaters.each(function(){s(i(this))})}});return false};var p=function(z){z.stopPropagation();z.preventDefault();var x=i(this).closest(".InputfieldRepeater");var A=x.children(".InputfieldContent").children(".Inputfields").children(".InputfieldRepeaterItem");if(!A.length){return false}var w=A.eq(0);if(w.hasClass("InputfieldStateCollapsed")){var y=ProcessWire.config.InputfieldRepeater.labels.openAll;var v=".InputfieldStateCollapsed"}else{var y=ProcessWire.config.InputfieldRepeater.labels.collapseAll;var v=".InputfieldRepeaterItem:not(.InputfieldStateCollapsed)"}ProcessWire.confirm(y,function(){A.filter(v).each(function(){i(this).children(".InputfieldHeader").find(".toggle-icon").click()})});return false};function h(x,y){var w=x.children("label");var z=w.html();var v=z;if(y&&z.indexOf("#")>-1){num=x.siblings(".InputfieldRepeaterItem:visible").length+1;z=z.replace(/#[0-9]+/,"#"+num)}if(z.indexOf("{")>-1){z=z.replace(/\{/,'');z=z.replace(/}/,"")}if(z!=v){w.html(z)}}function l(x,B,w){var v=x.item.find(".InputfieldRepeaterDepth");var z=-1;var A=parseInt(v.val());var y=x.position.left;if(t){y-=b}if(y<0){z=A-Math.round(Math.abs(y)/b)}else{z=Math.round(y/b)+A}if(z<1){z=0}else{if(z>B){z=B}}if(w){if(z){x.item.css("margin-left",(z*b)+"px")}else{x.item.css("margin-left",0)}v.val(z);x.item.children(".InputfieldHeader").removeClass("ui-state-error")}return z}function o(v){v.find(".InputfieldRepeaterDepth").each(function(){var y=i(this);var z=y.val();var w=y.closest(".InputfieldRepeaterItem");var x=w.css("margin-left");if(x=="auto"){x=0}x=parseInt(x);var A=z*b;if(A!=x){w.css("margin-left",A+"px")}})}function a(y,v){var x=parseInt(y.attr("data-depth"));var w={items:"> li:not(.InputfieldRepeaterNewItem)",handle:".InputfieldRepeaterDrag",start:function(A,z){z.item.find(".InputfieldHeader").addClass("ui-state-highlight");z.item.find("textarea.InputfieldCKEditorNormal.InputfieldCKEditorLoaded").each(function(){i(this).removeClass("InputfieldCKEditorLoaded");var B=CKEDITOR.instances[i(this).attr("id")];B.destroy();CKEDITOR.remove(i(this).attr("id"))});z.item.find(".InputfieldTinyMCE textarea").each(function(){tinyMCE.execCommand("mceRemoveControl",false,i(this).attr("id"))})},stop:function(A,z){if(x>0){l(z,x,true)}z.item.find(".InputfieldHeader").removeClass("ui-state-highlight");i(this).children().each(function(B){i(this).find(".InputfieldRepeaterSort").slice(0,1).attr("value",B)});z.item.find("textarea.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)").each(function(){i(this).closest(".InputfieldCKEditor").trigger("reloaded",["InputfieldRepeaterSort"])});z.item.find(".InputfieldTinyMCE textarea").each(function(){tinyMCE.execCommand("mceAddControl",false,i(this).attr("id"))})}};if(x>0){o(y);w.grid=[b,1];w.sort=function(A,B){var C=l(B,99,false);var z=B.item.children(".InputfieldHeader");if(C>x){z.addClass("ui-state-error")}else{if(z.hasClass("ui-state-error")){z.removeClass("ui-state-error")}}}}else{w.axis="y"}i(".InputfieldRepeaterDrag",v).hover(function(){i(this).parent("label").addClass("ui-state-focus")},function(){i(this).parent("label").removeClass("ui-state-focus")});v.sortable(w)}function s(E){if(E.hasClass("InputfieldRepeaterItem")){var A=E;var w=E.closest(".InputfieldRepeater");var z=true}else{var A=E.find(".Inputfields:eq(0)");var w=E;var z=false}if(A.hasClass("InputfieldRepeaterInit")){return}A.addClass("InputfieldRepeaterInit");var y=A.closest(".InputfieldRenderValueMode").length>0;var D=i("").css("display","block");var B=i("");var v=i("");var C=ProcessWire.config.InputfieldRepeater;var x=!w.hasClass("InputfieldRepeaterNoAjaxAdd");if(C){v.attr("title",C.labels.toggle);B.attr("title",C.labels.remove);D.attr("title",C.labels.clone)}i("input.InputfieldRepeaterDelete",E).parents(".InputfieldCheckbox").hide();function F(G){G.each(function(){var N=i(this);if(N.hasClass("InputfieldRepeaterHeaderInit")){return}var M="fa-arrows";var K=N.parent();if(K.hasClass("InputfieldRepeaterNewItem")){M="fa-plus";N.addClass("ui-priority-secondary")}N.addClass("ui-state-default InputfieldRepeaterHeaderInit");N.prepend("");if(!y){var H=i("");var J=v.clone(true).addClass(N.parent().hasClass("InputfieldRepeaterOff")?"fa-toggle-off":"fa-toggle-on");var L=B.clone(true);var I=N.find(".toggle-icon");H.prepend(I);if(x){H.prepend(D.clone(true))}H.prepend(J).prepend(L);N.prepend(H);H.css("background-color",N.css("background-color"))}h(K,false)})}if(z){F(E.children(".InputfieldHeader"))}else{F(i(".InputfieldRepeaterItem > .InputfieldHeader",E))}if(y){o(w);return}i(".InputfieldRepeaterTrash",E).hover(function(){var G=i(this).closest("label");if(!G.parents().hasClass("InputfieldRepeaterDeletePending")){G.addClass("ui-state-error")}G.find(".InputfieldRepeaterItemControls").css("background-color",G.css("background-color"))},function(){var G=i(this).closest("label");if(!G.parent().hasClass("InputfieldRepeaterDeletePending")){G.removeClass("ui-state-error")}G.find(".InputfieldRepeaterItemControls").css("background-color",G.css("background-color"))});if(z){A=w.find(".Inputfields:eq(0)")}a(w,A);i(".InputfieldRepeaterAddLink:not(.InputfieldRepeaterAddLinkInit)",w).addClass("InputfieldRepeaterAddLinkInit").click(m);if(w.hasClass("InputfieldRepeaterMax")){g(w)}}function g(z){if(!z.hasClass("InputfieldRepeaterMax")){return}var v=parseInt(z.attr("data-max"));if(v<=0){return}var x=z.children(".InputfieldContent");var w=x.children(".Inputfields").children("li:not(.InputfieldRepeaterDeletePending)").length;var y=x.children(".InputfieldRepeaterAddItem");if(w>v){y.hide()}else{if(!y.is(":visible")){y.show()}}}function d(v){if(v.closest(".InputfieldRepeaterRememberOpen").length<1){return}var w="";i(".InputfieldRepeaterItem:not(.InputfieldStateCollapsed)").each(function(){var x=parseInt(i(this).attr("data-page"));if(x>0){w+=x+"|"}});i.cookie("repeaters_open",w)}function q(){i(".InputfieldRepeater").each(function(){s(i(this))});i(document).on("reloaded",".InputfieldRepeater",c).on("click",".InputfieldRepeaterTrash",k).on("dblclick",".InputfieldRepeaterTrash",n).on("click",".InputfieldRepeaterClone",e).on("dblclick",".InputfieldRepeaterToggle",p).on("click",".InputfieldRepeaterToggle",r).on("opened",".InputfieldRepeaterItem",u).on("closed",".InputfieldRepeaterItem",j).on("openReady",".InputfieldRepeaterItem",f)}q()}jQuery(document).ready(function(a){InputfieldRepeater(a)}); \ No newline at end of file +function InputfieldRepeater(i){var b=50;var x=i("body").hasClass("AdminThemeReno");var v=null;var c=function(A,B){if(typeof B!="undefined"){if(B=="InputfieldRepeaterItemEdit"||B=="InputfieldRepeaterItemAdd"){A.stopPropagation();var z=i(this).find(".InputfieldRepeater");if(z.length){w(z)}return}}w(i(this))};var k=function(F){var E=i(this);var C=E.closest(".InputfieldHeader");var B=C.parent();if(g(E)){return false}if(B.hasClass("InputfieldRepeaterNewItem")){var z=B.children(".InputfieldContent").children(".InputfieldRepeaterAddItem").children("input");z.attr("value",parseInt(z.attr("value")-1));B.remove()}else{var A=B.attr("data-page");var D=B.find("#delete_repeater"+A);if(D.is(":checked")){D.removeAttr("checked");C.removeClass("ui-state-error").addClass("ui-state-default");B.removeClass("InputfieldRepeaterDeletePending")}else{D.attr("checked","checked");C.removeClass("ui-state-default").addClass("ui-state-error");if(!B.hasClass("InputfieldStateCollapsed")){C.find(".toggle-icon").click()}B.addClass("InputfieldRepeaterDeletePending")}C.find(".InputfieldRepeaterItemControls").css("background-color",C.css("background-color"))}l(B.closest(".InputfieldRepeater"));F.stopPropagation()};var o=function(){var B=i(this);var C=i(this).closest("li");var z=C.hasClass("InputfieldRepeaterDeletePending");if(g(B)){return false}function A(){C.parent().children("li").each(function(){var D=i(this);var E=D.children(".InputfieldHeader").find(".InputfieldRepeaterTrash");if(D.hasClass("InputfieldRepeaterDeletePending")){if(z){E.click()}}else{if(!z){E.click()}}})}if(z){A()}else{ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.removeAll,A)}};var e=function(){var A=i(this);if(g(A)){return false}var z=A.closest(".InputfieldRepeaterItem");ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.clone,function(){var C=z.attr("data-page");var B=z.closest(".InputfieldRepeater").children(".InputfieldContent").children(".InputfieldRepeaterAddItem").find(".InputfieldRepeaterAddLink:eq(0)");B.attr("data-clone",C).click();i("html, body").animate({scrollTop:B.offset().top-100},250,"swing")});return false};var u=function(D){var C=i(this);var B=C.attr("data-on");var z=C.attr("data-off");var A=C.closest(".InputfieldRepeaterItem");var E=A.find(".InputfieldRepeaterPublish");if(v){clearTimeout(v)}v=setTimeout(function(){if(g(C)){return false}if(C.hasClass(B)){C.removeClass(B).addClass(z);A.addClass("InputfieldRepeaterUnpublished InputfieldRepeaterOff");E.val("-1")}else{C.removeClass(z).addClass(B);A.removeClass("InputfieldRepeaterUnpublished InputfieldRepeaterOff");E.val("1")}l(A.closest(".InputfieldRepeater"))},250);D.stopPropagation()};var f=function(){var z=i(this);var A=z.find(".InputfieldRepeaterLoaded");if(parseInt(A.val())>0){return}z.addClass("InputfieldRepeaterItemLoading")};var y=function(){var G=i(this);var J=G.find(".InputfieldRepeaterLoaded");d(G);if(parseInt(J.val())>0){r(G);return}J.val("1");var A=G.find(".InputfieldContent").hide();var C=G.closest(".InputfieldRepeater");var F=C.attr("data-page");var B=parseInt(G.attr("data-page"));var I=C.attr("id");var H=I.replace("wrap_Inputfield_","");var z=ProcessWire.config.InputfieldRepeater.editorUrl+"?id="+F+"&field="+H+"&repeater_edit="+B;var D=G.find(".InputfieldRepeaterDrag");var E=J.closest(".Inputfields");D.removeClass("fa-arrows").addClass("fa-spin fa-spinner");I=I.replace(/_repeater\d+$/,"");i.get(z,function(M){var K=i(M).find("#"+I+" > .InputfieldContent > .Inputfields > .InputfieldRepeaterItem > .InputfieldContent > .Inputfields > .InputfieldWrapper > .Inputfields > .Inputfield");E.append(K);G.removeClass("InputfieldRepeaterItemLoading");InputfieldsInit(E);var L=K.filter(".InputfieldRepeater");if(L.length){L.each(function(){w(i(this))})}A.slideDown("fast",function(){D.removeClass("fa-spin fa-spinner").addClass("fa-arrows");r(G)});setTimeout(function(){E.find(".Inputfield").trigger("reloaded",["InputfieldRepeaterItemEdit"])},50)})};var j=function(){d(i(this))};var n=function(){var C=i(this);var I=C.parent("p").prev("ul.Inputfields");var A=C.closest(".InputfieldRepeater");var D=C.parent().children("input");var H=0;var N=C.attr("data-noajax").length==0;var B=C.attr("data-clone");function L(O){var P=O.attr("id")+"_";while(i("#"+P).length>0){P+="_"}O.attr("id",P);I.append(O);O.css("display","block");h(O,true);C.trigger("repeateradd",[O])}if(typeof B=="undefined"||!B){B=null}if(B){C.removeAttr("data-clone")}if(!N){var E=I.children(".InputfieldRepeaterNewItem");H=E.length;if(H>0){if(H>1){E=E.slice(0,1)}var G=E.clone(true);L(G);D.attr("value",H);l(A)}return false}var J=A.attr("data-page");var M=A.attr("id").replace("wrap_Inputfield_","");var F=C.parent().find(".InputfieldRepeaterSpinner");var z=ProcessWire.config.InputfieldRepeater.editorUrl+"?id="+J+"&field="+M;F.removeClass(F.attr("data-off")).addClass(F.attr("data-on"));if(B){z+="&repeater_clone="+B}else{z+="&repeater_add="+C.attr("data-type")}var K=I.find(".InputfieldRepeaterUnpublished:not(.InputfieldRepeaterMinItem)");if(K.length){z+="&repeater_not=";K.each(function(){z+=i(this).attr("data-page")+","})}i.get(z,function(Q){F.removeClass(F.attr("data-on")).addClass(F.attr("data-off"));var O=i(Q).find(".InputfieldRepeaterItemRequested");if(!O.length){return}L(O);O.wrap("");InputfieldsInit(O.parent());w(O);O.unwrap();O.find(".Inputfield").trigger("reloaded",["InputfieldRepeaterItemAdd"]);O.find(".InputfieldRepeaterSort").val(I.children().length);i("html, body").animate({scrollTop:O.offset().top},500,"swing");d(O);l(A);r(O);var P=O.find(".InputfieldRepeater");if(P.length){P.each(function(){w(i(this))})}});return false};var q=function(D){D.stopPropagation();D.preventDefault();if(v){clearTimeout(v)}if(i(this).closest(".InputfieldRepeater").hasClass("InputfieldRepeaterAccordion")){return false}var B=i(this).closest(".InputfieldRepeater");var E=B.children(".InputfieldContent").children(".Inputfields").children(".InputfieldRepeaterItem");if(!E.length){return false}var A=E.eq(0);if(A.hasClass("InputfieldStateCollapsed")){var C=ProcessWire.config.InputfieldRepeater.labels.openAll;var z=".InputfieldStateCollapsed"}else{var C=ProcessWire.config.InputfieldRepeater.labels.collapseAll;var z=".InputfieldRepeaterItem:not(.InputfieldStateCollapsed)"}ProcessWire.confirm(C,function(){E.filter(z).each(function(){i(this).children(".InputfieldHeader").find(".toggle-icon").click()})});return false};function g(z){if(z.hasClass("pw-icon-disabled")){ProcessWire.alert(ProcessWire.config.InputfieldRepeater.labels.disabledMinMax);return true}return false}function r(z){if(!z.closest(".InputfieldRepeater").hasClass("InputfieldRepeaterAccordion")){return false}var D=z.attr("id");var B=false;var A=z.parent().children(".InputfieldRepeaterItem");var C=false;var E=false;A.each(function(){var F=i(this);if(F.attr("id")==D){C=true;return}if(F.hasClass("InputfieldStateCollapsed")){return}if(!F.is(":visible")){return}if(!C){B=true}F.children(".InputfieldHeader").find(".toggle-icon").trigger("click",[{duration:0}]);E=true});if(B&&E){i("html, body").animate({scrollTop:z.offset().top-10},0)}return true}function h(B,D){var A;A=B.children(".InputfieldHeader").find(".InputfieldRepeaterItemLabel");if(typeof A=="undefined"){A=B.children("label")}var E=A.html();var z=E;if(typeof E!="undefined"){if(D&&E.indexOf("#")>-1){var C=B.siblings(".InputfieldRepeaterItem:visible").length+1;E=E.replace(/#[0-9]+/,"#"+C)}while(E.indexOf("}")>-1){E=E.replace(/\{/,'');E=E.replace(/}/,"")}if(E!=z){A.html(E)}}}function m(B,F,A){var z=B.item.find(".InputfieldRepeaterDepth");var D=-1;var E=parseInt(z.val());var C=B.position.left;if(x){C-=b}if(C<0){D=E-Math.round(Math.abs(C)/b)}else{D=Math.round(C/b)+E}if(D<1){D=0}else{if(D>F){D=F}}if(A){if(D){B.item.css("margin-left",(D*b)+"px")}else{B.item.css("margin-left",0)}z.val(D);B.item.children(".InputfieldHeader").removeClass("ui-state-error")}return D}function p(z){z.find(".InputfieldRepeaterDepth").each(function(){var C=i(this);var D=C.val();var A=C.closest(".InputfieldRepeaterItem");var B=A.css("margin-left");if(B=="auto"){B=0}B=parseInt(B);var E=D*b;if(E!=B){A.css("margin-left",E+"px")}})}function a(C,z){var B=parseInt(C.attr("data-depth"));var A={items:"> li:not(.InputfieldRepeaterNewItem)",handle:".InputfieldRepeaterDrag",start:function(E,D){D.item.find(".InputfieldHeader").addClass("ui-state-highlight");D.item.find("textarea.InputfieldCKEditorNormal.InputfieldCKEditorLoaded").each(function(){i(this).removeClass("InputfieldCKEditorLoaded");var F=CKEDITOR.instances[i(this).attr("id")];F.destroy();CKEDITOR.remove(i(this).attr("id"))});D.item.find(".InputfieldTinyMCE textarea").each(function(){tinyMCE.execCommand("mceRemoveControl",false,i(this).attr("id"))})},stop:function(E,D){if(B>0){m(D,B,true)}D.item.find(".InputfieldHeader").removeClass("ui-state-highlight");i(this).children().each(function(F){i(this).find(".InputfieldRepeaterSort").slice(0,1).attr("value",F)});D.item.find("textarea.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)").each(function(){i(this).closest(".InputfieldCKEditor").trigger("reloaded",["InputfieldRepeaterSort"])});D.item.find(".InputfieldTinyMCE textarea").each(function(){tinyMCE.execCommand("mceAddControl",false,i(this).attr("id"))})}};if(B>0){p(C);A.grid=[b,1];A.sort=function(E,F){var G=m(F,99,false);var D=F.item.children(".InputfieldHeader");if(G>B){D.addClass("ui-state-error")}else{if(D.hasClass("ui-state-error")){D.removeClass("ui-state-error")}}}}else{A.axis="y"}i(".InputfieldRepeaterDrag",z).hover(function(){i(this).parent("label").addClass("ui-state-focus")},function(){i(this).parent("label").removeClass("ui-state-focus")});z.sortable(A)}function t(E,G,D){var F=i("").css("display","block");var A=i("");var C=i("");var z=ProcessWire.config.InputfieldRepeater;var B=!G.hasClass("InputfieldRepeaterNoAjaxAdd");if(z){C.attr("title",z.labels.toggle);A.attr("title",z.labels.remove);F.attr("title",z.labels.clone)}E.each(function(){var N=i(this);if(N.hasClass("InputfieldRepeaterHeaderInit")){return}var M="fa-arrows";var K=N.parent();if(K.hasClass("InputfieldRepeaterNewItem")){M="fa-plus";N.addClass("ui-priority-secondary")}N.addClass("ui-state-default InputfieldRepeaterHeaderInit");N.prepend("");if(!D){var H=i("");var J=C.clone(true).addClass(N.parent().hasClass("InputfieldRepeaterOff")?"fa-toggle-off":"fa-toggle-on");var L=A.clone(true);var I=N.find(".toggle-icon");H.prepend(I);if(B){H.prepend(F.clone(true))}H.prepend(J).prepend(L);N.prepend(H);H.css("background-color",N.css("background-color"))}h(K,false)})}function w(C){if(C.hasClass("InputfieldRepeaterItem")){var z=C;var D=C.closest(".InputfieldRepeater");var B=true}else{var z=C.find(".Inputfields:eq(0)");var D=C;var B=false}if(z.hasClass("InputfieldRepeaterInit")){return}var A=z.closest(".InputfieldRenderValueMode").length>0;z.addClass("InputfieldRepeaterInit");i("input.InputfieldRepeaterDelete",C).parents(".InputfieldCheckbox").hide();if(B){t(C.children(".InputfieldHeader"),D,A)}else{t(i(".InputfieldRepeaterItem > .InputfieldHeader",C),D,A)}if(A){p(D);return}i(".InputfieldRepeaterTrash",C).hover(function(){var E=i(this).closest("label");if(!E.parents().hasClass("InputfieldRepeaterDeletePending")){E.addClass("ui-state-error")}E.find(".InputfieldRepeaterItemControls").css("background-color",E.css("background-color"))},function(){var E=i(this).closest("label");if(!E.parent().hasClass("InputfieldRepeaterDeletePending")){E.removeClass("ui-state-error")}E.find(".InputfieldRepeaterItemControls").css("background-color",E.css("background-color"))});if(B){z=D.find(".Inputfields:eq(0)")}a(D,z);i(".InputfieldRepeaterAddLink:not(.InputfieldRepeaterAddLinkInit)",D).addClass("InputfieldRepeaterAddLinkInit").click(n);if(D.hasClass("InputfieldRepeaterMax")){l(D)}}function l(z){if(!z.hasClass("InputfieldRepeaterMax")&&!z.hasClass("InputfieldRepeaterMin")){return}var H=parseInt(z.attr("data-max"));var E=parseInt(z.attr("data-min"));if(H<=0&&E<=0){return}var A=z.children(".InputfieldContent");var F=A.children(".Inputfields").children("li:not(.InputfieldRepeaterDeletePending):not(.InputfieldRepeaterOff):visible").length;var D=A.children(".InputfieldRepeaterAddItem");var C="";var B="";if(H>0){if(F>=H){D.hide();C="hide"}else{if(!D.is(":visible")){D.show();C="show"}}}if(E>0){if(F<=E){B="hide";A.addClass("InputfieldRepeaterTrashHidden")}else{if(A.hasClass("InputfieldRepeaterTrashHidden")){A.removeClass("InputfieldRepeaterTrashHidden");B="show"}}}if(C.length||B.length){var G=A.children(".Inputfields").children(".InputfieldRepeaterItem");if(C.length){G.each(function(){var I=i(this).children(".InputfieldHeader").find(".InputfieldRepeaterClone");if(C==="show"){I.removeClass("pw-icon-disabled")}else{I.addClass("pw-icon-disabled")}})}if(B.length){G.each(function(){var J=i(this).children(".InputfieldHeader");var I=J.find(".InputfieldRepeaterTrash");var K=J.find(".InputfieldRepeaterToggle.fa-toggle-on");if(B==="show"){I.removeClass("pw-icon-disabled");K.removeClass("pw-icon-disabled")}else{I.addClass("pw-icon-disabled");K.addClass("pw-icon-disabled")}});if(B=="hide"){A.children(".Inputfields").children("li.InputfieldRepeaterDeletePending").each(function(){var I=i(this).children(".InputfieldHeader").find(".InputfieldRepeaterTrash");I.removeClass("pw-icon-disabled")})}}}}function d(z){if(z.closest(".InputfieldRepeaterRememberOpen").length<1){return}var A="";i(".InputfieldRepeaterItem:not(.InputfieldStateCollapsed)").each(function(){var B=parseInt(i(this).attr("data-page"));if(B>0){A+=B+"|"}});i.cookie("repeaters_open",A)}function s(){i(".InputfieldRepeater").each(function(){w(i(this))});i(document).on("reloaded",".InputfieldRepeater",c).on("click",".InputfieldRepeaterTrash",k).on("dblclick",".InputfieldRepeaterTrash",o).on("click",".InputfieldRepeaterClone",e).on("dblclick",".InputfieldRepeaterToggle",q).on("click",".InputfieldRepeaterToggle",u).on("opened",".InputfieldRepeaterItem",y).on("closed",".InputfieldRepeaterItem",j).on("openReady",".InputfieldRepeaterItem",f)}s()}jQuery(document).ready(function(a){InputfieldRepeater(a)}); \ No newline at end of file diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module index 79e5f4b7..29cbc353 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module @@ -9,7 +9,9 @@ * https://processwire.com * * @property int $repeaterMaxItems + * @property int $repeaterMinItems * @property int $repeaterDepth + * @property bool $accordionMode * * @method string renderRepeaterLabel($label, $cnt, Page $page) * @@ -22,7 +24,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { return array( 'title' => __('Repeater', __FILE__), // Module Title 'summary' => __('Repeats fields from another template. Provides the input for FieldtypeRepeater.', __FILE__), // Module Summary - 'version' => 105, + 'version' => 106, 'requires' => 'FieldtypeRepeater', ); } @@ -86,8 +88,10 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { public function __construct() { parent::__construct(); // these are part of the Fieldtype's config, and automatically set from it - $this->set('repeaterMaxItems', 0); + $this->set('repeaterMaxItems', 0); + $this->set('repeaterMinItems', 0); $this->set('repeaterDepth', 0); + $this->set('accordionMode', false); } /** @@ -278,9 +282,22 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { $openIDs = array(); } + $minItems = $this->repeaterMinItems; + + // if there are a minimum required number of items, set them up now + if(!$itemID && $minItems > 0) { + $notIDs = $value->explode('id'); + while($value->count() < $this->repeaterMinItems) { + $item = $this->getNextReadyPage($notIDs); + $value->add($item); + $notIDs[] = $item->id; + } + } + $repeaterCollapse = (int) $this->field->get('repeaterCollapse'); $cnt = 0; $numVisible = 0; + $numOpen = 0; // create field for each repeater iteration foreach($value as $key => $page) { @@ -293,6 +310,9 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { $isReadyItem = $isHidden && $isUnpublished; $isClone = $page->get('_repeater_clone'); $isOpen = in_array($page->id, $openIDs) || $isClone; + $isMinItem = $isReadyItem && $minItems && $cnt < $minItems; + + if($isOpen && $numOpen > 0 && $this->accordionMode) $isOpen = false; // get the inputfields for the repeater page if(is_null($loadInputsForIDs) || in_array($page->id, $loadInputsForIDs) || $isOpen) { @@ -354,8 +374,11 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { if($isOpen) { $wrap->collapsed = Inputfield::collapsedNo; + $numOpen++; } else if($repeaterCollapse == FieldtypeRepeater::collapseExisting && !$page->get('_repeater_new') && !$isHidden) { $wrap->collapsed = Inputfield::collapsedYes; + } else if($repeaterCollapse == FieldtypeRepeater::collapseExisting && $isMinItem) { + $wrap->collapsed = Inputfield::collapsedYes; } else if($repeaterCollapse == FieldtypeRepeater::collapseAll) { $wrap->collapsed = Inputfield::collapsedYes; } @@ -393,6 +416,12 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { if($depth) $wrap->prepend($depth); $wrap->prepend($loaded); + if($isMinItem) { + // allow this ready item to be added so that minimum is met + $wrap->addClass('InputfieldRepeaterMinItem'); + $isReadyItem = false; + } + if(!$isReadyItem) { $form->add($wrap); $numVisible++; @@ -532,11 +561,18 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { if($this->repeaterMaxItems > 0) { $this->addClass('InputfieldRepeaterMax', 'wrapClass'); } + if($this->repeaterMinItems > 0) { + $this->addClass('InputfieldRepeaterMin', 'wrapClass'); + } if($this->repeaterDepth > 0) { $this->addClass('InputfieldRepeaterDepth', 'wrapClass'); } + if($this->accordionMode) { + $this->addClass('InputfieldRepeaterAccordion', 'wrapClass'); + } $this->wrapAttr('data-page', $this->page->id); $this->wrapAttr('data-max', (int) $this->repeaterMaxItems); + $this->wrapAttr('data-min', (int) $this->repeaterMinItems); $this->wrapAttr('data-depth', (int) $this->repeaterDepth); list($editorUrl, $queryString) = explode('?', $this->page->editUrl()); @@ -550,7 +586,8 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { 'toggle' => $this->_x('Click to turn item on/off, or double-click to open/collapse all items', 'repeater-item-action'), 'clone' => $this->_x('Clone this item?', 'repeater-item-action'), 'openAll' => $this->_x('Open all items?', 'repeater-item-action'), - 'collapseAll' => $this->_x('Collapse all items?', 'repeater-item-action') + 'collapseAll' => $this->_x('Collapse all items?', 'repeater-item-action'), + 'disabledMinMax' => $this->_('This action is disabled per min and/or max item settings.') ) )); diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss index b8a0db29..e6397003 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss @@ -12,45 +12,56 @@ overflow: hidden; position: relative; - .InputfieldRepeaterItemLabel { - display: inline-block; - padding-left: 0.25em; - } + .InputfieldRepeaterItemLabel, .InputfieldRepeaterItemControls { - padding-right: 0.5em; - padding-left: 0.5em; - margin-top: 0.5em; - position: absolute; - top: 0; - right: 0; - z-index: 1; - display: block; - white-space: nowrap; - height: 100%; - - .InputfieldRepeaterClone, - .InputfieldRepeaterToggle, - .InputfieldRepeaterTrash, + display: none; + } + + &.InputfieldRepeaterHeaderInit { + .InputfieldRepeaterItemLabel { + display: inline-block; + padding-left: 0.25em; + } + .InputfieldRepeaterItemControls { + display: block; + padding-right: 0.5em; + padding-left: 0.5em; + margin-top: 0.5em; + position: absolute; + top: 0; + right: 0; + z-index: 1; + display: block; + white-space: nowrap; + height: 100%; + + .InputfieldRepeaterClone, + .InputfieldRepeaterToggle, + .InputfieldRepeaterTrash, + .toggle-icon { + cursor: pointer; + float: right; + } + .InputfieldRepeaterTrash { + padding-right: 3px; + } + .InputfieldRepeaterToggle { + margin-right: 1em; + } + .InputfieldRepeaterClone { + margin-right: 1em; + } + .pw-icon-disabled { + opacity: 0.3; + } + } + .toggle-icon { - cursor: pointer; - float: right; - } - .InputfieldRepeaterTrash { - padding-right: 3px; - } - .InputfieldRepeaterToggle { - margin-right: 1em; - } - .InputfieldRepeaterClone { - margin-right: 1em; + line-height: 1em; + margin-right: 0.5em; } } - .toggle-icon { - line-height: 1em; - margin-right: 0.5em; - } - } .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending) { diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/config.php b/wire/modules/Fieldtype/FieldtypeRepeater/config.php index 6d40d333..7916caf3 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/config.php +++ b/wire/modules/Fieldtype/FieldtypeRepeater/config.php @@ -186,13 +186,21 @@ class FieldtypeRepeaterConfigHelper extends Wire { $f->label = $this->_('Remember which repeater items are open?'); $f->description = $this->_('When checked, opened repeater items remain open after saving or reloading from the page editor (unless the user closes them).'); $f->icon = 'lightbulb-o'; - if((int) $field->get('rememberOpen')) { - $f->attr('checked', 'checked'); - } else { - $f->collapsed = Inputfield::collapsedYes; - } + if((int) $field->get('rememberOpen')) $f->attr('checked', 'checked'); + $f->columnWidth = 50; $inputfields->add($f); + // ------------------------------------------------- + + $f = $this->wire('modules')->get('InputfieldCheckbox'); + $f->attr('name', 'accordionMode'); + $f->label = $this->_('Use accordion mode?'); + $f->description = $this->_('When checked, only one repeater item will be open at a time.'); + $f->icon = 'map-o'; + if((int) $field->get('accordionMode')) $f->attr('checked', 'checked'); + $f->columnWidth = 50; + $inputfields->add($f); + // ------------------------------------------------- $value = (int) $field->get('repeaterMaxItems'); @@ -200,9 +208,21 @@ class FieldtypeRepeaterConfigHelper extends Wire { $f->attr('name', 'repeaterMaxItems'); $f->attr('value', $value > 0 ? $value : ''); $f->label = $this->_('Maximum number of items'); - $f->collapsed = Inputfield::collapsedBlank; $f->description = $this->_('If you need to limit the number of items allowed, enter the limit here (0=no limit).'); $f->icon = 'hand-stop-o'; + $f->columnWidth = 50; + $inputfields->add($f); + + // ------------------------------------------------- + + $value = (int) $field->get('repeaterMinItems'); + $f = $this->wire('modules')->get('InputfieldInteger'); + $f->attr('name', 'repeaterMinItems'); + $f->attr('value', $value > 0 ? $value : ''); + $f->label = $this->_('Minimum number of items'); + $f->description = $this->_('This many items will always be open and ready-to-edit (0=no minimum).'); + $f->icon = 'hand-peace-o'; + $f->columnWidth = 50; $inputfields->add($f); // ------------------------------------------------- diff --git a/wire/templates-admin/scripts/inputfields.js b/wire/templates-admin/scripts/inputfields.js index d11435e4..5e4d7c35 100644 --- a/wire/templates-admin/scripts/inputfields.js +++ b/wire/templates-admin/scripts/inputfields.js @@ -1035,7 +1035,7 @@ function InputfieldStates($target) { if($newTab.hasClass('collapsed10')) InputfieldStateAjaxClick($newTab); }); - $(document).on('click', '.InputfieldStateToggle, .toggle-icon', function() { + $(document).on('click', '.InputfieldStateToggle, .toggle-icon', function(event, data) { var $t = $(this); var $li = $t.closest('.Inputfield'); @@ -1043,7 +1043,13 @@ function InputfieldStates($target) { var $icon = isIcon ? $t : $li.children('.InputfieldHeader, .ui-widget-header').find('.toggle-icon'); var isCollapsed = $li.hasClass("InputfieldStateCollapsed"); var wasCollapsed = $li.hasClass("InputfieldStateWasCollapsed"); + var duration = 100; + if($li.hasClass('InputfieldAjaxLoading')) return false; + + if(typeof data != "undefined") { + if(typeof data.duration != "undefined") duration = data.duration; + } if(isCollapsed && ($li.hasClass('collapsed10') || $li.hasClass('collapsed11'))) { if(InputfieldStateAjaxClick($li)) return false; @@ -1052,7 +1058,7 @@ function InputfieldStates($target) { if(isCollapsed || wasCollapsed || isIcon) { $li.addClass('InputfieldStateWasCollapsed'); // this class only used here $li.trigger(isCollapsed ? 'openReady' : 'closeReady'); - $li.toggleClass('InputfieldStateCollapsed', 100, function() { + $li.toggleClass('InputfieldStateCollapsed', duration, function() { if(isCollapsed) { $li.trigger('opened'); if($li.hasClass('InputfieldColumnWidth')) $li.children('.InputfieldContent').show(); diff --git a/wire/templates-admin/scripts/inputfields.min.js b/wire/templates-admin/scripts/inputfields.min.js index 7b07d4e0..44aa41bd 100644 --- a/wire/templates-admin/scripts/inputfields.min.js +++ b/wire/templates-admin/scripts/inputfields.min.js @@ -1 +1 @@ -var InputfieldDebugMode=false;function consoleLog(a){if(InputfieldDebugMode){console.log(a)}}var InputfieldDependenciesProcessing=false;function InputfieldDependencies(d){if(InputfieldDependenciesProcessing){return}if(typeof d=="undefined"){var d=$(".InputfieldForm:not(.InputfieldFormNoDependencies)")}else{if(d.hasClass("InputfieldForm")){if(d.hasClass("InputfieldFormNoDependencies")){return}}else{if(d.closest(".InputfieldFormNoDependencies").length>0){return}}}function h(l){l=jQuery.trim(l);var m=l.substring(0,1);var k=l.substring(l.length-1,l.length);if((m=='"'||m=="'")&&m==k){l=l.substring(1,l.length-1)}return l}function i(k){return g(h(k))}function b(m){var l="";var k=m.indexOf(".");if(k>0){l=m.substring(k+1);m=m.substring(0,k)}return{field:m,subfield:l}}function g(n,l){n=jQuery.trim(n);if(n.length>0&&!jQuery.isNumeric(n)){return n}if(n.length==0){var k=typeof l;if(k!="undefined"){if(k=="integer"){return 0}if(k=="float"){return 0}return n}else{return n}}var o=n.indexOf(".");var m=n.lastIndexOf(".");if(o==-1&&/^-?\d+$/.test(n)){return parseInt(n)}if(m>-1&&o!=m){return n}if(/^-?[\d.]+$/.test(n)){return parseFloat(n)}return n}function c(n,l,m,o){var k=0;switch(l){case"=":if(m==o){k++}break;case"!=":if(m!=o){k++}break;case">":if(m>o){k++}break;case"<":if(m