diff --git a/templates/Individuals/index.php b/templates/Individuals/index.php index 4930899..84ed353 100644 --- a/templates/Individuals/index.php +++ b/templates/Individuals/index.php @@ -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 ''; -?> +?> \ No newline at end of file diff --git a/templates/Organisations/index.php b/templates/Organisations/index.php index 4a98718..0448c77 100644 --- a/templates/Organisations/index.php +++ b/templates/Organisations/index.php @@ -26,6 +26,10 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'data' => '', 'searchKey' => 'value', 'allowFilering' => true + ], + [ + 'type' => 'table_action', + 'table_setting_id' => 'organisation_index', ] ] ], diff --git a/templates/element/genericElements/IndexTable/headers.php b/templates/element/genericElements/IndexTable/headers.php index e38714a..8366449 100644 --- a/templates/element/genericElements/IndexTable/headers.php +++ b/templates/element/genericElements/IndexTable/headers.php @@ -22,7 +22,8 @@ } $headersHtml .= sprintf( - '%s', + '%s', + h(\Cake\Utility\Inflector::variable(!empty($header['name']) ? $header['name'] : \Cake\Utility\Inflector::humanize($header['data_path']))), $header_data ); } diff --git a/templates/element/genericElements/IndexTable/index_table.php b/templates/element/genericElements/IndexTable/index_table.php index fc2a066..a893b90 100644 --- a/templates/element/genericElements/IndexTable/index_table.php +++ b/templates/element/genericElements/IndexTable/index_table.php @@ -59,6 +59,7 @@ '/genericElements/ListTopBar/scaffold', [ 'data' => $data['top_bar'], + 'table_data' => $data, 'tableRandomValue' => $tableRandomValue ] ); diff --git a/templates/element/genericElements/IndexTable/row.php b/templates/element/genericElements/IndexTable/row.php index c019110..b426534 100644 --- a/templates/element/genericElements/IndexTable/row.php +++ b/templates/element/genericElements/IndexTable/row.php @@ -31,7 +31,7 @@ ); } $rowHtml .= sprintf( - '%s', + '%s', (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 diff --git a/templates/element/genericElements/ListTopBar/group_table_action.php b/templates/element/genericElements/ListTopBar/group_table_action.php new file mode 100644 index 0000000..a50184c --- /dev/null +++ b/templates/element/genericElements/ListTopBar/group_table_action.php @@ -0,0 +1,57 @@ +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'], +]); +?> + + 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' + ], + ] + ]); + ?> + diff --git a/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php b/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php new file mode 100644 index 0000000..a6647a4 --- /dev/null +++ b/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php @@ -0,0 +1,100 @@ + + + + ', + 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; +?> + + \ No newline at end of file diff --git a/templates/element/genericElements/ListTopBar/scaffold.php b/templates/element/genericElements/ListTopBar/scaffold.php index 675a660..8bd056b 100644 --- a/templates/element/genericElements/ListTopBar/scaffold.php +++ b/templates/element/genericElements/ListTopBar/scaffold.php @@ -1,8 +1,13 @@ 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"; diff --git a/webroot/js/main.js b/webroot/js/main.js index 3fb2fa8..a1584a5 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -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') diff --git a/webroot/js/settings.js b/webroot/js/settings.js index e8b4808..2530740 100644 --- a/webroot/js/settings.js +++ b/webroot/js/settings.js @@ -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)