new: [genericElements:index_table] Support of meta_fields in table column

pull/93/head
Sami Mokaddem 2021-11-10 09:06:39 +01:00
parent d71f48fc9f
commit a005d0491f
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
8 changed files with 247 additions and 51 deletions

View File

@ -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;
}

View File

@ -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()))));
}
}

View File

@ -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',

View File

@ -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',
[

View File

@ -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,

View File

@ -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>

View File

@ -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;
?>

View File

@ -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
})
}