diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 6b1f6a2..eb0c601 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -13,6 +13,7 @@ use Cake\Routing\Router; use Cake\Http\Exception\MethodNotAllowedException; use Cake\Http\Exception\NotFoundException; use Cake\Collection\Collection; +use App\Utility\UI\IndexSetting; class CRUDComponent extends Component { @@ -73,6 +74,9 @@ class CRUDComponent extends Component } $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json'); } else { + if ($this->metaFieldsSupported()) { + $query = $this->includeRequestedMetaFields($query); + } $this->Controller->loadComponent('Paginator'); $data = $this->Controller->Paginator->paginate($query); if (isset($options['afterFind'])) { @@ -83,6 +87,15 @@ class CRUDComponent extends Component } } $this->setFilteringContext($options['contextFilters'] ?? [], $params); + if ($this->metaFieldsSupported()) { + $data = $data->toArray(); + $metaTemplates = $this->getMetaTemplates()->toArray(); + foreach ($data as $i => $row) { + $data[$i] = $this->attachMetaTemplatesIfNeeded($row, $metaTemplates); + } + $this->Controller->set('meta_templates', $metaTemplates); + } + $this->Controller->set('model', $this->Table); $this->Controller->set('data', $data); } } @@ -513,6 +526,32 @@ class CRUDComponent extends Component return $data; } + protected function includeRequestedMetaFields($query) + { + $user = $this->Controller->ACL->getUser(); + $tableSettings = IndexSetting::getTableSetting($user, $this->Table); + if (empty($tableSettings['visible_meta_column'])) { + return $query; + } + $containConditions = ['OR' => []]; + $requestedMetaFields = []; + foreach ($tableSettings['visible_meta_column'] as $template_id => $fields) { + $containConditions['OR'][] = [ + 'meta_template_id' => $template_id, + 'meta_template_field_id IN' => array_map('intval', $fields), + ]; + foreach ($fields as $field) { + $requestedMetaFields[] = ['template_id' => $template_id, 'meta_template_field_id' => intval($field)]; + } + } + $this->Controller->set('requestedMetaFields', $requestedMetaFields); + return $query->contain([ + 'MetaFields' => [ + 'conditions' => $containConditions + ] + ]); + } + public function view(int $id, array $params = []): void { if (empty($id)) { @@ -543,13 +582,23 @@ class CRUDComponent extends Component $this->Controller->set('entity', $data); } - public function attachMetaTemplatesIfNeeded($data) + public function attachMetaTemplatesIfNeeded($data, array $metaTemplates = null) { if (!$this->metaFieldsSupported()) { return $data; } - $metaTemplates = $this->getMetaTemplates(); - $data = $this->attachMetaTemplates($data, $metaTemplates->toArray()); + if (!is_null($metaTemplates)) { + // We night be in the case where $metaTemplates gets re-used in a while loop + // We deep copy the meta-template so that the data attached is not preserved for the next iteration + $metaTemplates = array_map(function ($metaTemplate) { + $tmpEntity = $this->MetaTemplates->newEntity($metaTemplate->toArray()); + $tmpEntity['meta_template_fields'] = Hash::combine($tmpEntity['meta_template_fields'], '{n}.id', '{n}'); // newEntity resets array indexing + return $tmpEntity; + }, $metaTemplates); + } else { + $metaTemplates = $this->getMetaTemplates()->toArray(); + } + $data = $this->attachMetaTemplates($data, $metaTemplates); return $data; } diff --git a/src/Utility/UI/IndexSetting.php b/src/Utility/UI/IndexSetting.php new file mode 100644 index 0000000..ac3560e --- /dev/null +++ b/src/Utility/UI/IndexSetting.php @@ -0,0 +1,30 @@ +user_settings_by_name['ui.table_setting']['value']) ? json_decode($user->user_settings_by_name['ui.table_setting']['value'], true) : []; + return $rawSetting; + } + + public static function getTableSetting($user, $tableId): array + { + $rawSetting = IndexSetting::getAllSetting($user); + if (is_object($tableId)) { + $tableId = IndexSetting::getIDFromTable($tableId); + } + $tableSettings = !empty($rawSetting[$tableId]) ? $rawSetting[$tableId] : []; + return $tableSettings; + } + + public static function getIDFromTable(Object $table): string + { + return sprintf('%s_index', Inflector::variable(Inflector::singularize(($table->getAlias())))); + } +} \ No newline at end of file diff --git a/templates/Individuals/index.php b/templates/Individuals/index.php index 84ed353..7aa3115 100644 --- a/templates/Individuals/index.php +++ b/templates/Individuals/index.php @@ -28,7 +28,6 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], [ 'type' => 'table_action', - 'table_setting_id' => 'individual_index', ] ] ], @@ -73,7 +72,6 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('ContactDB Individuals Index'), 'description' => __('A list of individuals known by your Cerebrate instance. This list can get populated either directly, by adding new individuals or by fetching them from trusted remote sources. Additionally, users created for the platform will always have an individual identity.'), - 'pull' => 'right', 'actions' => [ [ 'url' => '/individuals/view', diff --git a/templates/element/genericElements/IndexTable/index_table.php b/templates/element/genericElements/IndexTable/index_table.php index a893b90..1e8c8dc 100644 --- a/templates/element/genericElements/IndexTable/index_table.php +++ b/templates/element/genericElements/IndexTable/index_table.php @@ -1,5 +1,5 @@ element('/genericElements/IndexTable/index_table', [ * 'top_bar' => ( * // search/filter bar information compliant with ListTopBar @@ -16,6 +16,23 @@ * )); * */ + + $newMetaFields = []; + if (!empty($requestedMetaFields)) { // Create mapping for new index table fields on the fly + foreach ($requestedMetaFields as $requestedMetaField) { + $template_id = $requestedMetaField['template_id']; + $meta_template_field_id = $requestedMetaField['meta_template_field_id']; + $newMetaFields[] = [ + 'name' => $meta_templates[$template_id]['meta_template_fields'][$meta_template_field_id]['field'], + 'data_path' => "MetaTemplates.{$template_id}.meta_template_fields.{$meta_template_field_id}.metaFields.{n}.value", + 'element' => 'generic_field', + '_metafield' => true, + '_automatic_field' => true, + ]; + } + } + $data['fields'] = array_merge($data['fields'], $newMetaFields); + $tableRandomValue = Cake\Utility\Security::randomString(8); echo '
'; if (!empty($data['title'])) { @@ -104,9 +121,10 @@ } $tbody = '' . $rows . ''; echo sprintf( - '%s%s
', + '%s%s
', $tableRandomValue, $tableRandomValue, + h($this->Url->build(['action' => $this->request->getParam('action'),])), $this->element( '/genericElements/IndexTable/headers', [ diff --git a/templates/element/genericElements/ListTopBar/group_table_action.php b/templates/element/genericElements/ListTopBar/group_table_action.php index 64d3cb2..bee116e 100644 --- a/templates/element/genericElements/ListTopBar/group_table_action.php +++ b/templates/element/genericElements/ListTopBar/group_table_action.php @@ -1,9 +1,12 @@ user_settings_by_name['ui.table_setting']['value']) ? json_decode($loggedUser->user_settings_by_name['ui.table_setting']['value'], true) : []; -$tableSettings = !empty($tableSettings[$data['table_setting_id']]) ? $tableSettings[$data['table_setting_id']] : []; +$data['table_setting_id'] = !empty($data['table_setting_id']) ? $data['table_setting_id'] : IndexSetting::getIDFromTable($model); +$tableSettings = IndexSetting::getTableSetting($loggedUser, $data['table_setting_id']); $compactDisplay = !empty($tableSettings['compact_display']); $availableColumnsHtml = $this->element('/genericElements/ListTopBar/group_table_action/hiddenColumns', [ @@ -11,6 +14,34 @@ $availableColumnsHtml = $this->element('/genericElements/ListTopBar/group_table_ 'tableSettings' => $tableSettings, 'table_setting_id' => $data['table_setting_id'], ]); + +$metaTemplateColumnMenu = []; +if (!empty($meta_templates)) { + foreach ($meta_templates as $meta_template) { + $numberActiveMetaField = !empty($tableSettings['visible_meta_column'][$meta_template->id]) ? count($tableSettings['visible_meta_column'][$meta_template->id]) : 0; + $metaTemplateColumnMenu[] = [ + 'text' => $meta_template->name, + 'icon' => 'object-group', + 'badge' => [ + 'text' => $numberActiveMetaField, + 'variant' => 'secondary', + 'title' => __n('{0} meta-field active for this meta-template', '{0} meta-fields active for this meta-template', $numberActiveMetaField, $numberActiveMetaField), + ], + 'keepOpen' => true, + 'menu' => [ + [ + 'html' => $this->element('/genericElements/ListTopBar/group_table_action/hiddenMetaColumns', [ + 'tableSettings' => $tableSettings, + 'table_setting_id' => $data['table_setting_id'], + 'meta_template' => $meta_template, + ]) + ] + ], + ]; + } +} +$indexColumnMenu = array_merge([['html' => $availableColumnsHtml]], $metaTemplateColumnMenu); + $compactDisplayHtml = $this->element('/genericElements/ListTopBar/group_table_action/compactDisplay', [ 'table_data' => $table_data, 'tableSettings' => $tableSettings, @@ -35,24 +66,11 @@ $compactDisplayHtml = $this->element('/genericElements/ListTopBar/group_table_ac 'data-table_setting_id' => $data['table_setting_id'], ], 'menu' => [ - // [ - // 'text' => __('Group by'), - // 'icon' => 'layer-group', - // 'menu' => [ - // [ - // 'text' => 'fields to be grouped by', TODO:implement - // ] - // ], - // ], [ 'text' => __('Show/hide columns'), 'icon' => 'eye-slash', 'keepOpen' => true, - 'menu' => [ - [ - 'html' => $availableColumnsHtml, - ] - ], + 'menu' => $indexColumnMenu, ], [ 'html' => $compactDisplayHtml, diff --git a/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php b/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php index 9714be4..ed2c970 100644 --- a/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php +++ b/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php @@ -4,7 +4,10 @@ $tableSettings['hidden_column'] = $tableSettings['hidden_column'] ?? []; $availableColumnsHtml = ''; $availableColumns = []; foreach ($table_data['fields'] as $field) { - if (!empty($field['element']) && $field['element'] === 'selector') { + if ( + (!empty($field['element']) && $field['element'] === 'selector') || + !empty($field['_automatic_field']) + ) { continue; } $fieldName = !empty($field['name']) ? $field['name'] : \Cake\Utility\Inflector::humanize($field['data_path']); @@ -13,7 +16,7 @@ foreach ($table_data['fields'] as $field) { $availableColumnsHtml .= sprintf( '
-
', @@ -34,19 +37,28 @@ echo $availableColumnsHtml; \ No newline at end of file diff --git a/templates/element/genericElements/ListTopBar/group_table_action/hiddenMetaColumns.php b/templates/element/genericElements/ListTopBar/group_table_action/hiddenMetaColumns.php new file mode 100644 index 0000000..8415dce --- /dev/null +++ b/templates/element/genericElements/ListTopBar/group_table_action/hiddenMetaColumns.php @@ -0,0 +1,33 @@ +meta_template_fields as $j => $meta_template_field) { + $fieldName = $meta_template_field['field']; + $fieldId = "metatemplate-{$meta_template_field->meta_template_id}-{$meta_template_field->id}"; + $isVisible = false; + if (!empty($tableSettings['visible_meta_column']) && !empty($tableSettings['visible_meta_column'][$meta_template_field->meta_template_id])) { + $isVisible = in_array($meta_template_field->id, $tableSettings['visible_meta_column'][$meta_template_field->meta_template_id]); + } + $availableMetaColumnsHtml .= sprintf( + '
+ + +
', + h($fieldId), + h($fieldId), + $isVisible ? 'checked' : '', + h($fieldId), + h($fieldName) + ); + } +} + +$availableMetaColumnsHtml = $this->Bootstrap->genNode('form', [ + 'class' => ['visible-meta-column-form', 'px-2 py-1'], +], $availableMetaColumnsHtml); +echo $availableMetaColumnsHtml; +?> diff --git a/webroot/js/table-settings.js b/webroot/js/table-settings.js index 95db30a..bb23099 100644 --- a/webroot/js/table-settings.js +++ b/webroot/js/table-settings.js @@ -1,13 +1,12 @@ -// function saveHiddenColumns(table_setting_id, newTableSettings) { -function mergeAndSaveSettings(table_setting_id, newTableSettings) { +function mergeAndSaveSettings(table_setting_id, newTableSettings, automaticFeedback=true) { const settingName = 'ui.table_setting' const urlGet = `/user-settings/getSettingByName/${settingName}` - AJAXApi.quickFetchJSON(urlGet).then(tableSettings => { + return AJAXApi.quickFetchJSON(urlGet).then(tableSettings => { tableSettings = JSON.parse(tableSettings.value) newTableSettings = mergeNewTableSettingsIntoOld(table_setting_id, tableSettings, newTableSettings) - saveTableSetting(settingName, newTableSettings) + return saveTableSetting(settingName, newTableSettings, automaticFeedback) }).catch((e) => { // setting probably doesn't exist - saveTableSetting(settingName, newTableSettings) + return saveTableSetting(settingName, newTableSettings, automaticFeedback) }) } @@ -18,17 +17,20 @@ function mergeNewTableSettingsIntoOld(table_setting_id, oldTableSettings, newTab return tableSettings } -function saveTableSetting(settingName, newTableSettings) { +function saveTableSetting(settingName, newTableSettings, automaticFeedback=true) { const urlSet = `/user-settings/setSetting/${settingName}` - AJAXApi.quickFetchAndPostForm(urlSet, { + return AJAXApi.quickFetchAndPostForm(urlSet, { value: JSON.stringify(newTableSettings) }, { provideFeedback: false - }).then(() => { - UI.toast({ - variant: 'success', - title: 'Table setting saved', - delay: 3000 - }) + }).then((postResult) => { + if (automaticFeedback) { + UI.toast({ + variant: 'success', + title: 'Table setting saved', + delay: 3000 + }) + } + return postResult }) } \ No newline at end of file