kempo 2.10.0 → 2.10.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.
Files changed (99) hide show
  1. package/dist/components/Search.js +1 -1
  2. package/dist/components/Sortable.js +1 -0
  3. package/dist/components/Table.js +1 -1
  4. package/dist/components/tableControls/DeleteRecord.js +1 -0
  5. package/dist/components/tableControls/DeleteSelected.js +1 -1
  6. package/dist/components/tableControls/Edit.js +1 -1
  7. package/dist/components/tableControls/ExportCSV.js +1 -0
  8. package/dist/components/tableControls/ExportJson.js +1 -0
  9. package/dist/components/tableControls/FieldSortHide.js +1 -0
  10. package/dist/components/tableControls/Filters.js +1 -1
  11. package/dist/components/tableControls/FirstPage.js +1 -0
  12. package/dist/components/tableControls/HiddenCount.js +1 -1
  13. package/dist/components/tableControls/Hide.js +1 -1
  14. package/dist/components/tableControls/LastPage.js +1 -0
  15. package/dist/components/tableControls/NextPage.js +1 -0
  16. package/dist/components/tableControls/PageSelect.js +1 -1
  17. package/dist/components/tableControls/PageSize.js +1 -1
  18. package/dist/components/tableControls/PrevPage.js +1 -0
  19. package/dist/components/tableControls/Search.js +1 -0
  20. package/dist/components/tableControls/SelectCount.js +1 -1
  21. package/dist/components/tableControls/ShowAll.js +1 -1
  22. package/dist/components/tableControls/ShowHideFields.js +1 -0
  23. package/dist/components/tableControls/TableControl.js +1 -0
  24. package/dist/index.js +1 -1
  25. package/docs/components/sortable.html +96 -0
  26. package/docs/components/table.html +32 -7
  27. package/docs/components/tableControls.html +10 -24
  28. package/docs/components/tableCustomFields.html +2 -1
  29. package/docs/components/tableFetchRecords.html +114 -0
  30. package/docs/components/tableFieldSortHide.html +75 -0
  31. package/docs/components/tablePagination.html +33 -25
  32. package/docs/components/tableRecordEditing.html +8 -8
  33. package/docs/components/tableRecordFiltering.html +6 -13
  34. package/docs/components/tableRecordHiding.html +10 -14
  35. package/docs/components/tableRecordSearching.html +75 -0
  36. package/docs/components/tableRecordSelection.html +8 -19
  37. package/docs/components/tableRowControls.html +10 -35
  38. package/docs/components/tableSorting.html +73 -0
  39. package/docs/icons/arrow-down.svg +1 -0
  40. package/docs/icons/arrow-up.svg +1 -0
  41. package/docs/icons/drag-handle.svg +1 -0
  42. package/docs/icons/export-file.svg +1 -0
  43. package/docs/icons/first.svg +1 -0
  44. package/docs/icons/last.svg +1 -0
  45. package/docs/icons/table-visibility.svg +1 -0
  46. package/docs/kempo/components/Search.js +1 -1
  47. package/docs/kempo/components/Sortable.js +1 -0
  48. package/docs/kempo/components/Table.js +1 -1
  49. package/docs/kempo/components/tableControls/DeleteRecord.js +1 -0
  50. package/docs/kempo/components/tableControls/DeleteSelected.js +1 -1
  51. package/docs/kempo/components/tableControls/Edit.js +1 -1
  52. package/docs/kempo/components/tableControls/ExportCSV.js +1 -0
  53. package/docs/kempo/components/tableControls/ExportJson.js +1 -0
  54. package/docs/kempo/components/tableControls/FieldSortHide.js +1 -0
  55. package/docs/kempo/components/tableControls/Filters.js +1 -1
  56. package/docs/kempo/components/tableControls/FirstPage.js +1 -0
  57. package/docs/kempo/components/tableControls/HiddenCount.js +1 -1
  58. package/docs/kempo/components/tableControls/Hide.js +1 -1
  59. package/docs/kempo/components/tableControls/LastPage.js +1 -0
  60. package/docs/kempo/components/tableControls/NextPage.js +1 -0
  61. package/docs/kempo/components/tableControls/PageSelect.js +1 -1
  62. package/docs/kempo/components/tableControls/PageSize.js +1 -1
  63. package/docs/kempo/components/tableControls/PrevPage.js +1 -0
  64. package/docs/kempo/components/tableControls/Search.js +1 -0
  65. package/docs/kempo/components/tableControls/SelectCount.js +1 -1
  66. package/docs/kempo/components/tableControls/ShowAll.js +1 -1
  67. package/docs/kempo/components/tableControls/ShowHideFields.js +1 -0
  68. package/docs/kempo/components/tableControls/TableControl.js +1 -0
  69. package/docs/kempo/index.js +1 -1
  70. package/icons/arrow-down.svg +1 -0
  71. package/icons/arrow-up.svg +1 -0
  72. package/icons/drag-handle.svg +1 -0
  73. package/icons/export-file.svg +1 -0
  74. package/icons/first.svg +1 -0
  75. package/icons/last.svg +1 -0
  76. package/icons/table-visibility.svg +1 -0
  77. package/package.json +1 -1
  78. package/src/components/Search.js +1 -0
  79. package/src/components/Sortable.js +169 -0
  80. package/src/components/Table.js +422 -295
  81. package/src/components/tableControls/DeleteRecord.js +43 -0
  82. package/src/components/tableControls/DeleteSelected.js +11 -13
  83. package/src/components/tableControls/Edit.js +22 -26
  84. package/src/components/tableControls/ExportCSV.js +91 -0
  85. package/src/components/tableControls/ExportJson.js +62 -0
  86. package/src/components/tableControls/FieldSortHide.js +77 -0
  87. package/src/components/tableControls/Filters.js +18 -17
  88. package/src/components/tableControls/FirstPage.js +69 -0
  89. package/src/components/tableControls/HiddenCount.js +10 -15
  90. package/src/components/tableControls/Hide.js +12 -15
  91. package/src/components/tableControls/LastPage.js +67 -0
  92. package/src/components/tableControls/NextPage.js +69 -0
  93. package/src/components/tableControls/PageSelect.js +20 -23
  94. package/src/components/tableControls/PageSize.js +12 -15
  95. package/src/components/tableControls/PrevPage.js +69 -0
  96. package/src/components/tableControls/Search.js +49 -0
  97. package/src/components/tableControls/SelectCount.js +5 -10
  98. package/src/components/tableControls/ShowAll.js +9 -12
  99. package/src/components/tableControls/TableControl.js +51 -0
