|
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