new: [genericElement:indexTable] Table actions - WiP

Table actions allow to perform actions on the table such as hide/show columns, regroup rows by fields and so on
pull/72/head
Sami Mokaddem 2021-10-20 09:39:12 +02:00
parent b811d2ed99
commit 7941a6530a
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
10 changed files with 252 additions and 13 deletions

View File

@ -25,6 +25,10 @@ echo $this->element('genericElements/IndexTable/index_table', [
'data' => '',
'searchKey' => 'value',
'allowFilering' => true
],
[
'type' => 'table_action',
'table_setting_id' => 'individual_index',
]
]
],
@ -90,4 +94,4 @@ echo $this->element('genericElements/IndexTable/index_table', [
]
]);
echo '</div>';
?>
?>

View File

@ -26,6 +26,10 @@ echo $this->element('genericElements/IndexTable/index_table', [
'data' => '',
'searchKey' => 'value',
'allowFilering' => true
],
[
'type' => 'table_action',
'table_setting_id' => 'organisation_index',
]
]
],

View File

@ -22,7 +22,8 @@
}
$headersHtml .= sprintf(
'<th scope="col">%s</th>',
'<th scope="col" data-columnname="%s">%s</th>',
h(\Cake\Utility\Inflector::variable(!empty($header['name']) ? $header['name'] : \Cake\Utility\Inflector::humanize($header['data_path']))),
$header_data
);
}

View File

@ -59,6 +59,7 @@
'/genericElements/ListTopBar/scaffold',
[
'data' => $data['top_bar'],
'table_data' => $data,
'tableRandomValue' => $tableRandomValue
]
);

View File

@ -31,7 +31,7 @@
);
}
$rowHtml .= sprintf(
'<td%s%s%s%s%s%s%s>%s</td>',
'<td%s%s%s%s%s%s%s%s>%s</td>',
(empty($field['id'])) ? '' : sprintf('id="%s"', $field['id']),
(empty($field['class'])) ? '' : sprintf(' class="%s"', $field['class']),
(empty($field['style'])) ? '' : sprintf(' style="%s"', $field['style']),
@ -42,6 +42,10 @@
h(implode(', ', $field['data_path'])) :
(h($field['data_path']))
),
sprintf(
' data-columnname="%s"',
h(\Cake\Utility\Inflector::variable(!empty($field['name']) ? $field['name'] : \Cake\Utility\Inflector::humanize($field['data_path'])))
),
(empty($field['encode_raw_value']) || empty($field['data_path'])) ? '' : sprintf(' data-value="%s"', (h($this->Hash->extract($row, $field['data_path'])[0]))),
(empty($field['ondblclick'])) ? '' : sprintf(' ondblclick="%s"', $field['ondblclick']),
$valueField

View File

