Skip to content

Commit 1754913

Browse files
committed
Add menu to hide/show columns
1 parent 09f7e5b commit 1754913

File tree

2 files changed

+177
-125
lines changed

2 files changed

+177
-125
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script lang="ts">
2+
import { popup } from '@skeletonlabs/skeleton';
3+
import type { PopupSettings } from '@skeletonlabs/skeleton';
4+
5+
export let columns: { id: string; label: string; visible: boolean }[] = [];
6+
export let tableId: string;
7+
8+
const popupCombobox: PopupSettings = {
9+
event: 'click',
10+
target: `${tableId}-columns-menu`,
11+
placement: 'bottom'
12+
};
13+
</script>
14+
15+
<button
16+
type="button"
17+
class="btn btn-sm variant-filled-primary rounded-full order-last"
18+
use:popup={popupCombobox}>Columns</button
19+
>
20+
21+
<div
22+
class="bg-white dark:bg-surface-500 p-4 rounded-md shadow-md z-10"
23+
data-popup="{tableId}-columns-menu"
24+
>
25+
{#each columns as column}
26+
<div class="flex gap-3 items-center">
27+
<input type="checkbox" bind:checked={column.visible} />
28+
<span>{column.label}</span>
29+
</div>
30+
{/each}
31+
32+
<div class="arrow bg-white dark:bg-surface-500" />
33+
</div>

src/lib/components/Table/TableContent.svelte

Lines changed: 144 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
addExpandedRows,
1212
addColumnFilters,
1313
addTableFilter,
14-
addDataExport
14+
addDataExport,
15+
addHiddenColumns
1516
} from 'svelte-headless-table/plugins';
1617
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
1718
import { SlideToggle, storePopup } from '@skeletonlabs/skeleton';
@@ -24,6 +25,7 @@
2425
import TableFilterServer from './TableFilterServer.svelte';
2526
import TablePagination from './TablePagination.svelte';
2627
import TablePaginationServer from './TablePaginationServer.svelte';
28+
import ColumnsMenu from './ColumnsMenu.svelte';
2729
import { columnFilter, searchFilter } from './filter';
2830
import {
2931
cellStyle,
@@ -90,6 +92,7 @@
9092
fn: searchFilter,
9193
serverSide
9294
}),
95+
hideColumns: addHiddenColumns(),
9396
sort: addSortBy({
9497
disableMultiSort: true,
9598
serverSide
@@ -121,137 +124,149 @@
121124
// Creating an array of all the keys
122125
const accessors: AccessorType[] = Object.keys(allCols) as AccessorType[];
123126
127+
// Filtering out the excluded columns
128+
const unexcludedColumns = accessors.filter((accessor) => {
129+
// Filtering only unexcluded columns
130+
const key = accessor as string;
131+
if (columns !== undefined && key in columns && columns[key].exclude === true) {
132+
return false;
133+
}
134+
return true;
135+
});
136+
137+
// To control the visibility of columns in menu
138+
let shownColumns = unexcludedColumns.map((c) => {
139+
const key = c as string;
140+
const label = key.charAt(0).toUpperCase() + key.slice(1);
141+
return {
142+
id: c as string,
143+
label: columns && columns[key] && columns[key].header ? columns[key].header : label,
144+
visible: true
145+
};
146+
});
147+
124148
// Configuring every table column with the provided options
125149
const tableColumns = [
126-
...accessors
127-
.filter((accessor) => {
128-
// Filtering only unexcluded columns
129-
const key = accessor as string;
130-
if (columns !== undefined && key in columns && columns[key].exclude === true) {
131-
return false;
132-
}
133-
return true;
134-
})
135-
.map((accessor) => {
136-
const key = accessor as string;
137-
// Applying configuration options for configured columns
138-
if (columns !== undefined && key in columns) {
139-
const {
140-
header, // Custom header to display
141-
colFilterFn, // Custom column filter function
142-
colFilterComponent, // Custom column filter component
143-
instructions, // Custom instructions for the column cells (sorting, filtering, searching, rendering)
144-
disableFiltering = false, // Whether to disable filtering for the column
145-
disableSorting = false // Whether to disable sorting for the column
146-
} = columns[key];
147-
148-
const { toSortableValueFn, toFilterableValueFn, toStringFn, renderComponent } =
149-
instructions ?? {};
150-
151-
return table.column({
152-
// If header is not provided, use the key as the header
153-
header: header ?? key,
154-
accessor: accessor,
155-
// Render the cell with the provided component, or use the toStringFn if provided, or just use the value
156-
cell: ({ value, row }) => {
157-
return renderComponent
158-
? createRender(renderComponent, { value, row, dispatchFn: actionDispatcher })
159-
: toStringFn
160-
? toStringFn(value)
161-
: value;
150+
...unexcludedColumns.map((accessor) => {
151+
const key = accessor as string;
152+
// Applying configuration options for configured columns
153+
if (columns !== undefined && key in columns) {
154+
const {
155+
header, // Custom header to display
156+
colFilterFn, // Custom column filter function
157+
colFilterComponent, // Custom column filter component
158+
instructions, // Custom instructions for the column cells (sorting, filtering, searching, rendering)
159+
disableFiltering = false, // Whether to disable filtering for the column
160+
disableSorting = false // Whether to disable sorting for the column
161+
} = columns[key];
162+
163+
const { toSortableValueFn, toFilterableValueFn, toStringFn, renderComponent } =
164+
instructions ?? {};
165+
166+
return table.column({
167+
// If header is not provided, use the key as the header
168+
header: header ?? key,
169+
accessor: accessor,
170+
// Render the cell with the provided component, or use the toStringFn if provided, or just use the value
171+
cell: ({ value, row }) => {
172+
return renderComponent
173+
? createRender(renderComponent, { value, row, dispatchFn: actionDispatcher })
174+
: toStringFn
175+
? toStringFn(value)
176+
: value;
177+
},
178+
plugins: {
179+
// Sorting config
180+
sort: {
181+
disable: disableSorting,
182+
getSortValue: (row) => {
183+
// If provided, use the custom sorting function toSortableValueFn(), or just use the value
184+
return toSortableValueFn ? toSortableValueFn(row) : row;
185+
}
162186
},
163-
plugins: {
164-
// Sorting config
165-
sort: {
166-
disable: disableSorting,
167-
getSortValue: (row) => {
168-
// If provided, use the custom sorting function toSortableValueFn(), or just use the value
169-
return toSortableValueFn ? toSortableValueFn(row) : row;
170-
}
171-
},
172-
colFilter: !disableFiltering
173-
? {
174-
fn: ({ filterValue, value }) => {
175-
// If provided, use the custom filtering function toFilterableValueFn(), or just use the value
176-
const val = toFilterableValueFn ? toFilterableValueFn(value) : value;
177-
// If provided, use the custom filtering function colFilterFn(), or just use the default columnFilter()
178-
return colFilterFn
179-
? colFilterFn({ filterValue, value: val })
180-
: columnFilter({ filterValue, value: val });
181-
},
182-
render: ({ filterValue, values, id }) => {
183-
filterValue.set($filters[key]);
184-
return serverSide
185-
? createRender(TableFilterServer, {
186-
id,
187-
tableId,
188-
values,
189-
updateTable,
190-
pageIndex,
191-
toFilterableValueFn,
192-
filters,
193-
toStringFn
194-
})
195-
: createRender(colFilterComponent ?? TableFilter, {
196-
filterValue,
197-
id,
198-
tableId,
199-
values,
200-
toFilterableValueFn,
201-
filters,
202-
toStringFn,
203-
pageIndex
204-
});
205-
}
206-
}
207-
: undefined,
208-
tableFilter: {
209-
// Search filter config
210-
getFilterValue: (row) => {
211-
// If provided, use the custom toString function toStringFn(), or just use the value
212-
return toStringFn ? toStringFn(row) : row;
213-
}
187+
colFilter: !disableFiltering
188+
? {
189+
fn: ({ filterValue, value }) => {
190+
// If provided, use the custom filtering function toFilterableValueFn(), or just use the value
191+
const val = toFilterableValueFn ? toFilterableValueFn(value) : value;
192+
// If provided, use the custom filtering function colFilterFn(), or just use the default columnFilter()
193+
return colFilterFn
194+
? colFilterFn({ filterValue, value: val })
195+
: columnFilter({ filterValue, value: val });
196+
},
197+
render: ({ filterValue, values, id }) => {
198+
filterValue.set($filters[key]);
199+
return serverSide
200+
? createRender(TableFilterServer, {
201+
id,
202+
tableId,
203+
values,
204+
updateTable,
205+
pageIndex,
206+
toFilterableValueFn,
207+
filters,
208+
toStringFn
209+
})
210+
: createRender(colFilterComponent ?? TableFilter, {
211+
filterValue,
212+
id,
213+
tableId,
214+
values,
215+
toFilterableValueFn,
216+
filters,
217+
toStringFn,
218+
pageIndex
219+
});
220+
}
221+
}
222+
: undefined,
223+
tableFilter: {
224+
// Search filter config
225+
getFilterValue: (row) => {
226+
// If provided, use the custom toString function toStringFn(), or just use the value
227+
return toStringFn ? toStringFn(row) : row;
214228
}
215229
}
216-
});
217-
} else {
218-
return table.column({
219-
header: key,
220-
accessor: accessor,
221-
cell: ({ value }) => {
222-
// If null or undefined, return an empty string
223-
return value ? value : '';
224-
},
225-
plugins: {
226-
// Sorting enabled by default
227-
sort: {},
228-
// Filtering enabled by default
229-
colFilter: {
230-
fn: columnFilter,
231-
render: ({ filterValue, values, id }) => {
232-
return serverSide
233-
? createRender(TableFilterServer, {
234-
id,
235-
tableId,
236-
values,
237-
updateTable,
238-
pageIndex,
239-
filters
240-
})
241-
: createRender(TableFilter, {
242-
filterValue,
243-
id,
244-
tableId,
245-
values,
246-
filters,
247-
pageIndex
248-
});
249-
}
230+
}
231+
});
232+
} else {
233+
return table.column({
234+
header: key,
235+
accessor: accessor,
236+
cell: ({ value }) => {
237+
// If null or undefined, return an empty string
238+
return value ? value : '';
239+
},
240+
plugins: {
241+
// Sorting enabled by default
242+
sort: {},
243+
// Filtering enabled by default
244+
colFilter: {
245+
fn: columnFilter,
246+
render: ({ filterValue, values, id }) => {
247+
return serverSide
248+
? createRender(TableFilterServer, {
249+
id,
250+
tableId,
251+
values,
252+
updateTable,
253+
pageIndex,
254+
filters
255+
})
256+
: createRender(TableFilter, {
257+
filterValue,
258+
id,
259+
tableId,
260+
values,
261+
filters,
262+
pageIndex
263+
});
250264
}
251265
}
252-
});
253-
}
254-
})
266+
}
267+
});
268+
}
269+
})
255270
];
256271
257272
// If optionsComponent is provided, add a column for it at the end
@@ -286,6 +301,8 @@
286301
const { exportedData } = pluginStates.export;
287302
// Page configuration
288303
const { pageIndex, pageSize } = pluginStates.page;
304+
// Column visibility configuration
305+
const { hiddenColumnIds } = pluginStates.hideColumns;
289306
290307
const updateTable = async () => {
291308
sendModel.limit = $pageSize;
@@ -362,6 +379,7 @@
362379
$: sortKeys = pluginStates.sort.sortKeys;
363380
$: serverSide && updateTable();
364381
$: serverSide && sortServer($sortKeys[0]?.order, $sortKeys[0]?.id);
382+
$: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => col.id);
365383
</script>
366384

367385
<div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
@@ -439,6 +457,7 @@
439457
>Export as CSV</button
440458
>
441459
{/if}
460+
<ColumnsMenu bind:columns={shownColumns} {tableId} />
442461
</div>
443462
</div>
444463

0 commit comments

Comments
 (0)