@@ -1,14 +1,4 @@
1
1
  import Component from './Component.js';
2
- import Icon from './Icon.js';
3
- import SelectCount from './tableControls/SelectCount.js';
4
- import PageSelect from './tableControls/PageSelect.js';
5
- import PageSize from './tableControls/PageSize.js';
6
- import DeleteSelected from './tableControls/DeleteSelected.js';
7
- import Edit from './tableControls/Edit.js';
8
- import Hide from './tableControls/Hide.js';
9
- import HiddenCount from './tableControls/HiddenCount.js';
10
- import ShowAll from './tableControls/ShowAll.js';
11
- import Filters from './tableControls/Filters.js';
12
2
  import { toTitleCase } from '../utils/string.js';
13
3
  import { onEvent, offEvent, dispatchEvent } from '../utils/element.js';
14
4
 
@@ -22,53 +12,107 @@ export default class Table extends Component {
22
12
  records = [],
23
13
  fields = [],
24
14
  filters = [],
25
- controls = { before: [], after: [], top: [], bottom: [] },
26
15
  enablePages = false,
27
- pageSize = 100,
16
+ pageSize = 50,
28
17
  pageSizeOptions = [10, 25, 50, 100, 500],
29
18
  currentPage = 1,
30
19
  enableSelection = false,
20
+ enableSorting = false
31
21
  } = {}) {
32
22
  super();
33
23
 
34
- this.registerProps({
35
- fields: fields,
36
- records: records,
37
- controls: controls,
38
- filters: filters,
24
+ this.registerAttributes({
25
+ enablePages,
39
26
  pageSize,
40
- pageSizeOptions,
41
27
  currentPage,
28
+ pageSizeOptions,
42
29
  enableSelection,
43
- enablePages,
44
- sort: []
30
+ enableSorting
45
31
  });
46
32
 
47
- /* Init */
48
- //
33
+ this.registerProps({
34
+ fields: fields,
35
+ records: records,
36
+ filters: filters,
37
+ sort: [],
38
+ columnSizes: {},
39
+ fetchPending: false
40
+ });
49
41
  }
50
42
  async render(force){
51
43
  if(await super.render(force)){
52
44
  this.setData({
53
45
  records: this.records,
54
46
  fields: this.fields,
55
- controls: this.controls,
56
- filters: this.filters,
57
- pageSize: this.pageSize,
58
- pageSizeOptions: this.pageSizeOptions,
59
- currentPage: this.currentPage
47
+ filters: this.filters
60
48
  });
61
49
  return true;
62
50
  }
63
51
  return false;
64
52
  }
65
53
 
54
+
55
+ /*
56
+ Render Functions
57
+ */
58
+
66
59
  renderFields(){
67
60
  if(!this.rendered) return;
68
- const beforeControls = this.controls?.before?.length ? '<th></th>' : '';
69
- const afterControls = this.controls?.after?.length ? '<th></th>' : '';
70
- const selectionControl = this.enableSelection ? '<th class="selection"><input type="checkbox" id="select-all"></th>' : '';
71
- this.shadowRoot.getElementById('fields').innerHTML = `${selectionControl}${beforeControls}${this.fields.map(({label})=>`<th>${label}</th>`).join('')}${afterControls}`;
61
+ this.calculateColumnSizes();
62
+ this.hasTopControls()?this.setAttribute('top-controls', 'true'):this.removeAttribute('top-controls');
63
+ this.hasBottomControls()?this.setAttribute('bottom-controls', 'true'):this.removeAttribute('bottom-controls');
64
+
65
+ const $fields = this.shadowRoot.getElementById('fields');
66
+ $fields.innerHTML = '';
67
+
68
+ if(this.enableSelection){
69
+ const $selectDiv = document.createElement('div');
70
+ $selectDiv.classList.add('field', 'controls', 'cell', 'field-select');
71
+ $selectDiv.style.width = '40px';
72
+ $selectDiv.innerHTML = `<input type="checkbox" id="select-all" />`;
73
+ $fields.appendChild($selectDiv);
74
+ }
75
+ if(this.hasBeforeControls()){
76
+ const $beforeDiv = document.createElement('div');
77
+ $beforeDiv.classList.add('field', 'cell', 'field-before-controls');
78
+ $beforeDiv.style.width = this.columnSizes.beforeControls + 'px';
79
+ $fields.appendChild($beforeDiv);
80
+ }
81
+ this.fields.forEach( ({name, label, hidden}) => {
82
+ if (hidden) return; // Skip hidden fields
83
+ const $fieldDiv = document.createElement('div');
84
+ $fieldDiv.classList.add('field', 'cell');
85
+ $fieldDiv.style.width = this.columnSizes[name] + 'px';
86
+ $fieldDiv.innerHTML = label;
87
+ if (this.enableSorting) {
88
+ $fieldDiv.style.cursor = 'pointer';
89
+ const currentSort = this.sort.find(s => s.name === name);
90
+ if (currentSort) {
91
+ $fieldDiv.classList.add(currentSort.asc ? 'sort-asc' : 'sort-desc');
92
+ const $icon = document.createElement('k-icon');
93
+ $icon.classList.add('icon-sort');
94
+ $icon.setAttribute('name', currentSort.asc ? 'arrow-down' : 'arrow-up');
95
+ $fieldDiv.appendChild($icon);
96
+ }
97
+ $fieldDiv.addEventListener('click', () => {
98
+ const currentSort = this.sort.find(s => s.name === name);
99
+ const asc = currentSort ? !currentSort.asc : true;
100
+ this.sortBy(name, asc);
101
+ });
102
+ }
103
+ $fields.appendChild($fieldDiv);
104
+ });
105
+ if(this.hasAfterControls()){
106
+ const $afterDiv = document.createElement('div');
107
+ $afterDiv.classList.add('field', 'cell', 'field-after-controls');
108
+ $afterDiv.style.width = this.columnSizes.afterControls + 'px';
109
+ $fields.appendChild($afterDiv);
110
+ }
111
+
112
+ $fields.style.width =
113
+ this.shadowRoot.getElementById('top').style.width =
114
+ this.shadowRoot.getElementById('bottom').style.width =
115
+ this.shadowRoot.getElementById('records').style.width = this.columnSizes.total + 'px';
72
116
 
73
117
  if (this.enableSelection) {
74
118
  const selectAllCheckbox = this.shadowRoot.getElementById('select-all');
@@ -93,24 +137,47 @@ export default class Table extends Component {
93
137
  if (!this.rendered) return;
94
138
  const $records = this.shadowRoot.getElementById('records');
95
139
  $records.innerHTML = '';
96
- const start = (this.currentPage - 1) * this.pageSize;
97
- const end = start + this.pageSize;
98
- const paginatedRecords = this.getDisplayedRecords().slice(start, end);
99
- let fetchRecords = false;
100
- paginatedRecords.forEach((record) => {
101
- $records.appendChild(this.renderRecord(record));
140
+
141
+ let displayedRecords = this.getDisplayedRecords(),
142
+ start = 0,
143
+ end = this.pageSize;
144
+
145
+ if(this.enablePages){
146
+ start = (this.currentPage - 1) * this.pageSize;
147
+ end = start + this.pageSize;
148
+ displayedRecords = displayedRecords.slice(start, end);
149
+ }
150
+
151
+ let fetchStart,
152
+ fetchCount = 0;
153
+ displayedRecords.forEach((record, index) => {
154
+ if(record !== null){
155
+ $records.appendChild(this.renderRecord(record));
156
+ } else {
157
+ if(!fetchStart) fetchStart = start + index;
158
+ fetchCount++;
159
+ const $div = document.createElement('div');
160
+ $div.classList.add('record', 'fetching');
161
+ $div.innerHTML = '<div class="cell">Loading...</div>';
162
+ $records.appendChild($div);
163
+ }
102
164
  });
103
- if (fetchRecords) {
104
- dispatchEvent(this, 'fetchRecords', { start, end });
165
+ if(fetchStart && !this.fetchPending){
166
+ dispatchEvent(this, 'fetchRecords', {
167
+ start: fetchStart,
168
+ count: fetchCount
169
+ });
105
170
  }
106
171
  }
107
172
 
108
173
  renderRecord(record){
109
- const $tr = document.createElement('tr');
110
- $tr.dataset.index = record[index];
174
+ const $record = document.createElement('div');
175
+ $record.classList.add('record');
176
+ $record.dataset.index = record[index];
111
177
  if (this.enableSelection) {
112
- const $td = document.createElement('td');
113
- $td.classList.add('selection');
178
+ const $selectionDiv = document.createElement('div');
179
+ $selectionDiv.style.width = '40px';
180
+ $selectionDiv.classList.add('cell', 'selection', 'controls');
114
181
  const $checkbox = document.createElement('input');
115
182
  $checkbox.type = 'checkbox';
116
183
  $checkbox.checked = record[selected];
@@ -118,141 +185,121 @@ export default class Table extends Component {
118
185
  record[selected] = !!event.target.checked;
119
186
  dispatchEvent(this, 'selectionChange');
120
187
  });
121
- $td.appendChild($checkbox);
122
- $tr.appendChild($td);
123
- }
124
- if (this.controls?.before?.length) {
125
- $tr.appendChild(this.renderRowControls(this.controls.before, record));
126
- }
127
- if (record === null) {
128
- const $td = document.createElement('td');
129
- $td.colSpan = this.fields.length;
130
- $td.innerHTML = '<i>Loading...</i>';
131
- fetchRecords = true;
132
- $tr.appendChild($td);
133
- } else {
134
- this.fields.forEach(({ name, formatter, calculator, type, editor }) => {
135
- const $td = document.createElement('td');
136
- $td.dataset.field = name;
137
- let value = record[name] || '';
138
- if(record[editing]){
139
- $tr.setAttribute('editing', true);
140
- if (calculator) {
141
- $td.appendChild(Table.editors.calculated(calculator(record, this)));
142
- } else if(editor){
143
- $td.appendChild(editor(value));
144
- } else {
145
- let editorGen = Table.editors[type || typeof value];
146
- if(!editorGen){
147
- editorGen = Table.editors.string;
148
- }
149
- $td.appendChild(editorGen(value));
150
- }
188
+ $selectionDiv.appendChild($checkbox);
189
+ $record.appendChild($selectionDiv);
190
+ }
191
+ if (this.hasBeforeControls()) {
192
+ $record.appendChild(this.renderBeforeControls());
193
+ }
194
+ this.fields.forEach(({ name, formatter, calculator, type, editor, hidden }) => {
195
+ if (hidden) return; // Skip hidden fields
196
+ const $div = document.createElement('div');
197
+ $div.classList.add('cell');
198
+ $div.dataset.field = name;
199
+ $div.style.width = this.columnSizes[name] + 'px';
200
+ let value = record[name] || '';
201
+ if(record[editing]){
202
+ $record.setAttribute('editing', true);
203
+ if (calculator) {
204
+ $div.appendChild(Table.editors.calculated(calculator(record, this)));
205
+ } else if(editor){
206
+ $div.appendChild(editor(value));
151
207
  } else {
152
- if (calculator) {
153
- $td.innerHTML = calculator(record, this);
154
- } else if (formatter) {
155
- $td.innerHTML = formatter(value);
156
- } else {
157
- $td.innerHTML = value;
208
+ let editorGen = Table.editors[type || typeof value];
209
+ if(!editorGen){
210
+ editorGen = Table.editors.string;
158
211
  }
212
+ $div.appendChild(editorGen(value));
159
213
  }
160
- $tr.appendChild($td);
161
- });
162
- }
163
- if (this.controls?.after?.length) {
164
- $tr.appendChild(this.renderRowControls(this.controls.after, record));
214
+ } else {
215
+ if (calculator) {
216
+ $div.innerHTML = calculator(record, this);
217
+ } else if (formatter) {
218
+ $div.innerHTML = formatter(value);
219
+ } else {
220
+ $div.innerHTML = value;
221
+ }
222
+ }
223
+ $record.appendChild($div);
224
+ });
225
+ if (this.hasAfterControls()) {
226
+ $record.appendChild(this.renderAfterControls());
165
227
  }
166
- return $tr;
228
+ return $record;
229
+ }
230
+
231
+ renderBeforeControls(){
232
+ const $div = document.createElement('div');
233
+ $div.style.width = this.columnSizes.beforeControls + 'px';
234
+ $div.classList.add('cell', 'controls', 'controls-before');
235
+ this.querySelectorAll('[slot="before"]').forEach($control => {
236
+ $div.appendChild($control.cloneNode(true));
237
+ });
238
+ return $div;
239
+ }
240
+
241
+ renderAfterControls(){
242
+ const $div = document.createElement('div');
243
+ $div.style.width = this.columnSizes.afterControls + 'px';
244
+ $div.classList.add('cell', 'controls', 'controls-after');
245
+ this.querySelectorAll('[slot="after"]').forEach($control => {
246
+ $div.appendChild($control.cloneNode(true));
247
+ });
248
+ return $div;
249
+ }
250
+
251
+
252
+
253
+
254
+
255
+ hasBeforeControls(){
256
+ return !!this.querySelector('[slot="before"]');
257
+ }
258
+
259
+ hasAfterControls(){
260
+ return !!this.querySelector('[slot="after"]');
261
+ }
262
+
263
+ hasTopControls(){
264
+ return !!this.querySelector('[slot="top"]');
265
+ }
266
+
267
+ hasBottomControls(){
268
+ return !!this.querySelector(':scope > :not([slot])'); // bottom is default slot, detect direct child with no slot
167
269
  }
168
270
 
169
271
  editRecord(record){
170
272
  record[editing] = true;
171
- const $currentTr = this.shadowRoot.querySelector(`tr[data-index="${record[index]}"]`);
172
- const $newTr = this.renderRecord(record);
173
- $currentTr.replaceWith($newTr);
273
+ const $currentRecord = this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`);
274
+ const $newRecord = this.renderRecord(record);
275
+ $currentRecord.replaceWith($newRecord);
174
276
  dispatchEvent(this, 'editingChange');
175
277
  }
176
278
 
177
279
  saveEditedRecord(record){
178
280
  record[editing] = false;
179
- const $currentTr = this.shadowRoot.querySelector(`tr[data-index="${record[index]}"]`);
180
- $currentTr.querySelectorAll('td').forEach(($td) => {
181
- const field = $td.dataset.field;
281
+ const $currentRecord = this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`);
282
+ $currentRecord.querySelectorAll('.cell').forEach(($cell) => {
283
+ const field = $cell.dataset.field;
182
284
  const fieldDef = this.fields.find(f => f.name === field);
183
285
  if(fieldDef && !fieldDef.calculator){ // calculated fields cannot be udated
184
- const $input = $td.children.length === 1 ? $td.firstChild : $td.querySelector('input, select');
286
+ const $input = $cell.children.length === 1 ? $cell.firstChild : $cell.querySelector('input, select');
185
287
  if ($input) {
186
288
  record[field] = $input.value;
187
289
  }
188
290
  }
189
291
  });
190
- const $newTr = this.renderRecord(record);
191
- $currentTr.replaceWith($newTr);
292
+ $currentRecord.replaceWith(this.renderRecord(record));
192
293
  }
193
294
 
194
295
  cancelEditedRecord(record){
195
296
  record[editing] = false;
196
- const $currentTr = this.shadowRoot.querySelector(`tr[data-index="${record[index]}"]`);
197
- const $newTr = this.renderRecord(record);
198
- $currentTr.replaceWith($newTr);
297
+ this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`).replaceWith(this.renderRecord(record));
199
298
  }
200
299
 
201
300
  recordIsEditing(record){
202
301
  return record[editing];
203
302
  }
204
-
205
- renderRowControls(controls = [], record) {
206
- if(!this.rendered) return;
207
- const $td = document.createElement('td');
208
- controls.filter(c=>!!c).forEach(({ html, icon, action, render }) => {
209
- if(html){
210
- $td.appendChild(document.createRange().createContextualFragment(html));
211
- } else if(render && typeof(render) === 'function'){
212
- const rendered = render(this, record);
213
- if(rendered instanceof HTMLElement){
214
- $td.appendChild(rendered);
215
- } else if(typeof(rendered) === 'string'){
216
- $td.appendChild(document.createRange().createContextualFragment(rendered));
217
- }
218
- } else if(icon){
219
- const $button = document.createElement('button');
220
- $button.classList.add('pq', 'no-btn');
221
- $button.appendChild(new Icon(icon));
222
- if (action) {
223
- onEvent($button, 'click', () => action(this, record));
224
- }
225
- $td.appendChild($button);
226
- }
227
- });
228
- return $td;
229
- }
230
-
231
- renderControls(controls = [], position) {
232
- if(!this.rendered) return;
233
- const $container = this.shadowRoot.getElementById(position);
234
- $container.innerHTML = '';
235
- controls.filter(c=>!!c).forEach(({ html, icon, action, render }) => {
236
- if(html){
237
- $container.appendChild(document.createRange().createContextualFragment(html));
238
- } else if(render && typeof(render) === 'function'){
239
- const rendered = render(this);
240
- if(rendered instanceof HTMLElement){
241
- $container.appendChild(rendered);
242
- } else if(typeof(rendered) === 'string'){
243
- $container.appendChild(document.createRange().createContextualFragment(rendered));
244
- }
245
- } else if(icon){
246
- const $button = document.createElement('button');
247
- $button.classList.add('pq', 'no-btn');
248
- $button.appendChild(new Icon(icon));
249
- if (action) {
250
- onEvent($button, 'click', () => action(this));
251
- }
252
- $container.appendChild($button);
253
- }
254
- });
255
- }
256
303
 
257
304
  getCurrentPage() {
258
305
  return this.currentPage;
@@ -270,6 +317,12 @@ export default class Table extends Component {
270
317
  dispatchEvent(this, 'pageChange');
271
318
  }
272
319
 
320
+ firstPage(){
321
+ if(this.currentPage !== 1){
322
+ this.setPage(1);
323
+ }
324
+ }
325
+
273
326
  nextPage() {
274
327
  if (this.currentPage < this.getTotalPages()) {
275
328
  this.setPage(this.currentPage + 1);
@@ -282,6 +335,12 @@ export default class Table extends Component {
282
335
  }
283
336
  }
284
337
 
338
+ lastPage() {
339
+ if (this.currentPage !== this.getTotalPages()) {
340
+ this.setPage(this.getTotalPages());
341
+ }
342
+ }
343
+
285
344
  setPageSize(pageSize) {
286
345
  this.pageSize = pageSize;
287
346
  this.currentPage = 1;
@@ -293,14 +352,12 @@ export default class Table extends Component {
293
352
  setData({
294
353
  records = false,
295
354
  fields = false,
296
- controls = false,
297
355
  pageSize = false,
298
356
  pageSizeOptions = false,
299
357
  currentPage = false,
300
- enableSelection = false // Add enableSelection property
358
+ enableSelection
301
359
  } = {}) {
302
360
  let rerender = false,
303
- rerenderControls = false,
304
361
  pageCountBefore = this.getTotalPages(),
305
362
  pageBefore = this.currentPage;
306
363
  if(records){
@@ -321,10 +378,6 @@ export default class Table extends Component {
321
378
  if (pageSizeOptions) {
322
379
  this.pageSizeOptions = pageSizeOptions;
323
380
  }
324
- if (controls) {
325
- this.controls = controls;
326
- rerenderControls = true;
327
- }
328
381
  if(currentPage){
329
382
  this.currentPage = currentPage;
330
383
  rerender = true;
@@ -337,10 +390,6 @@ export default class Table extends Component {
337
390
  this.renderFields();
338
391
  this.renderRecords();
339
392
  }
340
- if(rerenderControls){
341
- this.renderControls(this.controls.top, 'top');
342
- this.renderControls(this.controls.bottom, 'bottom');
343
- }
344
393
  const newPageCount = this.getTotalPages();
345
394
  if(newPageCount !== pageCountBefore){
346
395
  dispatchEvent(this, 'pageCountChanged', { totalPages: this.getTotalPages() });
@@ -350,9 +399,10 @@ export default class Table extends Component {
350
399
  }
351
400
  }
352
401
 
353
- setRecords(records) {
402
+ setRecords(records, fields) {
354
403
  let pageCountBefore = this.getTotalPages(),
355
404
  pageBefore = this.currentPage;
405
+
356
406
  this.records = records.map(r=>({...r}));
357
407
  this.records.forEach((record, idx) => {
358
408
  record[index] = idx;
@@ -360,6 +410,8 @@ export default class Table extends Component {
360
410
  record[hidden] = false;
361
411
  record[editing] = false;
362
412
  });
413
+ this.fields = fields || Table.extractFieldsFromRecords(this.records);
414
+ this.renderFields();
363
415
  this.renderRecords();
364
416
  dispatchEvent(this, 'recordsSet', { records });
365
417
  const newPageCount = this.getTotalPages();
@@ -379,9 +431,12 @@ export default class Table extends Component {
379
431
  }
380
432
 
381
433
  onEvent(this, 'fetchRecords', async (event) => {
382
- const { start, end } = event.detail;
383
- const records = await callback(start, end - start);
434
+ if(this.fetchPending) return;
435
+ this.fetchPending = true;
436
+ const { start, count } = event.detail;
437
+ const records = await callback(start, count);
384
438
  this.records.splice(start, records.length, ...records);
439
+ this.fetchPending = false;
385
440
  this.renderRecords();
386
441
  });
387
442
  }
@@ -518,6 +573,7 @@ export default class Table extends Component {
518
573
  sortBy(field, asc = true) {
519
574
  this.sort = this.sort.filter(item => item.name !== field);
520
575
  this.sort.push({ name: field, asc });
576
+ this.renderFields(); // Re-render fields to update sort classes and icons
521
577
  this.renderRecords();
522
578
  }
523
579
 
@@ -549,6 +605,10 @@ export default class Table extends Component {
549
605
  this.records.forEach(record => {
550
606
  record[hidden] = false;
551
607
  });
608
+ if(this.filters.length){
609
+ this.filters = [];
610
+ dispatchEvent(this, 'filterRemoved filterChange');
611
+ }
552
612
  this.renderRecords();
553
613
  dispatchEvent(this, 'recordShown allRecordsShown');
554
614
  }
@@ -559,45 +619,88 @@ export default class Table extends Component {
559
619
  this.renderRecords();
560
620
  }
561
621
 
562
- removeFilter(field, condition, value) {
563
- this.filters = this.filters.filter(f => f.field !== field || f.condition !== condition || f.value !== value);
564
- dispatchEvent(this, 'filterRemoved filterChange');
565
- this.renderRecords();
622
+ removeFilter(field, condition, value, rerender = true) {
623
+ const filterIndex = this.filters.findIndex(f => f.field === field && f.condition === condition && f.value === value);
624
+ if (filterIndex !== -1) {
625
+ this.records.forEach(record => {
626
+ if(!this.testFilter(record, field, condition, value)){
627
+ record[hidden] = false;
628
+ }
629
+ });
630
+ this.filters.splice(filterIndex, 1);
631
+ dispatchEvent(this, 'filterRemoved filterChange');
632
+ if(rerender) this.renderRecords();
633
+ }
634
+ }
635
+
636
+ testFilter(record, field, condition, value) {
637
+ const recordValue = record[field];
638
+ switch (condition) {
639
+ case 'equals':
640
+ return recordValue === value;
641
+ case 'not-equals':
642
+ return recordValue !== value;
643
+ case 'contains':
644
+ return recordValue.includes(value);
645
+ case 'not-contains':
646
+ return !recordValue.includes(value);
647
+ case 'greater-than':
648
+ return recordValue > value;
649
+ case 'less-than':
650
+ return recordValue < value;
651
+ case 'greater-than-or-equal':
652
+ return recordValue >= value;
653
+ case 'less-than-or-equal':
654
+ return recordValue <= value;
655
+ default:
656
+ return true;
657
+ }
566
658
  }
567
659
 
568
- getDisplayedRecords() {
569
- let displayedRecords = this.records.filter(record => !record[hidden]);
660
+ removeAllFilters() {
661
+ if(this.filters.length){
662
+ this.filters.forEach(({ field, condition, value }) => {
663
+ this.removeFilter(field, condition, value, false);
664
+ });
665
+ this.renderRecords();
666
+ }
667
+ }
570
668
 
571
- this.filters.forEach(({ field, condition, value }) => {
572
- displayedRecords = displayedRecords.filter(record => {
573
- if (condition === 'eq') {
574
- return record[field] === value;
575
- }
576
- if (condition === 'neq') {
577
- return record[field] !== value;
578
- }
579
- if (condition === 'gt') {
580
- return record[field] > value;
581
- }
582
- if (condition === 'lt') {
583
- return record[field] < value;
584
- }
585
- if (condition === 'gte') {
586
- return record[field] >= value;
587
- }
588
- if (condition === 'lte') {
589
- return record[field] <= value;
590
- }
591
- if (condition === 'contains') {
592
- return record[field].includes(value);
669
+ search(term) {
670
+ const t = term.trim().toLowerCase();
671
+ let changed = false;
672
+ this.records.forEach(record => {
673
+ if (record[hidden]) return;
674
+ let match = false;
675
+ this.fields.forEach(({ name }) => {
676
+ const val = record[name]?.toString().toLowerCase() || '';
677
+ if (val.includes(t)) {
678
+ match = true;
593
679
  }
594
- if (condition === 'ncontains') {
595
- return !record[field].includes(value);
680
+ });
681
+ if (record[hidden] !== !match) {
682
+ record[hidden] = !match;
683
+ changed = true;
684
+ }
685
+ });
686
+ if (changed) {
687
+ dispatchEvent(this, 'recordHidden');
688
+ this.renderRecords();
689
+ }
690
+ dispatchEvent(this, 'search', { term });
691
+ }
692
+
693
+ getDisplayedRecords() {
694
+ this.filters.forEach(({ field, condition, value }) => {
695
+ this.records.forEach(record => {
696
+ if (!this.testFilter(record, field, condition, value)) {
697
+ record[hidden] = true;
596
698
  }
597
- return true;
598
699
  });
599
700
  });
600
701
 
702
+ let displayedRecords = this.records.filter(record => record === null || !record[hidden]);
703
+
601
704
  this.sort.forEach(({ name, asc }) => {
602
705
  displayedRecords.sort((a, b) => {
603
706
  if (a[name] < b[name]) return asc ? -1 : 1;
@@ -613,16 +716,86 @@ export default class Table extends Component {
613
716
  return this.records.filter(record => record[hidden]);
614
717
  }
615
718
 
719
+ calculateColumnSizes() {
720
+ this.columnSizes = {};
721
+ this.columnSizes.total = 0;
722
+ if(this.enableSelection) this.columnSizes.total += 40;
723
+ this.columnSizes.beforeControls = Array.from(this.querySelectorAll('[slot="before"]')).reduce((total, el) => total + (el.maxWidth || 40), 0);
724
+ this.columnSizes.afterControls = Array.from(this.querySelectorAll('[slot="after"]')).reduce((total, el) => total + (el.maxWidth || 40), 0);
725
+ if(this.hasBeforeControls()) this.columnSizes.total += this.columnSizes.beforeControls;
726
+ if(this.hasAfterControls()) this.columnSizes.total += this.columnSizes.afterControls;
727
+
728
+ this.fields.forEach(field => {
729
+ if (field.size) {
730
+ this.columnSizes[field.name] = field.size;
731
+ this.columnSizes.total += field.size;
732
+ } else {
733
+ let maxLength = 0;
734
+ this.records.slice(0, 100).forEach(record => {
735
+ let value = record[field.name];
736
+ if (field.calculator) {
737
+ value = field.calculator(record, this);
738
+ }
739
+ if (field.formatter) {
740
+ value = field.formatter(value);
741
+ }
742
+ if (value && value.toString().length > maxLength) {
743
+ maxLength = value.toString().length;
744
+ }
745
+ });
746
+ this.columnSizes[field.name] = Math.max((maxLength * 10 + 32), 128);
747
+ if(!field.hidden) this.columnSizes.total += this.columnSizes[field.name];
748
+ }
749
+ });
750
+ return this.columnSizes;
751
+ }
752
+
753
+ setFieldHiddenState(fieldName, hidden) {
754
+ const field = this.fields.find(f => f.name === fieldName);
755
+ if (field) {
756
+ field.hidden = hidden;
757
+ this.calculateColumnSizes();
758
+ this.renderFields();
759
+ this.renderRecords();
760
+ dispatchEvent(this, 'fieldVisibilityChanged ' + (hidden ? 'fieldHidden' : 'fieldShown'), { field });
761
+ }
762
+ }
763
+
764
+ hideField(fieldName) {
765
+ this.setFieldHiddenState(fieldName, true);
766
+ }
767
+
768
+ showField(fieldName) {
769
+ this.setFieldHiddenState(fieldName, false);
770
+ }
771
+
772
+ reorderFields(newOrder) {
773
+ const newFields = [];
774
+ newOrder.forEach(fieldName => {
775
+ const field = this.fields.find(f => f.name === fieldName);
776
+ if (field) {
777
+ newFields.push(field);
778
+ }
779
+ });
780
+ this.fields = newFields;
781
+ this.renderFields();
782
+ this.renderRecords();
783
+ }
784
+
785
+ /* Shadow DOM */
616
786
  get shadowTemplate(){
617
787
  return /*html*/`
618
- ${super.shadowTemplate}
619
- <div class="responsive-table">
620
- <div id="top"></div>
621
- <table class="b0">
622
- <thead id="fields"></thead>
623
- <tbody id="records"></tbody>
624
- </table>
625
- <div id="bottom"></div>
788
+ <div id="wrapper">
789
+ <div id="top"><slot name="top"></slot></div>
790
+ <div id="table">
791
+ <div id="fields"></div>
792
+ <div id="records"></div>
793
+ </div>
794
+ <div id="bottom">${super.shadowTemplate}</div>
795
+ </div>
796
+ <div style="display: none">
797
+ <slot name="before"></slot>
798
+ <slot name="after"></slot>
626
799
  </div>
627
800
  `;
628
801
  }
@@ -632,58 +805,74 @@ export default class Table extends Component {
632
805
  ${super.shadowStyles}
633
806
  :host {
634
807
  display: block;
808
+ width: 100%;
809
+ overflow: auto;
635
810
  margin-bottom: var(--spacer);
811
+ }
812
+ #wrapper {
813
+ width: min-content;
636
814
  border: 1px solid var(--c_border);
637
815
  border-radius: var(--radius);
638
816
  }
639
- #top, #bottom {
640
- display: flex;
817
+ #table {
818
+ width: min-content;
641
819
  }
642
- th {
643
- border-top: none;
820
+ #fields,
821
+ .record {
822
+ display: flex;
644
823
  }
645
- tr:last-child td:first-child {
646
- border-bottom-left-radius: 0;
824
+ #fields {
825
+ background-color: var(--c_bg__alt);
826
+ border-bottom: 1px solid var(--c_border);
647
827
  }
648
- tr:last-child td:last-child {
649
- border-bottom-right-radius: 0;
828
+ .record:not([editing="true"]) .cell:not(.controls),
829
+ #fields .cell:not(.controls) {
830
+ padding: calc(0.5 * var(--spacer)) var(--spacer);
650
831
  }
651
- tr:last-child td {
652
- border-bottom: none;
832
+ .cell:not(:first-child) {
833
+ border-left: 1px solid var(--c_border);
653
834
  }
654
- th:first-child, td:first-child {
655
- border-left: none;
835
+ .record:not(:last-child) .cell {
836
+ border-bottom: 1px solid var(--c_border);
656
837
  }
657
- th:last-child, td:last-child {
658
- border-right: none;
838
+ #top, #bottom {
839
+ display: flex;
840
+ width: 100%;
659
841
  }
660
- #top:not(:empty) {
842
+ #top slot {
843
+ display: block;
844
+ width: 100%;
661
845
  border-bottom: 1px solid var(--c_border);
662
846
  }
663
- #bottom:not(:empty) {
847
+ #bottom slot {
848
+ display: block;
849
+ width: 100%;
664
850
  border-top: 1px solid var(--c_border);
665
851
  }
666
- th.selection, td.selection {
667
- width: 50px;
852
+ :host(:not([top-controls])) #top,
853
+ :host(:not([bottom-controls])) #bottom {
854
+ display: none;
668
855
  }
669
- th.selection input,
670
- td.selection input {
671
- margin: 0;
672
- width: 1.35rem;
673
- height: 1.35rem;
856
+ .field-select,
857
+ .selection {
858
+ display: flex;
859
+ justify-content: center;
860
+ align-items: center;
674
861
  }
675
- tr[editing] td {
676
- padding: 0;
862
+ .field-select input,
863
+ .selection input {
864
+ width: 1.25rem;
865
+ height: 1.25rem;
677
866
  }
678
- tr[editing] input,
679
- tr[editing] select,
680
- tr[editing] .input {
681
- padding: calc(0.75 * var(--spacer)) var(--spacer) !important;
682
- width: 100%;
867
+ .icon-sort {
868
+ float: right;
869
+ opacity: 0.5;
683
870
  }
684
871
  `;
685
872
  }
686
873
 
874
+
875
+ /* Static Methods */
687
876
  static extractFieldsFromRecords(records, recordLimit = 100){
688
877
  const names = new Set();
689
878
  records.slice(0, recordLimit).forEach( record => {
@@ -692,69 +881,7 @@ export default class Table extends Component {
692
881
  return [...names].map(name=>({name,label:toTitleCase(name)}));
693
882
  };
694
883
 
695
- static controls = {
696
- prevPage: {
697
- render: (table) => {
698
- const $button = document.createElement('button');
699
- $button.classList.add('pq', 'no-btn');
700
- $button.appendChild(new Icon('chevron-left'));
701
- onEvent($button, 'click', () => table.prevPage());
702
-
703
- // Disable button if on the first page
704
- onEvent(table, 'pageChange', () => {
705
- $button.disabled = table.getCurrentPage() === 1;
706
- });
707
-
708
- return $button;
709
- }
710
- },
711
- nextPage: {
712
- render: (table) => {
713
- const $button = document.createElement('button');
714
- $button.classList.add('pq', 'no-btn');
715
- $button.appendChild(new Icon('chevron-right'));
716
- onEvent($button, 'click', () => table.nextPage());
717
-
718
- // Disable button if on the last page
719
- onEvent(table, 'pageChange', () => {
720
- $button.disabled = table.getCurrentPage() === table.getTotalPages();
721
- });
722
-
723
- return $button;
724
- }
725
- },
726
- pageSelect: {
727
- render: (table) => new PageSelect(table)
728
- },
729
- pageSize: {
730
- render: (table) => new PageSize(table)
731
- },
732
- selectCount: {
733
- render: (table) => new SelectCount(table)
734
- },
735
- deleteSelected: {
736
- render: (table) => new DeleteSelected(table)
737
- },
738
- spacer: {
739
- html: '<div class="flex"></div>'
740
- },
741
- edit: {
742
- render: (table, record) => new Edit(table, record)
743
- },
744
- hide: {
745
- render: (table, record) => new Hide(table, record)
746
- },
747
- hiddenCount: {
748
- render: (table) => new HiddenCount(table)
749
- },
750
- showAll: {
751
- render: (table) => new ShowAll(table)
752
- },
753
- filters: {
754
- render: (table) => new Filters(table)
755
- }
756
- };
757
-
884
+ /* Static Members */
758
885
  static format(value){
759
886
  const f = Array.isArray(value) ? Table.formatters.array : Table.formatters[typeof value];
760
887
  return f(value);