diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index 24f1fbc4a..41f5d8a87 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -2,6 +2,7 @@ use Backend\Classes\FormField; use Backend\Classes\FormWidgetBase; +use ApplicationException; /** * Repeater Form Widget @@ -9,6 +10,7 @@ use Backend\Classes\FormWidgetBase; class Repeater extends FormWidgetBase { const INDEX_PREFIX = '___index_'; + const GROUP_PREFIX = '___group_'; // // Configurable properties @@ -29,6 +31,11 @@ class Repeater extends FormWidgetBase */ public $sortable = false; + /** + * @var int Maximum repeated items allowable. + */ + public $maxItems = null; + // // Object properties // @@ -38,11 +45,21 @@ class Repeater extends FormWidgetBase */ protected $defaultAlias = 'repeater'; + /** + * @var string Form field name for capturing an index. + */ + protected $indexInputName; + /** * @var int Count of repeated items. */ protected $indexCount = 0; + /** + * @var array Meta data associated to each field, organised by index + */ + protected $indexMeta = []; + /** * @var array Collection of form widgets. */ @@ -53,10 +70,14 @@ class Repeater extends FormWidgetBase */ protected static $onAddItemCalled = false; + protected $useGroups = false; + /** - * @var int Maximum repeated items allowable. + * @var string Form field name for capturing an index. */ - protected $maxItems = null; + protected $groupInputName; + + protected $groupDefinitions = []; /** * @inheritDoc @@ -70,6 +91,12 @@ class Repeater extends FormWidgetBase 'maxItems', ]); + $fieldName = $this->formField->getName(false); + $this->indexInputName = self::INDEX_PREFIX.$fieldName.'[]'; + $this->groupInputName = self::GROUP_PREFIX.$fieldName.'[]'; + + $this->processGroupMode(); + if (!self::$onAddItemCalled) { $this->processExistingItems(); } @@ -89,10 +116,15 @@ class Repeater extends FormWidgetBase */ public function prepareVars() { - $this->vars['indexName'] = self::INDEX_PREFIX.$this->formField->getName(false).'[]'; + $this->vars['indexInputName'] = $this->indexInputName; + $this->vars['groupInputName'] = $this->groupInputName; + $this->vars['prompt'] = $this->prompt; $this->vars['formWidgets'] = $this->formWidgets; $this->vars['maxItems'] = $this->maxItems; + + $this->vars['useGroups'] = $this->useGroups; + $this->vars['groupDefinitions'] = $this->groupDefinitions; } /** @@ -109,9 +141,21 @@ class Repeater extends FormWidgetBase */ public function getSaveValue($value) { + // traceLog($value); + // return ''; return (array) $value; } + // Format the save value to index _index and _group and strip the array keys + protected function processSaveValue() + { + + } + + /** + * Processes existing form data and applies it to the form widgets. + * @return void + */ protected function processExistingItems() { $loadValue = $this->getLoadValue(); @@ -119,26 +163,42 @@ class Repeater extends FormWidgetBase $loadValue = array_keys($loadValue); } - $itemIndexes = post(self::INDEX_PREFIX.$this->formField->getName(false), $loadValue); + $itemIndexes = post($this->indexInputName, $loadValue); + $itemGroups = post($this->groupInputName, $loadValue); - if (!is_array($itemIndexes)) { + $items = array_combine( + (array) $itemIndexes, + (array) ($this->useGroups ? $itemGroups : $itemIndexes) + ); + + if (!is_array($items)) { return; } - foreach ($itemIndexes as $itemIndex) { - $this->makeItemFormWidget($itemIndex); + foreach ($items as $itemIndex => $groupCode) { + $this->makeItemFormWidget($itemIndex, $groupCode); $this->indexCount = max((int) $itemIndex, $this->indexCount); } } - protected function makeItemFormWidget($index = 0) + /** + * Creates a form widget based on a field index and optional group code. + * @param int $index + * @param string $index + * @return \Backend\Widgets\Form + */ + protected function makeItemFormWidget($index = 0, $groupCode = null) { $loadValue = $this->getLoadValue(); if (!is_array($loadValue)) { $loadValue = []; } - $config = $this->makeConfig($this->form); + $configDefinition = $this->useGroups + ? $this->getGroupFormFieldConfig($groupCode) + : $this->form; + + $config = $this->makeConfig($configDefinition); $config->model = $this->model; $config->data = array_get($loadValue, $index, []); $config->alias = $this->alias . 'Form'.$index; @@ -148,17 +208,29 @@ class Repeater extends FormWidgetBase $widget = $this->makeWidget('Backend\Widgets\Form', $config); $widget->bindToController(); + $this->indexMeta[$index] = [ + 'groupCode' => $groupCode + ]; + return $this->formWidgets[$index] = $widget; } + // + // AJAX handlers + // + public function onAddItem() { self::$onAddItemCalled = true; $this->indexCount++; + traceLog($this->indexCount); + + $groupCode = post('_repeater_group'); + $this->prepareVars(); - $this->vars['widget'] = $this->makeItemFormWidget($this->indexCount); + $this->vars['widget'] = $this->makeItemFormWidget($this->indexCount, $groupCode); $this->vars['indexValue'] = $this->indexCount; $itemContainer = '@#'.$this->getId('items'); @@ -173,9 +245,71 @@ class Repeater extends FormWidgetBase public function onRefresh() { $index = post('_repeater_index'); + $group = post('_repeater_group'); - $widget = $this->makeItemFormWidget($index); + $widget = $this->makeItemFormWidget($index, $group); return $widget->onRefresh(); } + + // + // Group mode + // + + /** + * Returns the form field configuration for a group, identified by code. + * @param string $code + * @return array|null + */ + protected function getGroupFormFieldConfig($code) + { + if (!$code) { + return null; + } + + $fields = array_get($this->groupDefinitions, $code.'.fields'); + + if (!$fields) { + return null; + } + + return ['fields' => $fields]; + } + + /** + * Process features related to group mode. + * @return void + */ + protected function processGroupMode() + { + $palette = []; + + if (!$group = $this->getConfig('groups', [])) { + $this->useGroups = false; + return; + } + + foreach ($group as $code => $config) { + $palette[$code] = [ + 'code' => $code, + 'name' => array_get($config, 'name'), + 'icon' => array_get($config, 'icon', 'icon-square-o'), + 'description' => array_get($config, 'description'), + 'fields' => array_get($config, 'fields') + ]; + } + + $this->groupDefinitions = $palette; + $this->useGroups = true; + } + + /** + * Returns a field group code from its index. + * @param $index int + * @return string + */ + public function getGroupCodeFromIndex($index) + { + return array_get($this->indexMeta, $index.'.groupCode'); + } } diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index a70ed8984..5f661946b 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -43,6 +43,7 @@ this.$el.on('ajaxDone', '> .field-repeater-items > .field-repeater-item > .repeater-item-remove > [data-repeater-remove]', this.proxy(this.onRemoveItemSuccess)) this.$el.on('ajaxDone', '> .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess)) this.$el.on('click', '> ul > li > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse)) + this.$el.on('click', '> .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton)) this.$el.one('dispose-control', this.proxy(this.dispose)) @@ -55,6 +56,7 @@ this.$el.off('ajaxDone', '> .field-repeater-items > .field-repeater-item > .repeater-item-remove > [data-repeater-remove]', this.proxy(this.onRemoveItemSuccess)) this.$el.off('ajaxDone', '> .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess)) this.$el.off('click', '> .field-repeater-items > .field-repeater-item > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse)) + this.$el.off('click', '> .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton)) this.$el.off('dispose-control', this.proxy(this.dispose)) this.$el.removeData('oc.repeater') @@ -80,6 +82,14 @@ this.$sortable.sortable(sortableOptions) } + Repeater.prototype.clickAddGroupButton = function(ev) { + var templateHtml = $('> [data-group-palette-template]', this.$el).html() + + $(ev.target).ocPopover({ + content: templateHtml + }) + } + Repeater.prototype.onRemoveItemSuccess = function(ev) { $(ev.target).closest('.field-repeater-item').remove() this.togglePrompt() diff --git a/modules/backend/formwidgets/repeater/partials/_repeater.htm b/modules/backend/formwidgets/repeater/partials/_repeater.htm index 8178fbd71..d68c8a1b4 100644 --- a/modules/backend/formwidgets/repeater/partials/_repeater.htm +++ b/modules/backend/formwidgets/repeater/partials/_repeater.htm @@ -5,7 +5,7 @@ data-sortable-handle=".getId('items') ?>-handle">
- - - + + + + + + + + +
- + + + \ No newline at end of file diff --git a/modules/backend/formwidgets/repeater/partials/_repeater_item.htm b/modules/backend/formwidgets/repeater/partials/_repeater_item.htm index 5e195c338..76a28bf7b 100644 --- a/modules/backend/formwidgets/repeater/partials/_repeater_item.htm +++ b/modules/backend/formwidgets/repeater/partials/_repeater_item.htm @@ -1,3 +1,6 @@ +getGroupCodeFromIndex($indexValue) : ''; +?>
  • @@ -11,7 +14,7 @@ aria-label="Remove" data-repeater-remove data-request="getEventHandler('onRemoveItem') ?>" - data-request-data="'index': ''" + data-request-data="'_repeater_index': '', '_repeater_group': ''" data-request-confirm=""> @@ -28,12 +31,15 @@
    + data-refresh-data="'_repeater_index': '', '_repeater_group': ''"> getFields() as $field): ?> renderField($field) ?>
    - + + + +