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 onpull/72/head
parent
b811d2ed99
commit
7941a6530a
|
@ -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>';
|
||||
?>
|
||||
?>
|
|
@ -26,6 +26,10 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'data' => '',
|
||||
'searchKey' => 'value',
|
||||
'allowFilering' => true
|
||||
],
|
||||
[
|
||||
'type' => 'table_action',
|
||||
'table_setting_id' => 'organisation_index',
|
||||
]
|
||||
]
|
||||
],
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
'/genericElements/ListTopBar/scaffold',
|
||||
[
|
||||
'data' => $data['top_bar'],
|
||||
'table_data' => $data,
|
||||
'tableRandomValue' => $tableRandomValue
|
||||
]
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; ?>
|
|
@ -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>
|
|
@ -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";
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue