|
11 | 11 | addExpandedRows,
|
12 | 12 | addColumnFilters,
|
13 | 13 | addTableFilter,
|
14 |
| - addDataExport |
| 14 | + addDataExport, |
| 15 | + addHiddenColumns |
15 | 16 | } from 'svelte-headless-table/plugins';
|
16 | 17 | import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
|
17 | 18 | import { SlideToggle, storePopup } from '@skeletonlabs/skeleton';
|
|
24 | 25 | import TableFilterServer from './TableFilterServer.svelte';
|
25 | 26 | import TablePagination from './TablePagination.svelte';
|
26 | 27 | import TablePaginationServer from './TablePaginationServer.svelte';
|
| 28 | + import ColumnsMenu from './ColumnsMenu.svelte'; |
27 | 29 | import { columnFilter, searchFilter } from './filter';
|
28 | 30 | import {
|
29 | 31 | cellStyle,
|
|
90 | 92 | fn: searchFilter,
|
91 | 93 | serverSide
|
92 | 94 | }),
|
| 95 | + hideColumns: addHiddenColumns(), |
93 | 96 | sort: addSortBy({
|
94 | 97 | disableMultiSort: true,
|
95 | 98 | serverSide
|
|
121 | 124 | // Creating an array of all the keys
|
122 | 125 | const accessors: AccessorType[] = Object.keys(allCols) as AccessorType[];
|
123 | 126 |
|
| 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 | +
|
124 | 148 | // Configuring every table column with the provided options
|
125 | 149 | 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 | + } |
162 | 186 | },
|
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; |
214 | 228 | }
|
215 | 229 | }
|
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 | + }); |
250 | 264 | }
|
251 | 265 | }
|
252 |
| - }); |
253 |
| - } |
254 |
| - }) |
| 266 | + } |
| 267 | + }); |
| 268 | + } |
| 269 | + }) |
255 | 270 | ];
|
256 | 271 |
|
257 | 272 | // If optionsComponent is provided, add a column for it at the end
|
|
286 | 301 | const { exportedData } = pluginStates.export;
|
287 | 302 | // Page configuration
|
288 | 303 | const { pageIndex, pageSize } = pluginStates.page;
|
| 304 | + // Column visibility configuration |
| 305 | + const { hiddenColumnIds } = pluginStates.hideColumns; |
289 | 306 |
|
290 | 307 | const updateTable = async () => {
|
291 | 308 | sendModel.limit = $pageSize;
|
|
362 | 379 | $: sortKeys = pluginStates.sort.sortKeys;
|
363 | 380 | $: serverSide && updateTable();
|
364 | 381 | $: serverSide && sortServer($sortKeys[0]?.order, $sortKeys[0]?.id);
|
| 382 | + $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => col.id); |
365 | 383 | </script>
|
366 | 384 |
|
367 | 385 | <div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
|
|
439 | 457 | >Export as CSV</button
|
440 | 458 | >
|
441 | 459 | {/if}
|
| 460 | + <ColumnsMenu bind:columns={shownColumns} {tableId} /> |
442 | 461 | </div>
|
443 | 462 | </div>
|
444 | 463 |
|
|
0 commit comments