kempo 2.8.0 → 2.9.0

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.
@@ -255,6 +255,29 @@
255
255
  });
256
256
  </script>
257
257
 
258
+ <h3 id="recordSelection"><a href="#recordSelection" class="no-link">Record Selection</a></h3>
259
+ <p>The table component supports record selection. You can enable record selection by setting the <code>enableSelection</code> property to <code>true</code>.</p>
260
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"recordSelectionExample"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-table</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript"><br /> <span class="hljs-keyword">import</span> Table <span class="hljs-keyword">from</span> <span class="hljs-string">'../kempo/components/Table.js'</span>;<br /><br /> <span class="hljs-keyword">const</span> $recordSelectionExample = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'recordSelectionExample'</span>);<br /> $recordSelectionExample.setData({<br /> <span class="hljs-attr">records</span>: contacts,<br /> <span class="hljs-attr">enableSelection</span>: <span class="hljs-literal">true</span>,<br /> <span class="hljs-attr">controls</span>: {<br /> <span class="hljs-attr">bottom</span>: [<br /> Table.controls.prevPage,<br /> Table.controls.nextPage,<br /> Table.controls.spacer,<br /> Table.controls.selectCount,<br /> Table.controls.deleteSelected<br /> ]<br /> },<br /> <span class="hljs-attr">pageSize</span>: <span class="hljs-number">10</span><br /> });<br /></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
261
+ <k-table id="recordSelectionExample"></k-table>
262
+ <script type="module">
263
+ import Table from '../kempo/components/Table.js';
264
+
265
+ const $recordSelectionExample = document.getElementById('recordSelectionExample');
266
+ $recordSelectionExample.setData({
267
+ records: contacts,
268
+ enableSelection: true,
269
+ controls: {
270
+ bottom: [
271
+ Table.controls.prevPage,
272
+ Table.controls.nextPage,
273
+ Table.controls.spacer,
274
+ Table.controls.selectCount,
275
+ Table.controls.deleteSelected
276
+ ]
277
+ },
278
+ pageSize: 10
279
+ });
280
+ </script>
258
281
 
259
282
  <h2>JavaScript Reference</h2>
260
283
  <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
@@ -332,6 +355,22 @@
332
355
  <h5><code>goToPage(<i>number</i> pageNumber)<i>: undefined</i></code></h5>
333
356
  <p>Navigates to the specified page number.</p>
334
357
 
358
+ <h5><code>getSelectedRecords()<i>: Array</i></code></h5>
359
+ <p>Returns an array of the currently selected records.</p>
360
+
361
+ <h5><code>deleteSelectedRecords()<i>: undefined</i></code></h5>
362
+ <p>Deletes all currently selected records from the table.</p>
363
+
364
+ <h5><code>selectAllOnPage()<i>: undefined</i></code></h5>
365
+ <p>Selects all the records on the current page.</p>
366
+
367
+ <h5><code>deselectAllOnPage()<i>: undefined</i></code></h5>
368
+ <p>Deselects all the records on the current page.</p>
369
+
370
+ <h5><code>allOnPageSelected()<i>: undefined</i></code></h5>
371
+ <p>Returns <code>true</code> if all the records on the current page are selected, and <code>false</code> if they are not.</p>
372
+
373
+
335
374
  </main>
336
375
  <div style="height: 33.333vh"></div>
337
376
  <script type="module" src="../kempo/components/Import.js"></script>
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "type": "module",
5
5
  "description": "A web component library",
6
6
  "main": "dist/index.js",
@@ -1,5 +1,9 @@
1
1
  import Component from './Component.js';
2
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';
3
7
  import { toTitleCase } from '../utils/string.js';
4
8
  import { onEvent, dispatchEvent } from '../utils/element.js';
5
9
 
@@ -41,6 +45,12 @@ export default class Table extends Component {
41
45
  pageSize: {
42
46
  render: (table) => new PageSize(table)
43
47
  },
48
+ selectCount: {
49
+ render: (table) => new SelectCount(table)
50
+ },
51
+ deleteSelected: {
52
+ render: (table) => new DeleteSelected(table)
53
+ },
44
54
  spacer: {
45
55
  html: '<div class="flex"></div>'
46
56
  }
@@ -53,6 +63,7 @@ export default class Table extends Component {
53
63
  pageSize = 100,
54
64
  pageSizeOptions = [10, 25, 50, 100, 500],
55
65
  currentPage = 1,
66
+ enableSelection = false,
56
67
  } = {}) {
57
68
  super();
58
69
 
@@ -63,6 +74,8 @@ export default class Table extends Component {
63
74
  pageSize,
64
75
  pageSizeOptions,
65
76
  currentPage,
77
+ enableSelection,
78
+ selectedIndexes: [], // Renamed property
66
79
  });
67
80
 
68
81
  /* Init */
@@ -86,7 +99,26 @@ export default class Table extends Component {
86
99
  if(!this.rendered) return;
87
100
  const beforeControls = this.controls?.before?.length ? '<th></th>' : '';
88
101
  const afterControls = this.controls?.after?.length ? '<th></th>' : '';
89
- this.shadowRoot.getElementById('fields').innerHTML = `${beforeControls}${this.fields.map(({label})=>`<th>${label}</th>`).join('')}${afterControls}`;
102
+ const selectionControl = this.enableSelection ? '<th class="selection"><input type="checkbox" id="select-all"></th>' : '';
103
+ this.shadowRoot.getElementById('fields').innerHTML = `${selectionControl}${beforeControls}${this.fields.map(({label})=>`<th>${label}</th>`).join('')}${afterControls}`;
104
+
105
+ if (this.enableSelection) {
106
+ const selectAllCheckbox = this.shadowRoot.getElementById('select-all');
107
+ selectAllCheckbox.addEventListener('change', () => {
108
+ if (selectAllCheckbox.checked) {
109
+ this.selectAllOnPage();
110
+ } else {
111
+ this.deselectAllOnPage();
112
+ }
113
+ });
114
+ onEvent(this, 'selectionChange pageChange', () => {
115
+ if(this.allOnPageSelected()){
116
+ selectAllCheckbox.checked = true;
117
+ } else {
118
+ selectAllCheckbox.checked = false;
119
+ }
120
+ });
121
+ }
90
122
  }
