@keenthemes/ktui 1.2.3 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ktui.js +1681 -957
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +110 -1
- package/lib/cjs/components/context-menu/context-menu.d.ts +66 -0
- package/lib/cjs/components/context-menu/context-menu.d.ts.map +1 -0
- package/lib/cjs/components/context-menu/context-menu.js +423 -0
- package/lib/cjs/components/context-menu/context-menu.js.map +1 -0
- package/lib/cjs/components/context-menu/index.d.ts +7 -0
- package/lib/cjs/components/context-menu/index.d.ts.map +1 -0
- package/lib/cjs/components/context-menu/index.js +10 -0
- package/lib/cjs/components/context-menu/index.js.map +1 -0
- package/lib/cjs/components/context-menu/types.d.ts +30 -0
- package/lib/cjs/components/context-menu/types.d.ts.map +1 -0
- package/lib/cjs/components/context-menu/types.js +7 -0
- package/lib/cjs/components/context-menu/types.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-contracts.d.ts +66 -0
- package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-contracts.js +7 -0
- package/lib/cjs/components/datatable/datatable-contracts.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.d.ts +7 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.js +16 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts +25 -0
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-local-provider.js +184 -0
- package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts +15 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js +128 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts +25 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.js +188 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-store.d.ts +21 -0
- package/lib/cjs/components/datatable/datatable-state-store.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-store.js +81 -0
- package/lib/cjs/components/datatable/datatable-state-store.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +16 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.js +133 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.d.ts +9 -87
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +114 -686
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/select/index.d.ts +1 -1
- package/lib/cjs/components/select/index.d.ts.map +1 -1
- package/lib/cjs/index.d.ts +5 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +7 -7
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/init-all.d.ts +6 -0
- package/lib/cjs/init-all.d.ts.map +1 -0
- package/lib/cjs/init-all.js +17 -0
- package/lib/cjs/init-all.js.map +1 -0
- package/lib/cjs/legacy.d.ts +8 -0
- package/lib/cjs/legacy.d.ts.map +1 -0
- package/lib/cjs/legacy.js +26 -0
- package/lib/cjs/legacy.js.map +1 -0
- package/lib/esm/components/context-menu/context-menu.d.ts +66 -0
- package/lib/esm/components/context-menu/context-menu.d.ts.map +1 -0
- package/lib/esm/components/context-menu/context-menu.js +420 -0
- package/lib/esm/components/context-menu/context-menu.js.map +1 -0
- package/lib/esm/components/context-menu/index.d.ts +7 -0
- package/lib/esm/components/context-menu/index.d.ts.map +1 -0
- package/lib/esm/components/context-menu/index.js +6 -0
- package/lib/esm/components/context-menu/index.js.map +1 -0
- package/lib/esm/components/context-menu/types.d.ts +30 -0
- package/lib/esm/components/context-menu/types.d.ts.map +1 -0
- package/lib/esm/components/context-menu/types.js +6 -0
- package/lib/esm/components/context-menu/types.js.map +1 -0
- package/lib/esm/components/datatable/datatable-contracts.d.ts +66 -0
- package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-contracts.js +6 -0
- package/lib/esm/components/datatable/datatable-contracts.js.map +1 -0
- package/lib/esm/components/datatable/datatable-event-adapter.d.ts +7 -0
- package/lib/esm/components/datatable/datatable-event-adapter.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-event-adapter.js +13 -0
- package/lib/esm/components/datatable/datatable-event-adapter.js.map +1 -0
- package/lib/esm/components/datatable/datatable-local-provider.d.ts +25 -0
- package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-local-provider.js +181 -0
- package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts +15 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.js +125 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -0
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts +25 -0
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-remote-provider.js +185 -0
- package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -0
- package/lib/esm/components/datatable/datatable-state-store.d.ts +21 -0
- package/lib/esm/components/datatable/datatable-state-store.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-state-store.js +78 -0
- package/lib/esm/components/datatable/datatable-state-store.js.map +1 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts +16 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-table-renderer.js +130 -0
- package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -0
- package/lib/esm/components/datatable/datatable.d.ts +9 -87
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js +114 -686
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/select/index.d.ts +1 -1
- package/lib/esm/components/select/index.d.ts.map +1 -1
- package/lib/esm/index.d.ts +5 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +4 -5
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/init-all.d.ts +6 -0
- package/lib/esm/init-all.d.ts.map +1 -0
- package/lib/esm/init-all.js +13 -0
- package/lib/esm/init-all.js.map +1 -0
- package/lib/esm/legacy.d.ts +8 -0
- package/lib/esm/legacy.d.ts.map +1 -0
- package/lib/esm/legacy.js +8 -0
- package/lib/esm/legacy.js.map +1 -0
- package/package.json +34 -4
- package/src/__tests__/entrypoints.test.ts +71 -0
- package/src/components/context-menu/__tests__/context-menu.test.ts +117 -0
- package/src/components/context-menu/context-menu.css +32 -0
- package/src/components/context-menu/context-menu.ts +529 -0
- package/src/components/context-menu/index.ts +10 -0
- package/src/components/context-menu/types.ts +32 -0
- package/src/components/datatable/__tests__/architecture-boundaries.test.ts +259 -0
- package/src/components/datatable/datatable-contracts.ts +96 -0
- package/src/components/datatable/datatable-event-adapter.ts +21 -0
- package/src/components/datatable/datatable-local-provider.ts +194 -0
- package/src/components/datatable/datatable-pagination-renderer.ts +211 -0
- package/src/components/datatable/datatable-remote-provider.ts +175 -0
- package/src/components/datatable/datatable-state-store.ts +94 -0
- package/src/components/datatable/datatable-table-renderer.ts +206 -0
- package/src/components/datatable/datatable.ts +128 -839
- package/src/components/select/index.ts +1 -1
- package/src/index.ts +9 -5
- package/src/init-all.ts +15 -0
- package/src/legacy.ts +9 -0
- package/styles.css +1 -0
|
@@ -11,10 +11,8 @@ import {
|
|
|
11
11
|
KTDataTableSortOrderInterface,
|
|
12
12
|
KTDataTableStateInterface,
|
|
13
13
|
KTDataTableColumnFilterInterface,
|
|
14
|
-
KTDataTableAttributeInterface,
|
|
15
14
|
} from './types';
|
|
16
15
|
import { KTOptionType } from '../../types';
|
|
17
|
-
import KTUtils from '../../helpers/utils';
|
|
18
16
|
import KTComponents from '../../index';
|
|
19
17
|
import KTData from '../../helpers/data';
|
|
20
18
|
import {
|
|
@@ -22,6 +20,19 @@ import {
|
|
|
22
20
|
KTDataTableCheckboxAPI,
|
|
23
21
|
} from './datatable-checkbox';
|
|
24
22
|
import { createSortHandler, KTDataTableSortAPI } from './datatable-sort';
|
|
23
|
+
import {
|
|
24
|
+
KTDataTableCleanup,
|
|
25
|
+
KTDataTableEventAdapter,
|
|
26
|
+
KTDataTablePaginationRenderer,
|
|
27
|
+
KTDataTableStateStore,
|
|
28
|
+
KTDataTableTableRenderer,
|
|
29
|
+
} from './datatable-contracts';
|
|
30
|
+
import { createDataTableEventAdapter } from './datatable-event-adapter';
|
|
31
|
+
import { KTDataTableLocalDataProvider } from './datatable-local-provider';
|
|
32
|
+
import { KTDataTableRemoteDataProvider } from './datatable-remote-provider';
|
|
33
|
+
import { KTDataTableConfigStateStore } from './datatable-state-store';
|
|
34
|
+
import { KTDataTableDomPaginationRenderer } from './datatable-pagination-renderer';
|
|
35
|
+
import { KTDataTableDomTableRenderer } from './datatable-table-renderer';
|
|
25
36
|
|
|
26
37
|
/**
|
|
27
38
|
* Custom DataTable plugin class with server-side API, pagination, and sorting
|
|
@@ -69,22 +80,17 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
69
80
|
|
|
70
81
|
private _checkbox: KTDataTableCheckboxAPI;
|
|
71
82
|
private _sortHandler: KTDataTableSortAPI<T>;
|
|
83
|
+
private _eventAdapter: KTDataTableEventAdapter;
|
|
84
|
+
private _stateStore: KTDataTableStateStore;
|
|
85
|
+
private _localProvider: KTDataTableLocalDataProvider<T>;
|
|
86
|
+
private _remoteProvider: KTDataTableRemoteDataProvider<T>;
|
|
87
|
+
private _tableRenderer: KTDataTableTableRenderer<T>;
|
|
88
|
+
private _paginationRenderer: KTDataTablePaginationRenderer;
|
|
89
|
+
private _cleanupCallbacks: KTDataTableCleanup[] = [];
|
|
72
90
|
|
|
73
91
|
private _data: T[] = [];
|
|
74
92
|
private _isFetching: boolean = false;
|
|
75
93
|
|
|
76
|
-
/**
|
|
77
|
-
* AbortController for cancelling previous fetch requests
|
|
78
|
-
* Used to prevent race conditions when multiple requests are triggered rapidly
|
|
79
|
-
*/
|
|
80
|
-
private _abortController: AbortController | null = null;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Request ID counter for tracking request sequence
|
|
84
|
-
* Used to detect and ignore stale responses from older requests
|
|
85
|
-
*/
|
|
86
|
-
private _requestId: number = 0;
|
|
87
|
-
|
|
88
94
|
constructor(element: HTMLElement, config?: KTDataTableConfigInterface) {
|
|
89
95
|
super();
|
|
90
96
|
|
|
@@ -101,20 +107,25 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
101
107
|
|
|
102
108
|
this._init(element);
|
|
103
109
|
this._buildConfig();
|
|
110
|
+
this._stateStore = new KTDataTableConfigStateStore(this._config);
|
|
111
|
+
this._eventAdapter = createDataTableEventAdapter(
|
|
112
|
+
this._fireEvent.bind(this),
|
|
113
|
+
this._dispatchEvent.bind(this),
|
|
114
|
+
);
|
|
104
115
|
|
|
105
116
|
// Store the instance directly on the element
|
|
106
117
|
KTDataTable.asElementWithInstance(element).instance = this;
|
|
107
118
|
|
|
108
119
|
this._initElements();
|
|
120
|
+
this._tableRenderer = new KTDataTableDomTableRenderer<T>();
|
|
121
|
+
this._paginationRenderer = new KTDataTableDomPaginationRenderer();
|
|
122
|
+
this._initDataProviders();
|
|
109
123
|
|
|
110
124
|
// Initialize checkbox handler
|
|
111
125
|
this._checkbox = createCheckboxHandler(
|
|
112
126
|
this._element,
|
|
113
127
|
this._config,
|
|
114
|
-
(
|
|
115
|
-
this._fireEvent(eventName, eventData);
|
|
116
|
-
this._dispatchEvent(eventName, eventData);
|
|
117
|
-
},
|
|
128
|
+
this._emit.bind(this),
|
|
118
129
|
);
|
|
119
130
|
|
|
120
131
|
// Initialize sort handler
|
|
@@ -126,8 +137,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
126
137
|
sortOrder: this.getState().sortOrder,
|
|
127
138
|
}),
|
|
128
139
|
(field, order) => {
|
|
129
|
-
this.
|
|
130
|
-
this._config._state.sortOrder = order;
|
|
140
|
+
this._stateStore.setSort(field as never, order);
|
|
131
141
|
},
|
|
132
142
|
this._fireEvent.bind(this),
|
|
133
143
|
this._dispatchEvent.bind(this),
|
|
@@ -146,8 +156,33 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
146
156
|
|
|
147
157
|
this._updateData();
|
|
148
158
|
|
|
149
|
-
this.
|
|
150
|
-
|
|
159
|
+
this._emit('init');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private _emit(eventName: string, eventData?: object): void {
|
|
163
|
+
this._eventAdapter.emit(eventName, eventData);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private _initDataProviders(): void {
|
|
167
|
+
this._localProvider = new KTDataTableLocalDataProvider<T>({
|
|
168
|
+
config: this._config,
|
|
169
|
+
elements: () => ({
|
|
170
|
+
tableElement: this._tableElement,
|
|
171
|
+
tbodyElement: this._tbodyElement,
|
|
172
|
+
theadElement: this._theadElement,
|
|
173
|
+
}),
|
|
174
|
+
getLogicalColumnCount: this._getLogicalColumnCount.bind(this),
|
|
175
|
+
storeOriginalClasses: this._storeOriginalClasses.bind(this),
|
|
176
|
+
stateStore: this._stateStore,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this._remoteProvider = new KTDataTableRemoteDataProvider<T>({
|
|
180
|
+
config: this._config,
|
|
181
|
+
createUrl: this._createUrl.bind(this),
|
|
182
|
+
eventAdapter: this._eventAdapter,
|
|
183
|
+
noticeOnTable: this._noticeOnTable.bind(this),
|
|
184
|
+
stateStore: this._stateStore,
|
|
185
|
+
});
|
|
151
186
|
}
|
|
152
187
|
|
|
153
188
|
/**
|
|
@@ -480,14 +515,20 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
480
515
|
try {
|
|
481
516
|
this._showSpinner(); // Show spinner before fetching data
|
|
482
517
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
518
|
+
this._emit('fetch');
|
|
519
|
+
const result =
|
|
520
|
+
typeof this._config.apiEndpoint === 'undefined'
|
|
521
|
+
? this._localProvider.fetchSync()
|
|
522
|
+
: await this._remoteProvider.fetch();
|
|
523
|
+
|
|
524
|
+
if (!result.skipped) {
|
|
525
|
+
this._data = result.data;
|
|
526
|
+
this._stateStore.patchState({ totalItems: result.totalItems });
|
|
527
|
+
await this._draw();
|
|
528
|
+
this._emit('fetched');
|
|
490
529
|
}
|
|
530
|
+
|
|
531
|
+
await this._finalize();
|
|
491
532
|
} finally {
|
|
492
533
|
// Finally block now correctly executes after promises resolve, not immediately
|
|
493
534
|
this._isFetching = false;
|
|
@@ -569,188 +610,6 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
569
610
|
}
|
|
570
611
|
}
|
|
571
612
|
|
|
572
|
-
/**
|
|
573
|
-
* Fetch data from the DOM
|
|
574
|
-
* Fetch data from the table element and save it to the `originalData` state property.
|
|
575
|
-
* This method is used when the data is not fetched from the server via an API endpoint.
|
|
576
|
-
*/
|
|
577
|
-
private async _fetchDataFromLocal(): Promise<void> {
|
|
578
|
-
this._fireEvent('fetch');
|
|
579
|
-
this._dispatchEvent('fetch');
|
|
580
|
-
|
|
581
|
-
const { sortField, sortOrder, page, pageSize, search } = this.getState();
|
|
582
|
-
let { originalData } = this.getState();
|
|
583
|
-
|
|
584
|
-
// If the table element or the original data is not defined, bail
|
|
585
|
-
if (
|
|
586
|
-
!this._tableElement ||
|
|
587
|
-
originalData === undefined ||
|
|
588
|
-
this._tableConfigInvalidate() ||
|
|
589
|
-
this._localTableHeaderInvalidate() ||
|
|
590
|
-
this._localTableContentInvalidate()
|
|
591
|
-
) {
|
|
592
|
-
this._deleteState();
|
|
593
|
-
|
|
594
|
-
const { originalData, originalDataAttributes } =
|
|
595
|
-
this._localExtractTableContent();
|
|
596
|
-
|
|
597
|
-
this._config._state.originalData = originalData;
|
|
598
|
-
this._config._state.originalDataAttributes = originalDataAttributes;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Update the original data variable
|
|
602
|
-
originalData = this.getState().originalData;
|
|
603
|
-
|
|
604
|
-
// Clone the original data
|
|
605
|
-
let _temp = (this._data = [...originalData] as T[]);
|
|
606
|
-
|
|
607
|
-
if (search) {
|
|
608
|
-
const searchTerm = typeof search === 'string' ? search : '';
|
|
609
|
-
_temp = this._data = this._config.search.callback.call(
|
|
610
|
-
this,
|
|
611
|
-
this._data,
|
|
612
|
-
searchTerm,
|
|
613
|
-
) as T[];
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// If sorting is defined, sort the data
|
|
617
|
-
if (
|
|
618
|
-
sortField !== undefined &&
|
|
619
|
-
sortOrder !== undefined &&
|
|
620
|
-
sortOrder !== ''
|
|
621
|
-
) {
|
|
622
|
-
if (typeof this._config.sort.callback === 'function') {
|
|
623
|
-
this._data = this._config.sort.callback.call(
|
|
624
|
-
this,
|
|
625
|
-
this._data,
|
|
626
|
-
sortField as string,
|
|
627
|
-
sortOrder,
|
|
628
|
-
) as T[];
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// If there is data, slice it to the current page size
|
|
633
|
-
if (this._data?.length > 0) {
|
|
634
|
-
// Calculate the start and end indices for the current page
|
|
635
|
-
const startIndex = (page - 1) * pageSize;
|
|
636
|
-
const endIndex = startIndex + pageSize;
|
|
637
|
-
|
|
638
|
-
this._data = this._data.slice(startIndex, endIndex) as T[];
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Determine number of total rows
|
|
642
|
-
this._config._state.totalItems = _temp.length;
|
|
643
|
-
|
|
644
|
-
// Draw the data
|
|
645
|
-
await this._draw();
|
|
646
|
-
this._fireEvent('fetched');
|
|
647
|
-
this._dispatchEvent('fetched');
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Checks if the table content has been invalidated by comparing the current checksum of the table body
|
|
652
|
-
* with the stored checksum in the state. If the checksums are different, the state is updated with the
|
|
653
|
-
* new checksum and `true` is returned. Otherwise, `false` is returned.
|
|
654
|
-
*
|
|
655
|
-
* @returns {boolean} `true` if the table content has been invalidated, `false` otherwise.
|
|
656
|
-
*/
|
|
657
|
-
private _localTableContentInvalidate(): boolean {
|
|
658
|
-
const checksum: string = KTUtils.checksum(
|
|
659
|
-
JSON.stringify(this._tbodyElement.innerHTML),
|
|
660
|
-
);
|
|
661
|
-
if (this.getState()._contentChecksum !== checksum) {
|
|
662
|
-
this._config._state._contentChecksum = checksum;
|
|
663
|
-
return true;
|
|
664
|
-
}
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
private _tableConfigInvalidate(): boolean {
|
|
669
|
-
// Remove _data and _state from config
|
|
670
|
-
const { _state, ...restConfig } = this._config;
|
|
671
|
-
const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
|
|
672
|
-
if (_state._configChecksum !== checksum) {
|
|
673
|
-
this._config._state._configChecksum = checksum;
|
|
674
|
-
return true;
|
|
675
|
-
}
|
|
676
|
-
return false;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Extract the table content and returns it as an object containing an array of original data and an array of original data attributes.
|
|
681
|
-
*
|
|
682
|
-
* @returns {{originalData: T[], originalDataAttributes: KTDataTableAttributeInterface[]}} - An object containing an array of original data and an array of original data attributes.
|
|
683
|
-
*/
|
|
684
|
-
private _localExtractTableContent(): {
|
|
685
|
-
originalData: T[];
|
|
686
|
-
originalDataAttributes: KTDataTableAttributeInterface[];
|
|
687
|
-
} {
|
|
688
|
-
const originalData: T[] = [];
|
|
689
|
-
const originalDataAttributes: KTDataTableAttributeInterface[] = [];
|
|
690
|
-
|
|
691
|
-
this._storeOriginalClasses();
|
|
692
|
-
|
|
693
|
-
const rows = this._tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
|
|
694
|
-
|
|
695
|
-
// Filter th elements to only include those with data-kt-datatable-column attribute
|
|
696
|
-
const allThs: NodeListOf<HTMLTableCellElement> = this._theadElement
|
|
697
|
-
? this._theadElement.querySelectorAll('th')
|
|
698
|
-
: ([] as unknown as NodeListOf<HTMLTableCellElement>);
|
|
699
|
-
|
|
700
|
-
const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
|
|
701
|
-
th.hasAttribute('data-kt-datatable-column'),
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
rows.forEach((row: HTMLTableRowElement) => {
|
|
705
|
-
const dataRow: T = {} as T;
|
|
706
|
-
const dataRowAttribute: KTDataTableAttributeInterface =
|
|
707
|
-
{} as KTDataTableAttributeInterface;
|
|
708
|
-
|
|
709
|
-
row.querySelectorAll<HTMLTableCellElement>('td').forEach((td, index) => {
|
|
710
|
-
const colName = ths[index]?.getAttribute('data-kt-datatable-column');
|
|
711
|
-
if (colName) {
|
|
712
|
-
dataRow[colName as keyof T] = td.innerHTML?.trim() as T[keyof T];
|
|
713
|
-
} else {
|
|
714
|
-
// Store the original HTML for fallback
|
|
715
|
-
dataRow[index as keyof T] = td.innerHTML?.trim() as T[keyof T];
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
if (Object.keys(dataRow).length > 0) {
|
|
720
|
-
originalData.push(dataRow);
|
|
721
|
-
originalDataAttributes.push(dataRowAttribute);
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
return { originalData, originalDataAttributes };
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Check if the table header is invalidated
|
|
730
|
-
* @returns {boolean} - Returns true if the table header is invalidated, false otherwise
|
|
731
|
-
*/
|
|
732
|
-
private _localTableHeaderInvalidate(): boolean {
|
|
733
|
-
const { originalData } = this.getState();
|
|
734
|
-
|
|
735
|
-
const totalColumns = originalData.length
|
|
736
|
-
? Object.keys(originalData[0]).length
|
|
737
|
-
: 0;
|
|
738
|
-
|
|
739
|
-
// Count th elements with data-kt-datatable-column; when none (e.g. multi-row headers), use logical column count so we don't falsely invalidate
|
|
740
|
-
const allThs: NodeListOf<HTMLTableCellElement> = this._theadElement
|
|
741
|
-
? this._theadElement.querySelectorAll('th')
|
|
742
|
-
: ([] as unknown as NodeListOf<HTMLTableCellElement>);
|
|
743
|
-
const thsWithColumn = Array.from(allThs).filter((th) =>
|
|
744
|
-
th.hasAttribute('data-kt-datatable-column'),
|
|
745
|
-
);
|
|
746
|
-
const currentTableHeaders =
|
|
747
|
-
thsWithColumn.length > 0
|
|
748
|
-
? thsWithColumn.length
|
|
749
|
-
: this._getLogicalColumnCount();
|
|
750
|
-
|
|
751
|
-
return currentTableHeaders !== totalColumns;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
613
|
/**
|
|
755
614
|
* Returns the logical data column count (number of data columns), used for multi-row headers
|
|
756
615
|
* where querySelectorAll('th') would overcount. Prefers state.originalData, then first tbody row td count.
|
|
@@ -771,184 +630,6 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
771
630
|
return 0;
|
|
772
631
|
}
|
|
773
632
|
|
|
774
|
-
/**
|
|
775
|
-
* Fetch data from the server
|
|
776
|
-
*/
|
|
777
|
-
private async _fetchDataFromServer(): Promise<void> {
|
|
778
|
-
// Increment request ID to track this specific request
|
|
779
|
-
const currentRequestId = ++this._requestId;
|
|
780
|
-
|
|
781
|
-
this._fireEvent('fetch');
|
|
782
|
-
this._dispatchEvent('fetch');
|
|
783
|
-
|
|
784
|
-
const queryParams = this._getQueryParamsForFetchRequest();
|
|
785
|
-
|
|
786
|
-
let response: Response;
|
|
787
|
-
try {
|
|
788
|
-
response = await this._performFetchRequest(queryParams);
|
|
789
|
-
} catch (error) {
|
|
790
|
-
// Silently ignore AbortError - request was cancelled
|
|
791
|
-
if ((error as Error).name === 'AbortError') {
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
throw error;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// Check if this response is stale (a newer request has been initiated)
|
|
798
|
-
if (currentRequestId !== this._requestId) {
|
|
799
|
-
// Ignore stale response - a more recent request is in progress or has completed
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
let responseData = null;
|
|
804
|
-
|
|
805
|
-
try {
|
|
806
|
-
responseData = await response.json();
|
|
807
|
-
} catch (error) {
|
|
808
|
-
// Fire event with complete error context for application handling
|
|
809
|
-
this._fireEvent('parseError', {
|
|
810
|
-
response,
|
|
811
|
-
error: String(error),
|
|
812
|
-
status: response.status,
|
|
813
|
-
statusText: response.statusText,
|
|
814
|
-
});
|
|
815
|
-
this._dispatchEvent('parseError', {
|
|
816
|
-
response,
|
|
817
|
-
error: String(error),
|
|
818
|
-
status: response.status,
|
|
819
|
-
statusText: response.statusText,
|
|
820
|
-
});
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Double-check request ID after JSON parsing (additional safety)
|
|
825
|
-
if (currentRequestId !== this._requestId) {
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
this._fireEvent('fetched', { response: responseData });
|
|
830
|
-
this._dispatchEvent('fetched', { response: responseData });
|
|
831
|
-
|
|
832
|
-
// Use the mapResponse function to transform the data if provided
|
|
833
|
-
if (typeof this._config.mapResponse === 'function') {
|
|
834
|
-
responseData = this._config.mapResponse.call(this, responseData);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
this._data = responseData.data;
|
|
838
|
-
|
|
839
|
-
this._config._state.totalItems = responseData.totalCount;
|
|
840
|
-
|
|
841
|
-
await this._draw();
|
|
842
|
-
this._fireEvent('fetched');
|
|
843
|
-
this._dispatchEvent('fetched');
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* Get the query params for a fetch request
|
|
848
|
-
* @returns The query params for the fetch request
|
|
849
|
-
*/
|
|
850
|
-
private _getQueryParamsForFetchRequest(): URLSearchParams {
|
|
851
|
-
// Get the current state of the datatable
|
|
852
|
-
const { page, pageSize, sortField, sortOrder, filters, search } =
|
|
853
|
-
this.getState();
|
|
854
|
-
|
|
855
|
-
// Create a new URLSearchParams object to store the query params
|
|
856
|
-
let queryParams = new URLSearchParams();
|
|
857
|
-
|
|
858
|
-
// Add the current page number and page size to the query params
|
|
859
|
-
queryParams.set('page', String(page));
|
|
860
|
-
queryParams.set('size', String(pageSize));
|
|
861
|
-
|
|
862
|
-
// If there is a sort order and field set, add them to the query params
|
|
863
|
-
if (sortOrder !== undefined) {
|
|
864
|
-
queryParams.set('sortOrder', String(sortOrder));
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
if (sortField !== undefined) {
|
|
868
|
-
queryParams.set('sortField', String(sortField));
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// If there are any filters set, add them to the query params
|
|
872
|
-
if (Array.isArray(filters) && filters.length) {
|
|
873
|
-
queryParams.set(
|
|
874
|
-
'filters',
|
|
875
|
-
JSON.stringify(
|
|
876
|
-
filters.map((filter: KTDataTableColumnFilterInterface) => ({
|
|
877
|
-
// Map the filter object to a simpler object with just the necessary properties
|
|
878
|
-
column: filter.column,
|
|
879
|
-
type: filter.type,
|
|
880
|
-
value: filter.value,
|
|
881
|
-
})),
|
|
882
|
-
),
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
if (search) {
|
|
887
|
-
queryParams.set(
|
|
888
|
-
'search',
|
|
889
|
-
typeof search === 'object' ? JSON.stringify(search) : search,
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// If a mapRequest function is provided, call it with the query params object
|
|
894
|
-
if (typeof this._config.mapRequest === 'function') {
|
|
895
|
-
queryParams = this._config.mapRequest.call(this, queryParams);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Return the query params object
|
|
899
|
-
return queryParams;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
private async _performFetchRequest(
|
|
903
|
-
queryParams: URLSearchParams,
|
|
904
|
-
): Promise<Response> {
|
|
905
|
-
const requestMethod: RequestInit['method'] = this._config.requestMethod;
|
|
906
|
-
let requestBody: RequestInit['body'] | undefined = undefined;
|
|
907
|
-
|
|
908
|
-
// Cancel previous request to prevent race conditions
|
|
909
|
-
if (this._abortController) {
|
|
910
|
-
this._abortController.abort();
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Create new AbortController for this request
|
|
914
|
-
this._abortController = new AbortController();
|
|
915
|
-
|
|
916
|
-
// If the request method is POST, send the query params as the request body
|
|
917
|
-
if (requestMethod === 'POST') {
|
|
918
|
-
requestBody = queryParams;
|
|
919
|
-
} else if (requestMethod === 'GET') {
|
|
920
|
-
// If the request method is GET, append the query params to the API endpoint
|
|
921
|
-
const apiEndpointWithQueryParams = this._createUrl(
|
|
922
|
-
this._config.apiEndpoint,
|
|
923
|
-
);
|
|
924
|
-
apiEndpointWithQueryParams.search = queryParams.toString();
|
|
925
|
-
this._config.apiEndpoint = apiEndpointWithQueryParams.toString();
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
return fetch(this._config.apiEndpoint, {
|
|
929
|
-
method: requestMethod,
|
|
930
|
-
body: requestBody,
|
|
931
|
-
headers: this._config.requestHeaders,
|
|
932
|
-
...(this._config.requestCredentials && {
|
|
933
|
-
credentials: this._config.requestCredentials,
|
|
934
|
-
}),
|
|
935
|
-
// Add abort signal if available
|
|
936
|
-
...(this._abortController && { signal: this._abortController.signal }),
|
|
937
|
-
}).catch((error) => {
|
|
938
|
-
// Silently ignore AbortError - this is expected when requests are cancelled
|
|
939
|
-
if (error.name === 'AbortError') {
|
|
940
|
-
return Promise.reject(error);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Trigger an error event for non-abort errors
|
|
944
|
-
this._fireEvent('error', { error });
|
|
945
|
-
this._dispatchEvent('error', { error });
|
|
946
|
-
|
|
947
|
-
this._noticeOnTable('Error performing fetch request: ' + String(error));
|
|
948
|
-
throw error;
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
633
|
/**
|
|
953
634
|
* Creates a complete URL from a relative path or a full URL.
|
|
954
635
|
*
|
|
@@ -988,11 +669,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
988
669
|
* @returns {Promise<void>} A promise that resolves when the table and pagination controls are updated
|
|
989
670
|
*/
|
|
990
671
|
private async _draw(): Promise<void> {
|
|
991
|
-
this.
|
|
992
|
-
|
|
672
|
+
this._stateStore.patchState({
|
|
673
|
+
totalPages:
|
|
674
|
+
Math.ceil(this.getState().totalItems / this.getState().pageSize) || 0,
|
|
675
|
+
});
|
|
993
676
|
|
|
994
|
-
this.
|
|
995
|
-
this._dispatchEvent('draw');
|
|
677
|
+
this._emit('draw');
|
|
996
678
|
|
|
997
679
|
this._dispose();
|
|
998
680
|
|
|
@@ -1005,8 +687,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1005
687
|
this._updatePagination();
|
|
1006
688
|
}
|
|
1007
689
|
|
|
1008
|
-
this.
|
|
1009
|
-
this._dispatchEvent('drew');
|
|
690
|
+
this._emit('drew');
|
|
1010
691
|
|
|
1011
692
|
// Spinner is hidden in _finalize() to ensure it stays visible until the entire request completes
|
|
1012
693
|
// Removed duplicate _hideSpinner() call here to prevent premature hiding
|
|
@@ -1021,143 +702,18 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1021
702
|
* @returns {HTMLTableSectionElement} The new table body element
|
|
1022
703
|
*/
|
|
1023
704
|
private _updateTable(): HTMLTableSectionElement {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
this
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
this.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
tbodyElement.className = this._originalTbodyClass;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
this._updateTableContent(tbodyElement);
|
|
1039
|
-
|
|
1040
|
-
return tbodyElement;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* Update the table content
|
|
1045
|
-
* @param tbodyElement The table body element
|
|
1046
|
-
* @returns {HTMLTableSectionElement} The updated table body element
|
|
1047
|
-
*/
|
|
1048
|
-
private _updateTableContent(
|
|
1049
|
-
tbodyElement: HTMLTableSectionElement,
|
|
1050
|
-
): HTMLTableSectionElement {
|
|
1051
|
-
const fragment = document.createDocumentFragment();
|
|
1052
|
-
|
|
1053
|
-
tbodyElement.textContent = ''; // Clear the tbody element
|
|
1054
|
-
|
|
1055
|
-
if (this._data.length === 0) {
|
|
1056
|
-
this._noticeOnTable(this._config.infoEmpty || '');
|
|
1057
|
-
return tbodyElement;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// Filter th elements to only include those with data-kt-datatable-column attribute
|
|
1061
|
-
// This prevents creating blank td elements for merged header cells (colspan/rowspan)
|
|
1062
|
-
const allThs: NodeListOf<HTMLTableCellElement> = this._theadElement
|
|
1063
|
-
? this._theadElement.querySelectorAll('th')
|
|
1064
|
-
: ([] as unknown as NodeListOf<HTMLTableCellElement>);
|
|
1065
|
-
|
|
1066
|
-
const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
|
|
1067
|
-
th.hasAttribute('data-kt-datatable-column'),
|
|
1068
|
-
);
|
|
1069
|
-
// When no th has data-kt-datatable-column (e.g. multi-row headers), use logical column count from tbody so we don't overcount thead cells
|
|
1070
|
-
const columnsToRender: HTMLTableCellElement[] = ths.length > 0 ? ths : [];
|
|
1071
|
-
const logicalColumnCount =
|
|
1072
|
-
ths.length > 0 ? ths.length : this._getLogicalColumnCount();
|
|
1073
|
-
|
|
1074
|
-
this._data.forEach((item: T, rowIndex: number) => {
|
|
1075
|
-
const row = document.createElement('tr');
|
|
1076
|
-
|
|
1077
|
-
// Apply original tr class if available
|
|
1078
|
-
if (this._originalTrClasses && this._originalTrClasses[rowIndex]) {
|
|
1079
|
-
row.className = this._originalTrClasses[rowIndex];
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
if (!this._config.columns) {
|
|
1083
|
-
const dataRowAttributes = this.getState().originalDataAttributes
|
|
1084
|
-
? this.getState().originalDataAttributes[rowIndex]
|
|
1085
|
-
: null;
|
|
1086
|
-
|
|
1087
|
-
for (let colIndex = 0; colIndex < logicalColumnCount; colIndex++) {
|
|
1088
|
-
const th = columnsToRender[colIndex];
|
|
1089
|
-
const colName = th?.getAttribute('data-kt-datatable-column');
|
|
1090
|
-
const td = document.createElement('td');
|
|
1091
|
-
let value: KTOptionType | '';
|
|
1092
|
-
if (colName && Object.prototype.hasOwnProperty.call(item, colName)) {
|
|
1093
|
-
value = item[colName as keyof T];
|
|
1094
|
-
} else if (Object.prototype.hasOwnProperty.call(item, colIndex)) {
|
|
1095
|
-
value = item[colIndex as keyof T];
|
|
1096
|
-
} else {
|
|
1097
|
-
value = '';
|
|
1098
|
-
}
|
|
1099
|
-
td.innerHTML = value as string;
|
|
1100
|
-
|
|
1101
|
-
// Apply original td class if available
|
|
1102
|
-
if (
|
|
1103
|
-
this._originalTdClasses &&
|
|
1104
|
-
this._originalTdClasses[rowIndex] &&
|
|
1105
|
-
this._originalTdClasses[rowIndex][colIndex]
|
|
1106
|
-
) {
|
|
1107
|
-
td.className = this._originalTdClasses[rowIndex][colIndex];
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (dataRowAttributes && dataRowAttributes[colIndex]) {
|
|
1111
|
-
for (const attr in dataRowAttributes[colIndex]) {
|
|
1112
|
-
td.setAttribute(attr, dataRowAttributes[colIndex][attr]);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
row.appendChild(td);
|
|
1117
|
-
}
|
|
1118
|
-
} else {
|
|
1119
|
-
Object.keys(this._config.columns).forEach(
|
|
1120
|
-
(key: keyof T, colIndex: number) => {
|
|
1121
|
-
const td = document.createElement('td');
|
|
1122
|
-
const columnDef = this._config.columns[key as string];
|
|
1123
|
-
|
|
1124
|
-
// Apply original td class if available
|
|
1125
|
-
if (
|
|
1126
|
-
this._originalTdClasses &&
|
|
1127
|
-
this._originalTdClasses[rowIndex] &&
|
|
1128
|
-
this._originalTdClasses[rowIndex][colIndex]
|
|
1129
|
-
) {
|
|
1130
|
-
td.className = this._originalTdClasses[rowIndex][colIndex];
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
if (typeof columnDef.render === 'function') {
|
|
1134
|
-
const result = columnDef.render.call(this, item[key], item, this);
|
|
1135
|
-
if (
|
|
1136
|
-
result instanceof HTMLElement ||
|
|
1137
|
-
result instanceof DocumentFragment
|
|
1138
|
-
) {
|
|
1139
|
-
td.appendChild(result);
|
|
1140
|
-
} else if (typeof result === 'string') {
|
|
1141
|
-
td.innerHTML = result as string;
|
|
1142
|
-
}
|
|
1143
|
-
} else {
|
|
1144
|
-
td.textContent = item[key] as string;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
if (typeof columnDef.createdCell === 'function') {
|
|
1148
|
-
columnDef.createdCell.call(this, td, item[key], item, row);
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
row.appendChild(td);
|
|
1152
|
-
},
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
fragment.appendChild(row);
|
|
705
|
+
return this._tableRenderer.render({
|
|
706
|
+
config: this._config,
|
|
707
|
+
context: this,
|
|
708
|
+
data: this._data,
|
|
709
|
+
getLogicalColumnCount: this._getLogicalColumnCount.bind(this),
|
|
710
|
+
getState: this.getState.bind(this),
|
|
711
|
+
originalTbodyClass: this._originalTbodyClass,
|
|
712
|
+
originalTrClasses: this._originalTrClasses,
|
|
713
|
+
originalTdClasses: this._originalTdClasses,
|
|
714
|
+
tableElement: this._tableElement,
|
|
715
|
+
theadElement: this._theadElement,
|
|
1157
716
|
});
|
|
1158
|
-
|
|
1159
|
-
tbodyElement.appendChild(fragment);
|
|
1160
|
-
return tbodyElement;
|
|
1161
717
|
}
|
|
1162
718
|
|
|
1163
719
|
/**
|
|
@@ -1166,82 +722,30 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1166
722
|
* @returns {void}
|
|
1167
723
|
*/
|
|
1168
724
|
private _noticeOnTable(message: string = ''): void {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
cell.innerHTML = message;
|
|
725
|
+
this._tableRenderer.notice(
|
|
726
|
+
this._tableElement,
|
|
727
|
+
this._getLogicalColumnCount.bind(this),
|
|
728
|
+
message,
|
|
729
|
+
);
|
|
1175
730
|
}
|
|
1176
731
|
|
|
1177
732
|
private _updatePagination(): void {
|
|
1178
|
-
this.
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
*/
|
|
1189
|
-
private _removeChildElements(container: HTMLElement): void {
|
|
1190
|
-
if (!container) {
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
733
|
+
const cleanup = this._paginationRenderer.render({
|
|
734
|
+
config: this._config,
|
|
735
|
+
dataLength: this._data.length,
|
|
736
|
+
infoElement: this._infoElement,
|
|
737
|
+
paginateData: this._paginateData.bind(this),
|
|
738
|
+
paginationElement: this._paginationElement,
|
|
739
|
+
reloadPageSize: this._reloadPageSize.bind(this),
|
|
740
|
+
sizeElement: this._sizeElement,
|
|
741
|
+
state: this.getState(),
|
|
742
|
+
});
|
|
1193
743
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
// Remove the first child element (which is the first element in the list of child elements)
|
|
1197
|
-
container.removeChild(container.firstChild);
|
|
744
|
+
if (typeof cleanup === 'function') {
|
|
745
|
+
this._cleanupCallbacks.push(cleanup);
|
|
1198
746
|
}
|
|
1199
747
|
}
|
|
1200
748
|
|
|
1201
|
-
/**
|
|
1202
|
-
* Creates a container element for the items per page selector.
|
|
1203
|
-
* @param _sizeElement The element to create the page size controls in.
|
|
1204
|
-
* @returns The container element.
|
|
1205
|
-
*/
|
|
1206
|
-
private _createPageSizeControls(
|
|
1207
|
-
_sizeElement: HTMLSelectElement,
|
|
1208
|
-
): HTMLSelectElement {
|
|
1209
|
-
// If no element is provided, return early
|
|
1210
|
-
if (!_sizeElement) {
|
|
1211
|
-
return _sizeElement;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// Wait for the element to be attached to the DOM
|
|
1215
|
-
setTimeout(() => {
|
|
1216
|
-
// Create <option> elements for each page size option
|
|
1217
|
-
const options = this._config.pageSizes.map((size: number) => {
|
|
1218
|
-
const option = document.createElement('option') as HTMLOptionElement;
|
|
1219
|
-
option.value = String(size);
|
|
1220
|
-
option.text = String(size);
|
|
1221
|
-
option.selected = this.getState().pageSize === size;
|
|
1222
|
-
return option;
|
|
1223
|
-
});
|
|
1224
|
-
|
|
1225
|
-
// Add the <option> elements to the provided element
|
|
1226
|
-
_sizeElement.append(...options);
|
|
1227
|
-
}, 100);
|
|
1228
|
-
|
|
1229
|
-
// Create an event listener for the "change" event on the element
|
|
1230
|
-
const _pageSizeControlsEvent = (event: Event) => {
|
|
1231
|
-
// When the element changes, reload the page with the new page size and page number 1
|
|
1232
|
-
this._reloadPageSize(
|
|
1233
|
-
Number((event.target as HTMLSelectElement).value),
|
|
1234
|
-
1,
|
|
1235
|
-
);
|
|
1236
|
-
};
|
|
1237
|
-
|
|
1238
|
-
// Bind the event listener to the component instance
|
|
1239
|
-
_sizeElement.onchange = _pageSizeControlsEvent.bind(this);
|
|
1240
|
-
|
|
1241
|
-
// Return the element
|
|
1242
|
-
return _sizeElement;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
749
|
/**
|
|
1246
750
|
* Reloads the data with the specified page size and optional page number.
|
|
1247
751
|
* @param pageSize The new page size.
|
|
@@ -1249,191 +753,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1249
753
|
*/
|
|
1250
754
|
private _reloadPageSize(pageSize: number, page: number = 1): void {
|
|
1251
755
|
// Update the page size and page number in the state
|
|
1252
|
-
this.
|
|
1253
|
-
this._config._state.page = page;
|
|
756
|
+
this._stateStore.setPageSize(pageSize, page);
|
|
1254
757
|
|
|
1255
758
|
// Update the data with the new page size and page number
|
|
1256
759
|
this._updateData();
|
|
1257
760
|
}
|
|
1258
761
|
|
|
1259
|
-
/**
|
|
1260
|
-
* Creates the pagination controls for the component.
|
|
1261
|
-
* @param _infoElement The element to set the info text in.
|
|
1262
|
-
* @param _paginationElement The element to create the pagination controls in.
|
|
1263
|
-
* @return {HTMLElement} The element containing the pagination controls.
|
|
1264
|
-
*/
|
|
1265
|
-
private _createPaginationControls(
|
|
1266
|
-
_infoElement: HTMLElement,
|
|
1267
|
-
_paginationElement: HTMLElement,
|
|
1268
|
-
): HTMLElement {
|
|
1269
|
-
if (!_infoElement || !_paginationElement || this._data.length === 0) {
|
|
1270
|
-
return null;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
this._setPaginationInfoText(_infoElement);
|
|
1274
|
-
const paginationContainer =
|
|
1275
|
-
this._createPaginationContainer(_paginationElement);
|
|
1276
|
-
|
|
1277
|
-
if (paginationContainer) {
|
|
1278
|
-
this._createPaginationButtons(paginationContainer);
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
return paginationContainer;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* Sets the info text for the pagination controls.
|
|
1286
|
-
* @param _infoElement The element to set the info text in.
|
|
1287
|
-
*/
|
|
1288
|
-
private _setPaginationInfoText(_infoElement: HTMLElement): void {
|
|
1289
|
-
_infoElement.textContent = this._config.info
|
|
1290
|
-
.replace(
|
|
1291
|
-
'{start}',
|
|
1292
|
-
(this.getState().page - 1) * this.getState().pageSize + 1 + '',
|
|
1293
|
-
)
|
|
1294
|
-
.replace(
|
|
1295
|
-
'{end}',
|
|
1296
|
-
Math.min(
|
|
1297
|
-
this.getState().page * this.getState().pageSize,
|
|
1298
|
-
this.getState().totalItems,
|
|
1299
|
-
) + '',
|
|
1300
|
-
)
|
|
1301
|
-
.replace('{total}', this.getState().totalItems + '');
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
/**
|
|
1305
|
-
* Creates the container element for the pagination controls.
|
|
1306
|
-
* @param _paginationElement The element to create the pagination controls in.
|
|
1307
|
-
* @return {HTMLElement} The container element.
|
|
1308
|
-
*/
|
|
1309
|
-
private _createPaginationContainer(
|
|
1310
|
-
_paginationElement: HTMLElement,
|
|
1311
|
-
): HTMLElement {
|
|
1312
|
-
// No longer create a wrapping div. Just return the pagination element itself.
|
|
1313
|
-
return _paginationElement;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
/**
|
|
1317
|
-
* Creates the pagination buttons for the component.
|
|
1318
|
-
* @param paginationContainer The container element for the pagination controls.
|
|
1319
|
-
*/
|
|
1320
|
-
private _createPaginationButtons(paginationContainer: HTMLElement): void {
|
|
1321
|
-
const { page: currentPage, totalPages } = this.getState();
|
|
1322
|
-
const { previous, next, number, more } = this._config.pagination;
|
|
1323
|
-
|
|
1324
|
-
// Helper function to create a button
|
|
1325
|
-
const createButton = (
|
|
1326
|
-
text: string,
|
|
1327
|
-
className: string,
|
|
1328
|
-
disabled: boolean,
|
|
1329
|
-
handleClick: () => void,
|
|
1330
|
-
): HTMLButtonElement => {
|
|
1331
|
-
const button = document.createElement('button') as HTMLButtonElement;
|
|
1332
|
-
button.className = className;
|
|
1333
|
-
button.innerHTML = text;
|
|
1334
|
-
button.disabled = disabled;
|
|
1335
|
-
button.onclick = handleClick;
|
|
1336
|
-
return button;
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
// Add Previous Button
|
|
1340
|
-
paginationContainer.appendChild(
|
|
1341
|
-
createButton(
|
|
1342
|
-
previous.text,
|
|
1343
|
-
`${previous.class}${currentPage === 1 ? ' disabled' : ''}`,
|
|
1344
|
-
currentPage === 1,
|
|
1345
|
-
() => this._paginateData(currentPage - 1),
|
|
1346
|
-
),
|
|
1347
|
-
);
|
|
1348
|
-
|
|
1349
|
-
// Calculate range of pages
|
|
1350
|
-
const pageMoreEnabled = this._config.pageMore;
|
|
1351
|
-
|
|
1352
|
-
if (pageMoreEnabled) {
|
|
1353
|
-
const maxButtons = this._config.pageMoreLimit;
|
|
1354
|
-
const range = this._calculatePageRange(
|
|
1355
|
-
currentPage,
|
|
1356
|
-
totalPages,
|
|
1357
|
-
maxButtons,
|
|
1358
|
-
);
|
|
1359
|
-
|
|
1360
|
-
// Add start ellipsis
|
|
1361
|
-
if (range.start > 1) {
|
|
1362
|
-
paginationContainer.appendChild(
|
|
1363
|
-
createButton(more.text, more.class, false, () =>
|
|
1364
|
-
this._paginateData(Math.max(1, range.start - 1)),
|
|
1365
|
-
),
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// Add page buttons
|
|
1370
|
-
for (let i = range.start; i <= range.end; i++) {
|
|
1371
|
-
paginationContainer.appendChild(
|
|
1372
|
-
createButton(
|
|
1373
|
-
number.text.replace('{page}', i.toString()),
|
|
1374
|
-
`${number.class}${currentPage === i ? ' active disabled' : ''}`,
|
|
1375
|
-
currentPage === i,
|
|
1376
|
-
() => this._paginateData(i),
|
|
1377
|
-
),
|
|
1378
|
-
);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// Add end ellipsis
|
|
1382
|
-
if (pageMoreEnabled && range.end < totalPages) {
|
|
1383
|
-
paginationContainer.appendChild(
|
|
1384
|
-
createButton(more.text, more.class, false, () =>
|
|
1385
|
-
this._paginateData(Math.min(totalPages, range.end + 1)),
|
|
1386
|
-
),
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
|
-
} else {
|
|
1390
|
-
// Add page buttons
|
|
1391
|
-
for (let i = 1; i <= totalPages; i++) {
|
|
1392
|
-
paginationContainer.appendChild(
|
|
1393
|
-
createButton(
|
|
1394
|
-
number.text.replace('{page}', i.toString()),
|
|
1395
|
-
`${number.class}${currentPage === i ? ' active disabled' : ''}`,
|
|
1396
|
-
currentPage === i,
|
|
1397
|
-
() => this._paginateData(i),
|
|
1398
|
-
),
|
|
1399
|
-
);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
// Add Next Button
|
|
1404
|
-
paginationContainer.appendChild(
|
|
1405
|
-
createButton(
|
|
1406
|
-
next.text,
|
|
1407
|
-
`${next.class}${currentPage === totalPages ? ' disabled' : ''}`,
|
|
1408
|
-
currentPage === totalPages,
|
|
1409
|
-
() => this._paginateData(currentPage + 1),
|
|
1410
|
-
),
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// New helper method to calculate page range
|
|
1415
|
-
private _calculatePageRange(
|
|
1416
|
-
currentPage: number,
|
|
1417
|
-
totalPages: number,
|
|
1418
|
-
maxButtons: number,
|
|
1419
|
-
): { start: number; end: number } {
|
|
1420
|
-
let startPage: number, endPage: number;
|
|
1421
|
-
const halfMaxButtons = Math.floor(maxButtons / 2);
|
|
1422
|
-
|
|
1423
|
-
if (totalPages <= maxButtons) {
|
|
1424
|
-
startPage = 1;
|
|
1425
|
-
endPage = totalPages;
|
|
1426
|
-
} else {
|
|
1427
|
-
startPage = Math.max(currentPage - halfMaxButtons, 1);
|
|
1428
|
-
endPage = Math.min(startPage + maxButtons - 1, totalPages);
|
|
1429
|
-
if (endPage - startPage < maxButtons - 1) {
|
|
1430
|
-
startPage = Math.max(endPage - maxButtons + 1, 1);
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
return { start: startPage, end: endPage };
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
762
|
/**
|
|
1438
763
|
* Method for handling pagination
|
|
1439
764
|
* @param page - The page number to navigate to
|
|
@@ -1443,11 +768,10 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1443
768
|
return;
|
|
1444
769
|
}
|
|
1445
770
|
|
|
1446
|
-
this.
|
|
1447
|
-
this._dispatchEvent('pagination', { page: page });
|
|
771
|
+
this._emit('pagination', { page: page });
|
|
1448
772
|
|
|
1449
773
|
if (page >= 1 && page <= this.getState().totalPages) {
|
|
1450
|
-
this.
|
|
774
|
+
this._stateStore.setPage(page);
|
|
1451
775
|
this._updateData();
|
|
1452
776
|
}
|
|
1453
777
|
}
|
|
@@ -1498,8 +822,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1498
822
|
* @returns {void}
|
|
1499
823
|
*/
|
|
1500
824
|
private _saveState(): void {
|
|
1501
|
-
this.
|
|
1502
|
-
this._dispatchEvent('stateSave');
|
|
825
|
+
this._emit('stateSave');
|
|
1503
826
|
|
|
1504
827
|
const ns: string = this._tableNamespace();
|
|
1505
828
|
|
|
@@ -1521,7 +844,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1521
844
|
|
|
1522
845
|
try {
|
|
1523
846
|
const state = JSON.parse(stateString) as KTDataTableStateInterface;
|
|
1524
|
-
if (state) this.
|
|
847
|
+
if (state) this._stateStore.replaceState(state);
|
|
1525
848
|
return state;
|
|
1526
849
|
} catch {}
|
|
1527
850
|
|
|
@@ -1574,6 +897,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1574
897
|
* This method is called before re-rendering or when disposing the component.
|
|
1575
898
|
*/
|
|
1576
899
|
private _dispose() {
|
|
900
|
+
this._cleanupCallbacks.forEach((cleanup) => cleanup());
|
|
901
|
+
this._cleanupCallbacks = [];
|
|
902
|
+
|
|
1577
903
|
// --- 1. Remove search input event listener (debounced) ---
|
|
1578
904
|
const tableId: string = this._tableId();
|
|
1579
905
|
const searchElement: HTMLInputElement | null =
|
|
@@ -1583,14 +909,13 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1583
909
|
if (searchElement) {
|
|
1584
910
|
const searchWithDebounce =
|
|
1585
911
|
KTDataTable.asSearchElementWithDebounce(searchElement);
|
|
1586
|
-
if (
|
|
1587
|
-
|
|
912
|
+
if (searchWithDebounce._debouncedSearch) {
|
|
913
|
+
searchElement.removeEventListener(
|
|
914
|
+
'keyup',
|
|
915
|
+
searchWithDebounce._debouncedSearch,
|
|
916
|
+
);
|
|
917
|
+
delete searchWithDebounce._debouncedSearch;
|
|
1588
918
|
}
|
|
1589
|
-
searchElement.removeEventListener(
|
|
1590
|
-
'keyup',
|
|
1591
|
-
searchWithDebounce._debouncedSearch,
|
|
1592
|
-
);
|
|
1593
|
-
delete searchWithDebounce._debouncedSearch;
|
|
1594
919
|
}
|
|
1595
920
|
|
|
1596
921
|
// --- 2. Remove page size dropdown event listener ---
|
|
@@ -1672,31 +997,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1672
997
|
* @returns {KTDataTableStateInterface} The current state of the table.
|
|
1673
998
|
*/
|
|
1674
999
|
public getState(): KTDataTableStateInterface {
|
|
1675
|
-
return
|
|
1676
|
-
/**
|
|
1677
|
-
* The current page number.
|
|
1678
|
-
*/
|
|
1679
|
-
page: 1,
|
|
1680
|
-
/**
|
|
1681
|
-
* The field that the data is sorted by.
|
|
1682
|
-
*/
|
|
1683
|
-
sortField: null,
|
|
1684
|
-
/**
|
|
1685
|
-
* The sort order (ascending or descending).
|
|
1686
|
-
*/
|
|
1687
|
-
sortOrder: '',
|
|
1688
|
-
/**
|
|
1689
|
-
* The number of rows to display per page.
|
|
1690
|
-
*/
|
|
1691
|
-
pageSize: this._config.pageSize,
|
|
1692
|
-
|
|
1693
|
-
filters: [],
|
|
1694
|
-
|
|
1695
|
-
/**
|
|
1696
|
-
* Any additional state that may have been stored in the config.
|
|
1697
|
-
*/
|
|
1698
|
-
...this._config._state,
|
|
1699
|
-
};
|
|
1000
|
+
return this._stateStore.getState();
|
|
1700
1001
|
}
|
|
1701
1002
|
|
|
1702
1003
|
/**
|
|
@@ -1712,10 +1013,8 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1712
1013
|
field,
|
|
1713
1014
|
);
|
|
1714
1015
|
this._sortHandler.setSortIcon(field as keyof T, sortOrder);
|
|
1715
|
-
this.
|
|
1716
|
-
this.
|
|
1717
|
-
this._fireEvent('sort', { field, order: sortOrder });
|
|
1718
|
-
this._dispatchEvent('sort', { field, order: sortOrder });
|
|
1016
|
+
this._stateStore.setSort(field as never, sortOrder);
|
|
1017
|
+
this._emit('sort', { field, order: sortOrder });
|
|
1719
1018
|
this._updateData();
|
|
1720
1019
|
}
|
|
1721
1020
|
|
|
@@ -1753,16 +1052,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1753
1052
|
* Triggers the 'reload' event and the 'kt.datatable.reload' custom event.
|
|
1754
1053
|
*/
|
|
1755
1054
|
public reload(): void {
|
|
1756
|
-
this.
|
|
1757
|
-
this._dispatchEvent('reload');
|
|
1055
|
+
this._emit('reload');
|
|
1758
1056
|
|
|
1759
1057
|
// Fetch the data from the server using the current sort and filter settings
|
|
1760
1058
|
this._updateData();
|
|
1761
1059
|
}
|
|
1762
1060
|
|
|
1763
1061
|
public redraw(page: number = 1): void {
|
|
1764
|
-
this.
|
|
1765
|
-
this._dispatchEvent('redraw');
|
|
1062
|
+
this._emit('redraw');
|
|
1766
1063
|
|
|
1767
1064
|
this._paginateData(page);
|
|
1768
1065
|
}
|
|
@@ -1795,23 +1092,17 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1795
1092
|
* @throws Error if the filter object is null or undefined.
|
|
1796
1093
|
*/
|
|
1797
1094
|
public setFilter(filter: KTDataTableColumnFilterInterface): KTDataTable<T> {
|
|
1798
|
-
this.
|
|
1799
|
-
...(this.getState().filters || []).filter(
|
|
1800
|
-
(f) => f.column !== filter.column,
|
|
1801
|
-
),
|
|
1802
|
-
filter,
|
|
1803
|
-
];
|
|
1804
|
-
this._config._state.page = 1;
|
|
1095
|
+
this._stateStore.setFilter(filter);
|
|
1805
1096
|
return this;
|
|
1806
1097
|
}
|
|
1807
1098
|
|
|
1808
1099
|
public override dispose(): void {
|
|
1100
|
+
this._remoteProvider?.dispose();
|
|
1809
1101
|
this._dispose();
|
|
1810
1102
|
}
|
|
1811
1103
|
|
|
1812
1104
|
public search(query: string | object): void {
|
|
1813
|
-
this.
|
|
1814
|
-
this._config._state.page = 1;
|
|
1105
|
+
this._stateStore.setSearch(query);
|
|
1815
1106
|
this.reload();
|
|
1816
1107
|
}
|
|
1817
1108
|
|
|
@@ -1924,8 +1215,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1924
1215
|
*/
|
|
1925
1216
|
public check(): void {
|
|
1926
1217
|
this._checkbox.check();
|
|
1927
|
-
this.
|
|
1928
|
-
this._dispatchEvent('checked');
|
|
1218
|
+
this._emit('checked');
|
|
1929
1219
|
}
|
|
1930
1220
|
|
|
1931
1221
|
/**
|
|
@@ -1934,8 +1224,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1934
1224
|
*/
|
|
1935
1225
|
public uncheck(): void {
|
|
1936
1226
|
this._checkbox.uncheck();
|
|
1937
|
-
this.
|
|
1938
|
-
this._dispatchEvent('unchecked');
|
|
1227
|
+
this._emit('unchecked');
|
|
1939
1228
|
}
|
|
1940
1229
|
|
|
1941
1230
|
/**
|