@ -0,0 +1,57 @@
<?php
if (empty($data['table_setting_id'])) {
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']] : [];
$availableColumnsHtml = $this->element('/genericElements/ListTopBar/group_table_action/hiddenColumns', [
'table_data' => $table_data,
'tableSettings' => $tableSettings,
'table_setting_id' => $data['table_setting_id'],
]);
?>
<?php if (!isset($data['requirement']) || $data['requirement']) : ?>
<?php
echo $this->Bootstrap->dropdownMenu([
'dropdown-class' => 'ms-1',
'alignment' => 'end',
'direction' => 'down',
'toggle-button' => [
'icon' => 'sliders-h',
'variant' => 'primary',
],
'submenu_alignment' => 'end',
'submenu_direction' => 'start',
'params' => [
'data-table-random-value' => $tableRandomValue,
'data-table_setting_id' => $data['table_setting_id'],
],
'menu' => [
[
'text' => __('Group by'),
'icon' => 'layer-group',
'menu' => [
[
'text' => 'fields to be grouped by',
]
],
],
[
'text' => __('Show/hide columns'),
'icon' => 'eye-slash',
'keepOpen' => true,
'menu' => [
[
'html' => $availableColumnsHtml,
]
],
],
[
'text' => __('Compact display'),
'icon' => 'text-height'
],
]
]);
?>
<?php endif; ?>

View File

@ -0,0 +1,100 @@
<?php
$tableSettings['hidden_column'] = $tableSettings['hidden_column'] ?? [];
$availableColumnsHtml = '';
$availableColumns = [];
foreach ($table_data['fields'] as $field) {
$fieldName = !empty($field['name']) ? $field['name'] : \Cake\Utility\Inflector::humanize($field['data_path']);
$isVisible = !in_array(h(\Cake\Utility\Inflector::variable($fieldName)), $tableSettings['hidden_column']);
$availableColumns[] = $fieldName;
$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" for="columnCheck-%s">
%s
</label>
</div>',
h(\Cake\Utility\Inflector::variable($fieldName)),
h(\Cake\Utility\Inflector::variable($fieldName)),
$isVisible ? 'checked' : '',
h(\Cake\Utility\Inflector::variable($fieldName)),
h($fieldName)
);
}
$availableColumnsHtml = $this->Bootstrap->genNode('form', [
'class' => ['visible-column-form', 'px-2 py-1'],
], $availableColumnsHtml);
echo $availableColumnsHtml;
?>
<script>
const debouncedHiddenColumnSaver = debounce(saveHiddenColumns, 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)
tableSettings = {}
tableSettings[table_setting_id] = genTableSettings($container)
debouncedHiddenColumnSaver(table_setting_id, tableSettings)
})
function toggleColumn(columnName, isVisible, $table) {
if (isVisible) {
$table.find(`th[data-columnname="${columnName}"],td[data-columnname="${columnName}"]`).show()
} else {
$table.find(`th[data-columnname="${columnName}"],td[data-columnname="${columnName}"]`).hide()
}
}
function saveHiddenColumns(table_setting_id, newTableSettings) {
const settingName = 'ui.table_setting'
const urlGet = `/user-settings/getSettingByName/${settingName}`
AJAXApi.quickFetchJSON(urlGet).then(tableSettings => {
tableSettings = JSON.parse(tableSettings.value)
newTableSettings = mergeNewTableSettingsIntoOld(table_setting_id, tableSettings, newTableSettings)
saveTableSetting(settingName, newTableSettings)
}).catch((e) => { // setting probably doesn't exist
saveTableSetting(settingName, newTableSettings)
})
}
function genTableSettings($container) {
let tableSetting = {};
const $hiddenColumns = $container.find('form.visible-column-form').find('input').not(':checked')
const hiddenColumns = Array.from($hiddenColumns.map(function() {
return $(this).data('columnname')
}))
tableSetting['hidden_column'] = hiddenColumns
return tableSetting
}
function mergeNewTableSettingsIntoOld(table_setting_id, oldTableSettings, newTableSettings) {
tableSettings = Object.assign(oldTableSettings, newTableSettings)
return tableSettings
}
function saveTableSetting(settingName, newTableSettings) {
const urlSet = `/user-settings/setSetting/${settingName}`
AJAXApi.quickFetchAndPostForm(urlSet, {
value: JSON.stringify(newTableSettings)
}, {
provideFeedback: false
})
}
$(document).ready(function() {
const $form = $('form.visible-column-form')
const $checkboxes = $form.find('input').not(':checked')
const $dropdownMenu = $form.closest('.dropdown')
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}"]`)
$checkboxes.each(function() {
toggleColumn(this.getAttribute('data-columnname'), this.checked, $table)
})
})
</script>

View File

@ -1,8 +1,13 @@
<?php
$groups = '';
$hasGroupSearch = false;
foreach ($data['children'] as $group) {
$groups .= $this->element('/genericElements/ListTopBar/group_' . (empty($group['type']) ? 'simple' : h($group['type'])), array('data' => $group, 'tableRandomValue' => $tableRandomValue));
$groups .= $this->element('/genericElements/ListTopBar/group_' . (empty($group['type']) ? 'simple' : h($group['type'])), array(
'data' => $group,
'tableRandomValue' => $tableRandomValue,
'table_data' => $table_data,
));
$hasGroupSearch = $hasGroupSearch || (!empty($group['type']) && $group['type'] == 'search');
}
$tempClass = "btn-toolbar";

