From b4b4b1b566d9c117896d7c24ae835d0be2f916c0 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 4 Apr 2019 01:23:32 -0600 Subject: [PATCH 01/10] Remove extra __index_ and __group_ fields from the Repeater and use only the data itself to handle initializing widgets and processing data. Refs: #4230 --- modules/backend/formwidgets/Repeater.php | 88 ++++++------------- .../repeater/partials/_repeater_item.htm | 5 -- 2 files changed, 29 insertions(+), 64 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index 3374f40c4..85a454c6f 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -10,9 +10,6 @@ use October\Rain\Html\Helper as HtmlHelper; */ class Repeater extends FormWidgetBase { - const INDEX_PREFIX = '___index_'; - const GROUP_PREFIX = '___group_'; - // // Configurable properties // @@ -56,11 +53,6 @@ class Repeater extends FormWidgetBase */ protected $defaultAlias = 'repeater'; - /** - * @var string Form field name for capturing an index. - */ - protected $indexInputName; - /** * @var int Count of repeated items. */ @@ -83,11 +75,6 @@ class Repeater extends FormWidgetBase protected $useGroups = false; - /** - * @var string Form field name for capturing an index. - */ - protected $groupInputName; - protected $groupDefinitions = []; /** @@ -109,13 +96,11 @@ class Repeater extends FormWidgetBase } $fieldName = $this->formField->getName(false); - $this->indexInputName = $this->alias.self::INDEX_PREFIX.$fieldName; - $this->groupInputName = $this->alias.self::GROUP_PREFIX.$fieldName; $this->processGroupMode(); if (!self::$onAddItemCalled) { - $this->processExistingItems(); + $this->processItems(); } } @@ -136,7 +121,7 @@ class Repeater extends FormWidgetBase // Refresh the loaded data to support being modified by filterFields // @see https://github.com/octobercms/october/issues/2613 if (!self::$onAddItemCalled) { - $this->processExistingItems(); + $this->processItems(); } if ($this->previewMode) { @@ -145,9 +130,6 @@ class Repeater extends FormWidgetBase } } - $this->vars['indexInputName'] = $this->indexInputName; - $this->vars['groupInputName'] = $this->groupInputName; - $this->vars['prompt'] = $this->prompt; $this->vars['formWidgets'] = $this->formWidgets; $this->vars['titleFrom'] = $this->titleFrom; @@ -186,15 +168,6 @@ class Repeater extends FormWidgetBase return $value; } - if ($this->useGroups) { - foreach ($value as $index => &$data) { - $data['_group'] = $this->getGroupCodeFromIndex($index); - } - // Make sure the $data reference is removed from memory so that the next loop won't modify it - // which would cause the last item to receive the group code of the second-last item - unset($data); - } - if ($this->minItems && count($value) < $this->minItems) { throw new ApplicationException(Lang::get('backend::lang.repeater.min_items_failed', ['name' => $this->fieldName, 'min' => $this->minItems, 'items' => count($value)])); } @@ -219,51 +192,48 @@ class Repeater extends FormWidgetBase } /** - * Processes existing form data and applies it to the form widgets. + * Processes form data and applies it to the form widgets. * @return void */ - protected function processExistingItems() + protected function processItems() { - $loadedIndexes = $loadedGroups = []; - $loadValue = $this->getLoadValue(); + $indexes = $groups = []; + $currentValue = post($this->formField->fieldName, $this->getLoadValue()); // Ensure that the minimum number of items are preinitialized // ONLY DONE WHEN NOT IN GROUP MODE if (!$this->useGroups && $this->minItems > 0) { - if (!is_array($loadValue)) { - $loadValue = []; + if (!is_array($currentValue)) { + $currentValue = []; for ($i = 0; $i < $this->minItems; $i++) { - $loadValue[$i] = []; + $currentValue[$i] = []; } - } elseif (count($loadValue) < $this->minItems) { - for ($i = 0; $i < ($this->minItems - count($loadValue)); $i++) { - $loadValue[] = []; + } elseif (count($currentValue) < $this->minItems) { + for ($i = 0; $i < ($this->minItems - count($currentValue)); $i++) { + $currentValue[] = []; } } } - if (is_array($loadValue)) { - foreach ($loadValue as $index => $loadedValue) { - $loadedIndexes[] = $index; - $loadedGroups[] = array_get($loadedValue, '_group'); + if (is_array($currentValue)) { + foreach ($currentValue as $index => $value) { + $indexes[] = $index; + $groups[] = array_get($value, '_group'); } } - $itemIndexes = post($this->indexInputName, $loadedIndexes); - $itemGroups = post($this->groupInputName, $loadedGroups); - - if (!count($itemIndexes)) { + if (!count($indexes)) { return; } $items = array_combine( - (array) $itemIndexes, - (array) ($this->useGroups ? $itemGroups : $itemIndexes) + (array) $indexes, + (array) ($this->useGroups ? $groups : $indexes) ); - foreach ($items as $itemIndex => $groupCode) { - $this->makeItemFormWidget($itemIndex, $groupCode); - $this->indexCount = max((int) $itemIndex, $this->indexCount); + foreach ($items as $index => $groupCode) { + $this->makeItemFormWidget($index, $groupCode); + $this->indexCount = max((int) $index, $this->indexCount); } } @@ -281,7 +251,7 @@ class Repeater extends FormWidgetBase $config = $this->makeConfig($configDefinition); $config->model = $this->model; - $config->data = $this->getLoadValueFromIndex($index); + $config->data = $this->getValueFromIndex($index); $config->alias = $this->alias . 'Form'.$index; $config->arrayName = $this->getFieldName().'['.$index.']'; $config->isNested = true; @@ -300,17 +270,17 @@ class Repeater extends FormWidgetBase } /** - * Returns the load data at a given index. + * Returns the data at a given index. * @param int $index */ - protected function getLoadValueFromIndex($index) + protected function getValueFromIndex($index) { - $loadValue = $this->getLoadValue(); - if (!is_array($loadValue)) { - $loadValue = []; + $value = post($this->formField->fieldName, $this->getLoadValue()); + if (!is_array($value)) { + $value = []; } - return array_get($loadValue, $index, []); + return array_get($value, $index, []); } // diff --git a/modules/backend/formwidgets/repeater/partials/_repeater_item.htm b/modules/backend/formwidgets/repeater/partials/_repeater_item.htm index fe1082962..89438db6a 100644 --- a/modules/backend/formwidgets/repeater/partials/_repeater_item.htm +++ b/modules/backend/formwidgets/repeater/partials/_repeater_item.htm @@ -45,9 +45,4 @@ - - - - - From bfe03803e612fb36bfcce5f3b3d12dc4f0218ca1 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 5 Apr 2019 09:00:26 -0600 Subject: [PATCH 02/10] Fix the repeater on every context, not just static page fields --- modules/backend/formwidgets/Repeater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index 85a454c6f..d9d6be2cd 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -198,7 +198,7 @@ class Repeater extends FormWidgetBase protected function processItems() { $indexes = $groups = []; - $currentValue = post($this->formField->fieldName, $this->getLoadValue()); + $currentValue = post($this->formField->getName(), $this->getLoadValue()); // Ensure that the minimum number of items are preinitialized // ONLY DONE WHEN NOT IN GROUP MODE From 332ed6293166f07d81abb0689c1c9333b08acc6c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 8 Apr 2019 14:37:36 +0800 Subject: [PATCH 03/10] Increment index count after new item is created Effectively starts the repeater item indexes from 0 --- modules/backend/formwidgets/Repeater.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index d9d6be2cd..f2adcda28 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -233,8 +233,8 @@ class Repeater extends FormWidgetBase foreach ($items as $index => $groupCode) { $this->makeItemFormWidget($index, $groupCode); - $this->indexCount = max((int) $index, $this->indexCount); } + $this->indexCount = max(count($items), $this->indexCount); } /** @@ -291,8 +291,6 @@ class Repeater extends FormWidgetBase { self::$onAddItemCalled = true; - $this->indexCount++; - $groupCode = post('_repeater_group'); $this->prepareVars(); @@ -300,7 +298,13 @@ class Repeater extends FormWidgetBase $this->vars['indexValue'] = $this->indexCount; $itemContainer = '@#'.$this->getId('items'); - return [$itemContainer => $this->makePartial('repeater_item')]; + + // Increase index count after item is created + ++$this->indexCount; + + return [ + $itemContainer => $this->makePartial('repeater_item') + ]; } public function onRemoveItem() From 98832fbc5715287a8204b16bc92bb13477447aeb Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 8 Apr 2019 15:57:04 +0800 Subject: [PATCH 04/10] Initial work on handlers for re-ordering repeater items --- .../repeater/assets/js/repeater.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index 32d2fdd5e..e3d5cfc0e 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -22,6 +22,9 @@ this.$el = $(element) this.$sortable = $(options.sortableContainer, this.$el) + // Sortable tracking + this.sortingStartIndex = null + $.oc.foundation.controlUtils.markDisposable(element) Base.call(this) this.init() @@ -77,7 +80,9 @@ Repeater.prototype.bindSorting = function() { var sortableOptions = { handle: this.options.sortableHandle, - nested: false + nested: false, + onDragStart: this.proxy(this.onSortStart), + onDrop: this.proxy(this.onSortStop) } this.$sortable.sortable(sortableOptions) @@ -224,6 +229,18 @@ return defaultText } + Repeater.prototype.onSortStart = function($item, container, callback, event) { + this.sortingStartIndex = $item.index() + + callback($item, container, callback, event); + } + + Repeater.prototype.onSortStop = function($item, container, callback, event) { + var endIndex = $item.index() + + callback($item, container, callback, event); + } + // FIELD REPEATER PLUGIN DEFINITION // ============================ From f49b5ab4b812e2ea99c455bcf35adc0b5dc3358e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 8 Apr 2019 23:40:41 +0800 Subject: [PATCH 05/10] Add callback for handling sorted repeater items --- modules/backend/formwidgets/Repeater.php | 5 +++++ .../formwidgets/repeater/assets/js/repeater.js | 12 ++++++++++-- .../formwidgets/repeater/partials/_repeater.htm | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index f2adcda28..ffa9a3bed 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -322,6 +322,11 @@ class Repeater extends FormWidgetBase return $widget->onRefresh(); } + public function onReorder() + { + // Handle reordering of repeater items + } + // // Group mode // diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index e3d5cfc0e..c436b220f 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -35,6 +35,7 @@ Repeater.DEFAULTS = { sortableHandle: '.repeater-item-handle', + sortableHandler: null, sortableContainer: 'ul.field-repeater-items', titleFrom: null, minItems: null, @@ -232,13 +233,20 @@ Repeater.prototype.onSortStart = function($item, container, callback, event) { this.sortingStartIndex = $item.index() - callback($item, container, callback, event); + callback($item, container, callback, event) } Repeater.prototype.onSortStop = function($item, container, callback, event) { var endIndex = $item.index() - callback($item, container, callback, event); + this.$el.request(this.options.sortableHandler, { + data: { + _repeater_index: this.sortingStartIndex, + _repeater_new_index: endIndex + } + }) + + callback($item, container, callback, event) } // FIELD REPEATER PLUGIN DEFINITION diff --git a/modules/backend/formwidgets/repeater/partials/_repeater.htm b/modules/backend/formwidgets/repeater/partials/_repeater.htm index 995c42cd8..2647b6ae5 100644 --- a/modules/backend/formwidgets/repeater/partials/_repeater.htm +++ b/modules/backend/formwidgets/repeater/partials/_repeater.htm @@ -3,6 +3,7 @@ + data-sortable-handler="getEventHandler('onReorder') ?>" data-sortable-container="#getId('items') ?>" data-sortable-handle=".getId('items') ?>-handle"> From 744d578df709cdb8ed422db0c28ec8fc1e56ef58 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 9 Apr 2019 10:52:14 +0800 Subject: [PATCH 06/10] Only run sortable handler if specified --- .../formwidgets/repeater/assets/js/repeater.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index c436b220f..f5b48ae46 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -239,12 +239,14 @@ Repeater.prototype.onSortStop = function($item, container, callback, event) { var endIndex = $item.index() - this.$el.request(this.options.sortableHandler, { - data: { - _repeater_index: this.sortingStartIndex, - _repeater_new_index: endIndex - } - }) + if (this.options.sortableHandler) { + this.$el.request(this.options.sortableHandler, { + data: { + _repeater_index: this.sortingStartIndex, + _repeater_new_index: endIndex + } + }) + } callback($item, container, callback, event) } From 9b5bd83f10a73e92e5b88a9b9f23230d8bf374eb Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 9 Apr 2019 15:35:07 +0800 Subject: [PATCH 07/10] Reset indexes when processing POST data --- modules/backend/formwidgets/Repeater.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index ffa9a3bed..af16b9f90 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -197,7 +197,7 @@ class Repeater extends FormWidgetBase */ protected function processItems() { - $indexes = $groups = []; + $groups = []; $currentValue = post($this->formField->getName(), $this->getLoadValue()); // Ensure that the minimum number of items are preinitialized @@ -216,25 +216,19 @@ class Repeater extends FormWidgetBase } if (is_array($currentValue)) { - foreach ($currentValue as $index => $value) { - $indexes[] = $index; + foreach ($currentValue as $value) { $groups[] = array_get($value, '_group'); } } - if (!count($indexes)) { + if (!count($groups)) { return; } - $items = array_combine( - (array) $indexes, - (array) ($this->useGroups ? $groups : $indexes) - ); - - foreach ($items as $index => $groupCode) { + foreach ($groups as $index => $groupCode) { $this->makeItemFormWidget($index, $groupCode); } - $this->indexCount = max(count($items), $this->indexCount); + $this->indexCount = max(count($groups), $this->indexCount); } /** From a00e546f5f7419d523ce9db204cf80c8199f3816 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 10 Apr 2019 13:20:51 +0800 Subject: [PATCH 08/10] Clean up processItems() method --- modules/backend/formwidgets/Repeater.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index af16b9f90..fa69e371d 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -197,7 +197,7 @@ class Repeater extends FormWidgetBase */ protected function processItems() { - $groups = []; + $groupMap = []; $currentValue = post($this->formField->getName(), $this->getLoadValue()); // Ensure that the minimum number of items are preinitialized @@ -215,20 +215,20 @@ class Repeater extends FormWidgetBase } } - if (is_array($currentValue)) { + if (is_array($currentValue) && count($currentValue)) { foreach ($currentValue as $value) { - $groups[] = array_get($value, '_group'); + $groupMap[] = array_get($value, '_group'); } } - if (!count($groups)) { + if (!count($groupMap)) { return; } - foreach ($groups as $index => $groupCode) { + foreach ($groupMap as $index => $groupCode) { $this->makeItemFormWidget($index, $groupCode); } - $this->indexCount = max(count($groups), $this->indexCount); + $this->indexCount = max(count($currentValue), $this->indexCount); } /** From ac98f70a2519a67f993e0b9a04fac4eea7877ab0 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 11 Apr 2019 10:37:39 +0800 Subject: [PATCH 09/10] Revert reordering handler changes --- modules/backend/formwidgets/Repeater.php | 5 ---- .../repeater/assets/js/repeater.js | 29 +------------------ .../repeater/partials/_repeater.htm | 1 - 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index fa69e371d..a8b7d7e21 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -316,11 +316,6 @@ class Repeater extends FormWidgetBase return $widget->onRefresh(); } - public function onReorder() - { - // Handle reordering of repeater items - } - // // Group mode // diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index f5b48ae46..32d2fdd5e 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -22,9 +22,6 @@ this.$el = $(element) this.$sortable = $(options.sortableContainer, this.$el) - // Sortable tracking - this.sortingStartIndex = null - $.oc.foundation.controlUtils.markDisposable(element) Base.call(this) this.init() @@ -35,7 +32,6 @@ Repeater.DEFAULTS = { sortableHandle: '.repeater-item-handle', - sortableHandler: null, sortableContainer: 'ul.field-repeater-items', titleFrom: null, minItems: null, @@ -81,9 +77,7 @@ Repeater.prototype.bindSorting = function() { var sortableOptions = { handle: this.options.sortableHandle, - nested: false, - onDragStart: this.proxy(this.onSortStart), - onDrop: this.proxy(this.onSortStop) + nested: false } this.$sortable.sortable(sortableOptions) @@ -230,27 +224,6 @@ return defaultText } - Repeater.prototype.onSortStart = function($item, container, callback, event) { - this.sortingStartIndex = $item.index() - - callback($item, container, callback, event) - } - - Repeater.prototype.onSortStop = function($item, container, callback, event) { - var endIndex = $item.index() - - if (this.options.sortableHandler) { - this.$el.request(this.options.sortableHandler, { - data: { - _repeater_index: this.sortingStartIndex, - _repeater_new_index: endIndex - } - }) - } - - callback($item, container, callback, event) - } - // FIELD REPEATER PLUGIN DEFINITION // ============================ diff --git a/modules/backend/formwidgets/repeater/partials/_repeater.htm b/modules/backend/formwidgets/repeater/partials/_repeater.htm index 2647b6ae5..995c42cd8 100644 --- a/modules/backend/formwidgets/repeater/partials/_repeater.htm +++ b/modules/backend/formwidgets/repeater/partials/_repeater.htm @@ -3,7 +3,6 @@ - data-sortable-handler="getEventHandler('onReorder') ?>" data-sortable-container="#getId('items') ?>" data-sortable-handle=".getId('items') ?>-handle"> From ee2b53fe71fb1e9e5d0a49693362cadb0dcfbedc Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 11 Apr 2019 17:57:27 +0800 Subject: [PATCH 10/10] Add hidden "loaded" flag for repeaters This allows the repeater to retrieve the load value from the model only on initialisation. Any further requests to the repeater (ie. AJAX requests) should use the POST data. --- modules/backend/formwidgets/Repeater.php | 30 +++++++++++++++++-- .../repeater/partials/_repeater.htm | 2 ++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index a8b7d7e21..21eef54c0 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -77,6 +77,13 @@ class Repeater extends FormWidgetBase protected $groupDefinitions = []; + /** + * Determines if repeater has been initialised previously + * + * @var boolean + */ + protected $loaded = false; + /** * @inheritDoc */ @@ -95,6 +102,11 @@ class Repeater extends FormWidgetBase $this->previewMode = true; } + // Check for loaded flag in POST + if ((bool) post($this->alias . '_loaded') === true) { + $this->loaded = true; + } + $fieldName = $this->formField->getName(false); $this->processGroupMode(); @@ -197,8 +209,17 @@ class Repeater extends FormWidgetBase */ protected function processItems() { + $currentValue = ($this->loaded === true) + ? post($this->formField->getName()) + : $this->getLoadValue(); + + if ($currentValue === null) { + $this->indexCount = 0; + $this->formWidgets = []; + return; + } + $groupMap = []; - $currentValue = post($this->formField->getName(), $this->getLoadValue()); // Ensure that the minimum number of items are preinitialized // ONLY DONE WHEN NOT IN GROUP MODE @@ -269,7 +290,10 @@ class Repeater extends FormWidgetBase */ protected function getValueFromIndex($index) { - $value = post($this->formField->fieldName, $this->getLoadValue()); + $value = ($this->loaded === true) + ? post($this->formField->getName()) + : $this->getLoadValue(); + if (!is_array($value)) { $value = []; } @@ -291,7 +315,7 @@ class Repeater extends FormWidgetBase $this->vars['widget'] = $this->makeItemFormWidget($this->indexCount, $groupCode); $this->vars['indexValue'] = $this->indexCount; - $itemContainer = '@#'.$this->getId('items'); + $itemContainer = '@#' . $this->getId('items'); // Increase index count after item is created ++$this->indexCount; diff --git a/modules/backend/formwidgets/repeater/partials/_repeater.htm b/modules/backend/formwidgets/repeater/partials/_repeater.htm index 995c42cd8..d5198b6de 100644 --- a/modules/backend/formwidgets/repeater/partials/_repeater.htm +++ b/modules/backend/formwidgets/repeater/partials/_repeater.htm @@ -34,6 +34,8 @@ + +