tablevu 0.2.22 → 0.3.0-beta.2

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.
@@ -0,0 +1,551 @@
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
+ const tablevu = {
85
+ props: {
86
+ settings: { type: Object, default: null },
87
+ data: { type: [Array, Function] },
88
+ group: { type: [String, Object] },
89
+ search: { type: String },
90
+ filter: { type: [Array, Object] },
91
+ command: { type: Array, default: () => [] },
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'],
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 : null,
109
+ content: (props.settings && props.settings.group) ? props.settings.group.content : null,
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,
116
+ current: 0,
117
+ type: null
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
+ });
132
+ if (props.data) {
133
+ if (Array.isArray(props.data)) {
134
+ adapter = adaptArray;
135
+ }
136
+ else {
137
+ adapter = props.data;
138
+ }
139
+ }
140
+ if (props.search)
141
+ spec.search = props.search;
142
+ watch(() => props.search, (n, o) => {
143
+ const x = parseInt(String(props.settings?.searchMinChar)) || 3;
144
+ if (timer) {
145
+ clearTimeout(timer);
146
+ timer = null;
147
+ }
148
+ if (n && n.length > x) {
149
+ spec.search = n;
150
+ timer = setTimeout(() => { refresh(); timer = null; }, 444);
151
+ }
152
+ else if (o && o.length > x && (n == undefined || n == null || n.length <= x)) {
153
+ spec.search = null;
154
+ timer = setTimeout(() => { refresh(); timer = null; }, 444);
155
+ }
156
+ });
157
+ if (props.filter)
158
+ spec.filter = props.filter;
159
+ watch(() => props.filter, () => {
160
+ if (props.filter)
161
+ spec.filter = props.filter;
162
+ else
163
+ spec.filter = null;
164
+ refresh();
165
+ }, { deep: true });
166
+ watch(() => props.command, (n, o) => {
167
+ if (n && n.length > 0) {
168
+ const c = props.command[n.length - 1];
169
+ if (c && c.name) {
170
+ switch (c.name.toLowerCase()) {
171
+ case 'refresh':
172
+ refresh();
173
+ break;
174
+ case 'first':
175
+ first();
176
+ break;
177
+ case 'prior':
178
+ prior();
179
+ break;
180
+ case 'next':
181
+ next();
182
+ break;
183
+ case 'last':
184
+ last();
185
+ break;
186
+ default: console.log('Unknown command');
187
+ }
188
+ }
189
+ props.command.length = 0;
190
+ }
191
+ }, { deep: true });
192
+ function prepareGroup(g, r) {
193
+ grps = [];
194
+ rowg = [];
195
+ for (let i = 0; i < r.length; i++) {
196
+ let v = null;
197
+ let c = null;
198
+ const o = r[i];
199
+ if (g.value) {
200
+ if (typeof g.value == 'function')
201
+ v = g.value(o);
202
+ else
203
+ v = o[g.value];
204
+ }
205
+ else if (typeof g == 'function') {
206
+ v = g(o);
207
+ }
208
+ else {
209
+ if (typeof g == 'bigint' || typeof g == 'number') {
210
+ const l = parseFloat(String(g));
211
+ if (l)
212
+ v = o[l];
213
+ }
214
+ else if (typeof g == 'string')
215
+ v = o[g];
216
+ }
217
+ if (g.content) {
218
+ if (typeof g.content == 'function')
219
+ c = g.content(o);
220
+ else
221
+ c = o[g.content];
222
+ }
223
+ let n = grps.findIndex(f => f.group == v);
224
+ if (n == -1) {
225
+ n = grps.push({ group: v, content: c, expand: spec.group.expandAll }) - 1;
226
+ }
227
+ rowg.push(n);
228
+ }
229
+ }
230
+ function first() { page(1); }
231
+ function prior() {
232
+ if (spec.page.current > 1) {
233
+ page(spec.page.current - 1);
234
+ }
235
+ else {
236
+ page(spec.page.current);
237
+ }
238
+ }
239
+ function next() { page(spec.page.current + 1); }
240
+ function last() { page(spec.page.count); }
241
+ function go() {
242
+ let p;
243
+ let g = spec.page.current;
244
+ do {
245
+ p = prompt(undefined, String(spec.page.current));
246
+ g = parseInt(p || '0');
247
+ } while (p && isNaN(g));
248
+ if (g && g > 0 && g != spec.page.current)
249
+ page(g);
250
+ }
251
+ function refresh() { page(spec.page.current); }
252
+ function orderby(ind, dir) {
253
+ if (spec.sort && spec.sort.length > 0) {
254
+ let l = spec.sort.findIndex(f => f.index == ind);
255
+ if (l < 0) {
256
+ let n = spec.columns[ind]?.name;
257
+ l = spec.sort.findIndex(f => f.name == n);
258
+ }
259
+ if (l == 0) {
260
+ spec.sort[l].dir = dir ? dir : (spec.sort[0].dir ? null : 'desc');
261
+ }
262
+ else if (l > 0) {
263
+ spec.sort.splice(l, 1);
264
+ }
265
+ if (l != 0) {
266
+ spec.sort.unshift({ index: ind, dir: dir ? dir : null });
267
+ }
268
+ if (spec.sort.length > 2) {
269
+ spec.sort.pop();
270
+ }
271
+ }
272
+ else {
273
+ spec.sort.push({ index: ind, dir: dir ? dir : null });
274
+ }
275
+ page(spec.page.current);
276
+ }
277
+ function page(currentPage) {
278
+ let dat = null;
279
+ if (props.data) {
280
+ if (Array.isArray(props.data))
281
+ dat = props.data;
282
+ }
283
+ else
284
+ return;
285
+ spec.status = TablevuStatus.Loading;
286
+ if (adapter) {
287
+ adapter({
288
+ pageSize: spec.page.size,
289
+ currPage: currentPage,
290
+ sort: spec.sort,
291
+ cols: spec.columns,
292
+ filter: spec.filter,
293
+ search: spec.search
294
+ }, dat).then(ada => {
295
+ if (spec.onData)
296
+ rows = spec.onData(ada.rows);
297
+ else
298
+ rows = ada.rows;
299
+ const groupValue = spec.group.value;
300
+ if (groupValue && (typeof groupValue == 'string' || typeof groupValue == 'number' || typeof groupValue == 'bigint' || (typeof groupValue == 'object' && groupValue.value)))
301
+ prepareGroup(spec.group, rows);
302
+ spec.page.count = (ada.count > 0 && spec.page.size > 0) ? Math.ceil(ada.count / spec.page.size) : 0;
303
+ spec.page.current = currentPage;
304
+ spec.status = TablevuStatus.Ready;
305
+ spec.time = Date.now();
306
+ }).catch(err => {
307
+ console.error(err);
308
+ spec.status = TablevuStatus.Error;
309
+ });
310
+ }
311
+ }
312
+ function renderValue(col, val) {
313
+ if (val) {
314
+ let v;
315
+ if (col.type == 'datetime' || col.type == 'date' || col.type == 'time') {
316
+ const t = new Date(val);
317
+ if (col.type == 'date')
318
+ v = t.toLocaleDateString();
319
+ else if (col.type == 'time')
320
+ v = t.toLocaleTimeString();
321
+ else
322
+ v = t.toLocaleString();
323
+ }
324
+ else {
325
+ if (col.type == 'numeric') {
326
+ v = parseFloat(val);
327
+ }
328
+ else {
329
+ if (col.type == 'integer') {
330
+ v = parseInt(val);
331
+ }
332
+ else
333
+ v = val;
334
+ }
335
+ }
336
+ return v;
337
+ }
338
+ else
339
+ return '';
340
+ }
341
+ function renderCell(col, val) {
342
+ const o = { style: {} };
343
+ if (col.colspan && col.colspan > 1)
344
+ o.colspan = col.colspan;
345
+ if (col.style)
346
+ o.style = { ...o.style, ...col.style };
347
+ if (col.align)
348
+ o.style['text-align'] = col.align;
349
+ if (col.width)
350
+ o.style['max-width'] = col.width;
351
+ const v = renderValue(col, val);
352
+ return h('td', o, v);
353
+ }
354
+ function renderCustomCell(col, row) {
355
+ const o = { style: {} };
356
+ if (col.colspan && col.colspan > 1)
357
+ o.colspan = col.colspan;
358
+ if (col.style)
359
+ o.style = { ...o.style, ...col.style };
360
+ if (col.align)
361
+ o.style['text-align'] = col.align;
362
+ if (col.width)
363
+ o.style['max-width'] = col.width;
364
+ const f = col.render(row);
365
+ return h('td', o, f);
366
+ }
367
+ function renderGroup(grp) {
368
+ const b = h('button', {
369
+ title: String(grp.expand),
370
+ onClick: (event) => {
371
+ grp.expand = !grp.expand;
372
+ event.currentTarget.value = grp.expand ? '▼' : '►';
373
+ spec.time = Date.now();
374
+ event.preventDefault();
375
+ },
376
+ style: { padding: 0, margin: '0 6px 0 0', width: '12px', height: '100%', border: 'none', outline: '0', backgroundColor: 'transparent' }
377
+ }, grp.expand ? '▼' : '►');
378
+ const v = grp.content || grp.group;
379
+ return h('tr', {}, h('td', { colspan: spec.columns.length, class: spec.group.class }, [b, v]));
380
+ }
381
+ function renderRow(index, row) {
382
+ const o = { key: index, index: index };
383
+ if (props.rowid)
384
+ o.rowid = row[props.rowid];
385
+ const r = [];
386
+ for (let i = 0; i < spec.columns.length; i++) {
387
+ const c = spec.columns[i];
388
+ if (c.render) {
389
+ r.push(renderCustomCell(c, row));
390
+ }
391
+ else {
392
+ let v;
393
+ if (c.depth) {
394
+ v = row;
395
+ for (let j = 0; j < c.depth.length; j++) {
396
+ if (v)
397
+ v = v[c.depth[j]];
398
+ }
399
+ }
400
+ else {
401
+ v = row[c.name];
402
+ }
403
+ r.push(renderCell(c, v));
404
+ }
405
+ }
406
+ o.ondblclick = (event) => {
407
+ const target = event.currentTarget;
408
+ const idx = target.getAttribute('index');
409
+ if (idx !== null) {
410
+ const rowData = rows[parseInt(idx)];
411
+ emit('onRowDblClick', event, rowData);
412
+ }
413
+ };
414
+ if (props.onRowClick) {
415
+ o.onClick = (event) => {
416
+ const target = event.currentTarget;
417
+ const idx = target.getAttribute('index');
418
+ if (idx !== null) {
419
+ const rowData = rows[parseInt(idx)];
420
+ props.onRowClick(event, rowData);
421
+ }
422
+ };
423
+ }
424
+ return h('tr', o, r);
425
+ }
426
+ function renderHeader() {
427
+ const ar = [];
428
+ for (let i = 0; i < spec.columns.length; i++) {
429
+ const cl = [];
430
+ if (spec.columns[i].label)
431
+ cl.push(h('span', spec.columns[i].label));
432
+ else
433
+ cl.push(h('span', spec.columns[i].name));
434
+ if (spec.sort && spec.sort.length > 0 && spec.sort[0].index == i) {
435
+ if (spec.sort[0].dir == 'desc')
436
+ cl.push(h('span', { style: { float: 'right' } }, '▼'));
437
+ else
438
+ cl.push(h('span', { style: { float: 'right' } }, '▲'));
439
+ }
440
+ const ch = { position: 'relative' };
441
+ if (spec.columns[i].width) {
442
+ ch.width = spec.columns[i].width;
443
+ ch.minWidth = spec.columns[i].width;
444
+ ch.maxWidth = spec.columns[i].width;
445
+ }
446
+ const cy = { display: 'block', 'font-weight': 500, textDecoration: 'none', textAlign: 'center' };
447
+ let a;
448
+ let f;
449
+ if (spec.columns[i].popup) {
450
+ const r = ref(null);
451
+ const c = [
452
+ h('a', { href: '#', onClick: (event) => { orderby(i); event.preventDefault(); }, style: { display: 'block', textDecoration: 'none', color: 'black', fontWeight: 'initial', padding: '.25rem' } }, 'Order by Ascending'),
453
+ h('a', { href: '#', onClick: (event) => { orderby(i, 'desc'); event.preventDefault(); }, style: { display: 'block', textDecoration: 'none', color: 'black', fontWeight: 'initial', padding: '.25rem' } }, 'Order by Descending'),
454
+ ];
455
+ a = h('a', {
456
+ href: '#',
457
+ onClick: (event) => {
458
+ if (r.value && r.value.style && r.value.style.display && r.value.style.display == 'block')
459
+ r.value.style.display = 'none';
460
+ else if (r.value && r.value.style)
461
+ r.value.style.display = 'block';
462
+ event.preventDefault();
463
+ },
464
+ style: cy
465
+ }, cl);
466
+ f = h('div', {
467
+ ref: r,
468
+ 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' }
469
+ }, c);
470
+ }
471
+ else
472
+ a = h('a', { href: '#', onClick: (event) => { orderby(i); event.preventDefault(); }, style: cy }, cl);
473
+ if (spec.columns[i].filter) ;
474
+ ar.push(h('th', { colspan: null, style: ch }, [a, f]));
475
+ }
476
+ const tr = h('tr', {}, ar);
477
+ return [tr];
478
+ }
479
+ function renderBody() {
480
+ const dr = [];
481
+ if (rows && rows.length > 0) {
482
+ if (grps && grps.length > 0) {
483
+ const r = [];
484
+ for (let g = 0; g < grps.length; g++)
485
+ r.push({ index: g, childs: [] });
486
+ for (let i = 0; i < rows.length; i++) {
487
+ const x = rowg[i];
488
+ r[x].childs.push(renderRow(i, rows[i]));
489
+ }
490
+ for (let g = 0; g < grps.length; g++) {
491
+ dr.push(renderGroup(grps[g]));
492
+ if (grps[g].expand)
493
+ dr.push(r[g].childs);
494
+ }
495
+ }
496
+ else {
497
+ for (let i = 0; i < rows.length; i++) {
498
+ dr.push(renderRow(i, rows[i]));
499
+ }
500
+ }
501
+ }
502
+ return dr;
503
+ }
504
+ function renderFooter() {
505
+ const fr = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: first, disabled: spec.page.current == 1 }, '◀◀');
506
+ const ls = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: last, disabled: spec.page.count == 0 || spec.page.current == spec.page.count }, '▶▶');
507
+ const pg = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: go }, spec.page.current + (spec.page.count > 0 ? (' / ' + spec.page.count) : ''));
508
+ const pr = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: prior, disabled: spec.page.current == 1 }, '◀');
509
+ const nx = h('button', { class: spec.footer.button.class, style: 'font: initial; margin-right: .5rem', onClick: next, disabled: spec.page.count > 0 && spec.page.current == spec.page.count }, '▶');
510
+ const td = h('td', { colspan: spec.columns.length, align: 'center' }, [fr, pr, pg, nx, ls]);
511
+ const tr = h('tr', {}, [td]);
512
+ return [tr];
513
+ }
514
+ function renderCaption() {
515
+ return null;
516
+ }
517
+ if (spec.columns.length == 0) {
518
+ console.error('Columns not defined !');
519
+ }
520
+ for (let i = 0; i < spec.columns.length; i++)
521
+ if (spec.columns[i].name && spec.columns[i].name.indexOf('.') > -1) {
522
+ spec.columns[i].depth = spec.columns[i].name.split('.');
523
+ }
524
+ page(1);
525
+ onMounted(() => {
526
+ // mounted hook
527
+ });
528
+ return {
529
+ spec, renderHeader, renderBody, renderFooter, renderCaption
530
+ };
531
+ },
532
+ render(props) {
533
+ let prps = { class: 'tablevu' };
534
+ if (props.class)
535
+ prps.class = 'tablevu ' + props.class;
536
+ if (props.style)
537
+ prps.style = props.style;
538
+ const cntn = [];
539
+ cntn.push(h('thead', {}, this.renderHeader()));
540
+ if (this.spec.status == TablevuStatus.Ready)
541
+ cntn.push(h('tbody', { 'data-time': this.spec.time }, this.renderBody()));
542
+ if (this.spec.caption)
543
+ cntn.push(h('caption', {}, this.renderCaption()));
544
+ if (this.spec.status == TablevuStatus.Ready)
545
+ if (this.spec.page.size > 0)
546
+ cntn.push(h('tfoot', {}, this.renderFooter()));
547
+ return h('table', prps, cntn);
548
+ }
549
+ };
550
+
551
+ export { TablevuStatus, tablevu as default };