diff --git a/wire/core/Inputfield.php b/wire/core/Inputfield.php index e6d05f33..ae4a8110 100644 --- a/wire/core/Inputfield.php +++ b/wire/core/Inputfield.php @@ -69,7 +69,7 @@ * @property InputfieldWrapper|null $parent The parent InputfieldWrapper for this Inputfield or null if not set. #pw-internal * @property null|bool|Fieldtype $hasFieldtype The Fieldtype using this Inputfield, or boolean false when known not to have a Fieldtype, or null when not known. #pw-group-other * @property bool|null $useLanguages When multi-language support active, can be set to true to make it provide inputs for each language, where supported (default=false). #pw-group-behavior - * @property null|bool $entityEncodeLabel Set to boolean false to specifically disable entity encoding of field header/label (default=true). #pw-group-output + * @property null|bool|int $entityEncodeLabel Set to boolean false to specifically disable entity encoding of field header/label, or set to a Inputfield::textFormat constant. (default=true). #pw-group-output * @property null|bool $entityEncodeText Set to boolean false to specifically disable entity encoding for other text: description, notes, etc. (default=true). #pw-group-output * @property int $renderValueFlags Options that can be applied to renderValue mode, see "renderValue" constants (default=0). #pw-group-output * @property string $wrapClass Optional class name (CSS) to apply to the HTML element wrapping the Inputfield. #pw-group-other diff --git a/wire/core/InputfieldWrapper.php b/wire/core/InputfieldWrapper.php index 2fdb5f26..c235bcab 100644 --- a/wire/core/InputfieldWrapper.php +++ b/wire/core/InputfieldWrapper.php @@ -504,7 +504,13 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre if($label || $quietMode) { $for = $inputfield->getSetting('skipLabel') || $quietMode ? '' : $inputfield->attr('id'); // if $inputfield has a property of entityEncodeLabel with a value of boolean FALSE, we don't entity encode - if($inputfield->getSetting('entityEncodeLabel') !== false) $label = $inputfield->entityEncode($label); + $entityEncodeLabel = $inputfield->getSetting('entityEncodeLabel'); + if(is_int($entityEncodeLabel) && $entityEncodeLabel >= Inputfield::textFormatBasic) { + // uses an Inputfield::textFormat constant + $label = $inputfield->entityEncode($label, $entityEncodeLabel); + } else if($entityEncodeLabel !== false) { + $label = $inputfield->entityEncode($label); + } $icon = $inputfield->getSetting('icon'); $icon = $icon ? str_replace('{name}', $this->wire('sanitizer')->name(str_replace(array('icon-', 'fa-'), '', $icon)), $markup['item_icon']) : ''; $toggle = $collapsed == Inputfield::collapsedNever ? '' : $markup['item_toggle']; diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css index 7edcc08e..d0997bd3 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.css @@ -2,19 +2,39 @@ margin-bottom: 1em; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader { line-height: 1em; - padding: 0.5em; } + padding: 0.5em 0 0.5em 0.4em; + white-space: nowrap; + overflow: hidden; + position: relative; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemLabel { + display: inline-block; + padding-left: 0.25em; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .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%; } + .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 { + cursor: pointer; + float: right; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterTrash { + padding-right: 3px; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterToggle { + margin-right: 1em; } + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .InputfieldRepeaterItemControls .InputfieldRepeaterClone { + margin-right: 1em; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader .toggle-icon { line-height: 1em; margin-right: 0.5em; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader > .InputfieldRepeaterClone, .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader > .InputfieldRepeaterToggle, .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader > .InputfieldRepeaterTrash { - cursor: pointer; - float: right; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader > .InputfieldRepeaterTrash { - padding-right: 3px; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader > .InputfieldRepeaterToggle { - margin-right: 1em; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem > .InputfieldHeader > .InputfieldRepeaterClone { - margin-right: 1em; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending).InputfieldStateCollapsed > .InputfieldHeader { opacity: 0.90; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending).InputfieldStateCollapsed > .InputfieldHeader:hover { @@ -23,8 +43,10 @@ opacity: 0.7; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending).InputfieldRepeaterOff > .InputfieldHeader { opacity: 0.5; } - .Inputfields .InputfieldRepeater .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending).InputfieldRepeaterOff > .InputfieldHeader:not(:hover) { + .Inputfields .InputfieldRepeater .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending).InputfieldRepeaterOff > .InputfieldHeader:not(:hover) > .InputfieldRepeaterItemLabel { text-decoration: line-through; } +.Inputfields .InputfieldRepeater .InputfieldRepeaterItem.InputfieldRepeaterDeletePending > .InputfieldHeader > .InputfieldRepeaterItemLabel { + text-decoration: line-through; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem.InputfieldRepeaterItemLoading { margin-bottom: 1em; } .Inputfields .InputfieldRepeater .InputfieldRepeaterItem.InputfieldStateCollapsed > .InputfieldContent { @@ -44,6 +66,7 @@ .InputfieldRepeater ul.Inputfields + .InputfieldRepeaterAddItem { margin-top: 0; } .InputfieldRepeater .InputfieldRepeaterDrag { + display: inline-block; cursor: ns-resize; opacity: 0.7; line-height: 1em; } diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js index b0f26e9a..97034e6d 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.js @@ -3,128 +3,701 @@ * * Maintains a collection of fields that are repeated for any number of times. * - * ProcessWire 3.x (development), Copyright 2015 by Ryan Cramer + * ProcessWire 3.x, Copyright 2016 by Ryan Cramer * https://processwire.com * */ -var InputfieldRepeaterDepthSize = 50; -/** - * Delete click event (single item) - * - */ -function InputfieldRepeaterDeleteClick(e) { +function InputfieldRepeater($) { + + /** + * When depth is used, this indicates the indent (in pixels) used to show a single depth increment + * + * @type {number} + * + */ + var depthSize = 50; + + /** + * Whether or not AdminThemeReno is present + * + * @type {bool} + * + */ + var isReno = $('body').hasClass('AdminThemeReno'); - var $parent = $(this).parent('label').parent('li'); + /*** EVENTS ********************************************************************************************/ - if($parent.is('.InputfieldRepeaterNewItem')) { - // delete new item (noAjaxAdd mode) - var $numAddInput = $parent.parent().parent().find('.InputfieldRepeaterAddItem').children('input'); - $numAddInput.attr('value', parseInt($numAddInput.attr('value')-1)); // total number of new items to add, minus 1 - $parent.remove(); + /** + * Event handler for when an .InputfieldRepeater "reloaded" event is triggered + * + * @param event + * @param source + * + */ + var eventReloaded = function(event, source) { + if(typeof source != "undefined") { + if(source == 'InputfieldRepeaterItemEdit' || source == 'InputfieldRepeaterItemAdd') { + event.stopPropagation(); + var $r = $(this).find(".InputfieldRepeater"); + if($r.length) initRepeater($r); + return; + } + } + initRepeater($(this)); + }; - } else { - // delete existing item - var $checkbox = $parent.find('.InputfieldRepeaterDelete'); + /** + * Event handler for when the "delete" action is clicked + * + * @param e + * + */ + var eventDeleteClick = function(e) { + + var $header = $(this).closest('.InputfieldHeader'); + var $item = $header.parent(); + + 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 + $item.remove(); - if($checkbox.is(":checked")) { - $checkbox.removeAttr('checked'); - $parent.children('label').removeClass('ui-state-error').addClass('ui-state-default'); - //if($parent.is('.InputfieldStateCollapsed')) $parent.toggleClass('InputfieldStateCollapsed', 100); - $parent.removeClass('InputfieldRepeaterDeletePending'); } else { - $checkbox.attr('checked', 'checked'); - $parent.children('label').removeClass('ui-state-default').addClass('ui-state-error'); - if(!$parent.hasClass('InputfieldStateCollapsed')) $parent.toggleClass('InputfieldStateCollapsed', 100); - $parent.addClass('InputfieldRepeaterDeletePending'); + // delete existing item + var pageID = $item.attr('data-page'); + var $checkbox = $item.find('#delete_repeater' + pageID); + + if($checkbox.is(":checked")) { + $checkbox.removeAttr('checked'); + $header.removeClass('ui-state-error').addClass('ui-state-default'); + //if($parent.is('.InputfieldStateCollapsed')) $parent.toggleClass('InputfieldStateCollapsed', 100); + $item.removeClass('InputfieldRepeaterDeletePending'); + } else { + $checkbox.attr('checked', 'checked'); + $header.removeClass('ui-state-default').addClass('ui-state-error'); + if(!$item.hasClass('InputfieldStateCollapsed')) { + $header.find('.toggle-icon').click(); + //$item.toggleClass('InputfieldStateCollapsed', 100); + } + $item.addClass('InputfieldRepeaterDeletePending'); + } + $header.find('.InputfieldRepeaterItemControls').css('background-color', $header.css('background-color')); + } + + checkMax($item.closest('.InputfieldRepeater')); + e.stopPropagation(); + }; + + /** + * Event handler for when the "delete" link is double clicked + * + * @param e + * + */ + var eventDeleteDblClick = function(e) { + var $li = $(this).closest('li'); + var undelete = $li.hasClass('InputfieldRepeaterDeletePending'); + + function selectAll() { + $li.parent().children('li').each(function() { + var $item = $(this); + var $trashLink = $item.children('.InputfieldHeader').find('.InputfieldRepeaterTrash'); + if($item.hasClass('InputfieldRepeaterDeletePending')) { + if(undelete) $trashLink.click(); + } else { + if(!undelete) $trashLink.click(); + } + }); + } + + if(undelete) { + selectAll(); + } else { + ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.removeAll, selectAll); + } + }; + + /** + * Event handler for when the "clone" repeater item action is clicked + * + * @param e + * @returns {boolean} + * + */ + var eventCloneClick = function(e) { + 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') + .children('.InputfieldRepeaterAddItem').find('.InputfieldRepeaterAddLink:eq(0)'); + $addLink.attr('data-clone', itemID).click(); + $('html, body').animate({ scrollTop: $addLink.offset().top - 100}, 250, 'swing'); + }); + return false; + }; + + /** + * Event handler for when the repeater item "on/off" toggle is clicked + * + * @param e + * + */ + var eventToggleClick = function(e) { + var $this = $(this); + var toggleOn = $this.attr('data-on'); + var toggleOff = $this.attr('data-off'); + 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'); + } + + e.stopPropagation(); + }; + + /** + * Event handler for when a repeater item is about to be opened + * + * @param e + * + */ + var eventItemOpenReady = function(e) { + var $item = $(this); + var $loaded = $item.find(".InputfieldRepeaterLoaded"); + if(parseInt($loaded.val()) > 0) return; // item already loaded + $item.addClass('InputfieldRepeaterItemLoading'); + }; + + /** + * Event handler for when a repeater item is opened (primarily focused on ajax loaded items) + * + * @param e + * + */ + var eventItemOpened = function(e) { + var $item = $(this); + var $loaded = $item.find(".InputfieldRepeaterLoaded"); + + updateState($item); + + if(parseInt($loaded.val()) > 0) return; // item already loaded + + $loaded.val('1'); + + var $content = $item.find('.InputfieldContent').hide(); + var $repeater = $item.closest('.InputfieldRepeater'); + var pageID = $repeater.attr('data-page'); // $("#Inputfield_id").val(); + var itemID = parseInt($item.attr('data-page')); + var repeaterID = $repeater.attr('id'); + var fieldName = repeaterID.replace('wrap_Inputfield_', ''); + var ajaxURL = ProcessWire.config.InputfieldRepeater.editorUrl + '?id=' + pageID + '&field=' + fieldName + '&repeater_edit=' + itemID; + var $spinner = $item.find('.InputfieldRepeaterDrag'); + var $inputfields = $loaded.closest('.Inputfields'); + + $spinner.removeClass('fa-arrows').addClass('fa-spin fa-spinner'); + repeaterID = repeaterID.replace(/_repeater\d+$/, ''); + + $.get(ajaxURL, function(data) { + var $inputs = $(data).find('#' + repeaterID + ' > ' + + '.InputfieldContent > .Inputfields > ' + + '.InputfieldRepeaterItem > .InputfieldContent > .Inputfields > .InputfieldWrapper > ' + + '.Inputfields > .Inputfield'); + $inputfields.append($inputs); + $item.removeClass('InputfieldRepeaterItemLoading'); + InputfieldsInit($inputfields); + + var $repeaters = $inputs.filter('.InputfieldRepeater'); + if($repeaters.length) $repeaters.each(function() { + initRepeater($(this)); + }); + + $content.slideDown('fast', function() { + $spinner.removeClass('fa-spin fa-spinner').addClass('fa-arrows'); + }); + setTimeout(function() { + $inputfields.find('.Inputfield').trigger('reloaded', ['InputfieldRepeaterItemEdit']); + }, 50); + + }); + }; + + /** + * Event handler for when a repeater item is closed + * + * @param e + * + */ + var eventItemClosed = function(e) { + updateState($(this)); + }; + + /** + * Event handler for "add" link clicks + * + * Handles adding repeater items and initializing them + * + * @param e + * @returns {boolean} + * + */ + var eventAddLinkClick = function(e) { + var $addLink = $(this); + var $inputfields = $addLink.parent('p').prev('ul.Inputfields'); + var $inputfieldRepeater = $addLink.closest('.InputfieldRepeater'); + var $numAddInput = $addLink.parent().children('input'); + var newItemTotal = 0; // for noAjaxAdd mode + var useAjax = $addLink.attr('data-noajax').length == 0; + var cloneID = $addLink.attr('data-clone'); + + function addRepeaterItem($addItem) { + // make sure it has a unique ID + var id = $addItem.attr('id') + '_'; + while($('#' + id).length > 0) id += '_'; + $addItem.attr('id', id); + $inputfields.append($addItem); + $addItem.css('display', 'block'); + adjustItemLabel($addItem, true); + $addLink.trigger('repeateradd', [ $addItem ]); + } + + if(typeof cloneID == "undefined" || !cloneID) cloneID = null; + if(cloneID) $addLink.removeAttr('data-clone'); + + if(!useAjax) { + var $newItem = $inputfields.children('.InputfieldRepeaterNewItem'); // for noAjaxAdd mode, non-editable new item + newItemTotal = $newItem.length; + if(newItemTotal > 0) { + if(newItemTotal > 1) $newItem = $newItem.slice(0, 1); + var $addItem = $newItem.clone(true) + addRepeaterItem($addItem); + $numAddInput.attr('value', newItemTotal); + checkMax($inputfieldRepeater); + } + return false; + } + + // get addItem from ajax + var pageID = $inputfieldRepeater.attr('data-page'); + var fieldName = $inputfieldRepeater.attr('id').replace('wrap_Inputfield_', ''); + var $spinner = $addLink.parent().find('.InputfieldRepeaterSpinner'); + var ajaxURL = ProcessWire.config.InputfieldRepeater.editorUrl + '?id=' + pageID + '&field=' + fieldName; + + $spinner.removeClass($spinner.attr('data-off')).addClass($spinner.attr('data-on')); + + if(cloneID) { + ajaxURL += '&repeater_clone=' + cloneID; + } else { + ajaxURL += '&repeater_add=' + $addLink.attr('data-type'); + } + + // determine which page IDs we don't accept for new items (because we already have them rendered) + var $unpublishedItems = $inputfields.find('.InputfieldRepeaterUnpublished'); + if($unpublishedItems.length) { + ajaxURL += '&repeater_not='; + $unpublishedItems.each(function() { + ajaxURL += $(this).attr('data-page') + ','; + }); + } + + $.get(ajaxURL, function(data) { + //console.log(data); + $spinner.removeClass($spinner.attr('data-on')).addClass($spinner.attr('data-off')); + var $addItem = $(data).find(".InputfieldRepeaterItemRequested"); + if(!$addItem.length) { + // error + return; + } + addRepeaterItem($addItem); + $addItem.wrap("
"); // wrap for inputfields.js $target + InputfieldsInit($addItem.parent()); + initRepeater($addItem); + $addItem.unwrap(); // unwrap div once item initialized + $addItem.find('.Inputfield').trigger('reloaded', [ 'InputfieldRepeaterItemAdd' ]); + $addItem.find('.InputfieldRepeaterSort').val($inputfields.children().length); + $('html, body').animate({ + scrollTop: $addItem.offset().top + }, 500, 'swing'); + updateState($addItem); + checkMax($inputfieldRepeater); + $nestedRepeaters = $addItem.find('.InputfieldRepeater'); + if($nestedRepeaters.length) { + $nestedRepeaters.each(function() { + initRepeater($(this)); + }); + } + }); + + return false; + }; + + /** + * Event handler for the "open all" or "collapse all" functions + * + * @param e + * @returns {boolean} + * + */ + var eventOpenAllClick = function(e) { + e.stopPropagation(); + e.preventDefault(); + + var $repeater = $(this).closest('.InputfieldRepeater'); + var $items = $repeater.children('.InputfieldContent').children('.Inputfields').children('.InputfieldRepeaterItem'); + if(!$items.length) return false; + var $item = $items.eq(0); + + if($item.hasClass('InputfieldStateCollapsed')) { + var label = ProcessWire.config.InputfieldRepeater.labels.openAll; + var selector = '.InputfieldStateCollapsed'; + } else { + var label = ProcessWire.config.InputfieldRepeater.labels.collapseAll; + var selector = '.InputfieldRepeaterItem:not(.InputfieldStateCollapsed)'; + } + ProcessWire.confirm(label, function() { + $items.filter(selector).each(function() { + $(this).children('.InputfieldHeader').find('.toggle-icon').click(); + }); + }); + return false; + }; + + /*** GENERAL FUNCTIONS **********************************************************************************/ + + /** + * Given an InputfieldRepeaterItem update the label consistent with any present formatting sting + * + * 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) + * + */ + function adjustItemLabel($item, doIncrement) { + + var $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(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); } } - - InputfieldRepeaterCheckMax($parent.closest('.InputfieldRepeater')); - - e.stopPropagation(); -} -/** - * Delete double-click event (multi-item) - * - */ -function InputfieldRepeaterDeleteDblClick(e) { + /** + * Determine the sortable depth of a repeater item and either return it or apply it + * + * @param ui The 'ui' argument provided to jQuery UI sortable events + * @param maxDepth Maximum allowed depth + * @param updateNow Specify true to apply the determined depth now or false to just return it. + * @returns {number} Depth integer value between 0 and maxDepth + * + */ + function sortableDepth(ui, maxDepth, updateNow) { + + var $depth = ui.item.find('.InputfieldRepeaterDepth'); + var depth = -1; + var prevDepth = parseInt($depth.val()); + var left = ui.position.left; - var $li = $(this).closest('li'); - var undelete = $li.hasClass('InputfieldRepeaterDeletePending'); - - function selectAll() { - $li.parent().children('li').each(function() { - var $item = $(this); - var $trashLink = $item.children('.InputfieldHeader').children('.InputfieldRepeaterTrash'); - if($item.hasClass('InputfieldRepeaterDeletePending')) { - if(undelete) $trashLink.click(); + // AdminThemeReno has something different going on with the left positions, so we adjust for that here + if(isReno) left -= depthSize; + + if(left < 0) { + depth = prevDepth - Math.round(Math.abs(left) / depthSize); + // console.log('decrease depth to: ' + depth); + + } else { + depth = Math.round(left / depthSize) + prevDepth; + // console.log('increase depth to: ' + depth); + } + + if(depth < 1) { + depth = 0; + } else if(depth > maxDepth) { + depth = maxDepth; + } + + if(updateNow) { + if(depth) { + ui.item.css('margin-left', (depth * depthSize) + 'px'); } else { - if(!undelete) $trashLink.click(); + ui.item.css('margin-left', 0); + } + + $depth.val(depth); + ui.item.children('.InputfieldHeader').removeClass('ui-state-error'); + } + + return depth; + } + + /*** INIT FUNCTIONS **********************************************************************************/ + + /** + * Initialize repeater item depths + * + * Applies a left-margin to repeater items consistent with with value in + * each item's input.InputfieldRepeaterDepth hidden input. + * + * @param $inputfieldRepeater + * + */ + function initDepths($inputfieldRepeater) { + $inputfieldRepeater.find('.InputfieldRepeaterDepth').each(function() { + var $depth = $(this); + var depth = $depth.val(); + var $item = $depth.closest('.InputfieldRepeaterItem'); + var currentLeft = $item.css('margin-left'); + if(currentLeft == 'auto') currentLeft = 0; + currentLeft = parseInt(currentLeft); + var targetLeft = depth * depthSize; + if(targetLeft != currentLeft) { + $item.css('margin-left', targetLeft + 'px'); } }); } - if(undelete) { - selectAll(); - } else { - ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.removeAll, selectAll); + /** + * Make a repeater sortable + * + * @param $inputfieldRepeater The parent .InputfieldRepeater + * @param $inputfields The .Inputfields parent of the sortable items + * + */ + function initSortable($inputfieldRepeater, $inputfields) { + + var maxDepth = parseInt($inputfieldRepeater.attr('data-depth')); + var sortableOptions = { + items: '> li:not(.InputfieldRepeaterNewItem)', + handle: '.InputfieldRepeaterDrag', + start: function(e, ui) { + ui.item.find('.InputfieldHeader').addClass("ui-state-highlight"); + + // CKEditor doesn't like being sorted, do destroy when sort starts, and reload after sort + ui.item.find('textarea.InputfieldCKEditorNormal.InputfieldCKEditorLoaded').each(function() { + $(this).removeClass('InputfieldCKEditorLoaded'); + var editor = CKEDITOR.instances[$(this).attr('id')]; + editor.destroy(); + CKEDITOR.remove($(this).attr('id')); + }); + + // TinyMCE instances don't like to be dragged, so we disable them temporarily + ui.item.find('.InputfieldTinyMCE textarea').each(function() { + tinyMCE.execCommand('mceRemoveControl', false, $(this).attr('id')); + }); + }, + + stop: function(e, ui) { + if(maxDepth > 0) { + sortableDepth(ui, maxDepth, true); + } + + ui.item.find('.InputfieldHeader').removeClass("ui-state-highlight"); + $(this).children().each(function(n) { + $(this).find('.InputfieldRepeaterSort').slice(0,1).attr('value', n); + }); + + // Re-enable CKEditor instances + ui.item.find('textarea.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)').each(function() { + $(this).closest('.InputfieldCKEditor').trigger('reloaded', [ 'InputfieldRepeaterSort' ]); + }); + + // Re-enable the TinyMCE instances + ui.item.find('.InputfieldTinyMCE textarea').each(function() { + tinyMCE.execCommand('mceAddControl', false, $(this).attr('id')); + }); + } + }; + + if(maxDepth > 0) { + initDepths($inputfieldRepeater); + sortableOptions.grid = [ depthSize, 1 ]; + sortableOptions.sort = function(event, ui) { + var depth = sortableDepth(ui, 99, false); + var $header = ui.item.children('.InputfieldHeader'); + if(depth > maxDepth) { + // beyond max depth allowed + $header.addClass('ui-state-error'); + } else if($header.hasClass('ui-state-error')) { + // no problems + $header.removeClass('ui-state-error'); + } + }; + } else { + sortableOptions.axis = 'y'; + } + // apply "ui-state-focus" class when an item is being dragged + $(".InputfieldRepeaterDrag", $inputfields).hover(function() { + $(this).parent('label').addClass('ui-state-focus'); + }, function() { + $(this).parent('label').removeClass('ui-state-focus'); + }); + + $inputfields.sortable(sortableOptions); } -} -function InputfieldRepeaterCloneClick(e) { - 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') - .children('.InputfieldRepeaterAddItem').find('.InputfieldRepeaterAddLink:eq(0)'); - $addLink.attr('data-clone', itemID).click(); - $('html, body').animate({ scrollTop: $addLink.offset().top - 100}, 250, 'swing'); - }); - return false; -} + /** + * Initialize a repeater + * + * @param $this Can be an .InputfieldRepeater or an .InputfieldRepeaterItem + * + */ + function initRepeater($this) { -/** - * Event handler for the "publish" toggle in the header of each repeater item - * - */ -function InputfieldRepeaterToggleClick(e) { - var $this = $(this); - var toggleOn = $this.attr('data-on'); - var toggleOff = $this.attr('data-off'); - 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($this.hasClass('InputfieldRepeaterItem')) { + // single repeater item + var $inputfields = $this; + var $inputfieldRepeater = $this.closest('.InputfieldRepeater'); + var isItem = true; + } else { + // enter repeater + var $inputfields = $this.find('.Inputfields:eq(0)'); + var $inputfieldRepeater = $this; + var isItem = false; + } + + if($inputfields.hasClass('InputfieldRepeaterInit')) return; + + $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')); + } else { + initHeaders($(".InputfieldRepeaterItem > .InputfieldHeader", $this)); + } + + if(renderValueMode) { + // nothing further needed if only rendering the value + initDepths($inputfieldRepeater); + return; + } + + // hovering the trash gives a preview of what clicking it would do + $(".InputfieldRepeaterTrash", $this).hover(function() { + var $label = $(this).closest('label'); + if(!$label.parents().hasClass('InputfieldRepeaterDeletePending')) $label.addClass('ui-state-error'); + $label.find('.InputfieldRepeaterItemControls').css('background-color', $label.css('background-color')); + }, function() { + var $label = $(this).closest('label'); + if(!$label.parent().hasClass('InputfieldRepeaterDeletePending')) $label.removeClass('ui-state-error'); + $label.find('.InputfieldRepeaterItemControls').css('background-color', $label.css('background-color')); + }); + + // if we only init'd a single item, now make $inputfields refer to all repeater items for sortable init + if(isItem) $inputfields = $inputfieldRepeater.find('.Inputfields:eq(0)'); + + // setup the sortable + initSortable($inputfieldRepeater, $inputfields); + + // setup the add links + $(".InputfieldRepeaterAddLink:not(.InputfieldRepeaterAddLinkInit)", $inputfieldRepeater) + .addClass('InputfieldRepeaterAddLinkInit') + .click(eventAddLinkClick); + + // check for maximum items + if($inputfieldRepeater.hasClass('InputfieldRepeaterMax')) { + checkMax($inputfieldRepeater); + } } - e.stopPropagation(); -} -/** - * Prepares for open of ajax loaded item (Inputfields "openReady" event handler) - * - */ -function InputfieldRepeaterItemOpenReady(e) { - var $item = $(this); - var $loaded = $item.find(".InputfieldRepeaterLoaded"); - if(parseInt($loaded.val()) > 0) return; // item already loaded - $item.addClass('InputfieldRepeaterItemLoading'); -} + /** + * 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; + var max = parseInt($inputfieldRepeater.attr('data-max')); + if(max <= 0) return; + var $content = $inputfieldRepeater.children('.InputfieldContent'); + var num = $content.children('.Inputfields').children('li:not(.InputfieldRepeaterDeletePending)').length; + var $addItem = $content.children('.InputfieldRepeaterAddItem'); + if(num > max) { + $addItem.hide(); + } else if(!$addItem.is(":visible")) { + $addItem.show(); + } + } -/** - * Remember which repeater items are open - * - */ -function InputfieldRepeaterUpdateState($item) { - if($item.closest('.InputfieldRepeaterRememberOpen').length) { + /** + * Update state of the remembered open repeaters + * + * Note: this records state for all repeaters on the page in cookie 'repeaters_open' + * that are configured to be remembered. + * + * @param $item .InputfieldRepeaterItem + * + */ + function updateState($item) { + if($item.closest('.InputfieldRepeaterRememberOpen').length < 1) return; var val = ''; $(".InputfieldRepeaterItem:not(.InputfieldStateCollapsed)").each(function() { var id = parseInt($(this).attr('data-page')); @@ -134,415 +707,32 @@ function InputfieldRepeaterUpdateState($item) { }); $.cookie('repeaters_open', val); } -} -function InputfieldRepeaterCheckMax($inputfield) { - if(!$inputfield.hasClass('InputfieldRepeaterMax')) return; - var max = parseInt($inputfield.attr('data-max')); - if(max <= 0) return; - var $content = $inputfield.children('.InputfieldContent'); - var num = $content.children('.Inputfields').children('li:not(.InputfieldRepeaterDeletePending)').length; - var $addItem = $content.children('.InputfieldRepeaterAddItem'); - if(num > max) { - $addItem.hide(); - } else if(!$addItem.is(":visible")) { - $addItem.show(); - } -} - -function InputfieldRepeaterCheckDepths($inputfield) { - $inputfield.find('.InputfieldRepeaterDepth').each(function() { - var $depth = $(this); - var depth = $depth.val(); - var $item = $depth.closest('.InputfieldRepeaterItem'); - var currentLeft = $item.css('margin-left'); - if(currentLeft == 'auto') currentLeft = 0; - currentLeft = parseInt(currentLeft); - var targetLeft = depth * InputfieldRepeaterDepthSize; - if(targetLeft != currentLeft) { - $item.css('margin-left', targetLeft + 'px'); - $item.data('lastLeft', targetLeft); - } - }); -} - -/** - * Event called when repeater item is collapsed - * - */ -function InputfieldRepeaterItemClosed(e) { - InputfieldRepeaterUpdateState($(this)); -} - -/** - * Handles load of ajax editable items (Inputfields "opened" event handler) - * - */ -function InputfieldRepeaterItemOpened(e) { - - var $item = $(this); - var $loaded = $item.find(".InputfieldRepeaterLoaded"); - - InputfieldRepeaterUpdateState($item); - - if(parseInt($loaded.val()) > 0) return; // item already loaded - - $loaded.val('1'); - - var $content = $item.find('.InputfieldContent').hide(); - var $repeater = $item.closest('.InputfieldRepeater'); - var pageID = $repeater.attr('data-page'); // $("#Inputfield_id").val(); - var itemID = parseInt($item.attr('data-page')); - var repeaterID = $repeater.attr('id'); - var fieldName = repeaterID.replace('wrap_Inputfield_', ''); - var ajaxURL = ProcessWire.config.InputfieldRepeater.editorUrl + '?id=' + pageID + '&field=' + fieldName + '&repeater_edit=' + itemID; - var $spinner = $item.find('.InputfieldRepeaterDrag'); - var $inputfields = $loaded.closest('.Inputfields'); - - $spinner.removeClass('fa-arrows').addClass('fa-spin fa-spinner'); - repeaterID = repeaterID.replace(/_repeater\d+$/, ''); - - $.get(ajaxURL, function(data) { - var $inputs = $(data).find('#' + repeaterID + ' > ' + - '.InputfieldContent > .Inputfields > ' + - '.InputfieldRepeaterItem > .InputfieldContent > .Inputfields > .InputfieldWrapper > ' + - '.Inputfields > .Inputfield'); - $inputfields.append($inputs); - $item.removeClass('InputfieldRepeaterItemLoading'); - InputfieldsInit($inputfields); + /** + * Initialization for document.ready + * + */ + function init() { - var $repeaters = $inputs.filter('.InputfieldRepeater'); - if($repeaters.length) $repeaters.each(function() { - InputfieldRepeaterInit($(this)); + $('.InputfieldRepeater').each(function() { + initRepeater($(this)); }); - - $content.slideDown('fast', function() { - $spinner.removeClass('fa-spin fa-spinner').addClass('fa-arrows'); - }); - setTimeout(function() { - $inputfields.find('.Inputfield').trigger('reloaded', ['InputfieldRepeaterItemEdit']); - }, 50); - - }); + + $(document) + .on('reloaded', '.InputfieldRepeater', eventReloaded) + .on('click', '.InputfieldRepeaterTrash', eventDeleteClick) + .on('dblclick', '.InputfieldRepeaterTrash', eventDeleteDblClick) + .on('click', '.InputfieldRepeaterClone', eventCloneClick) + .on('dblclick', '.InputfieldRepeaterToggle', eventOpenAllClick) + .on('click', '.InputfieldRepeaterToggle', eventToggleClick) + .on('opened', '.InputfieldRepeaterItem', eventItemOpened) + .on('closed', '.InputfieldRepeaterItem', eventItemClosed) + .on('openReady', '.InputfieldRepeaterItem', eventItemOpenReady); + } + + init(); } -/** - * Update a repeater label for the given repeater $item, optionally incrementing the index number - * - */ -function InputfieldRepeaterAdjustLabel($item, doIncrement) { - - var $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(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); - } -} - -/** - * Initialize a repeater field - * - */ -function InputfieldRepeaterInit($this) { - - if($this.hasClass('InputfieldRepeaterItem')) { - // single repeater item - var $inputfields = $this; - var $inputfieldRepeater = $this.closest('.InputfieldRepeater'); - var isItem = true; - } else { - // enter repeater - var $inputfields = $this.find('.Inputfields:eq(0)'); - var $inputfieldRepeater = $this; - var isItem = false; - } - - //if(!$inputfields.length) return; - if($inputfields.hasClass('InputfieldRepeaterInit')) return; - $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 setupRepeaterHeaders($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)); - $t.prepend($toggle.clone(true).addClass($t.parent().hasClass('InputfieldRepeaterOff') ? 'fa-toggle-off' : 'fa-toggle-on')); - $t.prepend($delete.clone(true)); - } - InputfieldRepeaterAdjustLabel($item, false); - }); - } - - if(isItem) { - setupRepeaterHeaders($this.children('.InputfieldHeader')); - } else { - setupRepeaterHeaders($(".InputfieldRepeaterItem > .InputfieldHeader", $this)); - } - - if(renderValueMode) return; - - $(".InputfieldRepeaterDrag", $this).hover(function() { - $(this).parent('label').addClass('ui-state-focus'); - }, function() { - $(this).parent('label').removeClass('ui-state-focus'); - }); - - $(".InputfieldRepeaterTrash", $this).hover(function() { - var $label = $(this).parent('label'); - if(!$label.parent().is('.InputfieldRepeaterDeletePending')) $label.addClass('ui-state-error'); - }, function() { - var $label = $(this).parent('label'); - if(!$label.parent().is('.InputfieldRepeaterDeletePending')) $label.removeClass('ui-state-error'); - }); - - if(isItem) { - // if we only init'd a single item, now make $inputfields refer to all repeater items for sortable init - $inputfields = $this.closest('.InputfieldRepeater').find('.Inputfields:eq(0)'); - } - - var sortableOptions = { - items: '> li:not(.InputfieldRepeaterNewItem)', - handle: '.InputfieldRepeaterDrag', - start: function(e, ui) { - ui.item.find('.InputfieldHeader').addClass("ui-state-highlight"); - - // CKEditor doesn't like being sorted, do destroy when sort starts, and reload after sort - ui.item.find('textarea.InputfieldCKEditorNormal.InputfieldCKEditorLoaded').each(function() { - $(this).removeClass('InputfieldCKEditorLoaded'); - var editor = CKEDITOR.instances[$(this).attr('id')]; - editor.destroy(); - CKEDITOR.remove($(this).attr('id')); - }); - - // TinyMCE instances don't like to be dragged, so we disable them temporarily - ui.item.find('.InputfieldTinyMCE textarea').each(function() { - tinyMCE.execCommand('mceRemoveControl', false, $(this).attr('id')); - }); - }, - - stop: function(e, ui) { - - ui.item.find('.InputfieldHeader').removeClass("ui-state-highlight"); - $(this).children().each(function(n) { - $(this).find('.InputfieldRepeaterSort').slice(0,1).attr('value', n); - }); - - // Re-enable CKEditor instances - ui.item.find('textarea.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)').each(function() { - $(this).closest('.InputfieldCKEditor').trigger('reloaded', [ 'InputfieldRepeaterSort' ]); - }); - - // Re-enable the TinyMCE instances - ui.item.find('.InputfieldTinyMCE textarea').each(function() { - tinyMCE.execCommand('mceAddControl', false, $(this).attr('id')); - }); - } - }; - - var maxDepth = parseInt($inputfieldRepeater.attr('data-depth')); - if(maxDepth > 0) { - InputfieldRepeaterCheckDepths($inputfieldRepeater); - sortableOptions.grid = [ InputfieldRepeaterDepthSize, 1 ]; - sortableOptions.beforeStop = function(event, ui) { - var lastLeft = ui.item.data('lastLeft'); - if(!lastLeft) lastLeft = 0; - var left = lastLeft + ui.position.left; - left -= InputfieldRepeaterDepthSize / 2; - if(left > 25 && left < InputfieldRepeaterDepthSize) left = InputfieldRepeaterDepthSize; - var depth = Math.round(left / InputfieldRepeaterDepthSize); - if(depth < 1) depth = 0; - if(depth > maxDepth) depth = maxDepth; - if(depth) { - ui.item.css('margin-left', (depth * InputfieldRepeaterDepthSize) + 'px'); - } else { - ui.item.css('margin-left', 0); - } - ui.item.find('.InputfieldRepeaterDepth').val(depth); - ui.item.data('lastLeft', left); - ui.item.children('.InputfieldHeader').removeClass('ui-state-error'); - }; - sortableOptions.sort = function(event, ui) { - var lastLeft = ui.item.data('lastLeft'); - if(!lastLeft) lastLeft = 0; - var left = lastLeft + ui.position.left; - var $header = ui.item.children('.InputfieldHeader'); - if(left > (InputfieldRepeaterDepthSize * maxDepth) + (InputfieldRepeaterDepthSize / 2)) { - // beyond max depth allowed - $header.addClass('ui-state-error'); - } else if($header.hasClass('ui-state-error')) { - $header.removeClass('ui-state-error'); - } - }; - } else { - sortableOptions.axis = 'y'; - } - - $inputfields.sortable(sortableOptions); - - var $addLinks = $(".InputfieldRepeaterAddLink:not(.InputfieldRepeaterAddLinkInit)", $this); - $addLinks.addClass('InputfieldRepeaterAddLinkInit'); - $addLinks.click(function() { - - var $addLink = $(this); - var $inputfields = $(this).parent('p').prev('ul.Inputfields'); - var $numAddInput = $(this).parent().children('input'); - var newItemTotal = 0; // for noAjaxAdd mode - var useAjax = $addLink.attr('data-noajax').length == 0; - var cloneID = $addLink.attr('data-clone'); - - function addRepeaterItem($addItem) { - // make sure it has a unique ID - var id = $addItem.attr('id') + '_'; - while($('#' + id).length > 0) id += '_'; - $addItem.attr('id', id); - $inputfields.append($addItem); - $addItem.css('display', 'block'); - //$addItem.find('.InputfieldRepeaterTrash').click(InputfieldRepeaterDeleteClick); - InputfieldRepeaterAdjustLabel($addItem, true); - $addLink.trigger('repeateradd', [ $addItem ]); - } - - if(typeof cloneID == "undefined" || !cloneID) cloneID = null; - if(cloneID) $addLink.removeAttr('data-clone'); - - if(!useAjax) { - var $newItem = $inputfields.children('.InputfieldRepeaterNewItem'); // for noAjaxAdd mode, non-editable new item - newItemTotal = $newItem.length; - if(newItemTotal > 0) { - if(newItemTotal > 1) $newItem = $newItem.slice(0, 1); - var $addItem = $newItem.clone(true) - addRepeaterItem($addItem); - $numAddInput.attr('value', newItemTotal); - InputfieldRepeaterCheckMax($inputfieldRepeater); - } - - } else { - // get addItem from ajax - var pageID = $addLink.closest('.InputfieldRepeater').attr('data-page'); // $("#Inputfield_id").val(); - var fieldName = $addLink.closest('.InputfieldRepeater').attr('id').replace('wrap_Inputfield_', ''); - var $spinner = $addLink.parent().find('.InputfieldRepeaterSpinner'); - var ajaxURL = ProcessWire.config.InputfieldRepeater.editorUrl + '?id=' + pageID + '&field=' + fieldName; - - $spinner.removeClass($spinner.attr('data-off')).addClass($spinner.attr('data-on')); - - if(cloneID) { - ajaxURL += '&repeater_clone=' + cloneID; - } else { - ajaxURL += '&repeater_add=' + $addLink.attr('data-type'); - } - - // determine which page IDs we don't accept for new items (because we already have them rendered) - var $unpublishedItems = $inputfields.find('.InputfieldRepeaterUnpublished'); - if($unpublishedItems.length) { - ajaxURL += '&repeater_not='; - $unpublishedItems.each(function() { - ajaxURL += $(this).attr('data-page') + ','; - }); - } - - $.get(ajaxURL, function(data) { - //console.log(data); - $spinner.removeClass($spinner.attr('data-on')).addClass($spinner.attr('data-off')); - var $addItem = $(data).find(".InputfieldRepeaterItemRequested"); - if(!$addItem.length) { - // error - // console.log("Can't find item: .InputfieldRepeaterItem.InputfieldRepeaterUnpublished"); - return; - } - addRepeaterItem($addItem); - $addItem.wrap("
"); // wrap for inputfields.js $target - InputfieldsInit($addItem.parent()); - InputfieldRepeaterInit($addItem); - $addItem.unwrap(); // unwrap div once item initialized - //$addItem.find('input.InputfieldRepeaterPublish').attr('value', 1); - $addItem.find('.Inputfield').trigger('reloaded', [ 'InputfieldRepeaterItemAdd' ]); - $addItem.find('.InputfieldRepeaterSort').val($inputfields.children().length); - $('html, body').animate({ - scrollTop: $addItem.offset().top - }, 500, 'swing'); - InputfieldRepeaterUpdateState($addItem); - InputfieldRepeaterCheckMax($inputfieldRepeater); - $nestedRepeaters = $addItem.find('.InputfieldRepeater'); - if($nestedRepeaters.length) { - $nestedRepeaters.each(function() { - InputfieldRepeaterInit($(this)); - }); - } - }); - } - - return false; - }); - - //$(".InputfieldRepeaterUnpublished").children('.InputfieldHeader').addClass('ui-priority-secondary'); - - if($inputfieldRepeater.hasClass('InputfieldRepeaterMax')) InputfieldRepeaterCheckMax($inputfieldRepeater); - -} - -$(document).ready(function() { - - $(".InputfieldRepeater").each(function() { - InputfieldRepeaterInit($(this)); - }); - - $(document).on('reloaded', '.InputfieldRepeater', function(event, source) { - if(typeof source != "undefined") { - if(source == 'InputfieldRepeaterItemEdit' || source == 'InputfieldRepeaterItemAdd') { - event.stopPropagation(); - var $r = $(this).find(".InputfieldRepeater"); - if($r.length) InputfieldRepeaterInit($r); - return; - } - } - InputfieldRepeaterInit($(this)); - }); - - $(document) - .on('click', '.InputfieldRepeaterTrash', InputfieldRepeaterDeleteClick) - .on('dblclick', '.InputfieldRepeaterTrash', InputfieldRepeaterDeleteDblClick) - .on('click', '.InputfieldRepeaterClone', InputfieldRepeaterCloneClick) - .on('click', '.InputfieldRepeaterToggle', InputfieldRepeaterToggleClick) - .on('opened', '.InputfieldRepeaterItem', InputfieldRepeaterItemOpened) - .on('closed', '.InputfieldRepeaterItem', InputfieldRepeaterItemClosed) - .on('openReady', '.InputfieldRepeaterItem', InputfieldRepeaterItemOpenReady); -}); - +jQuery(document).ready(function($) { + InputfieldRepeater($); +}); diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js index ad52f61a..a5381ff6 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.min.js @@ -1 +1 @@ -var InputfieldRepeaterDepthSize=50;function InputfieldRepeaterDeleteClick(d){var c=$(this).parent("label").parent("li");if(c.is(".InputfieldRepeaterNewItem")){var a=c.parent().parent().find(".InputfieldRepeaterAddItem").children("input");a.attr("value",parseInt(a.attr("value")-1));c.remove()}else{var b=c.find(".InputfieldRepeaterDelete");if(b.is(":checked")){b.removeAttr("checked");c.children("label").removeClass("ui-state-error").addClass("ui-state-default");c.removeClass("InputfieldRepeaterDeletePending")}else{b.attr("checked","checked");c.children("label").removeClass("ui-state-default").addClass("ui-state-error");if(!c.hasClass("InputfieldStateCollapsed")){c.toggleClass("InputfieldStateCollapsed",100)}c.addClass("InputfieldRepeaterDeletePending")}}InputfieldRepeaterCheckMax(c.closest(".InputfieldRepeater"));d.stopPropagation()}function InputfieldRepeaterDeleteDblClick(c){var d=$(this).closest("li");var a=d.hasClass("InputfieldRepeaterDeletePending");function b(){d.parent().children("li").each(function(){var e=$(this);var f=e.children(".InputfieldHeader").children(".InputfieldRepeaterTrash");if(e.hasClass("InputfieldRepeaterDeletePending")){if(a){f.click()}}else{if(!a){f.click()}}})}if(a){b()}else{ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.removeAll,b)}}function InputfieldRepeaterCloneClick(b){var a=$(this).closest(".InputfieldRepeaterItem");ProcessWire.confirm(ProcessWire.config.InputfieldRepeater.labels.clone,function(){var d=a.attr("data-page");var c=a.closest(".InputfieldRepeater").children(".InputfieldContent").children(".InputfieldRepeaterAddItem").find(".InputfieldRepeaterAddLink:eq(0)");c.attr("data-clone",d).click();$("html, body").animate({scrollTop:c.offset().top-100},250,"swing")});return false}function InputfieldRepeaterToggleClick(f){var d=$(this);var c=d.attr("data-on");var a=d.attr("data-off");var b=d.closest(".InputfieldRepeaterItem");var g=b.find(".InputfieldRepeaterPublish");if(d.hasClass(c)){d.removeClass(c).addClass(a);b.addClass("InputfieldRepeaterUnpublished InputfieldRepeaterOff");g.val("-1")}else{d.removeClass(a).addClass(c);b.removeClass("InputfieldRepeaterUnpublished InputfieldRepeaterOff");g.val("1")}f.stopPropagation()}function InputfieldRepeaterItemOpenReady(c){var a=$(this);var b=a.find(".InputfieldRepeaterLoaded");if(parseInt(b.val())>0){return}a.addClass("InputfieldRepeaterItemLoading")}function InputfieldRepeaterUpdateState(a){if(a.closest(".InputfieldRepeaterRememberOpen").length){var b="";$(".InputfieldRepeaterItem:not(.InputfieldStateCollapsed)").each(function(){var c=parseInt($(this).attr("data-page"));if(c>0){b+=c+"|"}});$.cookie("repeaters_open",b)}}function InputfieldRepeaterCheckMax(d){if(!d.hasClass("InputfieldRepeaterMax")){return}var a=parseInt(d.attr("data-max"));if(a<=0){return}var c=d.children(".InputfieldContent");var b=c.children(".Inputfields").children("li:not(.InputfieldRepeaterDeletePending)").length;var e=c.children(".InputfieldRepeaterAddItem");if(b>a){e.hide()}else{if(!e.is(":visible")){e.show()}}}function InputfieldRepeaterCheckDepths(a){a.find(".InputfieldRepeaterDepth").each(function(){var d=$(this);var e=d.val();var b=d.closest(".InputfieldRepeaterItem");var c=b.css("margin-left");if(c=="auto"){c=0}c=parseInt(c);var f=e*InputfieldRepeaterDepthSize;if(f!=c){b.css("margin-left",f+"px");b.data("lastLeft",f)}})}function InputfieldRepeaterItemClosed(a){InputfieldRepeaterUpdateState($(this))}function InputfieldRepeaterItemOpened(h){var j=$(this);var m=j.find(".InputfieldRepeaterLoaded");InputfieldRepeaterUpdateState(j);if(parseInt(m.val())>0){return}m.val("1");var b=j.find(".InputfieldContent").hide();var d=j.closest(".InputfieldRepeater");var i=d.attr("data-page");var c=parseInt(j.attr("data-page"));var l=d.attr("id");var k=l.replace("wrap_Inputfield_","");var a=ProcessWire.config.InputfieldRepeater.editorUrl+"?id="+i+"&field="+k+"&repeater_edit="+c;var f=j.find(".InputfieldRepeaterDrag");var g=m.closest(".Inputfields");f.removeClass("fa-arrows").addClass("fa-spin fa-spinner");l=l.replace(/_repeater\d+$/,"");$.get(a,function(o){var e=$(o).find("#"+l+" > .InputfieldContent > .Inputfields > .InputfieldRepeaterItem > .InputfieldContent > .Inputfields > .InputfieldWrapper > .Inputfields > .Inputfield");g.append(e);j.removeClass("InputfieldRepeaterItemLoading");InputfieldsInit(g);var n=e.filter(".InputfieldRepeater");if(n.length){n.each(function(){InputfieldRepeaterInit($(this))})}b.slideDown("fast",function(){f.removeClass("fa-spin fa-spinner").addClass("fa-arrows")});setTimeout(function(){g.find(".Inputfield").trigger("reloaded",["InputfieldRepeaterItemEdit"])},50)})}function InputfieldRepeaterAdjustLabel(c,d){var b=c.children("label");var e=b.html();var a=e;if(d&&e.indexOf("#")>-1){num=c.siblings(".InputfieldRepeaterItem:visible").length+1;e=e.replace(/#[0-9]+/,"#"+num)}if(e.indexOf("{")>-1){e=e.replace(/\{/,'');e=e.replace(/}/,"")}if(e!=a){b.html(e)}}function InputfieldRepeaterInit(l){if(l.hasClass("InputfieldRepeaterItem")){var h=l;var b=l.closest(".InputfieldRepeater");var g=true}else{var h=l.find(".Inputfields:eq(0)");var b=l;var g=false}if(h.hasClass("InputfieldRepeaterInit")){return}h.addClass("InputfieldRepeaterInit");var f=h.closest(".InputfieldRenderValueMode").length>0;var k=$("").css("display","block");var i=$("");var a=$("");var j=ProcessWire.config.InputfieldRepeater;var e=!b.hasClass("InputfieldRepeaterNoAjaxAdd");if(j){a.attr("title",j.labels.toggle);i.attr("title",j.labels.remove);k.attr("title",j.labels.clone)}$("input.InputfieldRepeaterDelete",l).parents(".InputfieldCheckbox").hide();function m(o){o.each(function(){var r=$(this);if(r.hasClass("InputfieldRepeaterHeaderInit")){return}var q="fa-arrows";var p=r.parent();if(p.hasClass("InputfieldRepeaterNewItem")){q="fa-plus";r.addClass("ui-priority-secondary")}r.addClass("ui-state-default InputfieldRepeaterHeaderInit");r.prepend("");if(!f){if(e){r.prepend(k.clone(true))}r.prepend(a.clone(true).addClass(r.parent().hasClass("InputfieldRepeaterOff")?"fa-toggle-off":"fa-toggle-on"));r.prepend(i.clone(true))}InputfieldRepeaterAdjustLabel(p,false)})}if(g){m(l.children(".InputfieldHeader"))}else{m($(".InputfieldRepeaterItem > .InputfieldHeader",l))}if(f){return}$(".InputfieldRepeaterDrag",l).hover(function(){$(this).parent("label").addClass("ui-state-focus")},function(){$(this).parent("label").removeClass("ui-state-focus")});$(".InputfieldRepeaterTrash",l).hover(function(){var o=$(this).parent("label");if(!o.parent().is(".InputfieldRepeaterDeletePending")){o.addClass("ui-state-error")}},function(){var o=$(this).parent("label");if(!o.parent().is(".InputfieldRepeaterDeletePending")){o.removeClass("ui-state-error")}});if(g){h=l.closest(".InputfieldRepeater").find(".Inputfields:eq(0)")}var d={items:"> li:not(.InputfieldRepeaterNewItem)",handle:".InputfieldRepeaterDrag",start:function(p,o){o.item.find(".InputfieldHeader").addClass("ui-state-highlight");o.item.find("textarea.InputfieldCKEditorNormal.InputfieldCKEditorLoaded").each(function(){$(this).removeClass("InputfieldCKEditorLoaded");var q=CKEDITOR.instances[$(this).attr("id")];q.destroy();CKEDITOR.remove($(this).attr("id"))});o.item.find(".InputfieldTinyMCE textarea").each(function(){tinyMCE.execCommand("mceRemoveControl",false,$(this).attr("id"))})},stop:function(p,o){o.item.find(".InputfieldHeader").removeClass("ui-state-highlight");$(this).children().each(function(q){$(this).find(".InputfieldRepeaterSort").slice(0,1).attr("value",q)});o.item.find("textarea.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)").each(function(){$(this).closest(".InputfieldCKEditor").trigger("reloaded",["InputfieldRepeaterSort"])});o.item.find(".InputfieldTinyMCE textarea").each(function(){tinyMCE.execCommand("mceAddControl",false,$(this).attr("id"))})}};var c=parseInt(b.attr("data-depth"));if(c>0){InputfieldRepeaterCheckDepths(b);d.grid=[InputfieldRepeaterDepthSize,1];d.beforeStop=function(o,p){var r=p.item.data("lastLeft");if(!r){r=0}var q=r+p.position.left;q-=InputfieldRepeaterDepthSize/2;if(q>25&&qc){s=c}if(s){p.item.css("margin-left",(s*InputfieldRepeaterDepthSize)+"px")}else{p.item.css("margin-left",0)}p.item.find(".InputfieldRepeaterDepth").val(s);p.item.data("lastLeft",q);p.item.children(".InputfieldHeader").removeClass("ui-state-error")};d.sort=function(p,q){var s=q.item.data("lastLeft");if(!s){s=0}var r=s+q.position.left;var o=q.item.children(".InputfieldHeader");if(r>(InputfieldRepeaterDepthSize*c)+(InputfieldRepeaterDepthSize/2)){o.addClass("ui-state-error")}else{if(o.hasClass("ui-state-error")){o.removeClass("ui-state-error")}}}}else{d.axis="y"}h.sortable(d);var n=$(".InputfieldRepeaterAddLink:not(.InputfieldRepeaterAddLinkInit)",l);n.addClass("InputfieldRepeaterAddLinkInit");n.click(function(){var q=$(this);var w=$(this).parent("p").prev("ul.Inputfields");var r=$(this).parent().children("input");var v=0;var B=q.attr("data-noajax").length==0;var p=q.attr("data-clone");function z(C){var D=C.attr("id")+"_";while($("#"+D).length>0){D+="_"}C.attr("id",D);w.append(C);C.css("display","block");InputfieldRepeaterAdjustLabel(C,true);q.trigger("repeateradd",[C])}if(typeof p=="undefined"||!p){p=null}if(p){q.removeAttr("data-clone")}if(!B){var s=w.children(".InputfieldRepeaterNewItem");v=s.length;if(v>0){if(v>1){s=s.slice(0,1)}var u=s.clone(true);z(u);r.attr("value",v);InputfieldRepeaterCheckMax(b)}}else{var x=q.closest(".InputfieldRepeater").attr("data-page");var A=q.closest(".InputfieldRepeater").attr("id").replace("wrap_Inputfield_","");var t=q.parent().find(".InputfieldRepeaterSpinner");var o=ProcessWire.config.InputfieldRepeater.editorUrl+"?id="+x+"&field="+A;t.removeClass(t.attr("data-off")).addClass(t.attr("data-on"));if(p){o+="&repeater_clone="+p}else{o+="&repeater_add="+q.attr("data-type")}var y=w.find(".InputfieldRepeaterUnpublished");if(y.length){o+="&repeater_not=";y.each(function(){o+=$(this).attr("data-page")+","})}$.get(o,function(D){t.removeClass(t.attr("data-on")).addClass(t.attr("data-off"));var C=$(D).find(".InputfieldRepeaterItemRequested");if(!C.length){return}z(C);C.wrap("
");InputfieldsInit(C.parent());InputfieldRepeaterInit(C);C.unwrap();C.find(".Inputfield").trigger("reloaded",["InputfieldRepeaterItemAdd"]);C.find(".InputfieldRepeaterSort").val(w.children().length);$("html, body").animate({scrollTop:C.offset().top},500,"swing");InputfieldRepeaterUpdateState(C);InputfieldRepeaterCheckMax(b);$nestedRepeaters=C.find(".InputfieldRepeater");if($nestedRepeaters.length){$nestedRepeaters.each(function(){InputfieldRepeaterInit($(this))})}})}return false});if(b.hasClass("InputfieldRepeaterMax")){InputfieldRepeaterCheckMax(b)}}$(document).ready(function(){$(".InputfieldRepeater").each(function(){InputfieldRepeaterInit($(this))});$(document).on("reloaded",".InputfieldRepeater",function(b,c){if(typeof c!="undefined"){if(c=="InputfieldRepeaterItemEdit"||c=="InputfieldRepeaterItemAdd"){b.stopPropagation();var a=$(this).find(".InputfieldRepeater");if(a.length){InputfieldRepeaterInit(a)}return}}InputfieldRepeaterInit($(this))});$(document).on("click",".InputfieldRepeaterTrash",InputfieldRepeaterDeleteClick).on("dblclick",".InputfieldRepeaterTrash",InputfieldRepeaterDeleteDblClick).on("click",".InputfieldRepeaterClone",InputfieldRepeaterCloneClick).on("click",".InputfieldRepeaterToggle",InputfieldRepeaterToggleClick).on("opened",".InputfieldRepeaterItem",InputfieldRepeaterItemOpened).on("closed",".InputfieldRepeaterItem",InputfieldRepeaterItemClosed).on("openReady",".InputfieldRepeaterItem",InputfieldRepeaterItemOpenReady)}); \ No newline at end of file +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 diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module index 209d23f9..79e5f4b7 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.module @@ -335,8 +335,12 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { $loaded->attr('value', $isLoaded ? 1 : 0); $wrap = $this->wire('modules')->get('InputfieldFieldset'); - $wrap->addClass('InputfieldRepeaterItem'); - $wrap->label = $this->renderRepeaterLabel($label, ++$cnt, $page); + $wrap->addClass('InputfieldRepeaterItem InputfieldNoFocus'); + $wrap->entityEncodeLabel = false; + $wrap->label = + "" . + $this->entityEncode($this->renderRepeaterLabel($label, ++$cnt, $page)) . + ""; $wrap->name = "repeater_item_{$page->id}"; $wrap->wrapAttr('data-page', $page->id); $wrap->wrapAttr('data-type', $this->getRepeaterItemType($page)); @@ -400,6 +404,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { // create a new/blank item to be used as a template for any new items added if(!$itemID) { + /** @var InputfieldWrapper $wrap */ $wrap = $this->wire('modules')->get('InputfieldFieldset'); $wrap->label = $this->renderRepeaterLabel($label, ++$cnt, new NullPage()); $wrap->class = 'InputfieldRepeaterItem InputfieldRepeaterNewItem'; @@ -540,10 +545,12 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList { $this->wire('config')->js('InputfieldRepeater', array( 'editorUrl' => $editorUrl, 'labels' => array( - 'remove' => $this->_x('Delete this item', 'repeater-item-action'), + 'remove' => $this->_x('Click to delete this item, or double-click to delete all', 'repeater-item-action'), 'removeAll' => $this->_x('Delete all items?', 'repeater-item-action'), - 'toggle' => $this->_x('Toggle published/unpublished', 'repeater-item-action'), - 'clone' => $this->_x('Clone this item?', 'repeater-item-action') + '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') ) )); diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss index 74a1293c..b8a0db29 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss +++ b/wire/modules/Fieldtype/FieldtypeRepeater/InputfieldRepeater.scss @@ -7,28 +7,50 @@ .InputfieldRepeaterItem > .InputfieldHeader { line-height: 1em; - padding: 0.5em; + padding: 0.5em 0 0.5em 0.4em; + white-space: nowrap; + overflow: hidden; + position: relative; + + .InputfieldRepeaterItemLabel { + display: inline-block; + padding-left: 0.25em; + } + .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, + .toggle-icon { + cursor: pointer; + float: right; + } + .InputfieldRepeaterTrash { + padding-right: 3px; + } + .InputfieldRepeaterToggle { + margin-right: 1em; + } + .InputfieldRepeaterClone { + margin-right: 1em; + } + } .toggle-icon { line-height: 1em; margin-right: 0.5em; } - - & > .InputfieldRepeaterClone, - & > .InputfieldRepeaterToggle, - & > .InputfieldRepeaterTrash { - cursor: pointer; - float: right; - } - & > .InputfieldRepeaterTrash { - padding-right: 3px; - } - & > .InputfieldRepeaterToggle { - margin-right: 1em; - } - & > .InputfieldRepeaterClone { - margin-right: 1em; - } + } .InputfieldRepeaterItem:not(.InputfieldRepeaterDeletePending) { @@ -48,12 +70,16 @@ &.InputfieldRepeaterOff > .InputfieldHeader { // off items faded yet even more opacity: 0.5; - &:not(:hover) { + &:not(:hover) > .InputfieldRepeaterItemLabel { text-decoration: line-through; } } } + .InputfieldRepeaterItem.InputfieldRepeaterDeletePending > .InputfieldHeader > .InputfieldRepeaterItemLabel { + text-decoration: line-through; + } + .InputfieldRepeaterItem.InputfieldRepeaterItemLoading { // override the margin-bottom 1.25em from admin theme stylesheet, so margin isn't showing while item is loading margin-bottom: 1em; @@ -82,6 +108,7 @@ position: relative; } + .InputfieldRepeaterAddItem input { // count of added items hidden position: absolute; @@ -96,6 +123,7 @@ .InputfieldRepeaterDrag { // draggable icon for repeater items + display: inline-block; cursor: ns-resize; opacity: 0.7; line-height: 1em; diff --git a/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.css b/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.css index 6470f9bd..67ea2b51 100644 --- a/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.css +++ b/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.css @@ -351,4 +351,11 @@ top: -1.1em; bottom: auto; } +.AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input textarea:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="date"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="datetime"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="datetime-local"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="email"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="month"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="number"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="password"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="search"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="tel"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="text"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="time"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="url"]:focus, .AdminThemeReno .vex.vex-theme-default .vex-dialog-form .vex-dialog-input input[type="week"]:focus { + -moz-box-shadow: inset 0 0 0 2px #86d7c1; + -webkit-box-shadow: inset 0 0 0 2px #86d7c1; + box-shadow: inset 0 0 0 2px #86d7c1; } +.AdminThemeReno .vex.vex-theme-default .vex-dialog-button.vex-dialog-button-primary { + background: #3eb998; } + /*# sourceMappingURL=vex-theme-default.css.map */ diff --git a/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.sass b/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.sass index 410998e6..1a45cab7 100755 --- a/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.sass +++ b/wire/modules/Jquery/JqueryUI/vex/styles/vex-theme-default.sass @@ -7,6 +7,7 @@ $blue: #3288e6 $green: #93BF0D // RJC +$reno-green: #3eb998 // RJC .vex.vex-theme-default padding-top: 160px @@ -128,3 +129,16 @@ $green: #93BF0D // RJC border-top-color: #bbb top: -1.1em bottom: auto + +.AdminThemeReno + .vex.vex-theme-default + .vex-dialog-form + .vex-dialog-input + textarea, input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="email"], input[type="month"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="time"], input[type="url"], input[type="week"] + &:focus + +box-shadow(inset 0 0 0 2px lighten($reno-green, 20%)) // RJC + + .vex-dialog-button + &.vex-dialog-button-primary + background: $reno-green // RJC +