new: [genericElements:index_table] Support of meta_fields in table column
parent
d71f48fc9f
commit
a005d0491f
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Utility\UI;
|
||||
|
||||
use Cake\Utility\Inflector;
|
||||
|
||||
class IndexSetting
|
||||
{
|
||||
public static function getAllSetting($user): array
|
||||
{
|
||||
$rawSetting = !empty($user->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()))));
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
/*
|
||||
/*
|
||||
* echo $this->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 '<div id="table-container-' . h($tableRandomValue) . '">';
|
||||
if (!empty($data['title'])) {
|
||||
|
@ -104,9 +121,10 @@
|
|||
}
|
||||
$tbody = '<tbody>' . $rows . '</tbody>';
|
||||
echo sprintf(
|
||||
'<table class="table table-hover" id="index-table-%s" data-table-random-value="%s">%s%s</table>',
|
||||
'<table class="table table-hover" id="index-table-%s" data-table-random-value="%s" data-reload-url="%s">%s%s</table>',
|
||||
$tableRandomValue,
|
||||
$tableRandomValue,
|
||||
h($this->Url->build(['action' => $this->request->getParam('action'),])),
|
||||
$this->element(
|
||||
'/genericElements/IndexTable/headers',
|
||||
[
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<?php
|
||||
if (empty($data['table_setting_id'])) {
|
||||
|
||||
use App\Utility\UI\IndexSetting;
|
||||
|
||||
if (empty($data['table_setting_id']) && empty($model)) {
|
||||
throw new Exception(__('`table_setting_id` must be set in order to use the `table_action` table topbar'));
|
||||
}
|
||||
$tableSettings = !empty($loggedUser->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,
|
||||
|
|
|
@ -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(
|
||||
'<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="columnCheck-%s" data-columnname="%s" %s>
|
||||
<label class="form-check-label w-100 cursor-pointer" for="columnCheck-%s">
|
||||
<label class="form-check-label w-100 cursor-pointer font-monospace user-select-none" for="columnCheck-%s">
|
||||
%s
|
||||
</label>
|
||||
</div>',
|
||||
|
@ -34,19 +37,28 @@ echo $availableColumnsHtml;
|
|||
<script>
|
||||
(function() {
|
||||
const debouncedHiddenColumnSaver = debounce(mergeAndSaveSettings, 2000)
|
||||
$('form.visible-column-form').find('input').change(function() {
|
||||
const $dropdownMenu = $(this).closest(`[data-table-random-value]`)
|
||||
const tableRandomValue = $dropdownMenu.attr('data-table-random-value')
|
||||
const $container = $dropdownMenu.closest('div[id^="table-container-"]')
|
||||
const $table = $container.find(`table[data-table-random-value="${tableRandomValue}"]`)
|
||||
const table_setting_id = $dropdownMenu.data('table_setting_id');
|
||||
toggleColumn(this.getAttribute('data-columnname'), this.checked, $table)
|
||||
let tableSettings = {}
|
||||
tableSettings[table_setting_id] = genTableSettings($container)
|
||||
debouncedHiddenColumnSaver(table_setting_id, tableSettings)
|
||||
})
|
||||
const debouncedHiddenColumnSaverWithReload = debounce(mergeAndSaveSettingsWithReload, 2000)
|
||||
|
||||
function attachListeners() {
|
||||
$('form.visible-column-form, form.visible-meta-column-form').find('input').change(function() {
|
||||
const $dropdownMenu = $(this).closest(`[data-table-random-value]`)
|
||||
const tableRandomValue = $dropdownMenu.attr('data-table-random-value')
|
||||
const $container = $dropdownMenu.closest('div[id^="table-container-"]')
|
||||
const $table = $container.find(`table[data-table-random-value="${tableRandomValue}"]`)
|
||||
const table_setting_id = $dropdownMenu.data('table_setting_id');
|
||||
toggleColumn(this.getAttribute('data-columnname'), this.checked, $table)
|
||||
let tableSettings = {}
|
||||
tableSettings[table_setting_id] = genTableSettings($container)
|
||||
if ($(this).closest('form').hasClass('visible-meta-column-form')) {
|
||||
debouncedHiddenColumnSaverWithReload(table_setting_id, tableSettings, $table)
|
||||
} else {
|
||||
debouncedHiddenColumnSaver(table_setting_id, tableSettings)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function toggleColumn(columnName, isVisible, $table) {
|
||||
// debugger;
|
||||
if (isVisible) {
|
||||
$table.find(`th[data-columnname="${columnName}"],td[data-columnname="${columnName}"]`).show()
|
||||
} else {
|
||||
|
@ -61,12 +73,47 @@ echo $availableColumnsHtml;
|
|||
return $(this).data('columnname')
|
||||
}))
|
||||
tableSetting['hidden_column'] = hiddenColumns
|
||||
|
||||
const $visibleMetaColumns = $container.find('form.visible-meta-column-form').find('input:checked')
|
||||
const visibleMetaColumns = Array.from($visibleMetaColumns.map(function() {
|
||||
const columnName = $(this).data('columnname')
|
||||
const split = columnName.split('-')
|
||||
return [
|
||||
[split[1], split[2]]
|
||||
]
|
||||
})).reduce((store, composedValue) => {
|
||||
let [templateId, fieldId] = composedValue
|
||||
if (store[templateId] === undefined) {
|
||||
store[templateId] = [];
|
||||
}
|
||||
store[templateId].push(fieldId)
|
||||
return store
|
||||
}, {})
|
||||
tableSetting['visible_meta_column'] = visibleMetaColumns
|
||||
return tableSetting
|
||||
}
|
||||
|
||||
function mergeAndSaveSettingsWithReload(table_setting_id, tableSettings, $table) {
|
||||
mergeAndSaveSettings(table_setting_id, tableSettings, false).then((apiResult) => {
|
||||
const theToast = UI.toast({
|
||||
variant: 'success',
|
||||
title: apiResult.message,
|
||||
bodyHtml: $('<div/>').append(
|
||||
$('<span/>').text('<?= __('The table needs to be reloaded for the new fields to be included.') ?>'),
|
||||
$('<button/>').addClass(['btn', 'btn-primary', 'btn-sm', 'ms-3']).text('<?= __('Reload table') ?>').click(function() {
|
||||
const reloadUrl = $table.data('reload-url');
|
||||
UI.reload(reloadUrl, $table.closest('div[id^="table-container-"]'), $(this)).then(() => {
|
||||
theToast.removeToast()
|
||||
})
|
||||
}),
|
||||
),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
addSupportOfNestedDropdown();
|
||||
const $form = $('form.visible-column-form')
|
||||
const $form = $('form.visible-column-form, form.visible-meta-column-form')
|
||||
const $checkboxes = $form.find('input').not(':checked')
|
||||
const $dropdownMenu = $form.closest('.dropdown')
|
||||
const tableRandomValue = $dropdownMenu.attr('data-table-random-value')
|
||||
|
@ -75,6 +122,7 @@ echo $availableColumnsHtml;
|
|||
$checkboxes.each(function() {
|
||||
toggleColumn(this.getAttribute('data-columnname'), this.checked, $table)
|
||||
})
|
||||
attachListeners()
|
||||
})
|
||||
})()
|
||||
</script>
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
$tableSettings['hidden_column'] = $tableSettings['hidden_column'] ?? [];
|
||||
|
||||
$availableMetaColumnsHtml = '';
|
||||
if (!empty($meta_template)) {
|
||||
foreach ($meta_template->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(
|
||||
'<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="columnCheck-%s" data-columnname="%s" %s>
|
||||
<label class="form-check-label w-100 cursor-pointer font-monospace user-select-none" for="columnCheck-%s">
|
||||
%s
|
||||
</label>
|
||||
</div>',
|
||||
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;
|
||||
?>
|
|
@ -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
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue