From 970d5622c0deeaeef38c618b42d43b82499fd7f2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 5 Feb 2019 02:50:40 +0800 Subject: [PATCH] Allow datatables to work within a repeater (#4102) Fixes #4063. Credit to @bennothommo. --- modules/backend/formwidgets/DataTable.php | 9 +- modules/backend/formwidgets/Repeater.php | 95 +++++++++++++++++++ .../repeater/assets/js/repeater.js | 11 +++ modules/backend/widgets/Table.php | 11 ++- 4 files changed, 120 insertions(+), 6 deletions(-) diff --git a/modules/backend/formwidgets/DataTable.php b/modules/backend/formwidgets/DataTable.php index faf9bc0a0..7071fafff 100644 --- a/modules/backend/formwidgets/DataTable.php +++ b/modules/backend/formwidgets/DataTable.php @@ -149,8 +149,13 @@ class DataTable extends FormWidgetBase $config = $this->makeConfig((array) $this->config); $config->dataSource = 'client'; - $config->alias = studly_case(HtmlHelper::nameToId($this->fieldName)) . 'datatable'; - $config->fieldName = $this->fieldName; + if (isset($this->getParentForm()->arrayName)) { + $config->alias = $this->getParentForm()->arrayName . '[' . studly_case(HtmlHelper::nameToId($this->fieldName)) . 'datatable' . ']'; + $config->fieldName = $this->getParentForm()->arrayName . '[' . $this->fieldName . ']'; + } else { + $config->alias = studly_case(HtmlHelper::nameToId($this->fieldName)) . 'datatable'; + $config->fieldName = $this->fieldName; + } $table = new Table($this->controller, $config); diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index bb490ddd1..cb5d7af34 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -3,6 +3,7 @@ use Lang; use ApplicationException; use Backend\Classes\FormWidgetBase; +use October\Rain\Html\Helper as HtmlHelper; /** * Repeater Form Widget @@ -198,6 +199,21 @@ class Repeater extends FormWidgetBase throw new ApplicationException(Lang::get('backend::lang.repeater.max_items_failed', ['name' => $this->fieldName, 'max' => $this->maxItems, 'items' => count($value)])); } + /* + * Give repeated form field widgets an opportunity to process the data. + */ + foreach ($this->formWidgets as $field => $form) { + foreach ($form->getFormWidgets() as $formField => $widget) { + $parts = HtmlHelper::nameToArray($field . '[' . $formField . ']'); + + $widgetValue = $widget->getSaveValue($this->dataArrayGet($value, $parts)); + if (empty($widgetValue) || !count($widgetValue)) { + continue; + } + $this->dataArraySet($value, $parts, $widgetValue); + } + } + return array_values($value); } @@ -403,4 +419,83 @@ class Repeater extends FormWidgetBase { return array_get($this->groupDefinitions, $groupCode.'.name'); } + + /** + * Internal helper for method existence checks. + * + * @param object $object + * @param string $method + * @return boolean + */ + protected function objectMethodExists($object, $method) + { + if (method_exists($object, 'methodExists')) { + return $object->methodExists($method); + } + + return method_exists($object, $method); + } + + /** + * Variant to array_get() but preserves dots in key names. + * + * @param array $array + * @param array $parts + * @param null $default + * @return array|null + */ + protected function dataArrayGet(array $array, array $parts, $default = null) + { + if ($parts === null) { + return $array; + } + + if (count($parts) === 1) { + $key = array_shift($parts); + if (isset($array[$key])) { + return $array[$key]; + } + + return $default; + } + + foreach ($parts as $segment) { + if (!is_array($array) || !array_key_exists($segment, $array)) { + return $default; + } + + $array = $array[$segment]; + } + + return $array; + } + + /** + * Variant to array_set() but preserves dots in key names. + * + * @param array $array + * @param array $parts + * @param string $value + * @return array + */ + protected function dataArraySet(array &$array, array $parts, $value) + { + if ($parts === null) { + return $value; + } + + while (count($parts) > 1) { + $key = array_shift($parts); + + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + + $array =& $array[$key]; + } + + $array[array_shift($parts)] = $value; + + return $array; + } } diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index 444e60782..32d2fdd5e 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -116,6 +116,17 @@ } Repeater.prototype.onRemoveItemSuccess = function(ev) { + // Allow any widgets inside a deleted item to be disposed + $(ev.target).closest('.field-repeater-item').find('[data-disposable]').each(function () { + var $elem = $(this), + control = $elem.data('control'), + widget = $elem.data('oc.' + control) + + if (widget && typeof widget['dispose'] === 'function') { + widget.dispose() + } + }) + $(ev.target).closest('.field-repeater-item').remove() this.togglePrompt() } diff --git a/modules/backend/widgets/Table.php b/modules/backend/widgets/Table.php index 990a99a59..09c5c425e 100644 --- a/modules/backend/widgets/Table.php +++ b/modules/backend/widgets/Table.php @@ -4,6 +4,7 @@ use Lang; use Input; use Request; use Backend\Classes\WidgetBase; +use October\Rain\Html\Helper as HtmlHelper; use SystemException; /** @@ -79,12 +80,14 @@ class Table extends WidgetBase if (Request::method() == 'POST' && $this->isClientDataSource()) { if (strpos($this->fieldName, '[') === false) { - $requestDataField = $this->fieldName.'TableData'; - } - else { - $requestDataField = $this->fieldName.'[TableData]'; + $requestDataField = $this->fieldName . 'TableData'; + } else { + $requestDataField = $this->fieldName . '[TableData]'; } + // Use dot notation for request data field + $requestDataField = implode('.', HtmlHelper::nameToArray($requestDataField)); + if (Request::exists($requestDataField)) { // Load data into the client memory data source on POST $this->dataSource->purge();