From 943999cef9aa446c94deefab4c6b7aa976655107 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Wed, 26 Aug 2026 16:45:06 +0200 Subject: [PATCH] adapt filter/tablePresets/tablePresetsModule to account for dynamic tables with a new presetColumns property. by default the preset holds all columns, if a scope is given it only holds those columns, their header filters and their sort; on apply the scope is merged into the current table columns so the dynamic ones keep their place; --- public/js/components/filter/Filter.js | 26 ++++-- .../components/filter/Filter/TablePresets.js | 41 ++++++--- .../customModules/TablePresetsModule.js | 89 ++++++++++++++----- 3 files changed, 117 insertions(+), 39 deletions(-) diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 70b11401e..7f3c803ff 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -106,6 +106,13 @@ export const CoreFilterCmpt = { type: Array, default: [], }, + // fields of the columns a preset is allowed to handle. Give the constant + // columns here if the table builds dynamic columns at runtime. An empty + // array gives the preset every column of the table. + presetColumns: { + type: Array, + default: [], + }, }, data: function () { return { @@ -942,6 +949,10 @@ export const CoreFilterCmpt = { return res; }, {}); }, + isColumnInPresetScope(columnField) { + if (!this.$props.presetColumns.length) return true; + return this.$props.presetColumns.includes(columnField); + }, afterTablePresetApplied(preset) { this.selectedFields = [...preset.displayedColumns]; this.appliedPreset = { ...preset }; @@ -973,6 +984,7 @@ export const CoreFilterCmpt = { const displayedColumns = this.tabulator.columnManager.columns .filter((column) => column.field !== "collapse") + .filter((column) => this.isColumnInPresetScope(column.field)) .filter((column) => column.visible) .map((column) => column.field); @@ -1029,12 +1041,13 @@ export const CoreFilterCmpt = { }); const activeSort = this.tabulator.getSorters()[0]; - const sort = activeSort - ? { - column: activeSort.field, - direction: activeSort.dir, - } - : null; + const sort = + activeSort && this.isColumnInPresetScope(activeSort.field) + ? { + column: activeSort.field, + direction: activeSort.dir, + } + : null; if ( sort?.column !== this.appliedPreset.sort?.column || sort?.direction !== this.appliedPreset.sort?.direction @@ -1148,6 +1161,7 @@ export const CoreFilterCmpt = { :presetsId="$props.presetsId" :tabulator="tabulator" :generalPresets="$props.generalPresets" + :presetColumns="$props.presetColumns" ref="tablePresets" class="card-body collapse" > diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js index a8a0c8e4c..e95b76f4b 100644 --- a/public/js/components/filter/Filter/TablePresets.js +++ b/public/js/components/filter/Filter/TablePresets.js @@ -26,6 +26,9 @@ export default { presetsId: { type: String }, tabulator: { type: Object }, generalPresets: { type: Array, default: [] }, + // fields of the columns the preset is allowed to handle. An empty array + // gives the preset every column of the table. + presetColumns: { type: Array, default: [] }, }, emits: ["tablePresetApplied"], data: function () { @@ -162,6 +165,10 @@ export default { }, }, methods: { + isInPresetScope(columnField) { + if (!this.$props.presetColumns.length) return true; + return this.$props.presetColumns.includes(columnField); + }, getPresetIndex() { return this.presetIndexBeingApplied; }, @@ -227,9 +234,9 @@ export default { } }, generateNewPreset() { - const columns = this.tabulator.columnManager.columns.filter( - (column) => column.field !== "collapse", - ); + const columns = this.tabulator.columnManager.columns + .filter((column) => column.field !== "collapse") + .filter((column) => this.isInPresetScope(column.field)); const displayedColumns = columns .filter((column) => column.visible) @@ -251,12 +258,13 @@ export default { ); const activeSort = this.tabulator.getSorters()[0]; - const sort = activeSort - ? { - column: activeSort.field, - direction: activeSort.dir, - } - : null; + const sort = + activeSort && this.isInPresetScope(activeSort.field) + ? { + column: activeSort.field, + direction: activeSort.dir, + } + : null; this.newPreset = { displayedColumns, @@ -325,8 +333,11 @@ export default { preset.displayedColumns.length; preset.displayedColumns = preset.displayedColumns.filter( (columnField) => { - return validColumns.some( - (column) => column.field === columnField, + return ( + this.isInPresetScope(columnField) && + validColumns.some( + (column) => column.field === columnField, + ) ); }, ); @@ -365,10 +376,11 @@ export default { if (preset.sort) { if ( + !this.isInPresetScope(preset.sort.column) || !validColumns.some( (column) => column.field === preset.sort.column && - column.headerSort, + column.headerSort !== false, ) ) { preset.sort = null; @@ -407,7 +419,10 @@ export default { window.localStorage["tabulatorPreset-" + this.$props.presetsId] = preset.id ? "custom:" + preset.id : "general:" + preset.name; - this.$props.tabulator.modules.tablePresets.applyPreset(preset); + this.$props.tabulator.modules.tablePresets.applyPreset( + preset, + this.$props.presetColumns, + ); this.$emit("tablePresetApplied", { preset }); diff --git a/public/js/tabulator/customModules/TablePresetsModule.js b/public/js/tabulator/customModules/TablePresetsModule.js index 644514dc5..8b05bd1ae 100644 --- a/public/js/tabulator/customModules/TablePresetsModule.js +++ b/public/js/tabulator/customModules/TablePresetsModule.js @@ -8,27 +8,36 @@ export default class TablePresetsModule extends Module { super(table); } - applyPreset(preset) { - let columnsConfig = this.table.options.columns; - let reorderedColumnsConfig = []; - preset.displayedColumns.forEach((columnField) => { - let columnConfig = columnsConfig.find( - (column) => column.field === columnField, - ); - if (!columnConfig) return; - columnConfig.visible = true; - reorderedColumnsConfig.push(columnConfig); - }); - - let hiddenColumnsConfig = columnsConfig - .filter((column) => !preset.displayedColumns.includes(column.field)) - .map((column) => { - column.visible = false; - return column; + // presetColumns limits the preset to the given column fields. A table with dynamic + // columns gives the fields of its constant columns here. + applyPreset(preset, presetColumns = []) { + if (presetColumns.length) { + this.applyColumnsInScope(preset, presetColumns); + } else { + let columnsConfig = this.table.options.columns; + let reorderedColumnsConfig = []; + preset.displayedColumns.forEach((columnField) => { + let columnConfig = columnsConfig.find( + (column) => column.field === columnField, + ); + if (!columnConfig) return; + columnConfig.visible = true; + reorderedColumnsConfig.push(columnConfig); }); - this.table.setColumns( - reorderedColumnsConfig.concat(hiddenColumnsConfig), - ); + + let hiddenColumnsConfig = columnsConfig + .filter( + (column) => + !preset.displayedColumns.includes(column.field), + ) + .map((column) => { + column.visible = false; + return column; + }); + this.table.setColumns( + reorderedColumnsConfig.concat(hiddenColumnsConfig), + ); + } this.table.clearHeaderFilter(); if (preset.headerFilters) { @@ -61,4 +70,44 @@ export default class TablePresetsModule extends Module { ); } } + + // The preset orders and hides the columns of the scope only. A column outside the + // scope keeps its position, because the table does not show it in every state. + applyColumnsInScope(preset, presetColumns) { + const displayedColumns = preset.displayedColumns ?? []; + + // a displayed column takes the rank of the preset. A column that the preset + // hides has no rank and keeps the current order at the end of its block. + const rank = (columnConfig) => { + const index = displayedColumns.indexOf(columnConfig.field); + return index === -1 ? displayedColumns.length : index; + }; + + let columnsConfig = []; + let scopeBlock = []; + + // the sort runs for each block of neighbouring scope columns. This way a scope + // column never moves over a column outside the scope. + const addScopeBlock = () => { + scopeBlock.sort((a, b) => rank(a) - rank(b)); + columnsConfig = columnsConfig.concat(scopeBlock); + scopeBlock = []; + }; + + this.table.getColumnDefinitions().forEach((columnConfig) => { + if (!presetColumns.includes(columnConfig.field)) { + addScopeBlock(); + columnsConfig.push(columnConfig); + return; + } + + columnConfig.visible = displayedColumns.includes( + columnConfig.field, + ); + scopeBlock.push(columnConfig); + }); + addScopeBlock(); + + this.table.setColumns(columnsConfig); + } }