@doyosi/laraisy 1.0.2 → 1.0.3
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/LICENSE +1 -1
- package/package.json +1 -1
- package/src/CodeInput.js +48 -48
- package/src/DSAlert.js +352 -352
- package/src/DSAvatar.js +207 -207
- package/src/DSDelete.js +274 -274
- package/src/DSForm.js +568 -568
- package/src/DSGridOrTable.js +453 -453
- package/src/DSLocaleSwitcher.js +239 -239
- package/src/DSLogout.js +293 -293
- package/src/DSNotifications.js +365 -365
- package/src/DSRestore.js +181 -181
- package/src/DSSelect.js +1071 -1071
- package/src/DSSelectBox.js +563 -563
- package/src/DSSimpleSlider.js +517 -517
- package/src/DSSvgFetch.js +69 -69
- package/src/DSTable/DSTableExport.js +68 -68
- package/src/DSTable/DSTableFilter.js +224 -224
- package/src/DSTable/DSTablePagination.js +136 -136
- package/src/DSTable/DSTableSearch.js +40 -40
- package/src/DSTable/DSTableSelection.js +192 -192
- package/src/DSTable/DSTableSort.js +58 -58
- package/src/DSTable.js +353 -353
- package/src/DSTabs.js +488 -488
- package/src/DSUpload.js +887 -887
- package/dist/CodeInput.d.ts +0 -10
- package/dist/DSAlert.d.ts +0 -112
- package/dist/DSAvatar.d.ts +0 -45
- package/dist/DSDelete.d.ts +0 -61
- package/dist/DSForm.d.ts +0 -151
- package/dist/DSGridOrTable/DSGOTRenderer.d.ts +0 -60
- package/dist/DSGridOrTable/DSGOTViewToggle.d.ts +0 -26
- package/dist/DSGridOrTable.d.ts +0 -296
- package/dist/DSLocaleSwitcher.d.ts +0 -71
- package/dist/DSLogout.d.ts +0 -76
- package/dist/DSNotifications.d.ts +0 -54
- package/dist/DSRestore.d.ts +0 -56
- package/dist/DSSelect.d.ts +0 -221
- package/dist/DSSelectBox.d.ts +0 -123
- package/dist/DSSimpleSlider.d.ts +0 -136
- package/dist/DSSvgFetch.d.ts +0 -17
- package/dist/DSTable/DSTableExport.d.ts +0 -11
- package/dist/DSTable/DSTableFilter.d.ts +0 -40
- package/dist/DSTable/DSTablePagination.d.ts +0 -12
- package/dist/DSTable/DSTableSearch.d.ts +0 -8
- package/dist/DSTable/DSTableSelection.d.ts +0 -46
- package/dist/DSTable/DSTableSort.d.ts +0 -8
- package/dist/DSTable.d.ts +0 -116
- package/dist/DSTabs.d.ts +0 -156
- package/dist/DSUpload.d.ts +0 -220
- package/dist/index.d.ts +0 -17
package/src/DSTable.js
CHANGED
|
@@ -1,353 +1,353 @@
|
|
|
1
|
-
import DSTablePagination from './DSTable/DSTablePagination.js';
|
|
2
|
-
import DSTableSearch from './DSTable/DSTableSearch.js';
|
|
3
|
-
import DSTableSort from './DSTable/DSTableSort.js';
|
|
4
|
-
import DSTableFilter from './DSTable/DSTableFilter.js';
|
|
5
|
-
import DSTableExport from './DSTable/DSTableExport.js';
|
|
6
|
-
import DSTableSelection from './DSTable/DSTableSelection.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* DSTable
|
|
10
|
-
*
|
|
11
|
-
* A comprehensive table plugin for data management.
|
|
12
|
-
* Supports: Pagination, Search, Sort, Filter, Export, Selection.
|
|
13
|
-
*/
|
|
14
|
-
export class DSTable {
|
|
15
|
-
static defaults = {
|
|
16
|
-
pagination: true,
|
|
17
|
-
pagination_translations: {
|
|
18
|
-
prev: 'Previous',
|
|
19
|
-
next: 'Next',
|
|
20
|
-
goto: 'Go to',
|
|
21
|
-
stats: 'Showing {from} to {to} of {total} entries'
|
|
22
|
-
},
|
|
23
|
-
search: true,
|
|
24
|
-
sort: true,
|
|
25
|
-
filter: true,
|
|
26
|
-
export: true,
|
|
27
|
-
selection: true,
|
|
28
|
-
|
|
29
|
-
table_source: 'ajax', // ajax | html | json
|
|
30
|
-
ajax_url: null,
|
|
31
|
-
ajax_data: {},
|
|
32
|
-
ajax_method: 'GET',
|
|
33
|
-
ajax_function: 'axios', // xhr | axios | fetch
|
|
34
|
-
|
|
35
|
-
success: null,
|
|
36
|
-
error: null,
|
|
37
|
-
beforeSend: null,
|
|
38
|
-
afterSend: null,
|
|
39
|
-
|
|
40
|
-
template_source: 'html', // function | html | response
|
|
41
|
-
template_function: null,
|
|
42
|
-
template_html: null,
|
|
43
|
-
template_response: 'data.*.html_response',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
emptyIcon: 'search_off',
|
|
47
|
-
|
|
48
|
-
// Selectors
|
|
49
|
-
tableSelector: 'table',
|
|
50
|
-
bodySelector: 'tbody',
|
|
51
|
-
messageSelector: '.ds-table-message',
|
|
52
|
-
table_translations: {
|
|
53
|
-
no_data: 'No data available',
|
|
54
|
-
loading: 'Loading...',
|
|
55
|
-
error: 'Error loading data',
|
|
56
|
-
empty: 'No data available',
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
constructor(wrapper, options = {}) {
|
|
62
|
-
this.wrapper = typeof wrapper === 'string' ? document.querySelector(wrapper) : wrapper;
|
|
63
|
-
if (!this.wrapper) throw new Error('DSTable: Wrapper element not found');
|
|
64
|
-
|
|
65
|
-
this.config = { ...DSTable.defaults, ...options };
|
|
66
|
-
|
|
67
|
-
// State
|
|
68
|
-
this.data = [];
|
|
69
|
-
this.meta = {}; // Pagination meta
|
|
70
|
-
this.params = {
|
|
71
|
-
page: 1,
|
|
72
|
-
per_page: 15,
|
|
73
|
-
sort_by: null,
|
|
74
|
-
sort_by: null,
|
|
75
|
-
sort_order: 'asc',
|
|
76
|
-
search: null,
|
|
77
|
-
filters: {}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
this.modules = {};
|
|
81
|
-
this.isLoading = false;
|
|
82
|
-
|
|
83
|
-
this._init();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
_init() {
|
|
87
|
-
this.table = this.wrapper.querySelector(this.config.tableSelector);
|
|
88
|
-
this.tbody = this.wrapper.querySelector(this.config.bodySelector) || this.table?.querySelector('tbody');
|
|
89
|
-
|
|
90
|
-
if (!this.table) throw new Error('DSTable: Table element not found inside wrapper');
|
|
91
|
-
|
|
92
|
-
// Initialize Modules
|
|
93
|
-
if (this.config.pagination) this.modules.pagination = new DSTablePagination(this);
|
|
94
|
-
if (this.config.search) this.modules.search = new DSTableSearch(this);
|
|
95
|
-
if (this.config.sort) this.modules.sort = new DSTableSort(this);
|
|
96
|
-
if (this.config.filter) this.modules.filter = new DSTableFilter(this);
|
|
97
|
-
if (this.config.export) this.modules.export = new DSTableExport(this);
|
|
98
|
-
if (this.config.selection) this.modules.selection = new DSTableSelection(this);
|
|
99
|
-
|
|
100
|
-
// Initial Load
|
|
101
|
-
this.loadData();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ================= DATA LOADING =================
|
|
105
|
-
|
|
106
|
-
async loadData() {
|
|
107
|
-
if (this.isLoading) return;
|
|
108
|
-
this.isLoading = true;
|
|
109
|
-
this._toggleLoading(true);
|
|
110
|
-
|
|
111
|
-
if (this.config.beforeSend) this.config.beforeSend({ params: this.params });
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
if (this.config.table_source === 'ajax') {
|
|
115
|
-
await this._loadFromAjax();
|
|
116
|
-
} else if (this.config.table_source === 'json') {
|
|
117
|
-
// Load from local JSON data provided in config
|
|
118
|
-
this._handleDataSuccess(this.config.data || []);
|
|
119
|
-
} else if (this.config.table_source === 'html') {
|
|
120
|
-
// Parse existing HTML? (Not fully implemented in this plan, mainly for Ajax)
|
|
121
|
-
this.isLoading = false;
|
|
122
|
-
this._toggleLoading(false);
|
|
123
|
-
}
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error('DSTable: Error loading data', error);
|
|
126
|
-
if (this.config.error) this.config.error(error);
|
|
127
|
-
this._showError('Error loading data');
|
|
128
|
-
this.isLoading = false;
|
|
129
|
-
this._toggleLoading(false);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async _loadFromAjax() {
|
|
134
|
-
const url = this.config.ajax_url;
|
|
135
|
-
const method = this.config.ajax_method;
|
|
136
|
-
const data = { ...this.config.ajax_data, ...this.params };
|
|
137
|
-
|
|
138
|
-
let response;
|
|
139
|
-
|
|
140
|
-
if (this.config.ajax_function === 'axios' && window.axios) {
|
|
141
|
-
response = await window.axios({ method, url, params: method === 'GET' ? data : undefined, data: method !== 'GET' ? data : undefined });
|
|
142
|
-
this._handleDataSuccess(response.data);
|
|
143
|
-
} else if (this.config.ajax_function === 'fetch' || window.fetch) {
|
|
144
|
-
// Basic fetch impl
|
|
145
|
-
const queryString = new URLSearchParams(data).toString();
|
|
146
|
-
const fetchUrl = method === 'GET' ? `${url}?${queryString}` : url;
|
|
147
|
-
const options = {
|
|
148
|
-
method,
|
|
149
|
-
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
150
|
-
body: method !== 'GET' ? JSON.stringify(data) : undefined
|
|
151
|
-
};
|
|
152
|
-
const res = await fetch(fetchUrl, options);
|
|
153
|
-
const json = await res.json();
|
|
154
|
-
this._handleDataSuccess(json);
|
|
155
|
-
} else {
|
|
156
|
-
throw new Error('DSTable: No valid ajax function found');
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
_handleDataSuccess(response) {
|
|
161
|
-
// Standardize response
|
|
162
|
-
// Expected: { success: true, data: [...], meta: {...} }
|
|
163
|
-
|
|
164
|
-
if (response.data) {
|
|
165
|
-
this.data = response.data;
|
|
166
|
-
this.meta = response.meta || {};
|
|
167
|
-
} else if (Array.isArray(response)) {
|
|
168
|
-
this.data = response;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
this.render();
|
|
172
|
-
|
|
173
|
-
if (this.config.success) this.config.success(response);
|
|
174
|
-
if (this.config.afterSend) this.config.afterSend(response);
|
|
175
|
-
|
|
176
|
-
// Notify modules
|
|
177
|
-
Object.values(this.modules).forEach(m => m.onDataLoaded && m.onDataLoaded(response));
|
|
178
|
-
|
|
179
|
-
this.isLoading = false;
|
|
180
|
-
this._toggleLoading(false);
|
|
181
|
-
this._emit('dataLoaded', response);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ================= RENDERING =================
|
|
185
|
-
|
|
186
|
-
render() {
|
|
187
|
-
if (!this.tbody) return;
|
|
188
|
-
this.tbody.innerHTML = '';
|
|
189
|
-
|
|
190
|
-
if (this.data.length === 0) {
|
|
191
|
-
this._showEmpty();
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const fragment = document.createDocumentFragment();
|
|
196
|
-
|
|
197
|
-
this.data.forEach((row, index) => {
|
|
198
|
-
const tr = document.createElement('tr');
|
|
199
|
-
let html = '';
|
|
200
|
-
|
|
201
|
-
if (this.config.template_source === 'function' && typeof this.config.template_function === 'function') {
|
|
202
|
-
html = this.config.template_function(row, index);
|
|
203
|
-
} else if (this.config.template_source === 'html' && this.config.template_html) {
|
|
204
|
-
html = this._renderTemplate(this.config.template_html, row);
|
|
205
|
-
} else if (this.config.template_source === 'response') {
|
|
206
|
-
// deeply get html_response from row using dot notation if needed
|
|
207
|
-
html = this._getNestedValue(row, this.config.template_response) || '';
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// If the template returns a full TR, we might need to parse it.
|
|
211
|
-
// Expectation: The template returns innerHTML for the TR, or the configuration handles the TR wrapper.
|
|
212
|
-
// Adjusting based on standard table needs: assumes content is <td>...</td>
|
|
213
|
-
// BUT if template_response is used, the example shows "<tr><td>...</td></tr>"
|
|
214
|
-
|
|
215
|
-
if (html.trim().startsWith('<tr')) {
|
|
216
|
-
const tempTable = document.createElement('table');
|
|
217
|
-
const tempTbody = document.createElement('tbody');
|
|
218
|
-
tempTable.appendChild(tempTbody);
|
|
219
|
-
tempTbody.innerHTML = html;
|
|
220
|
-
const newRow = tempTbody.firstElementChild;
|
|
221
|
-
if (newRow) {
|
|
222
|
-
fragment.appendChild(newRow);
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
tr.innerHTML = html;
|
|
226
|
-
fragment.appendChild(tr);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
this.tbody.appendChild(fragment);
|
|
231
|
-
this._emit('render');
|
|
232
|
-
|
|
233
|
-
// Re-initialize selection module for new rows if needed
|
|
234
|
-
if (this.modules.selection) this.modules.selection.update();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
_renderTemplate(template, data) {
|
|
238
|
-
return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
|
|
239
|
-
return data[key] !== undefined ? data[key] : '';
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
_showEmpty() {
|
|
244
|
-
this.tbody.innerHTML = `
|
|
245
|
-
<tr>
|
|
246
|
-
<td colspan="100%" class="text-center py-12">
|
|
247
|
-
<div class="flex flex-col items-center gap-2 text-base-content/50">
|
|
248
|
-
<span class="material-symbols-outlined text-5xl">${this.config.emptyIcon}</span>
|
|
249
|
-
<p class="text-base">${this.config.table_translations.empty}</p>
|
|
250
|
-
</div>
|
|
251
|
-
</td>
|
|
252
|
-
</tr>`;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
_showError(msg) {
|
|
256
|
-
this.tbody.innerHTML =
|
|
257
|
-
`
|
|
258
|
-
<tr>
|
|
259
|
-
<td colspan="100%" class="text-center py-12 text-error">
|
|
260
|
-
<div class="flex flex-col items-center gap-2">
|
|
261
|
-
<span class="material-symbols-outlined text-5xl">error</span>
|
|
262
|
-
<p class="text-base">${msg || this.config.table_translations.error}</p>
|
|
263
|
-
</div>
|
|
264
|
-
</td>
|
|
265
|
-
</tr>
|
|
266
|
-
`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
_toggleLoading(loading) {
|
|
270
|
-
if (loading) {
|
|
271
|
-
this.wrapper.classList.add('loading-state');
|
|
272
|
-
this._showSkeleton();
|
|
273
|
-
} else {
|
|
274
|
-
this.wrapper.classList.remove('loading-state');
|
|
275
|
-
// Skeleton will be cleared by render() normally,
|
|
276
|
-
// but if we just want to hide it before render?
|
|
277
|
-
// render() clears tbody, so we are good.
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
_showSkeleton() {
|
|
282
|
-
if (!this.tbody) return;
|
|
283
|
-
|
|
284
|
-
const perPage = this.params.per_page || 10;
|
|
285
|
-
const columns = this.table.querySelectorAll('thead th').length || 5;
|
|
286
|
-
let skeletonHtml = '';
|
|
287
|
-
|
|
288
|
-
for (let i = 0; i < perPage; i++) {
|
|
289
|
-
skeletonHtml += '<tr>';
|
|
290
|
-
|
|
291
|
-
// First column: Avatar + Text skeleton (matching user's "Skeleton - circle with content")
|
|
292
|
-
skeletonHtml += `
|
|
293
|
-
<td>
|
|
294
|
-
<div class="flex items-center gap-4">
|
|
295
|
-
<div class="skeleton h-12 w-12 shrink-0 rounded-full"></div>
|
|
296
|
-
<div class="flex flex-col gap-2">
|
|
297
|
-
<div class="skeleton h-3 w-20"></div>
|
|
298
|
-
<div class="skeleton h-3 w-28"></div>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
</td>
|
|
302
|
-
`;
|
|
303
|
-
|
|
304
|
-
// Other columns: Simple text lines
|
|
305
|
-
for (let j = 1; j < columns; j++) {
|
|
306
|
-
skeletonHtml += `
|
|
307
|
-
<td>
|
|
308
|
-
<div class="skeleton h-4 w-full opacity-50"></div>
|
|
309
|
-
</td>
|
|
310
|
-
`;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
skeletonHtml += '</tr>';
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
this.tbody.innerHTML = skeletonHtml;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ================= PUBLIC API =================
|
|
320
|
-
|
|
321
|
-
refresh() {
|
|
322
|
-
this.params.page = 1;
|
|
323
|
-
this.loadData();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
setParam(key, value) {
|
|
327
|
-
this.params[key] = value;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
getParam(key) {
|
|
331
|
-
return this.params[key];
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
registerModule(name, instance) {
|
|
335
|
-
this.modules[name] = instance;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ================= EVENTS =================
|
|
339
|
-
|
|
340
|
-
on(event, handler) {
|
|
341
|
-
this.wrapper.addEventListener(`dstable:${event}`, handler);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
_emit(event, detail = {}) {
|
|
345
|
-
this.wrapper.dispatchEvent(new CustomEvent(`dstable:${event}`, { bubbles: true, detail }));
|
|
346
|
-
}
|
|
347
|
-
_getNestedValue(obj, path) {
|
|
348
|
-
if (!path) return undefined;
|
|
349
|
-
return path.split('.').reduce((o, i) => (o ? o[i] : undefined), obj);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export default DSTable;
|
|
1
|
+
import DSTablePagination from './DSTable/DSTablePagination.js';
|
|
2
|
+
import DSTableSearch from './DSTable/DSTableSearch.js';
|
|
3
|
+
import DSTableSort from './DSTable/DSTableSort.js';
|
|
4
|
+
import DSTableFilter from './DSTable/DSTableFilter.js';
|
|
5
|
+
import DSTableExport from './DSTable/DSTableExport.js';
|
|
6
|
+
import DSTableSelection from './DSTable/DSTableSelection.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* DSTable
|
|
10
|
+
*
|
|
11
|
+
* A comprehensive table plugin for data management.
|
|
12
|
+
* Supports: Pagination, Search, Sort, Filter, Export, Selection.
|
|
13
|
+
*/
|
|
14
|
+
export class DSTable {
|
|
15
|
+
static defaults = {
|
|
16
|
+
pagination: true,
|
|
17
|
+
pagination_translations: {
|
|
18
|
+
prev: 'Previous',
|
|
19
|
+
next: 'Next',
|
|
20
|
+
goto: 'Go to',
|
|
21
|
+
stats: 'Showing {from} to {to} of {total} entries'
|
|
22
|
+
},
|
|
23
|
+
search: true,
|
|
24
|
+
sort: true,
|
|
25
|
+
filter: true,
|
|
26
|
+
export: true,
|
|
27
|
+
selection: true,
|
|
28
|
+
|
|
29
|
+
table_source: 'ajax', // ajax | html | json
|
|
30
|
+
ajax_url: null,
|
|
31
|
+
ajax_data: {},
|
|
32
|
+
ajax_method: 'GET',
|
|
33
|
+
ajax_function: 'axios', // xhr | axios | fetch
|
|
34
|
+
|
|
35
|
+
success: null,
|
|
36
|
+
error: null,
|
|
37
|
+
beforeSend: null,
|
|
38
|
+
afterSend: null,
|
|
39
|
+
|
|
40
|
+
template_source: 'html', // function | html | response
|
|
41
|
+
template_function: null,
|
|
42
|
+
template_html: null,
|
|
43
|
+
template_response: 'data.*.html_response',
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
emptyIcon: 'search_off',
|
|
47
|
+
|
|
48
|
+
// Selectors
|
|
49
|
+
tableSelector: 'table',
|
|
50
|
+
bodySelector: 'tbody',
|
|
51
|
+
messageSelector: '.ds-table-message',
|
|
52
|
+
table_translations: {
|
|
53
|
+
no_data: 'No data available',
|
|
54
|
+
loading: 'Loading...',
|
|
55
|
+
error: 'Error loading data',
|
|
56
|
+
empty: 'No data available',
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
constructor(wrapper, options = {}) {
|
|
62
|
+
this.wrapper = typeof wrapper === 'string' ? document.querySelector(wrapper) : wrapper;
|
|
63
|
+
if (!this.wrapper) throw new Error('DSTable: Wrapper element not found');
|
|
64
|
+
|
|
65
|
+
this.config = { ...DSTable.defaults, ...options };
|
|
66
|
+
|
|
67
|
+
// State
|
|
68
|
+
this.data = [];
|
|
69
|
+
this.meta = {}; // Pagination meta
|
|
70
|
+
this.params = {
|
|
71
|
+
page: 1,
|
|
72
|
+
per_page: 15,
|
|
73
|
+
sort_by: null,
|
|
74
|
+
sort_by: null,
|
|
75
|
+
sort_order: 'asc',
|
|
76
|
+
search: null,
|
|
77
|
+
filters: {}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this.modules = {};
|
|
81
|
+
this.isLoading = false;
|
|
82
|
+
|
|
83
|
+
this._init();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_init() {
|
|
87
|
+
this.table = this.wrapper.querySelector(this.config.tableSelector);
|
|
88
|
+
this.tbody = this.wrapper.querySelector(this.config.bodySelector) || this.table?.querySelector('tbody');
|
|
89
|
+
|
|
90
|
+
if (!this.table) throw new Error('DSTable: Table element not found inside wrapper');
|
|
91
|
+
|
|
92
|
+
// Initialize Modules
|
|
93
|
+
if (this.config.pagination) this.modules.pagination = new DSTablePagination(this);
|
|
94
|
+
if (this.config.search) this.modules.search = new DSTableSearch(this);
|
|
95
|
+
if (this.config.sort) this.modules.sort = new DSTableSort(this);
|
|
96
|
+
if (this.config.filter) this.modules.filter = new DSTableFilter(this);
|
|
97
|
+
if (this.config.export) this.modules.export = new DSTableExport(this);
|
|
98
|
+
if (this.config.selection) this.modules.selection = new DSTableSelection(this);
|
|
99
|
+
|
|
100
|
+
// Initial Load
|
|
101
|
+
this.loadData();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ================= DATA LOADING =================
|
|
105
|
+
|
|
106
|
+
async loadData() {
|
|
107
|
+
if (this.isLoading) return;
|
|
108
|
+
this.isLoading = true;
|
|
109
|
+
this._toggleLoading(true);
|
|
110
|
+
|
|
111
|
+
if (this.config.beforeSend) this.config.beforeSend({ params: this.params });
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
if (this.config.table_source === 'ajax') {
|
|
115
|
+
await this._loadFromAjax();
|
|
116
|
+
} else if (this.config.table_source === 'json') {
|
|
117
|
+
// Load from local JSON data provided in config
|
|
118
|
+
this._handleDataSuccess(this.config.data || []);
|
|
119
|
+
} else if (this.config.table_source === 'html') {
|
|
120
|
+
// Parse existing HTML? (Not fully implemented in this plan, mainly for Ajax)
|
|
121
|
+
this.isLoading = false;
|
|
122
|
+
this._toggleLoading(false);
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('DSTable: Error loading data', error);
|
|
126
|
+
if (this.config.error) this.config.error(error);
|
|
127
|
+
this._showError('Error loading data');
|
|
128
|
+
this.isLoading = false;
|
|
129
|
+
this._toggleLoading(false);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async _loadFromAjax() {
|
|
134
|
+
const url = this.config.ajax_url;
|
|
135
|
+
const method = this.config.ajax_method;
|
|
136
|
+
const data = { ...this.config.ajax_data, ...this.params };
|
|
137
|
+
|
|
138
|
+
let response;
|
|
139
|
+
|
|
140
|
+
if (this.config.ajax_function === 'axios' && window.axios) {
|
|
141
|
+
response = await window.axios({ method, url, params: method === 'GET' ? data : undefined, data: method !== 'GET' ? data : undefined });
|
|
142
|
+
this._handleDataSuccess(response.data);
|
|
143
|
+
} else if (this.config.ajax_function === 'fetch' || window.fetch) {
|
|
144
|
+
// Basic fetch impl
|
|
145
|
+
const queryString = new URLSearchParams(data).toString();
|
|
146
|
+
const fetchUrl = method === 'GET' ? `${url}?${queryString}` : url;
|
|
147
|
+
const options = {
|
|
148
|
+
method,
|
|
149
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
150
|
+
body: method !== 'GET' ? JSON.stringify(data) : undefined
|
|
151
|
+
};
|
|
152
|
+
const res = await fetch(fetchUrl, options);
|
|
153
|
+
const json = await res.json();
|
|
154
|
+
this._handleDataSuccess(json);
|
|
155
|
+
} else {
|
|
156
|
+
throw new Error('DSTable: No valid ajax function found');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_handleDataSuccess(response) {
|
|
161
|
+
// Standardize response
|
|
162
|
+
// Expected: { success: true, data: [...], meta: {...} }
|
|
163
|
+
|
|
164
|
+
if (response.data) {
|
|
165
|
+
this.data = response.data;
|
|
166
|
+
this.meta = response.meta || {};
|
|
167
|
+
} else if (Array.isArray(response)) {
|
|
168
|
+
this.data = response;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.render();
|
|
172
|
+
|
|
173
|
+
if (this.config.success) this.config.success(response);
|
|
174
|
+
if (this.config.afterSend) this.config.afterSend(response);
|
|
175
|
+
|
|
176
|
+
// Notify modules
|
|
177
|
+
Object.values(this.modules).forEach(m => m.onDataLoaded && m.onDataLoaded(response));
|
|
178
|
+
|
|
179
|
+
this.isLoading = false;
|
|
180
|
+
this._toggleLoading(false);
|
|
181
|
+
this._emit('dataLoaded', response);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ================= RENDERING =================
|
|
185
|
+
|
|
186
|
+
render() {
|
|
187
|
+
if (!this.tbody) return;
|
|
188
|
+
this.tbody.innerHTML = '';
|
|
189
|
+
|
|
190
|
+
if (this.data.length === 0) {
|
|
191
|
+
this._showEmpty();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const fragment = document.createDocumentFragment();
|
|
196
|
+
|
|
197
|
+
this.data.forEach((row, index) => {
|
|
198
|
+
const tr = document.createElement('tr');
|
|
199
|
+
let html = '';
|
|
200
|
+
|
|
201
|
+
if (this.config.template_source === 'function' && typeof this.config.template_function === 'function') {
|
|
202
|
+
html = this.config.template_function(row, index);
|
|
203
|
+
} else if (this.config.template_source === 'html' && this.config.template_html) {
|
|
204
|
+
html = this._renderTemplate(this.config.template_html, row);
|
|
205
|
+
} else if (this.config.template_source === 'response') {
|
|
206
|
+
// deeply get html_response from row using dot notation if needed
|
|
207
|
+
html = this._getNestedValue(row, this.config.template_response) || '';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// If the template returns a full TR, we might need to parse it.
|
|
211
|
+
// Expectation: The template returns innerHTML for the TR, or the configuration handles the TR wrapper.
|
|
212
|
+
// Adjusting based on standard table needs: assumes content is <td>...</td>
|
|
213
|
+
// BUT if template_response is used, the example shows "<tr><td>...</td></tr>"
|
|
214
|
+
|
|
215
|
+
if (html.trim().startsWith('<tr')) {
|
|
216
|
+
const tempTable = document.createElement('table');
|
|
217
|
+
const tempTbody = document.createElement('tbody');
|
|
218
|
+
tempTable.appendChild(tempTbody);
|
|
219
|
+
tempTbody.innerHTML = html;
|
|
220
|
+
const newRow = tempTbody.firstElementChild;
|
|
221
|
+
if (newRow) {
|
|
222
|
+
fragment.appendChild(newRow);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
tr.innerHTML = html;
|
|
226
|
+
fragment.appendChild(tr);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
this.tbody.appendChild(fragment);
|
|
231
|
+
this._emit('render');
|
|
232
|
+
|
|
233
|
+
// Re-initialize selection module for new rows if needed
|
|
234
|
+
if (this.modules.selection) this.modules.selection.update();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_renderTemplate(template, data) {
|
|
238
|
+
return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
|
|
239
|
+
return data[key] !== undefined ? data[key] : '';
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_showEmpty() {
|
|
244
|
+
this.tbody.innerHTML = `
|
|
245
|
+
<tr>
|
|
246
|
+
<td colspan="100%" class="text-center py-12">
|
|
247
|
+
<div class="flex flex-col items-center gap-2 text-base-content/50">
|
|
248
|
+
<span class="material-symbols-outlined text-5xl">${this.config.emptyIcon}</span>
|
|
249
|
+
<p class="text-base">${this.config.table_translations.empty}</p>
|
|
250
|
+
</div>
|
|
251
|
+
</td>
|
|
252
|
+
</tr>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_showError(msg) {
|
|
256
|
+
this.tbody.innerHTML =
|
|
257
|
+
`
|
|
258
|
+
<tr>
|
|
259
|
+
<td colspan="100%" class="text-center py-12 text-error">
|
|
260
|
+
<div class="flex flex-col items-center gap-2">
|
|
261
|
+
<span class="material-symbols-outlined text-5xl">error</span>
|
|
262
|
+
<p class="text-base">${msg || this.config.table_translations.error}</p>
|
|
263
|
+
</div>
|
|
264
|
+
</td>
|
|
265
|
+
</tr>
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_toggleLoading(loading) {
|
|
270
|
+
if (loading) {
|
|
271
|
+
this.wrapper.classList.add('loading-state');
|
|
272
|
+
this._showSkeleton();
|
|
273
|
+
} else {
|
|
274
|
+
this.wrapper.classList.remove('loading-state');
|
|
275
|
+
// Skeleton will be cleared by render() normally,
|
|
276
|
+
// but if we just want to hide it before render?
|
|
277
|
+
// render() clears tbody, so we are good.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_showSkeleton() {
|
|
282
|
+
if (!this.tbody) return;
|
|
283
|
+
|
|
284
|
+
const perPage = this.params.per_page || 10;
|
|
285
|
+
const columns = this.table.querySelectorAll('thead th').length || 5;
|
|
286
|
+
let skeletonHtml = '';
|
|
287
|
+
|
|
288
|
+
for (let i = 0; i < perPage; i++) {
|
|
289
|
+
skeletonHtml += '<tr>';
|
|
290
|
+
|
|
291
|
+
// First column: Avatar + Text skeleton (matching user's "Skeleton - circle with content")
|
|
292
|
+
skeletonHtml += `
|
|
293
|
+
<td>
|
|
294
|
+
<div class="flex items-center gap-4">
|
|
295
|
+
<div class="skeleton h-12 w-12 shrink-0 rounded-full"></div>
|
|
296
|
+
<div class="flex flex-col gap-2">
|
|
297
|
+
<div class="skeleton h-3 w-20"></div>
|
|
298
|
+
<div class="skeleton h-3 w-28"></div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</td>
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
// Other columns: Simple text lines
|
|
305
|
+
for (let j = 1; j < columns; j++) {
|
|
306
|
+
skeletonHtml += `
|
|
307
|
+
<td>
|
|
308
|
+
<div class="skeleton h-4 w-full opacity-50"></div>
|
|
309
|
+
</td>
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
skeletonHtml += '</tr>';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.tbody.innerHTML = skeletonHtml;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ================= PUBLIC API =================
|
|
320
|
+
|
|
321
|
+
refresh() {
|
|
322
|
+
this.params.page = 1;
|
|
323
|
+
this.loadData();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
setParam(key, value) {
|
|
327
|
+
this.params[key] = value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
getParam(key) {
|
|
331
|
+
return this.params[key];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
registerModule(name, instance) {
|
|
335
|
+
this.modules[name] = instance;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ================= EVENTS =================
|
|
339
|
+
|
|
340
|
+
on(event, handler) {
|
|
341
|
+
this.wrapper.addEventListener(`dstable:${event}`, handler);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_emit(event, detail = {}) {
|
|
345
|
+
this.wrapper.dispatchEvent(new CustomEvent(`dstable:${event}`, { bubbles: true, detail }));
|
|
346
|
+
}
|
|
347
|
+
_getNestedValue(obj, path) {
|
|
348
|
+
if (!path) return undefined;
|
|
349
|
+
return path.split('.').reduce((o, i) => (o ? o[i] : undefined), obj);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export default DSTable;
|