91
123
  renderRecords(){
92
124
  if(!this.rendered) return;
@@ -97,6 +129,23 @@ export default class Table extends Component {
97
129
  const paginatedRecords = this.records.slice(start, end);
98
130
  paginatedRecords.forEach((record, index) => {
99
131
  const $tr = document.createElement('tr');
132
+ if (this.enableSelection) {
133
+ const $td = document.createElement('td');
134
+ $td.classList.add('selection');
135
+ const $checkbox = document.createElement('input');
136
+ $checkbox.type = 'checkbox';
137
+ $checkbox.checked = this.selectedIndexes.includes(start + index);
138
+ $checkbox.addEventListener('change', () => {
139
+ if ($checkbox.checked) {
140
+ this.selectedIndexes.push(start + index);
141
+ } else {
142
+ this.selectedIndexes = this.selectedIndexes.filter(i => i !== start + index);
143
+ }
144
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
145
+ });
146
+ $td.appendChild($checkbox);
147
+ $tr.appendChild($td);
148
+ }
100
149
  if(this.controls?.before?.length){
101
150
  $tr.appendChild(this.renderRowControls(this.controls.before, record, start + index));
102
151
  }
@@ -228,7 +277,8 @@ export default class Table extends Component {
228
277
  controls = false,
229
278
  pageSize = false,
230
279
  pageSizeOptions = false,
231
- currentPage = false
280
+ currentPage = false,
281
+ enableSelection = false // Add enableSelection property
232
282
  } = {}) {
233
283
  let rerender = false;
234
284
  let rerenderControls = false;
@@ -252,6 +302,10 @@ export default class Table extends Component {
252
302
  this.currentPage = currentPage;
253
303
  rerender = true;
254
304
  }
305
+ if(enableSelection !== undefined){
306
+ this.enableSelection = enableSelection;
307
+ rerender = true;
308
+ }
255
309
  if(rerender){
256
310
  this.renderFields();
257
311
  this.renderRecords();
@@ -301,12 +355,50 @@ export default class Table extends Component {
301
355
 
302
356
  deleteRecord(index) {
303
357
  if (index >= 0 && index < this.records.length) {
358
+ // Deselect the record before deleting
359
+ this.selectedIndexes = this.selectedIndexes.filter(i => i !== index);
360
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
304
361
  this.records.splice(index, 1);
305
362
  this.renderRecords();
306
363
  dispatchEvent(this, 'recordDeleted', { index });
307
364
  }
308
365
  }
309
366
 
367
+ getSelectedRecords() {
368
+ return this.selectedIndexes.map(index => this.records[index]);
369
+ }
370
+
371
+ selectAllOnPage() {
372
+ const start = (this.currentPage - 1) * this.pageSize;
373
+ const end = Math.min(start + this.pageSize, this.records.length);
374
+ for (let i = start; i < end; i++) {
375
+ if (!this.selectedIndexes.includes(i)) {
376
+ this.selectedIndexes.push(i);
377
+ }
378
+ }
379
+ this.renderRecords();
380
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
381
+ }
382
+
383
+ deselectAllOnPage() {
384
+ const start = (this.currentPage - 1) * this.pageSize;
385
+ const end = Math.min(start + this.pageSize, this.records.length);
386
+ this.selectedIndexes = this.selectedIndexes.filter(index => index < start || index >= end);
387
+ this.renderRecords();
388
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
389
+ }
390
+
391
+ allOnPageSelected() {
392
+ const start = (this.currentPage - 1) * this.pageSize;
393
+ const end = Math.min(start + this.pageSize, this.records.length);
394
+ for (let i = start; i < end; i++) {
395
+ if (!this.selectedIndexes.includes(i)) {
396
+ return false;
397
+ }
398
+ }
399
+ return true;
400
+ }
401
+
310
402
  get shadowTemplate(){
311
403
  return /*html*/`
312
404
  ${super.shadowTemplate}
@@ -356,6 +448,15 @@ export default class Table extends Component {
356
448
  #bottom:not(:empty) {
357
449
  border-top: 1px solid var(--c_border);
358
450
  }
451
+ th.selection, td.selection {
452
+ width: 50px;
453
+ }
454
+ th.selection input,
455
+ td.selection input {
456
+ margin: 0;
457
+ width: 1.35rem;
458
+ height: 1.35rem;
459
+ }
359
460
  `;
360
461
  }
361
462
 
@@ -368,131 +469,3 @@ export default class Table extends Component {
368
469
  }
369
470
  }
370
471
  window.customElements.define('k-table', Table);
371
-
372
- class PageSelect extends Component {
373
- constructor(table) {
374
- super();
375
- this.table = table;
376
- this.classList.add('mxq');
377
- }
378
-
379
- connectedCallback() {
380
- this.render();
381
- }
382
-
383
- async render() {
384
- if(await super.render()){
385
- this.updateOptions();
386
- return true;
387
- }
388
- return false;
389
- }
390
-
391
- updateOptions() {
392
- const $select = this.shadowRoot.getElementById('pageSelect');
393
- $select.innerHTML = '';
394
- for (let i = 1; i <= this.table.getTotalPages(); i++) {
395
- const $option = document.createElement('option');
396
- $option.value = i;
397
- $option.textContent = i;
398
- $select.appendChild($option);
399
- }
400
- $select.value = this.table.getCurrentPage();
401
- const $totalPages = this.shadowRoot.getElementById('totalPages');
402
- $totalPages.textContent = this.table.getTotalPages();
403
-
404
- onEvent($select, 'change', () => {
405
- this.table.setPage(parseInt($select.value));
406
- });
407
-
408
- onEvent(this.table, 'pageSizeChange', () => {
409
- this.updateOptions();
410
- });
411
-
412
- onEvent(this.table, 'pageChange', () => {
413
- $select.value = this.table.getCurrentPage();
414
- });
415
- }
416
-
417
- get shadowTemplate() {
418
- return /*html*/`
419
- <select id="pageSelect" class="mxq"></select>
420
- <label> out of <span id="totalPages"></span></label>
421
- `;
422
- }
423
-
424
- get shadowStyles() {
425
- return /*css*/`
426
- :host {
427
- display: inline-flex;
428
- width: max-content;
429
- align-items: baseline;
430
- }
431
- #pageSelect, label {
432
- display: inline;
433
- }
434
- label {
435
- white-space: nowrap;
436
- }
437
- `;
438
- }
439
- }
440
- window.customElements.define('k-table-page-select', PageSelect);
441
-
442
- class PageSize extends Component {
443
- constructor(table) {
444
- super();
445
- this.table = table;
446
- this.classList.add('mxq');
447
- }
448
-
449
- connectedCallback() {
450
- this.render();
451
- }
452
-
453
- async render() {
454
- if(await super.render()){
455
- const $select = this.shadowRoot.getElementById('pageSizeSelect');
456
- const options = this.table.pageSizeOptions;
457
- options.forEach(size => {
458
- const $option = document.createElement('option');
459
- $option.value = size;
460
- $option.textContent = size;
461
- $select.appendChild($option);
462
- });
463
- $select.value = this.table.pageSize;
464
-
465
- onEvent($select, 'change', () => {
466
- this.table.setPageSize(parseInt($select.value));
467
- });
468
-
469
- onEvent(this.table, 'pageSizeChange', () => {
470
- $select.value = this.table.pageSize;
471
- });
472
-
473
- return true;
474
- }
475
- return false;
476
- }
477
-
478
- get shadowTemplate() {
479
- return /*html*/`
480
- <label for="pageSizeSelect">Page Size: </label>
481
- <select id="pageSizeSelect" class="mxq"></select>
482
- `;
483
- }
484
-
485
- get shadowStyles() {
486
- return /*css*/`
487
- :host {
488
- display: inline-flex;
489
- width: max-content;
490
- align-items: baseline;
491
- }
492
- label {
493
- white-space: nowrap;
494
- }
495
- `;
496
- }
497
- }
498
- window.customElements.define('k-table-page-size', PageSize);
@@ -0,0 +1,58 @@
1
+ import Component from '../Component.js';
2
+ import { onEvent } from '../../utils/element.js';
3
+
4
+ export default class DeleteSelected extends Component {
5
+ constructor(table) {
6
+ super();
7
+ this.table = table;
8
+ this.classList.add('mxq');
9
+ }
10
+
11
+ async render() {
12
+ if (await super.render()) {
13
+ this.updateButtonState();
14
+ const $button = this.shadowRoot.getElementById('deleteSelectedButton');
15
+ onEvent($button, 'click', () => this.deleteSelected());
16
+ onEvent(this.table, 'selectionChange', () => this.updateButtonState());
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+
22
+ deleteSelected() {
23
+ const selectedIndexes = this.table.selectedIndexes;
24
+ selectedIndexes.sort((a, b) => b - a); // Sort in descending order
25
+ selectedIndexes.forEach(index => this.table.deleteRecord(index));
26
+ this.table.selectedIndexes = [];
27
+ this.table.renderRecords();
28
+ this.updateButtonState();
29
+ }
30
+
31
+ updateButtonState() {
32
+ const $button = this.shadowRoot.getElementById('deleteSelectedButton');
33
+ $button.disabled = this.table.selectedIndexes.length === 0;
34
+ }
35
+
36
+ get shadowTemplate() {
37
+ return /*html*/`
38
+ <button id="deleteSelectedButton" class="pq">
39
+ <k-icon name="delete"></k-icon>
40
+ </button>
41
+ `;
42
+ }
43
+
44
+ get shadowStyles() {
45
+ return /*css*/`
46
+ :host {
47
+ display: inline-flex;
48
+ width: max-content;
49
+ align-items: baseline;
50
+ }
51
+ button {
52
+ display: flex;
53
+ align-items: center;
54
+ }
55
+ `;
56
+ }
57
+ }
58
+ window.customElements.define('k-table-delete-selected', DeleteSelected);
@@ -0,0 +1,68 @@
1
+ import Component from '../Component.js';
2
+ import { onEvent } from '../../utils/element.js';
3
+
4
+ export default class PageSelect extends Component {
5
+ constructor(table) {
6
+ super();
7
+ this.table = table;
8
+ this.classList.add('mxq');
9
+ }
10
+
11
+ async render() {
12
+ if(await super.render()){
13
+ this.updateOptions();
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+
19
+ updateOptions() {
20
+ const $select = this.shadowRoot.getElementById('pageSelect');
21
+ $select.innerHTML = '';
22
+ for (let i = 1; i <= this.table.getTotalPages(); i++) {
23
+ const $option = document.createElement('option');
24
+ $option.value = i;
25
+ $option.textContent = i;
26
+ $select.appendChild($option);
27
+ }
28
+ $select.value = this.table.getCurrentPage();
29
+ const $totalPages = this.shadowRoot.getElementById('totalPages');
30
+ $totalPages.textContent = this.table.getTotalPages();
31
+
32
+ onEvent($select, 'change', () => {
33
+ this.table.setPage(parseInt($select.value));
34
+ });
35
+
36
+ onEvent(this.table, 'pageSizeChange', () => {
37
+ this.updateOptions();
38
+ });
39
+
40
+ onEvent(this.table, 'pageChange', () => {
41
+ $select.value = this.table.getCurrentPage();
42
+ });
43
+ }
44
+
45
+ get shadowTemplate() {
46
+ return /*html*/`
47
+ <select id="pageSelect" class="mxq"></select>
48
+ <label> out of <span id="totalPages"></span></label>
49
+ `;
50
+ }
51
+
52
+ get shadowStyles() {
53
+ return /*css*/`
54
+ :host {
55
+ display: inline-flex;
56
+ width: max-content;
57
+ align-items: baseline;
58
+ }
59
+ #pageSelect, label {
60
+ display: inline;
61
+ }
62
+ label {
63
+ white-space: nowrap;
64
+ }
65
+ `;
66
+ }
67
+ }
68
+ window.customElements.define('k-table-page-select', PageSelect);
@@ -0,0 +1,56 @@
1
+ import Component from '../Component.js';
2
+ import { onEvent } from '../../utils/element.js';
3
+
4
+ export default class PageSize extends Component {
5
+ constructor(table) {
6
+ super();
7
+ this.table = table;
8
+ this.classList.add('mxq');
9
+ }
10
+
11
+ async render() {
12
+ if(await super.render()){
13
+ const $select = this.shadowRoot.getElementById('pageSizeSelect');
14
+ const options = this.table.pageSizeOptions;
15
+ options.forEach(size => {
16
+ const $option = document.createElement('option');
17
+ $option.value = size;
18
+ $option.textContent = size;
19
+ $select.appendChild($option);
20
+ });
21
+ $select.value = this.table.pageSize;
22
+
23
+ onEvent($select, 'change', () => {
24
+ this.table.setPageSize(parseInt($select.value));
25
+ });
26
+
27
+ onEvent(this.table, 'pageSizeChange', () => {
28
+ $select.value = this.table.pageSize;
29
+ });
30
+
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ get shadowTemplate() {
37
+ return /*html*/`
38
+ <label for="pageSizeSelect">Page Size: </label>
39
+ <select id="pageSizeSelect" class="mxq"></select>
40
+ `;
41
+ }
42
+
43
+ get shadowStyles() {
44
+ return /*css*/`
45
+ :host {
46
+ display: inline-flex;
47
+ width: max-content;
48
+ align-items: baseline;
49
+ }
50
+ label {
51
+ white-space: nowrap;
52
+ }
53
+ `;
54
+ }
55
+ }
56
+ window.customElements.define('k-table-page-size', PageSize);
@@ -0,0 +1,46 @@
1
+ import Component from '../Component.js';
2
+ import { onEvent } from '../../utils/element.js';
3
+
4
+ export default class SelectCount extends Component {
5
+ constructor(table) {
6
+ super();
7
+ this.table = table;
8
+ this.classList.add('mxq');
9
+ }
10
+
11
+ async render() {
12
+ if (await super.render()) {
13
+ this.updateCount();
14
+ onEvent(this.table, 'selectionChange', () => {
15
+ this.updateCount();
16
+ });
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+
22
+ updateCount() {
23
+ const $count = this.shadowRoot.getElementById('selectCount');
24
+ $count.textContent = this.table.getSelectedRecords().length;
25
+ }
26
+
27
+ get shadowTemplate() {
28
+ return /*html*/`
29
+ <label>Selected: <span id="selectCount"></span></label>
30
+ `;
31
+ }
32
+
33
+ get shadowStyles() {
34
+ return /*css*/`
35
+ :host {
36
+ display: inline-flex;
37
+ width: max-content;
38
+ align-items: baseline;
39
+ }
40
+ label {
41
+ white-space: nowrap;
42
+ }
43
+ `;
44
+ }
45
+ }
46
+ window.customElements.define('k-table-select-count', SelectCount);