@keenthemes/ktui 1.2.3 → 1.2.5
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 +2244 -1061
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +185 -40
- 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-checkbox.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js +34 -15
- package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
- 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 +190 -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 +144 -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 +191 -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 +141 -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 +234 -740
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.d.ts +2 -2
- package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.js +68 -31
- package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
- package/lib/cjs/components/input-number/index.d.ts +7 -0
- package/lib/cjs/components/input-number/index.d.ts.map +1 -0
- package/lib/cjs/components/input-number/index.js +10 -0
- package/lib/cjs/components/input-number/index.js.map +1 -0
- package/lib/cjs/components/input-number/input-number.d.ts +40 -0
- package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
- package/lib/cjs/components/input-number/input-number.js +248 -0
- package/lib/cjs/components/input-number/input-number.js.map +1 -0
- package/lib/cjs/components/input-number/types.d.ts +30 -0
- package/lib/cjs/components/input-number/types.d.ts.map +1 -0
- package/lib/cjs/components/input-number/types.js +7 -0
- package/lib/cjs/components/input-number/types.js.map +1 -0
- package/lib/cjs/components/select/config.d.ts +1 -0
- package/lib/cjs/components/select/config.d.ts.map +1 -1
- package/lib/cjs/components/select/config.js +2 -1
- package/lib/cjs/components/select/config.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/components/select/select.d.ts +8 -1
- package/lib/cjs/components/select/select.d.ts.map +1 -1
- package/lib/cjs/components/select/select.js +14 -1
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.d.ts.map +1 -1
- package/lib/cjs/components/select/tags.js +10 -0
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/index.d.ts +9 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +11 -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-checkbox.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.js +34 -15
- package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
- 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 +187 -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 +141 -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 +188 -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 +138 -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 +234 -740
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/dropdown/dropdown.d.ts +2 -2
- package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
- package/lib/esm/components/dropdown/dropdown.js +68 -31
- package/lib/esm/components/dropdown/dropdown.js.map +1 -1
- package/lib/esm/components/input-number/index.d.ts +7 -0
- package/lib/esm/components/input-number/index.d.ts.map +1 -0
- package/lib/esm/components/input-number/index.js +6 -0
- package/lib/esm/components/input-number/index.js.map +1 -0
- package/lib/esm/components/input-number/input-number.d.ts +40 -0
- package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
- package/lib/esm/components/input-number/input-number.js +245 -0
- package/lib/esm/components/input-number/input-number.js.map +1 -0
- package/lib/esm/components/input-number/types.d.ts +30 -0
- package/lib/esm/components/input-number/types.d.ts.map +1 -0
- package/lib/esm/components/input-number/types.js +6 -0
- package/lib/esm/components/input-number/types.js.map +1 -0
- package/lib/esm/components/select/config.d.ts +1 -0
- package/lib/esm/components/select/config.d.ts.map +1 -1
- package/lib/esm/components/select/config.js +2 -1
- package/lib/esm/components/select/config.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/components/select/select.d.ts +8 -1
- package/lib/esm/components/select/select.d.ts.map +1 -1
- package/lib/esm/components/select/select.js +14 -1
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.d.ts.map +1 -1
- package/lib/esm/components/select/tags.js +11 -1
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/index.d.ts +9 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +7 -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 +35 -11
- 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-checkbox.ts +34 -23
- 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 +193 -0
- package/src/components/datatable/datatable-pagination-renderer.ts +225 -0
- package/src/components/datatable/datatable-remote-provider.ts +178 -0
- package/src/components/datatable/datatable-state-store.ts +94 -0
- package/src/components/datatable/datatable-table-renderer.ts +214 -0
- package/src/components/datatable/datatable.ts +250 -918
- package/src/components/dropdown/dropdown.ts +86 -58
- package/src/components/input/input-group.css +14 -1
- package/src/components/input-number/__tests__/input-number.test.ts +278 -0
- package/src/components/input-number/index.ts +11 -0
- package/src/components/input-number/input-number.ts +267 -0
- package/src/components/input-number/types.ts +32 -0
- package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
- package/src/components/select/config.ts +3 -1
- package/src/components/select/index.ts +1 -1
- package/src/components/select/select.css +23 -20
- package/src/components/select/select.ts +15 -1
- package/src/components/select/tags.ts +14 -1
- package/src/index.ts +18 -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
|
|
@@ -63,28 +74,23 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
63
74
|
private _originalTdClasses: string[][] = []; // Store original td classes as a 2D array [row][col]
|
|
64
75
|
private _originalThClasses: string[] = []; // Store original th classes
|
|
65
76
|
|
|
66
|
-
private _infoElement: HTMLElement;
|
|
67
|
-
private _sizeElement: HTMLSelectElement;
|
|
68
|
-
private _paginationElement: HTMLElement;
|
|
77
|
+
private _infoElement: HTMLElement | null = null;
|
|
78
|
+
private _sizeElement: HTMLSelectElement | null = null;
|
|
79
|
+
private _paginationElement: HTMLElement | null = null;
|
|
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
|
|
|
@@ -100,21 +106,29 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
100
106
|
this._defaultConfig = this._initDefaultConfig(config);
|
|
101
107
|
|
|
102
108
|
this._init(element);
|
|
109
|
+
if (!this._element) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
103
112
|
this._buildConfig();
|
|
113
|
+
this._stateStore = new KTDataTableConfigStateStore(this._config);
|
|
114
|
+
this._eventAdapter = createDataTableEventAdapter(
|
|
115
|
+
this._fireEvent.bind(this),
|
|
116
|
+
this._dispatchEvent.bind(this),
|
|
117
|
+
);
|
|
104
118
|
|
|
105
119
|
// Store the instance directly on the element
|
|
106
120
|
KTDataTable.asElementWithInstance(element).instance = this;
|
|
107
121
|
|
|
108
122
|
this._initElements();
|
|
123
|
+
this._tableRenderer = new KTDataTableDomTableRenderer<T>();
|
|
124
|
+
this._paginationRenderer = new KTDataTableDomPaginationRenderer();
|
|
125
|
+
this._initDataProviders();
|
|
109
126
|
|
|
110
127
|
// Initialize checkbox handler
|
|
111
128
|
this._checkbox = createCheckboxHandler(
|
|
112
129
|
this._element,
|
|
113
130
|
this._config,
|
|
114
|
-
(
|
|
115
|
-
this._fireEvent(eventName, eventData);
|
|
116
|
-
this._dispatchEvent(eventName, eventData);
|
|
117
|
-
},
|
|
131
|
+
this._emit.bind(this),
|
|
118
132
|
);
|
|
119
133
|
|
|
120
134
|
// Initialize sort handler
|
|
@@ -126,8 +140,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
126
140
|
sortOrder: this.getState().sortOrder,
|
|
127
141
|
}),
|
|
128
142
|
(field, order) => {
|
|
129
|
-
this.
|
|
130
|
-
this._config._state.sortOrder = order;
|
|
143
|
+
this._stateStore.setSort(field as never, order);
|
|
131
144
|
},
|
|
132
145
|
this._fireEvent.bind(this),
|
|
133
146
|
this._dispatchEvent.bind(this),
|
|
@@ -146,8 +159,33 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
146
159
|
|
|
147
160
|
this._updateData();
|
|
148
161
|
|
|
149
|
-
this.
|
|
150
|
-
|
|
162
|
+
this._emit('init');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private _emit(eventName: string, eventData?: object): void {
|
|
166
|
+
this._eventAdapter.emit(eventName, eventData);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private _initDataProviders(): void {
|
|
170
|
+
this._localProvider = new KTDataTableLocalDataProvider<T>({
|
|
171
|
+
config: this._config,
|
|
172
|
+
elements: () => ({
|
|
173
|
+
tableElement: this._tableElement,
|
|
174
|
+
tbodyElement: this._tbodyElement,
|
|
175
|
+
theadElement: this._theadElement,
|
|
176
|
+
}),
|
|
177
|
+
getLogicalColumnCount: this._getLogicalColumnCount.bind(this),
|
|
178
|
+
storeOriginalClasses: this._storeOriginalClasses.bind(this),
|
|
179
|
+
stateStore: this._stateStore,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this._remoteProvider = new KTDataTableRemoteDataProvider<T>({
|
|
183
|
+
config: this._config,
|
|
184
|
+
createUrl: this._createUrl.bind(this),
|
|
185
|
+
eventAdapter: this._eventAdapter,
|
|
186
|
+
noticeOnTable: this._noticeOnTable.bind(this),
|
|
187
|
+
stateStore: this._stateStore,
|
|
188
|
+
});
|
|
151
189
|
}
|
|
152
190
|
|
|
153
191
|
/**
|
|
@@ -390,43 +428,35 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
390
428
|
* @returns {void}
|
|
391
429
|
*/
|
|
392
430
|
private _initElements(): void {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
431
|
+
const root = this._element;
|
|
432
|
+
const attrs = this._config.attributes;
|
|
433
|
+
if (!root || !attrs?.table) {
|
|
434
|
+
throw new Error('KTDataTable: root element and table selector are required');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const tableEl = root.querySelector<HTMLTableElement>(attrs.table);
|
|
438
|
+
if (!tableEl) {
|
|
439
|
+
throw new Error(`KTDataTable: table element not found (${attrs.table})`);
|
|
440
|
+
}
|
|
441
|
+
this._tableElement = tableEl;
|
|
442
|
+
|
|
402
443
|
this._tbodyElement =
|
|
403
444
|
this._tableElement.tBodies[0] || this._tableElement.createTBody();
|
|
404
|
-
/**
|
|
405
|
-
* Table head element
|
|
406
|
-
*/
|
|
407
|
-
this._theadElement = this._tableElement.tHead!;
|
|
408
445
|
|
|
409
|
-
|
|
446
|
+
this._theadElement =
|
|
447
|
+
this._tableElement.tHead ?? this._tableElement.createTHead();
|
|
448
|
+
|
|
410
449
|
this._storeOriginalClasses();
|
|
411
450
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
this.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
this._sizeElement = this._element.querySelector<HTMLSelectElement>(
|
|
422
|
-
this._config.attributes.size,
|
|
423
|
-
)!;
|
|
424
|
-
/**
|
|
425
|
-
* Pagination element
|
|
426
|
-
*/
|
|
427
|
-
this._paginationElement = this._element.querySelector<HTMLElement>(
|
|
428
|
-
this._config.attributes.pagination,
|
|
429
|
-
)!;
|
|
451
|
+
this._infoElement = attrs.info
|
|
452
|
+
? root.querySelector<HTMLElement>(attrs.info)
|
|
453
|
+
: null;
|
|
454
|
+
this._sizeElement = attrs.size
|
|
455
|
+
? root.querySelector<HTMLSelectElement>(attrs.size)
|
|
456
|
+
: null;
|
|
457
|
+
this._paginationElement = attrs.pagination
|
|
458
|
+
? root.querySelector<HTMLElement>(attrs.pagination)
|
|
459
|
+
: null;
|
|
430
460
|
}
|
|
431
461
|
|
|
432
462
|
/**
|
|
@@ -480,14 +510,20 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
480
510
|
try {
|
|
481
511
|
this._showSpinner(); // Show spinner before fetching data
|
|
482
512
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
513
|
+
this._emit('fetch');
|
|
514
|
+
const result =
|
|
515
|
+
typeof this._config.apiEndpoint === 'undefined'
|
|
516
|
+
? this._localProvider.fetchSync()
|
|
517
|
+
: await this._remoteProvider.fetch();
|
|
518
|
+
|
|
519
|
+
if (!result.skipped) {
|
|
520
|
+
this._data = result.data;
|
|
521
|
+
this._stateStore.patchState({ totalItems: result.totalItems });
|
|
522
|
+
await this._draw();
|
|
523
|
+
this._emit('fetched');
|
|
490
524
|
}
|
|
525
|
+
|
|
526
|
+
await this._finalize();
|
|
491
527
|
} finally {
|
|
492
528
|
// Finally block now correctly executes after promises resolve, not immediately
|
|
493
529
|
this._isFetching = false;
|
|
@@ -499,7 +535,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
499
535
|
* @returns {void}
|
|
500
536
|
*/
|
|
501
537
|
private _finalize(): void {
|
|
502
|
-
this._element
|
|
538
|
+
this._element?.classList.add('datatable-initialized');
|
|
503
539
|
|
|
504
540
|
// Initialize checkbox logic
|
|
505
541
|
this._checkbox.init();
|
|
@@ -559,7 +595,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
559
595
|
// Create a new debounced search function
|
|
560
596
|
const debouncedSearch = this._debounce(() => {
|
|
561
597
|
this.search(searchElement.value);
|
|
562
|
-
}, this._config.search
|
|
598
|
+
}, this._config.search?.delay ?? 500);
|
|
563
599
|
|
|
564
600
|
// Store the new debounced function as a property of the element
|
|
565
601
|
searchWithDebounce._debouncedSearch = debouncedSearch;
|
|
@@ -569,188 +605,6 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
569
605
|
}
|
|
570
606
|
}
|
|
571
607
|
|
|
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
608
|
/**
|
|
755
609
|
* Returns the logical data column count (number of data columns), used for multi-row headers
|
|
756
610
|
* where querySelectorAll('th') would overcount. Prefers state.originalData, then first tbody row td count.
|
|
@@ -771,184 +625,6 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
771
625
|
return 0;
|
|
772
626
|
}
|
|
773
627
|
|
|
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
628
|
/**
|
|
953
629
|
* Creates a complete URL from a relative path or a full URL.
|
|
954
630
|
*
|
|
@@ -966,7 +642,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
966
642
|
|
|
967
643
|
private _createUrl(
|
|
968
644
|
pathOrUrl: string,
|
|
969
|
-
baseUrl: string | null = window
|
|
645
|
+
baseUrl: string | null = typeof window !== 'undefined'
|
|
646
|
+
? window.location.origin
|
|
647
|
+
: null,
|
|
970
648
|
): URL {
|
|
971
649
|
// Regular expression to check if the input is a full URL
|
|
972
650
|
const isFullUrl = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(pathOrUrl);
|
|
@@ -980,7 +658,39 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
980
658
|
? pathOrUrl
|
|
981
659
|
: `/${pathOrUrl}`;
|
|
982
660
|
|
|
983
|
-
|
|
661
|
+
// Opaque origins (e.g. srcdoc iframes) serialize as the string "null", which is not a valid URL base.
|
|
662
|
+
const bases: string[] = [];
|
|
663
|
+
if (baseUrl && baseUrl !== 'null') {
|
|
664
|
+
bases.push(baseUrl);
|
|
665
|
+
}
|
|
666
|
+
if (typeof window !== 'undefined') {
|
|
667
|
+
const href = window.location.href;
|
|
668
|
+
if (href && !bases.includes(href)) {
|
|
669
|
+
bases.push(href);
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
if (window.parent !== window && window.parent.location?.href) {
|
|
673
|
+
const parentHref = window.parent.location.href;
|
|
674
|
+
if (parentHref && !bases.includes(parentHref)) {
|
|
675
|
+
bases.push(parentHref);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} catch {
|
|
679
|
+
// parent is cross-origin
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
for (const base of bases) {
|
|
684
|
+
try {
|
|
685
|
+
return new URL(normalizedPath, base);
|
|
686
|
+
} catch {
|
|
687
|
+
// try next base
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
throw new Error(
|
|
692
|
+
`KTDataTable: cannot resolve relative apiEndpoint "${pathOrUrl}" (no valid base URL; use an absolute apiEndpoint).`,
|
|
693
|
+
);
|
|
984
694
|
}
|
|
985
695
|
|
|
986
696
|
/**
|
|
@@ -988,11 +698,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
988
698
|
* @returns {Promise<void>} A promise that resolves when the table and pagination controls are updated
|
|
989
699
|
*/
|
|
990
700
|
private async _draw(): Promise<void> {
|
|
991
|
-
this.
|
|
992
|
-
|
|
701
|
+
this._stateStore.patchState({
|
|
702
|
+
totalPages:
|
|
703
|
+
Math.ceil(this.getState().totalItems / this.getState().pageSize) || 0,
|
|
704
|
+
});
|
|
993
705
|
|
|
994
|
-
this.
|
|
995
|
-
this._dispatchEvent('draw');
|
|
706
|
+
this._emit('draw');
|
|
996
707
|
|
|
997
708
|
this._dispose();
|
|
998
709
|
|
|
@@ -1001,12 +712,11 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1001
712
|
this._updateTable();
|
|
1002
713
|
}
|
|
1003
714
|
|
|
1004
|
-
if (this._infoElement
|
|
715
|
+
if (this._infoElement || this._sizeElement || this._paginationElement) {
|
|
1005
716
|
this._updatePagination();
|
|
1006
717
|
}
|
|
1007
718
|
|
|
1008
|
-
this.
|
|
1009
|
-
this._dispatchEvent('drew');
|
|
719
|
+
this._emit('drew');
|
|
1010
720
|
|
|
1011
721
|
// Spinner is hidden in _finalize() to ensure it stays visible until the entire request completes
|
|
1012
722
|
// Removed duplicate _hideSpinner() call here to prevent premature hiding
|
|
@@ -1021,143 +731,18 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1021
731
|
* @returns {HTMLTableSectionElement} The new table body element
|
|
1022
732
|
*/
|
|
1023
733
|
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);
|
|
734
|
+
return this._tableRenderer.render({
|
|
735
|
+
config: this._config,
|
|
736
|
+
context: this,
|
|
737
|
+
data: this._data,
|
|
738
|
+
getLogicalColumnCount: this._getLogicalColumnCount.bind(this),
|
|
739
|
+
getState: this.getState.bind(this),
|
|
740
|
+
originalTbodyClass: this._originalTbodyClass,
|
|
741
|
+
originalTrClasses: this._originalTrClasses,
|
|
742
|
+
originalTdClasses: this._originalTdClasses,
|
|
743
|
+
tableElement: this._tableElement,
|
|
744
|
+
theadElement: this._theadElement,
|
|
1157
745
|
});
|
|
1158
|
-
|
|
1159
|
-
tbodyElement.appendChild(fragment);
|
|
1160
|
-
return tbodyElement;
|
|
1161
746
|
}
|
|
1162
747
|
|
|
1163
748
|
/**
|
|
@@ -1166,80 +751,28 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1166
751
|
* @returns {void}
|
|
1167
752
|
*/
|
|
1168
753
|
private _noticeOnTable(message: string = ''): void {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
cell.innerHTML = message;
|
|
754
|
+
this._tableRenderer.notice(
|
|
755
|
+
this._tableElement,
|
|
756
|
+
this._getLogicalColumnCount.bind(this),
|
|
757
|
+
message,
|
|
758
|
+
);
|
|
1175
759
|
}
|
|
1176
760
|
|
|
1177
761
|
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
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// Loop through all child elements of the container and remove them one by one
|
|
1195
|
-
while (container.firstChild) {
|
|
1196
|
-
// Remove the first child element (which is the first element in the list of child elements)
|
|
1197
|
-
container.removeChild(container.firstChild);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
762
|
+
const cleanup = this._paginationRenderer.render({
|
|
763
|
+
config: this._config,
|
|
764
|
+
dataLength: this._data.length,
|
|
765
|
+
infoElement: this._infoElement,
|
|
766
|
+
paginateData: this._paginateData.bind(this),
|
|
767
|
+
paginationElement: this._paginationElement,
|
|
768
|
+
reloadPageSize: this._reloadPageSize.bind(this),
|
|
769
|
+
sizeElement: this._sizeElement,
|
|
770
|
+
state: this.getState(),
|
|
771
|
+
});
|
|
1200
772
|
|
|
1201
|
-
|
|
1202
|
-
|
|
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;
|
|
773
|
+
if (typeof cleanup === 'function') {
|
|
774
|
+
this._cleanupCallbacks.push(cleanup);
|
|
1212
775
|
}
|
|
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
776
|
}
|
|
1244
777
|
|
|
1245
778
|
/**
|
|
@@ -1249,191 +782,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1249
782
|
*/
|
|
1250
783
|
private _reloadPageSize(pageSize: number, page: number = 1): void {
|
|
1251
784
|
// Update the page size and page number in the state
|
|
1252
|
-
this.
|
|
1253
|
-
this._config._state.page = page;
|
|
785
|
+
this._stateStore.setPageSize(pageSize, page);
|
|
1254
786
|
|
|
1255
787
|
// Update the data with the new page size and page number
|
|
1256
788
|
this._updateData();
|
|
1257
789
|
}
|
|
1258
790
|
|
|
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
791
|
/**
|
|
1438
792
|
* Method for handling pagination
|
|
1439
793
|
* @param page - The page number to navigate to
|
|
@@ -1443,49 +797,59 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1443
797
|
return;
|
|
1444
798
|
}
|
|
1445
799
|
|
|
1446
|
-
this.
|
|
1447
|
-
this._dispatchEvent('pagination', { page: page });
|
|
800
|
+
this._emit('pagination', { page: page });
|
|
1448
801
|
|
|
1449
802
|
if (page >= 1 && page <= this.getState().totalPages) {
|
|
1450
|
-
this.
|
|
803
|
+
this._stateStore.setPage(page);
|
|
1451
804
|
this._updateData();
|
|
1452
805
|
}
|
|
1453
806
|
}
|
|
1454
807
|
|
|
1455
808
|
// Method to show the loading spinner
|
|
1456
809
|
private _showSpinner(): void {
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
810
|
+
const root = this._element;
|
|
811
|
+
const spinnerSel = this._config.attributes?.spinner;
|
|
812
|
+
const fromDom =
|
|
813
|
+
root && spinnerSel
|
|
814
|
+
? root.querySelector<HTMLElement>(spinnerSel)
|
|
815
|
+
: null;
|
|
816
|
+
const spinner = fromDom ?? this._createSpinner();
|
|
1461
817
|
if (spinner) {
|
|
1462
818
|
spinner.style.display = 'block';
|
|
1463
819
|
}
|
|
1464
|
-
|
|
820
|
+
root?.classList.add(this._config.loadingClass ?? 'loading');
|
|
1465
821
|
}
|
|
1466
822
|
|
|
1467
823
|
// Method to hide the loading spinner
|
|
1468
824
|
private _hideSpinner(): void {
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1471
|
-
|
|
825
|
+
const root = this._element;
|
|
826
|
+
const spinnerSel = this._config.attributes?.spinner;
|
|
827
|
+
const spinner =
|
|
828
|
+
root && spinnerSel
|
|
829
|
+
? root.querySelector<HTMLElement>(spinnerSel)
|
|
830
|
+
: null;
|
|
1472
831
|
if (spinner) {
|
|
1473
832
|
spinner.style.display = 'none';
|
|
1474
833
|
}
|
|
1475
|
-
|
|
834
|
+
root?.classList.remove(this._config.loadingClass ?? 'loading');
|
|
1476
835
|
}
|
|
1477
836
|
|
|
1478
837
|
// Method to create a spinner element if it doesn't exist
|
|
1479
|
-
private _createSpinner(): HTMLElement {
|
|
1480
|
-
|
|
838
|
+
private _createSpinner(): HTMLElement | null {
|
|
839
|
+
const loading = this._config.loading;
|
|
840
|
+
if (!loading) {
|
|
1481
841
|
return null;
|
|
1482
842
|
}
|
|
1483
843
|
|
|
1484
844
|
const template = document.createElement('template');
|
|
1485
|
-
template.innerHTML =
|
|
845
|
+
template.innerHTML = loading.template
|
|
1486
846
|
.trim()
|
|
1487
|
-
.replace('{content}',
|
|
1488
|
-
const
|
|
847
|
+
.replace('{content}', loading.content);
|
|
848
|
+
const first = template.content.firstChild;
|
|
849
|
+
if (!first || !(first instanceof HTMLElement)) {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
const spinner = first;
|
|
1489
853
|
spinner.setAttribute('data-kt-datatable-spinner', 'true');
|
|
1490
854
|
|
|
1491
855
|
this._tableElement.appendChild(spinner);
|
|
@@ -1498,8 +862,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1498
862
|
* @returns {void}
|
|
1499
863
|
*/
|
|
1500
864
|
private _saveState(): void {
|
|
1501
|
-
this.
|
|
1502
|
-
this._dispatchEvent('stateSave');
|
|
865
|
+
this._emit('stateSave');
|
|
1503
866
|
|
|
1504
867
|
const ns: string = this._tableNamespace();
|
|
1505
868
|
|
|
@@ -1521,7 +884,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1521
884
|
|
|
1522
885
|
try {
|
|
1523
886
|
const state = JSON.parse(stateString) as KTDataTableStateInterface;
|
|
1524
|
-
if (state) this.
|
|
887
|
+
if (state) this._stateStore.replaceState(state);
|
|
1525
888
|
return state;
|
|
1526
889
|
} catch {}
|
|
1527
890
|
|
|
@@ -1555,18 +918,15 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1555
918
|
}
|
|
1556
919
|
|
|
1557
920
|
private _tableId(): string {
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
id = this._tableElement.getAttribute('id') as string;
|
|
921
|
+
const tableIdAttr = this._tableElement?.getAttribute('id');
|
|
922
|
+
if (tableIdAttr) {
|
|
923
|
+
return tableIdAttr;
|
|
1562
924
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
id = this._element.getAttribute('id') as string;
|
|
925
|
+
const rootIdAttr = this._element?.getAttribute('id');
|
|
926
|
+
if (rootIdAttr) {
|
|
927
|
+
return rootIdAttr;
|
|
1567
928
|
}
|
|
1568
|
-
|
|
1569
|
-
return id;
|
|
929
|
+
return '';
|
|
1570
930
|
}
|
|
1571
931
|
|
|
1572
932
|
/**
|
|
@@ -1574,6 +934,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1574
934
|
* This method is called before re-rendering or when disposing the component.
|
|
1575
935
|
*/
|
|
1576
936
|
private _dispose() {
|
|
937
|
+
const root = this._element;
|
|
938
|
+
if (!root) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
this._cleanupCallbacks.forEach((cleanup) => cleanup());
|
|
943
|
+
this._cleanupCallbacks = [];
|
|
944
|
+
|
|
1577
945
|
// --- 1. Remove search input event listener (debounced) ---
|
|
1578
946
|
const tableId: string = this._tableId();
|
|
1579
947
|
const searchElement: HTMLInputElement | null =
|
|
@@ -1583,14 +951,13 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1583
951
|
if (searchElement) {
|
|
1584
952
|
const searchWithDebounce =
|
|
1585
953
|
KTDataTable.asSearchElementWithDebounce(searchElement);
|
|
1586
|
-
if (
|
|
1587
|
-
|
|
954
|
+
if (searchWithDebounce._debouncedSearch) {
|
|
955
|
+
searchElement.removeEventListener(
|
|
956
|
+
'keyup',
|
|
957
|
+
searchWithDebounce._debouncedSearch,
|
|
958
|
+
);
|
|
959
|
+
delete searchWithDebounce._debouncedSearch;
|
|
1588
960
|
}
|
|
1589
|
-
searchElement.removeEventListener(
|
|
1590
|
-
'keyup',
|
|
1591
|
-
searchWithDebounce._debouncedSearch,
|
|
1592
|
-
);
|
|
1593
|
-
delete searchWithDebounce._debouncedSearch;
|
|
1594
961
|
}
|
|
1595
962
|
|
|
1596
963
|
// --- 2. Remove page size dropdown event listener ---
|
|
@@ -1614,12 +981,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1614
981
|
if (this._checkbox && typeof checkboxWithDispose.dispose === 'function') {
|
|
1615
982
|
checkboxWithDispose.dispose();
|
|
1616
983
|
} else {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
984
|
+
const checkSel = this._config.attributes?.check;
|
|
985
|
+
if (checkSel) {
|
|
986
|
+
const headerCheckElement = root.querySelector<HTMLInputElement>(checkSel);
|
|
987
|
+
if (headerCheckElement) {
|
|
988
|
+
headerCheckElement.replaceWith(headerCheckElement.cloneNode(true));
|
|
989
|
+
}
|
|
1623
990
|
}
|
|
1624
991
|
}
|
|
1625
992
|
// KTDataTableSortAPI does not have a dispose method, but we can remove th click listeners by replacing them
|
|
@@ -1631,22 +998,23 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1631
998
|
}
|
|
1632
999
|
|
|
1633
1000
|
// --- 5. Remove spinner DOM node if it exists ---
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1001
|
+
const spinnerSel = this._config.attributes?.spinner;
|
|
1002
|
+
if (spinnerSel) {
|
|
1003
|
+
const spinner = root.querySelector<HTMLElement>(spinnerSel);
|
|
1004
|
+
if (spinner?.parentNode) {
|
|
1005
|
+
spinner.parentNode.removeChild(spinner);
|
|
1006
|
+
}
|
|
1639
1007
|
}
|
|
1640
|
-
|
|
1008
|
+
root.classList.remove(this._config.loadingClass ?? 'loading');
|
|
1641
1009
|
|
|
1642
1010
|
// --- 6. Remove instance reference from the DOM element ---
|
|
1643
|
-
const elementWithInstance = KTDataTable.asElementWithInstance(
|
|
1644
|
-
this._element,
|
|
1645
|
-
);
|
|
1011
|
+
const elementWithInstance = KTDataTable.asElementWithInstance(root);
|
|
1646
1012
|
if (elementWithInstance.instance) {
|
|
1647
1013
|
delete elementWithInstance.instance;
|
|
1648
1014
|
}
|
|
1649
1015
|
|
|
1016
|
+
KTData.remove(root, this._name);
|
|
1017
|
+
|
|
1650
1018
|
// --- 7. (Optional) Clear localStorage state ---
|
|
1651
1019
|
// Uncomment the following line if you want to clear state on dispose:
|
|
1652
1020
|
// this._deleteState();
|
|
@@ -1672,31 +1040,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1672
1040
|
* @returns {KTDataTableStateInterface} The current state of the table.
|
|
1673
1041
|
*/
|
|
1674
1042
|
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
|
-
};
|
|
1043
|
+
return this._stateStore.getState();
|
|
1700
1044
|
}
|
|
1701
1045
|
|
|
1702
1046
|
/**
|
|
@@ -1712,10 +1056,8 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1712
1056
|
field,
|
|
1713
1057
|
);
|
|
1714
1058
|
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 });
|
|
1059
|
+
this._stateStore.setSort(field as never, sortOrder);
|
|
1060
|
+
this._emit('sort', { field, order: sortOrder });
|
|
1719
1061
|
this._updateData();
|
|
1720
1062
|
}
|
|
1721
1063
|
|
|
@@ -1753,16 +1095,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1753
1095
|
* Triggers the 'reload' event and the 'kt.datatable.reload' custom event.
|
|
1754
1096
|
*/
|
|
1755
1097
|
public reload(): void {
|
|
1756
|
-
this.
|
|
1757
|
-
this._dispatchEvent('reload');
|
|
1098
|
+
this._emit('reload');
|
|
1758
1099
|
|
|
1759
1100
|
// Fetch the data from the server using the current sort and filter settings
|
|
1760
1101
|
this._updateData();
|
|
1761
1102
|
}
|
|
1762
1103
|
|
|
1763
1104
|
public redraw(page: number = 1): void {
|
|
1764
|
-
this.
|
|
1765
|
-
this._dispatchEvent('redraw');
|
|
1105
|
+
this._emit('redraw');
|
|
1766
1106
|
|
|
1767
1107
|
this._paginateData(page);
|
|
1768
1108
|
}
|
|
@@ -1795,23 +1135,17 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1795
1135
|
* @throws Error if the filter object is null or undefined.
|
|
1796
1136
|
*/
|
|
1797
1137
|
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;
|
|
1138
|
+
this._stateStore.setFilter(filter);
|
|
1805
1139
|
return this;
|
|
1806
1140
|
}
|
|
1807
1141
|
|
|
1808
1142
|
public override dispose(): void {
|
|
1143
|
+
this._remoteProvider?.dispose();
|
|
1809
1144
|
this._dispose();
|
|
1810
1145
|
}
|
|
1811
1146
|
|
|
1812
1147
|
public search(query: string | object): void {
|
|
1813
|
-
this.
|
|
1814
|
-
this._config._state.page = 1;
|
|
1148
|
+
this._stateStore.setSearch(query);
|
|
1815
1149
|
this.reload();
|
|
1816
1150
|
}
|
|
1817
1151
|
|
|
@@ -1924,8 +1258,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1924
1258
|
*/
|
|
1925
1259
|
public check(): void {
|
|
1926
1260
|
this._checkbox.check();
|
|
1927
|
-
this.
|
|
1928
|
-
this._dispatchEvent('checked');
|
|
1261
|
+
this._emit('checked');
|
|
1929
1262
|
}
|
|
1930
1263
|
|
|
1931
1264
|
/**
|
|
@@ -1934,8 +1267,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1934
1267
|
*/
|
|
1935
1268
|
public uncheck(): void {
|
|
1936
1269
|
this._checkbox.uncheck();
|
|
1937
|
-
this.
|
|
1938
|
-
this._dispatchEvent('unchecked');
|
|
1270
|
+
this._emit('unchecked');
|
|
1939
1271
|
}
|
|
1940
1272
|
|
|
1941
1273
|
/**
|