From 4d4642770f97c908ea2d8e5d82d3814eb2681c06 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Thu, 23 Feb 2023 12:55:18 +0100 Subject: [PATCH] new: [crud:filter] Added support of IN searches using dropdown --- src/Controller/Component/CRUDComponent.php | 31 +++++++-- .../Form/Fields/dropdownField.php | 12 ++-- templates/genericTemplates/filters.php | 66 +++++++++++++------ 3 files changed, 79 insertions(+), 30 deletions(-) diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 8f19c23..5eca579 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -226,22 +226,41 @@ class CRUDComponent extends Component } else { $this->Controller->set('metaFieldsEnabled', false); } - $filters = !empty($this->Controller->filterFields) ? $this->Controller->filterFields : []; + $filtersConfigRaw= !empty($this->Controller->filterFields) ? $this->Controller->filterFields : []; + $filtersConfig = []; + foreach ($filtersConfigRaw as $fieldConfig) { + if (is_array($fieldConfig)) { + $filtersConfig[$fieldConfig['name']] = $fieldConfig; + } else { + $filtersConfig[$fieldConfig] = ['name' => $fieldConfig]; + } + } + $filtersName = $this->getFilterFieldsName(); $typeMap = $this->Table->getSchema()->typeMap(); - $associatedtypeMap = !empty($this->Controller->filterFields) ? $this->_getAssociatedTypeMap() : []; + $associatedtypeMap = !empty($filtersName) ? $this->_getAssociatedTypeMap() : []; $typeMap = array_merge( $this->Table->getSchema()->typeMap(), $associatedtypeMap ); - $typeMap = array_filter($typeMap, function ($field) use ($filters) { - return in_array($field, $filters); + $typeMap = array_filter($typeMap, function ($field) use ($filtersName) { + return in_array($field, $filtersName); }, ARRAY_FILTER_USE_KEY); $this->Controller->set('typeMap', $typeMap); - $this->Controller->set('filters', $filters); + $this->Controller->set('filters', $filtersName); + $this->Controller->set('filtersConfig', $filtersConfig); $this->Controller->viewBuilder()->setLayout('ajax'); $this->Controller->render('/genericTemplates/filters'); } + public function getFilterFieldsName(): array + { + $filters = !empty($this->Controller->filterFields) ? $this->Controller->filterFields : []; + $filters = array_map(function($item) { + return is_array($item) ? $item['name'] : $item; + }, $filters); + return $filters; + } + /** * getResponsePayload Returns the adaquate response payload based on the request context * @@ -1613,7 +1632,7 @@ class CRUDComponent extends Component protected function _getAssociatedTypeMap(): array { $typeMap = []; - foreach ($this->Controller->filterFields as $filter) { + foreach ($this->getFilterFieldsName() as $filter) { $exploded = explode('.', $filter); if (count($exploded) > 1) { $model = $exploded[0]; diff --git a/templates/element/genericElements/Form/Fields/dropdownField.php b/templates/element/genericElements/Form/Fields/dropdownField.php index 4db9707..50be756 100644 --- a/templates/element/genericElements/Form/Fields/dropdownField.php +++ b/templates/element/genericElements/Form/Fields/dropdownField.php @@ -2,7 +2,7 @@ $seed = 's-' . mt_rand(); $controlParams = [ 'type' => 'select', - 'options' => $fieldData['options'], + 'options' => $fieldData['options'] ?? [], 'empty' => $fieldData['empty'] ?? false, 'value' => $fieldData['value'] ?? null, 'multiple' => $fieldData['multiple'] ?? false, @@ -20,8 +20,10 @@ if ($controlParams['options'] instanceof \Cake\ORM\Query) { $controlParams['options'] = $controlParams['options']->all()->toList(); } if (!empty($fieldData['select2'])) { + $fieldData['select2'] = $fieldData['select2'] === true ? [] : $fieldData['select2']; $controlParams['class'] .= ' select2-input'; } +$controlParams['class'] .= ' dropdown-custom-value' . "-$seed"; if (in_array('_custom', array_keys($controlParams['options']))) { $customInputValue = $this->Form->getSourceValue($fieldData['field']); if (!in_array($customInputValue, $controlParams['options'])) { @@ -34,7 +36,6 @@ if (in_array('_custom', array_keys($controlParams['options']))) { } else { $customInputValue = ''; } - $controlParams['class'] .= ' dropdown-custom-value' . "-$seed"; $adaptedField = $fieldData['field'] . '_custom'; $controlParams['templates']['formGroup'] = sprintf( '
{{input}}{{error}}%s
', @@ -57,9 +58,12 @@ echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $f if ($container.length == 0) { $container = $(document.body) } - $select.select2({ + const defaultSelect2Options = { dropdownParent: $container, - }) + } + const passedSelect2Options = ; + const select2Options = Object.assign({}, passedSelect2Options, defaultSelect2Options) + $select.select2(select2Options) }) diff --git a/templates/genericTemplates/filters.php b/templates/genericTemplates/filters.php index 858d5c3..4cb1df0 100644 --- a/templates/genericTemplates/filters.php +++ b/templates/genericTemplates/filters.php @@ -47,19 +47,28 @@ $filteringForm = $this->Bootstrap->table( __('Value'), sprintf('', __('Supports strict matches and LIKE matches with the `%` character. Example: `%.com`')) ), - 'formatter' => function ($field, $row) use ($typeMap, $formTypeMap) { + 'formatter' => function ($field, $row) use ($typeMap, $formTypeMap, $filtersConfig) { $fieldName = $row['fieldname']; $formType = $formTypeMap[$typeMap[$fieldName]] ?? 'text'; + $fieldData = [ + 'field' => $fieldName, + 'type' => $formType, + 'label' => '', + 'class' => 'fieldValue form-control-sm' + ]; + if (!empty($filtersConfig[$fieldName]['multiple'])) { + $fieldData['type'] = 'dropdown'; + $fieldData['multiple'] = true; + $fieldData['select2'] = [ + 'tags' => true, + 'tokenSeparators' => [',', ' '], + ]; + } $this->Form->setTemplates([ 'formGroup' => '
{{input}}
', ]); return $this->element('genericElements/Form/fieldScaffold', [ - 'fieldData' => [ - 'field' => $fieldName, - 'type' => $formType, - 'label' => '', - 'class' => 'fieldValue form-control-sm' - ], + 'fieldData' => $fieldData, 'params' => [] ]); } @@ -169,6 +178,36 @@ echo $this->Bootstrap->modal([ } setFilteringValues($filteringTable, field, value, operator) } + if (tags.length > 0) { + setFilteringTags($filteringTable, tags) + } + } + + function setFilteringValues($filteringTable, field, value, operator) { + $row = $filteringTable.find('td > span.fieldName').filter(function() { + return $(this).data('fieldname') == field + }).closest('tr') + $row.find('.fieldOperator').val(operator) + const $formElement = $row.find('.fieldValue'); + if ($formElement.attr('type') === 'datetime-local') { + $formElement.val(moment(value).format('yyyy-MM-DDThh:mm:ss')) + } else if ($formElement.is('select') && Array.isArray(value)) { + let newOptions = []; + value.forEach(aValue => { + const existingOption = $formElement.find('option').filter(function() { + return $(this).val() === aValue + }) + if (existingOption.length == 0) { + newOptions.push(new Option(aValue, aValue, true, true)) + } + }) + $formElement.append(newOptions).trigger('change'); + } else { + $formElement.val(value) + } + } + + function setFilteringTags($filteringTable, tags) { $select = $filteringTable.closest('.modal-body').find('select.select2-input') let passedTags = [] tags.forEach(tagname => { @@ -185,19 +224,6 @@ echo $this->Bootstrap->modal([ .trigger('change') } - function setFilteringValues($filteringTable, field, value, operator) { - $row = $filteringTable.find('td > span.fieldName').filter(function() { - return $(this).data('fieldname') == field - }).closest('tr') - $row.find('.fieldOperator').val(operator) - const $formElement = $row.find('.fieldValue'); - if ($formElement.attr('type') === 'datetime-local') { - $formElement.val(moment(value).format('yyyy-MM-DDThh:mm:ss')) - } else { - $formElement.val(value) - } - } - function getDataFromRow($row) { const rowData = {}; rowData['name'] = $row.find('td > span.fieldName').data('fieldname')