tablevu 0.3.0-beta.3 → 0.3.0-beta.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.
@@ -1,578 +0,0 @@
1
- import { h, reactive, watch, onMounted, ref } from 'vue';
2
-
3
- function adaptArray(props, data) {
4
- const d = Array.isArray(data) ? [...data] : [];
5
- return new Promise((resolve, reject) => {
6
- try {
7
- // Filter & Search //
8
- if (props.filter || props.search) {
9
- const searchableCols = props.cols
10
- ?.map((m, i) => ({ index: i, searchable: m.searchable !== false }))
11
- .filter(q => q.searchable)
12
- .map(m => m.index) || [];
13
- let filtered = d;
14
- if (props.filter) {
15
- filtered = filtered.filter(row => {
16
- return Object.entries(props.filter).every(([key, value]) => {
17
- return row[key] === value;
18
- });
19
- });
20
- }
21
- if (props.search && searchableCols.length > 0) {
22
- const searchLower = props.search.toLowerCase();
23
- filtered = filtered.filter(row => {
24
- return searchableCols.some(colIndex => {
25
- const col = props.cols?.[colIndex];
26
- if (!col)
27
- return false;
28
- const val = row[col.name];
29
- return val && String(val).toLowerCase().includes(searchLower);
30
- });
31
- });
32
- }
33
- // Update d with filtered results
34
- d.length = 0;
35
- d.push(...filtered);
36
- }
37
- // Sort //
38
- if (props.sort && props.sort.length > 0) {
39
- for (let i = props.sort.length - 1; i >= 0; i--) {
40
- const s = props.sort[i];
41
- let field;
42
- if (s.index !== undefined && s.index >= 0 && props.cols?.[s.index]) {
43
- field = props.cols[s.index].name;
44
- }
45
- else {
46
- field = s.name || '';
47
- }
48
- if (!field)
49
- continue;
50
- const dir = s.dir === 'desc' ? -1 : 1;
51
- d.sort((a, b) => {
52
- const av = a[field];
53
- const bv = b[field];
54
- if (av === bv)
55
- return 0;
56
- if (av === null || av === undefined)
57
- return 1;
58
- if (bv === null || bv === undefined)
59
- return -1;
60
- return (av > bv ? 1 : -1) * dir;
61
- });
62
- }
63
- }
64
- // Paging //
65
- let result = d;
66
- if (props.pageSize && props.pageSize > 0) {
67
- const start = (props.currPage - 1) * props.pageSize;
68
- const end = props.currPage * props.pageSize;
69
- result = d.slice(start, end);
70
- }
71
- resolve({ rows: result, count: d.length });
72
- }
73
- catch (err) {
74
- reject(err);
75
- }
76
- });
77
- }
78
-
79
- const TablevuStatus = {
80
- Error: -1,
81
- Ready: 0,
82
- Loading: 1
83
- };
84
-
85
- const tablevu = {
86
- props: {
87
- settings: { type: Object, default: null },
88
- data: { type: [Array, Function] },
89
- group: { type: [String, Object] },
90
- search: { type: String },
91
- filter: { type: [Array, Object] },
92
- rowid: { type: String },
93
- onRowClick: { type: Function },
94
- onRowDblClick: { type: Function },
95
- class: { type: String, default: 'table' },
96
- style: { type: String, default: null }
97
- },
98
- emits: ['onRowClick', 'onRowDblClick', 'selectionChange', 'columnVisibilityChange'],
99
- setup(props, { attrs, slots, emit, expose }) {
100
- let rows = [];
101
- let rowg = [];
102
- let grps = [];
103
- let adapter = null;
104
- let timer = null;
105
- const spec = reactive({
106
- caption: null,
107
- group: {
108
- value: (props.settings && props.settings.group) ? props.settings.group.value : props.group ? props.group : undefined,
109
- content: (props.settings && props.settings.group) ? props.settings.group.content : undefined,
110
- expandAll: true,
111
- class: 'table-light'
112
- },
113
- page: {
114
- count: 0,
115
- size: (props.settings && props.settings.page) ? (props.settings.page.size || 0) : 0,
116
- current: 0,
117
- type: undefined
118
- },
119
- sort: (props.settings && props.settings.sort) ? props.settings.sort : [],
120
- columns: (props.settings && props.settings.columns) ? props.settings.columns : [],
121
- footer: {
122
- button: {
123
- class: (props.settings && props.settings.footer && props.settings.footer.button && props.settings.footer.button.class) || 'btn'
124
- }
125
- },
126
- status: TablevuStatus.Ready,
127
- time: null,
128
- search: null,
129
- filter: null,
130
- onData: (props.settings && props.settings.onData) ? props.settings.onData : undefined,
131
- selectedRows: [],
132
- columnWidths: {},
133
- columnOrder: [],
134
- columnVisibility: {}
135
- });
136
- if (props.data) {
137
- if (Array.isArray(props.data)) {
138
- adapter = adaptArray;
139
- }
140
- else {
141
- adapter = props.data;
142
- }
143
- }
144
- if (props.search)
145
- spec.search = props.search;
146
- watch(() => props.search, (n, o) => {
147
- const x = parseInt(String(props.settings?.searchMinChar)) || 3;
148
- if (timer) {
149
- clearTimeout(timer);
150
- timer = null;
151
- }
152
- if (n && n.length > x) {
153
- spec.search = n;
154
- timer = setTimeout(() => { refresh(); timer = null; }, 444);
155
- }
156
- else if (o && o.length > x && (n == undefined || n == null || n.length <= x)) {
157
- spec.search = null;
158
- timer = setTimeout(() => { refresh(); timer = null; }, 444);
159
- }
160
- });
161
- if (props.filter)
162
- spec.filter = props.filter;
163
- watch(() => props.filter, () => {
164
- if (props.filter)
165
- spec.filter = props.filter;
166
- else
167
- spec.filter = null;
168
- refresh();
169
- }, { deep: true });
170
- function prepareGroup(g, r) {
171
- grps = [];
172
- rowg = [];
173
- for (let i = 0; i < r.length; i++) {
174
- let v = null;
175
- let c = null;
176
- const o = r[i];
177
- if (g.value) {
178
- if (typeof g.value == 'function')
179
- v = g.value(o);
180
- else
181
- v = o[g.value];
182
- }
183
- else if (typeof g == 'function') {
184
- v = g(o);
185
- }
186
- else {
187
- if (typeof g == 'bigint' || typeof g == 'number') {
188
- const l = parseFloat(String(g));
189
- if (l)
190
- v = o[l];
191
- }
192
- else if (typeof g == 'string')
193
- v = o[g];
194
- }
195
- if (g.content) {
196
- if (typeof g.content == 'function')
197
- c = g.content(o);
198
- else
199
- c = o[g.content];
200
- }
201
- let n = grps.findIndex(f => f.group == v);
202
- if (n == -1) {
203
- n = grps.push({ group: v, content: c, expand: spec.group.expandAll }) - 1;
204
- }
205
- rowg.push(n);
206
- }
207
- }
208
- function first() { loadPage(1); }
209
- function prior() {
210
- if (spec.page.current > 1) {
211
- loadPage(spec.page.current - 1);
212
- }
213
- else {
214
- loadPage(spec.page.current);
215
- }
216
- }
217
- function next() { loadPage(spec.page.current + 1); }
218
- function last() { loadPage(spec.page.count); }
219
- // Navigate to specific page number
220
- function page(currentPage) {
221
- if (currentPage < 1)
222
- currentPage = 1;
223
- if (currentPage > spec.page.count && spec.page.count > 0)
224
- currentPage = spec.page.count;
225
- loadPage(currentPage);
226
- }
227
- function refresh() { loadPage(spec.page.current); }
228
- function orderby(ind, dir) {
229
- if (spec.sort && spec.sort.length > 0) {
230
- let l = spec.sort.findIndex(f => f.index == ind);
231
- if (l < 0) {
232
- let n = spec.columns[ind]?.name;
233
- l = spec.sort.findIndex(f => f.name == n);
234
- }
235
- if (l == 0) {
236
- spec.sort[l].dir = dir ? dir : (spec.sort[0].dir ? null : 'desc');
237
- }
238
- else if (l > 0) {
239
- spec.sort.splice(l, 1);
240
- }
241
- if (l != 0) {
242
- spec.sort.unshift({ index: ind, dir: dir ? dir : null });
243
- }
244
- if (spec.sort.length > 2) {
245
- spec.sort.pop();
246
- }
247
- }
248
- else {
249
- spec.sort.push({ index: ind, dir: dir ? dir : null });
250
- }
251
- loadPage(spec.page.current);
252
- }
253
- // Internal load function (alias for adapter call)
254
- function loadPage(currentPage) {
255
- let dat = null;
256
- if (props.data) {
257
- if (Array.isArray(props.data))
258
- dat = props.data;
259
- }
260
- else
261
- return;
262
- spec.status = TablevuStatus.Loading;
263
- if (adapter) {
264
- const pageSize = spec.page.size || 20;
265
- adapter({
266
- pageSize,
267
- currPage: currentPage,
268
- sort: spec.sort,
269
- cols: spec.columns,
270
- filter: spec.filter,
271
- search: spec.search
272
- }, dat).then(ada => {
273
- if (spec.onData)
274
- rows = spec.onData(ada.rows);
275
- else
276
- rows = ada.rows;
277
- const groupValue = spec.group.value;
278
- if (groupValue && (typeof groupValue == 'string' || typeof groupValue == 'number' || typeof groupValue == 'bigint' || (typeof groupValue == 'object' && groupValue.value)))
279
- prepareGroup(spec.group, rows);
280
- spec.page.count = (ada.count > 0 && pageSize > 0) ? Math.ceil(ada.count / pageSize) : 0;
281
- spec.page.current = currentPage;
282
- spec.status = TablevuStatus.Ready;
283
- spec.time = Date.now();
284
- }).catch(err => {
285
- console.error(err);
286
- spec.status = TablevuStatus.Error;
287
- });
288
- }
289
- }
290
- function renderValue(col, val) {
291
- if (val) {
292
- let v;
293
- if (col.type == 'datetime' || col.type == 'date' || col.type == 'time') {
294
- const t = new Date(val);
295
- if (col.type == 'date')
296
- v = t.toLocaleDateString();
297
- else if (col.type == 'time')
298
- v = t.toLocaleTimeString();
299
- else
300
- v = t.toLocaleString();
301
- }
302
- else {
303
- if (col.type == 'numeric') {
304
- v = parseFloat(val);
305
- }
306
- else {
307
- if (col.type == 'integer') {
308
- v = parseInt(val);
309
- }
310
- else
311
- v = val;
312
- }
313
- }
314
- return v;
315
- }
316
- else
317
- return '';
318
- }
319
- function renderCell(col, val) {
320
- const o = { style: {} };
321
- if (col.colspan && col.colspan > 1)
322
- o.colspan = col.colspan;
323
- if (col.style)
324
- o.style = { ...o.style, ...col.style };
325
- if (col.align)
326
- o.style['text-align'] = col.align;
327
- if (col.width)
328
- o.style['max-width'] = col.width;
329
- const v = renderValue(col, val);
330
- return h('td', o, v);
331
- }
332
- function renderCustomCell(col, row) {
333
- const o = { style: {} };
334
- if (col.colspan && col.colspan > 1)
335
- o.colspan = col.colspan;
336
- if (col.style)
337
- o.style = { ...o.style, ...col.style };
338
- if (col.align)
339
- o.style['text-align'] = col.align;
340
- if (col.width)
341
- o.style['max-width'] = col.width;
342
- const f = col.render?.(row) ?? '';
343
- return h('td', { ...o, innerHTML: f });
344
- }
345
- function renderGroup(grp) {
346
- const b = h('button', {
347
- title: String(grp.expand),
348
- onClick: (event) => {
349
- grp.expand = !grp.expand;
350
- event.currentTarget.value = grp.expand ? '▼' : '►';
351
- spec.time = Date.now();
352
- event.preventDefault();
353
- },
354
- style: { padding: 0, margin: '0 6px 0 0', width: '12px', height: '100%', border: 'none', outline: '0', backgroundColor: 'transparent' }
355
- }, grp.expand ? '▼' : '►');
356
- const v = grp.content || grp.group;
357
- return h('tr', {}, h('td', { colspan: spec.columns.length, class: spec.group.class }, [b, v]));
358
- }
359
- function renderRow(index, row) {
360
- const o = { key: index, index: index };
361
- if (props.rowid)
362
- o.rowid = row[props.rowid];
363
- const r = [];
364
- for (let i = 0; i < spec.columns.length; i++) {
365
- const c = spec.columns[i];
366
- if (c.render) {
367
- r.push(renderCustomCell(c, row));
368
- }
369
- else {
370
- let v;
371
- if (c.depth) {
372
- v = row;
373
- for (let j = 0; j < c.depth.length; j++) {
374
- if (v)
375
- v = v[c.depth[j]];
376
- }
377
- }
378
- else {
379
- v = row[c.name];
380
- }
381
- r.push(renderCell(c, v));
382
- }
383
- }
384
- o.ondblclick = (event) => {
385
- const target = event.currentTarget;
386
- const idx = target.getAttribute('index');
387
- if (idx !== null) {
388
- const rowData = rows[parseInt(idx)];
389
- emit('onRowDblClick', event, rowData);
390
- }
391
- };
392
- if (props.onRowClick) {
393
- o.onClick = (event) => {
394
- const target = event.currentTarget;
395
- const idx = target.getAttribute('index');
396
- if (idx !== null) {
397
- const rowData = rows[parseInt(idx)];
398
- props.onRowClick(event, rowData);
399
- }
400
- };
401
- }
402
- // Selection
403
- if (spec.selectedRows.some(s => s[props.rowid || 'id'] === row[props.rowid || 'id'])) {
404
- o.class = (o.class || '') + ' selected';
405
- }
406
- return h('tr', o, r);
407
- }
408
- function renderHeader() {
409
- const ar = [];
410
- for (let i = 0; i < spec.columns.length; i++) {
411
- const cl = [];
412
- if (spec.columns[i].label)
413
- cl.push(h('span', spec.columns[i].label));
414
- else
415
- cl.push(h('span', spec.columns[i].name));
416
- if (spec.sort && spec.sort.length > 0 && spec.sort[0].index == i) {
417
- if (spec.sort[0].dir == 'desc')
418
- cl.push(h('span', { style: { float: 'right' } }, '▼'));
419
- else
420
- cl.push(h('span', { style: { float: 'right' } }, '▲'));
421
- }
422
- const ch = { position: 'relative' };
423
- if (spec.columns[i].width) {
424
- ch.width = spec.columns[i].width;
425
- ch.minWidth = spec.columns[i].width;
426
- ch.maxWidth = spec.columns[i].width;
427
- }
428
- const cy = { display: 'block', 'font-weight': 500, textDecoration: 'none', textAlign: 'center' };
429
- let a;
430
- let f;
431
- if (spec.columns[i].popup) {
432
- const r = ref(null);
433
- const c = [
434
- h('a', { href: '#', onClick: (event) => { orderby(i); event.preventDefault(); }, style: { display: 'block', textDecoration: 'none', color: 'black', fontWeight: 'initial', padding: '.25rem' } }, 'Order by Ascending'),
435
- h('a', { href: '#', onClick: (event) => { orderby(i, 'desc'); event.preventDefault(); }, style: { display: 'block', textDecoration: 'none', color: 'black', fontWeight: 'initial', padding: '.25rem' } }, 'Order by Descending'),
436
- ];
437
- a = h('a', {
438
- href: '#',
439
- onClick: (event) => {
440
- if (r.value && r.value.style && r.value.style.display && r.value.style.display == 'block')
441
- r.value.style.display = 'none';
442
- else if (r.value && r.value.style)
443
- r.value.style.display = 'block';
444
- event.preventDefault();
445
- },
446
- style: cy
447
- }, cl);
448
- f = h('div', {
449
- ref: r,
450
- style: { position: 'absolute', display: 'none', width: spec.columns[i].width || '12rem', height: 'fit-content', border: '1px solid lightgray', backgroundColor: 'whitesmoke', zIndex: 1, left: 0, top: '2rem', bottom: 0, right: 0, padding: '.25rem' }
451
- }, c);
452
- }
453
- else
454
- a = h('a', { href: '#', onClick: (event) => { orderby(i); event.preventDefault(); }, style: cy }, cl);
455
- if (spec.columns[i].filter) ;
456
- ar.push(h('th', {
457
- colspan: null,
458
- style: ch
459
- }, [a, f]));
460
- }
461
- const tr = h('tr', {}, ar);
462
- return [tr];
463
- }
464
- function renderBody() {
465
- const dr = [];
466
- if (rows && rows.length > 0) {
467
- if (grps && grps.length > 0) {
468
- const r = [];
469
- for (let g = 0; g < grps.length; g++)
470
- r.push({ index: g, childs: [] });
471
- for (let i = 0; i < rows.length; i++) {
472
- const x = rowg[i];
473
- r[x].childs.push(renderRow(i, rows[i]));
474
- }
475
- for (let g = 0; g < grps.length; g++) {
476
- dr.push(renderGroup(grps[g]));
477
- if (grps[g].expand)
478
- dr.push(r[g].childs);
479
- }
480
- }
481
- else {
482
- for (let i = 0; i < rows.length; i++) {
483
- dr.push(renderRow(i, rows[i]));
484
- }
485
- }
486
- }
487
- else if (spec.settings?.emptyMessage) {
488
- dr.push(h('tr', {}, h('td', { colspan: spec.columns.length, style: { textAlign: 'center', padding: '2rem', color: '#666' } }, spec.settings.emptyMessage)));
489
- }
490
- return dr;
491
- }
492
- // Prompt user for page number and navigate
493
- function goToPage() {
494
- const input = prompt('Page number:', String(spec.page.current));
495
- if (input) {
496
- const pageNum = parseInt(input, 10);
497
- if (!isNaN(pageNum) && pageNum > 0 && pageNum !== spec.page.current) {
498
- page(pageNum);
499
- }
500
- }
501
- }
502
- function makeHandler(fn) {
503
- return (e) => {
504
- e.preventDefault();
505
- e.stopPropagation();
506
- fn();
507
- };
508
- }
509
- function renderFooter() {
510
- const fr = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(first), disabled: spec.page.current == 1 }, '◀◀');
511
- const ls = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(last), disabled: spec.page.count == 0 || spec.page.current == spec.page.count }, '▶▶');
512
- const pg = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(goToPage) }, spec.page.current + (spec.page.count > 0 ? (' / ' + spec.page.count) : ''));
513
- const pr = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(prior), disabled: spec.page.current == 1 }, '◀');
514
- const nx = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: makeHandler(next), disabled: spec.page.count > 0 && spec.page.current == spec.page.count }, '▶');
515
- const td = h('td', { colspan: spec.columns.length, align: 'center' }, [fr, pr, pg, nx, ls]);
516
- const tr = h('tr', {}, [td]);
517
- return [tr];
518
- }
519
- function renderCaption() {
520
- return null;
521
- }
522
- // Initialize column metadata
523
- if (spec.columns.length == 0) {
524
- console.error('Columns not defined !');
525
- }
526
- for (let i = 0; i < spec.columns.length; i++) {
527
- if (spec.columns[i].name && spec.columns[i].name.indexOf('.') > -1) {
528
- spec.columns[i].depth = spec.columns[i].name.split('.');
529
- }
530
- // Initialize visibility
531
- if (spec.columns[i].visible === undefined) {
532
- spec.columns[i].visible = true;
533
- spec.columnVisibility[spec.columns[i].name] = true;
534
- }
535
- }
536
- loadPage(1);
537
- // Expose methods for external access via ref
538
- expose({
539
- first,
540
- prior,
541
- next,
542
- last,
543
- page,
544
- refresh,
545
- orderby,
546
- getSelectedRows: () => spec.selectedRows,
547
- setSelectedRows: (rows) => { spec.selectedRows = rows; }
548
- });
549
- onMounted(() => {
550
- // mounted hook
551
- });
552
- return {
553
- spec, renderHeader, renderBody, renderFooter, renderCaption,
554
- first, prior, next, last, page, refresh, orderby,
555
- getSelectedRows: () => spec.selectedRows,
556
- setSelectedRows: (rows) => { spec.selectedRows = rows; }
557
- };
558
- },
559
- render(props) {
560
- let prps = { class: 'tablevu' };
561
- if (props.class)
562
- prps.class = 'tablevu ' + props.class;
563
- if (props.style)
564
- prps.style = props.style;
565
- const cntn = [];
566
- cntn.push(h('thead', {}, this.renderHeader()));
567
- if (this.spec.status == TablevuStatus.Ready)
568
- cntn.push(h('tbody', { 'data-time': this.spec.time }, this.renderBody()));
569
- if (this.spec.caption)
570
- cntn.push(h('caption', {}, this.renderCaption()));
571
- if (this.spec.status == TablevuStatus.Ready)
572
- if (this.spec.page.size > 0)
573
- cntn.push(h('tfoot', {}, this.renderFooter()));
574
- return h('table', prps, cntn);
575
- }
576
- };
577
-
578
- export { tablevu as default };