View File

@ -152,6 +152,18 @@ function focusSearchResults(evt) {
}
}
function saveUserSetting(statusNode, settingName, settingValue) {
const url = window.saveSettingURL
const data = {
name: settingName,
value: settingValue,
}
const APIOptions = {
statusNode: statusNode,
}
return AJAXApi.quickFetchAndPostForm(url, data, APIOptions)
}
function openSaveBookmarkModal(bookmark_url = '') {
const url = '/user-settings/saveBookmark';
UI.submissionModal(url).then(([modalFactory, ajaxApi]) => {
@ -195,11 +207,70 @@ function deleteBookmark(bookmark, forSidebar=false) {
}).catch((e) => { })
}
function overloadBSDropdown() {
// Inspired from https://jsfiddle.net/dallaslu/mvk4uhzL/
(function ($bs) {
const CLASS_NAME_HAS_CHILD = 'has-child-dropdown-show';
const CLASS_NAME_KEEP_OPEN = 'keep-dropdown-show';
$bs.Dropdown.prototype.toggle = function (_orginal) {
return function () {
document.querySelectorAll('.' + CLASS_NAME_HAS_CHILD).forEach(function (e) {
e.classList.remove(CLASS_NAME_HAS_CHILD);
});
let dd = this._element.closest('.dropdown')
if (dd !== null) {
dd = dd.parentNode.closest('.dropdown');
for (; dd && dd !== document; dd = dd.parentNode.closest('.dropdown')) {
dd.classList.add(CLASS_NAME_HAS_CHILD);
}
if (this._element.classList.contains('open-form')) {
const openFormId = this._element.getAttribute('data-open-form-id')
document.querySelectorAll('.' + CLASS_NAME_KEEP_OPEN).forEach(function (e) {
e.classList.remove(CLASS_NAME_KEEP_OPEN);
});
let dd = this._element.closest('.dropdown')
dd.classList.add(CLASS_NAME_KEEP_OPEN);
dd.setAttribute('data-open-form-id', openFormId)
dd = dd.parentNode.closest('.dropdown');
for (; dd && dd !== document; dd = dd.parentNode.closest('.dropdown')) {
dd.setAttribute('data-open-form-id', openFormId)
dd.classList.add(CLASS_NAME_KEEP_OPEN);
}
}
}
return _orginal.call(this);
}
}($bs.Dropdown.prototype.toggle);
document.querySelectorAll('.dropdown').forEach(function (dd) {
dd.addEventListener('hide.bs.dropdown', function (e) {
if (this.classList.contains(CLASS_NAME_HAS_CHILD)) {
this.classList.remove(CLASS_NAME_HAS_CHILD);
e.preventDefault();
}
if (e.clickEvent !== undefined) {
let dd = e.clickEvent.target.closest('.dropdown')
if (dd !== null) {
if (dd.classList.contains('keep-dropdown-show')) {
e.preventDefault();
}
}
}
e.stopPropagation(); // do not need pop in multi level mode
});
});
})(bootstrap);
}
var UI
$(document).ready(() => {
if (typeof UIFactory !== "undefined") {
UI = new UIFactory()
}
overloadBSDropdown();
const debouncedGlobalSearch = debounce(performGlobalSearch, 400)
$('#globalSearch')

View File

@ -60,15 +60,7 @@ $(document).ready(function () {
})
function saveSetting(statusNode, $input, settingName, settingValue) {
const url = window.saveSettingURL
const data = {
name: settingName,
value: settingValue,
}
const APIOptions = {
statusNode: statusNode,
}
AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((result) => {
saveUserSetting(statusNode, settingName, settingValue).then((result) => {
window.settingsFlattened[settingName] = result.data
if ($input.attr('type') == 'checkbox') {
$input.prop('checked', result.data.value == true)