|
1 | 1 | <script lang="ts"> |
2 | | - import { afterUpdate, onDestroy, createEventDispatcher } from 'svelte'; |
| 2 | + import { afterUpdate, createEventDispatcher, onDestroy } from 'svelte'; |
3 | 3 | import { readable, writable } from 'svelte/store'; |
4 | 4 |
|
5 | 5 | import Fa from 'svelte-fa'; |
|
26 | 26 | import TablePagination from './TablePagination.svelte'; |
27 | 27 | import TablePaginationServer from './TablePaginationServer.svelte'; |
28 | 28 | import ColumnsMenu from './ColumnsMenu.svelte'; |
| 29 | + import * as utils from './utils'; |
29 | 30 | import { columnFilter, searchFilter } from './filter'; |
30 | | - import { |
31 | | - cellStyle, |
32 | | - exportAsCsv, |
33 | | - jsonToCsv, |
34 | | - fixedWidth, |
35 | | - normalizeFilters, |
36 | | - resetResize, |
37 | | - convertServerColumns, |
38 | | - minWidth |
39 | | - } from './shared'; |
40 | | - import { Receive, Send } from '$models/Models'; |
| 31 | + import { Send } from '$models/Models'; |
41 | 32 | import type { TableConfig } from '$models/Models'; |
42 | 33 | import type { FilterOptionsEnum } from '$models/Enums'; |
43 | 34 |
|
|
68 | 59 | let tableRef: HTMLTableElement; |
69 | 60 |
|
70 | 61 | const serverSide = server !== undefined; |
71 | | - const { baseUrl, entityId, versionId, sendModel = new Send() } = server ?? {}; |
| 62 | + const { sendModel = new Send() } = server ?? {}; |
72 | 63 |
|
73 | 64 | const filters = writable<{ |
74 | 65 | [key: string]: { [key in FilterOptionsEnum]?: number | string | Date }; |
|
86 | 77 | const colWidths = writable<number[]>([]); |
87 | 78 |
|
88 | 79 | // Server-side variables |
89 | | - const serverItems = serverSide ? writable<Number>(0) : undefined; |
| 80 | + const serverItems = serverSide ? writable<number>(0) : undefined; |
90 | 81 | const serverItemCount = serverSide |
91 | 82 | ? readable<Number>(0, (set) => { |
92 | 83 | serverItems!.subscribe((val) => set(val)); |
|
209 | 200 | id, |
210 | 201 | tableId, |
211 | 202 | values, |
212 | | - updateTable, |
| 203 | + updateTable: updateTableWithParams, |
213 | 204 | pageIndex, |
214 | 205 | toFilterableValueFn, |
215 | 206 | filters, |
|
257 | 248 | id, |
258 | 249 | tableId, |
259 | 250 | values, |
260 | | - updateTable, |
| 251 | + updateTable: updateTableWithParams, |
261 | 252 | pageIndex, |
262 | 253 | filters |
263 | 254 | }) |
|
312 | 303 | // Column visibility configuration |
313 | 304 | const { hiddenColumnIds } = pluginStates.hideColumns; |
314 | 305 |
|
315 | | - const updateTable = async () => { |
316 | | - if (!sendModel) throw new Error('Server-side configuration is missing'); |
317 | | -
|
318 | | - sendModel.limit = $pageSize; |
319 | | - sendModel.offset = $pageSize * $pageIndex; |
320 | | - sendModel.version = versionId || -1; |
321 | | - sendModel.id = entityId || -1; |
322 | | - sendModel.filter = normalizeFilters($filters); |
323 | | -
|
324 | | - let fetchData; |
325 | | -
|
326 | | - try { |
327 | | - isFetching = true; |
328 | | - fetchData = await fetch(baseUrl || '', { |
329 | | - headers: { |
330 | | - 'Content-Type': 'application/json' |
331 | | - }, |
332 | | - method: 'POST', |
333 | | - body: JSON.stringify(sendModel) |
334 | | - }); |
335 | | - } catch (error) { |
336 | | - throw new Error(`Network error: ${(error as Error).message}`); |
337 | | - } finally { |
338 | | - isFetching = false; |
339 | | - } |
340 | | -
|
341 | | - if (!fetchData.ok) { |
342 | | - throw new Error('Failed to fetch data'); |
343 | | - } |
344 | | -
|
345 | | - const response: Receive = await fetchData.json(); |
346 | | -
|
347 | | - // Format server columns to the client columns |
348 | | - if (response.columns !== undefined) { |
349 | | - columns = convertServerColumns(response.columns, columns); |
350 | | -
|
351 | | - const clientCols = response.columns.reduce((acc, col) => { |
352 | | - acc[col.key] = col.column; |
353 | | - return acc; |
354 | | - }, {}); |
355 | | -
|
356 | | - const tmpArr: any[] = []; |
357 | | -
|
358 | | - response.data.forEach((row, index) => { |
359 | | - const tmp: { [key: string]: any } = {}; |
360 | | - Object.keys(row).forEach((key) => { |
361 | | - tmp[clientCols[key]] = row[key]; |
362 | | - }); |
363 | | - tmpArr.push(tmp); |
364 | | - }); |
365 | | - dispatch('fetch', columns); |
366 | | - $data = tmpArr; |
367 | | - } |
368 | | -
|
369 | | - $serverItems = response.count; |
370 | | -
|
371 | | - return response; |
372 | | - }; |
373 | | -
|
374 | 306 | const sortServer = (order: 'asc' | 'desc' | undefined, id: string) => { |
375 | 307 | if (!sendModel) throw new Error('Server-side configuration is missing'); |
376 | 308 | // Set parameter for sorting |
|
383 | 315 | // Reset pagination |
384 | 316 | $pageIndex = 0; |
385 | 317 |
|
386 | | - updateTable(); |
| 318 | + updateTableWithParams(); |
387 | 319 | }; |
388 | 320 |
|
389 | | - const getMaxCellHeightInRow = () => { |
390 | | - if (!tableRef || resizable === 'columns' || resizable === 'none') return; |
391 | | -
|
392 | | - tableRef.querySelectorAll('tbody tr').forEach((row, index) => { |
393 | | - const cells = row.querySelectorAll('td'); |
394 | | -
|
395 | | - let maxHeight = optionsComponent ? 56 : 44; |
396 | | - let minHeight = optionsComponent ? 56 : 44; |
| 321 | + // Function to update the table with the provided parameters for easily passing to other components |
| 322 | + const updateTableWithParams = async () => { |
| 323 | + isFetching = true; |
| 324 | + const result = await utils.updateTable( |
| 325 | + $pageSize, |
| 326 | + $pageIndex, |
| 327 | + server, |
| 328 | + $filters, |
| 329 | + data, |
| 330 | + serverItems, |
| 331 | + columns, |
| 332 | + dispatch |
| 333 | + ); |
| 334 | + isFetching = false; |
397 | 335 |
|
398 | | - cells.forEach((cell) => { |
399 | | - const cellHeight = cell.getBoundingClientRect().height; |
400 | | - // + 2 pixels for rendering borders correctly |
401 | | - if (cellHeight > maxHeight) { |
402 | | - maxHeight = cellHeight + 2; |
403 | | - } |
404 | | - if (cellHeight < minHeight) { |
405 | | - minHeight = cellHeight + 2; |
406 | | - } |
407 | | - }); |
408 | | -
|
409 | | - rowHeights.update((rh) => { |
410 | | - const id = +row.id.split(`${tableId}-row-`)[1]; |
411 | | - return { |
412 | | - ...rh, |
413 | | - [id]: { |
414 | | - max: maxHeight - 24, |
415 | | - min: Math.max(minHeight - 24, rowHeight ?? 20) |
416 | | - } |
417 | | - }; |
418 | | - }); |
419 | | - }); |
| 336 | + return result; |
420 | 337 | }; |
421 | 338 |
|
422 | | - const getMinCellWidthInColumn = () => { |
423 | | - if (!tableRef || resizable === 'rows' || resizable === 'none') return; |
424 | | -
|
425 | | - // Initialize the colWidths array if it is empty |
426 | | - if ($colWidths.length === 0) { |
427 | | - $colWidths = Array.from({ length: $headerRows[0].cells.length }, () => 100); |
| 339 | + // Initializes observers for rows and columns |
| 340 | + const getDimensions = () => { |
| 341 | + if (!tableRef) return; |
| 342 | + if (resizable === 'none') return; |
| 343 | + else if (resizable === 'columns') { |
| 344 | + observeHeaderColumns(); |
| 345 | + } else if (resizable === 'rows') { |
| 346 | + observeFirstCells(); |
| 347 | + } else { |
| 348 | + observeHeaderColumns(); |
| 349 | + observeFirstCells(); |
428 | 350 | } |
429 | | -
|
430 | | - colWidths.update((cw) => { |
431 | | - tableRef?.querySelectorAll('thead tr th span').forEach((cell, index) => { |
432 | | - // + 12 pixels for padding and + 32 pixels for filter icon |
433 | | - // If the column width is 100, which means it has not been initialized, then calculate the width |
434 | | - cw[index] = cw[index] === 100 ? cell.getBoundingClientRect().width + 12 + 32 : cw[index]; |
435 | | - }); |
436 | | - return cw; |
437 | | - }); |
438 | 351 | }; |
439 | 352 |
|
| 353 | + // Resize observer for rows |
440 | 354 | const resizeRowsObserver = new ResizeObserver(() => { |
441 | | - getMaxCellHeightInRow(); |
| 355 | + utils.getMaxCellHeightInRow( |
| 356 | + tableRef, |
| 357 | + resizable, |
| 358 | + optionsComponent, |
| 359 | + rowHeights, |
| 360 | + tableId, |
| 361 | + rowHeight |
| 362 | + ); |
442 | 363 | }); |
443 | 364 |
|
| 365 | + // Resize observers for columns |
444 | 366 | const resizeColumnsObserver = new ResizeObserver(() => { |
445 | | - getMinCellWidthInColumn(); |
| 367 | + utils.getMinCellWidthInColumn(tableRef, colWidths, $headerRows[0].cells.length, resizable); |
446 | 368 | }); |
447 | 369 |
|
| 370 | + // Adds observers on the first cells of each row to resize rows |
448 | 371 | const observeFirstCells = () => { |
449 | 372 | if (!tableRef) return; |
450 | 373 |
|
451 | | - $pageRows.forEach((row) => { |
452 | | - const cell = tableRef.querySelector(`#${tableId}-row-${row.id}`); |
453 | | - if (cell) { |
454 | | - resizeRowsObserver.observe(cell); |
455 | | - } |
456 | | - }); |
457 | | -
|
458 | 374 | tableRef.querySelectorAll('tbody tr td:first-child').forEach((cell) => { |
459 | 375 | resizeRowsObserver.observe(cell); |
460 | 376 | }); |
| 377 | +
|
| 378 | + return resizeRowsObserver; |
461 | 379 | }; |
462 | 380 |
|
| 381 | + // Adds observers on the header columns to resize columns |
463 | 382 | const observeHeaderColumns = () => { |
464 | 383 | if (!tableRef) return; |
465 | 384 |
|
|
469 | 388 | }; |
470 | 389 |
|
471 | 390 | afterUpdate(() => { |
| 391 | + // If not resizable, return |
472 | 392 | if (resizable !== 'rows' && resizable !== 'both') { |
473 | 393 | return; |
474 | 394 | } |
|
480 | 400 | } |
481 | 401 | }); |
482 | 402 |
|
483 | | - // Remove the resize observer when the component is destroyed for performance reasons |
484 | 403 | onDestroy(() => { |
485 | | - resizeRowsObserver.disconnect(); |
486 | 404 | resizeColumnsObserver.disconnect(); |
| 405 | + resizeRowsObserver.disconnect(); |
487 | 406 | }); |
488 | 407 |
|
489 | | - const getDimensions = () => { |
490 | | - if (!tableRef) return; |
491 | | - if (resizable === 'none') return; |
492 | | - else if (resizable === 'columns') { |
493 | | - observeHeaderColumns(); |
494 | | - } else if (resizable === 'rows') { |
495 | | - observeFirstCells(); |
496 | | - } else { |
497 | | - observeHeaderColumns(); |
498 | | - observeFirstCells(); |
499 | | - } |
500 | | - }; |
501 | | -
|
502 | 408 | $: sortKeys = pluginStates.sort.sortKeys; |
503 | | - $: serverSide && updateTable(); |
| 409 | + $: serverSide && updateTableWithParams(); |
504 | 410 | $: serverSide && sortServer($sortKeys[0]?.order, $sortKeys[0]?.id); |
505 | 411 | $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => col.id); |
506 | 412 | </script> |
|
600 | 506 | class="btn btn-sm variant-filled-primary rounded-full order-last flex gap-2 items-center" |
601 | 507 | aria-label="Reset sizing of columns and rows" |
602 | 508 | on:click|preventDefault={() => |
603 | | - resetResize($headerRows, $pageRows, tableId, columns, resizable)} |
| 509 | + utils.resetResize($headerRows, $pageRows, tableId, columns, resizable)} |
604 | 510 | ><Fa icon={faCompress} /> Reset sizing</button |
605 | 511 | > |
606 | 512 | {/if} |
| 513 | + <!-- Enable export as CSV button if exportable === true --> |
607 | 514 | {#if exportable} |
608 | 515 | <button |
609 | 516 | type="button" |
610 | 517 | class="btn btn-sm variant-filled-primary rounded-full order-last flex items-center gap-2" |
611 | 518 | aria-label="Export table data as CSV" |
612 | | - on:click|preventDefault={() => exportAsCsv(tableId, jsonToCsv($exportedData))} |
| 519 | + on:click|preventDefault={() => |
| 520 | + utils.exportAsCsv(tableId, utils.jsonToCsv($exportedData))} |
613 | 521 | ><Fa icon={faDownload} /> Export as CSV</button |
614 | 522 | > |
615 | 523 | {/if} |
| 524 | + <!-- Enable show/hide columns menu if showColumnsMenu === true --> |
616 | 525 | {#if showColumnsMenu && shownColumns.length > 0} |
617 | 526 | <ColumnsMenu bind:columns={shownColumns} {tableId} /> |
618 | 527 | {/if} |
|
646 | 555 | {...attrs} |
647 | 556 | style={` |
648 | 557 | width: ${cell.isData() ? 'auto' : '0'}; |
649 | | - ${cellStyle(cell.id, columns)} |
| 558 | + ${utils.cellStyle(cell.id, columns)} |
650 | 559 | `} |
651 | 560 | > |
652 | 561 | <div |
653 | 562 | class="overflow-auto" |
654 | 563 | class:resize-x={(resizable === 'columns' || resizable === 'both') && |
655 | | - !fixedWidth(cell.id, columns)} |
| 564 | + !utils.fixedWidth(cell.id, columns)} |
656 | 565 | id="th-{tableId}-{cell.id}" |
657 | 566 | style={` |
658 | | - min-width: ${minWidth(cell.id, columns) ? minWidth(cell.id, columns) : $colWidths[index]}px; |
| 567 | + min-width: ${ |
| 568 | + utils.minWidth(cell.id, columns) |
| 569 | + ? utils.minWidth(cell.id, columns) |
| 570 | + : $colWidths[index] |
| 571 | + }px; |
659 | 572 | `} |
660 | 573 | > |
661 | 574 | <div class="flex justify-between items-center"> |
|
714 | 627 | ? 'resize-y overflow-auto' |
715 | 628 | : 'block'}" |
716 | 629 | id="{tableId}-{cell.id}-{row.id}" |
717 | | - style={` |
718 | | - min-height: ${$rowHeights && $rowHeights[+row.id] ? `${$rowHeights[+row.id].min}px` : 'auto'}; |
719 | | - max-height: ${ |
720 | | - index !== 0 && $rowHeights && $rowHeights[+row.id] |
721 | | - ? `${$rowHeights[+row.id].max}px` |
722 | | - : 'auto' |
723 | | - }; |
724 | | - height: ${$rowHeights && $rowHeights[+row.id] ? `${$rowHeights[+row.id].min}px` : 'auto'}; |
725 | | - `} |
| 630 | + style={utils.getResizeStyles($rowHeights, row.id, index)} |
726 | 631 | > |
727 | 632 | <!-- Adding config for initial rowHeight, if provided --> |
728 | 633 | <div |
|
735 | 640 | class="grow overflow-auto" |
736 | 641 | style={cell.isData() |
737 | 642 | ? `width: ${ |
738 | | - minWidth(cell.id, columns) |
739 | | - ? minWidth(cell.id, columns) |
| 643 | + utils.minWidth(cell.id, columns) |
| 644 | + ? utils.minWidth(cell.id, columns) |
740 | 645 | : $colWidths[index] |
741 | 646 | }px;` |
742 | 647 | : 'max-width: min-content;'} |
|
779 | 684 | {pageIndex} |
780 | 685 | {pageSize} |
781 | 686 | {serverItemCount} |
782 | | - {updateTable} |
| 687 | + updateTable={updateTableWithParams} |
783 | 688 | {pageSizes} |
784 | 689 | {pageIndexStringType} |
785 | 690 | id={tableId} |
786 | 691 | /> |
787 | 692 | {:else} |
788 | | - <TablePagination pageConfig={pluginStates.page} {pageSizes} id={tableId} {pageIndexStringType} /> |
| 693 | + <TablePagination |
| 694 | + pageConfig={pluginStates.page} |
| 695 | + {pageSizes} |
| 696 | + id={tableId} |
| 697 | + {pageIndexStringType} |
| 698 | + /> |
789 | 699 | {/if} |
790 | 700 | {/if} |
791 | 701 | </div> |
|
0 commit comments