@liedekef/ftable 1.3.6 → 1.3.7

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 (39) hide show
  1. package/ftable.esm.js +105 -252
  2. package/ftable.js +105 -252
  3. package/ftable.min.js +2 -2
  4. package/ftable.umd.js +105 -252
  5. package/localization/ftable.nl.js +4 -1
  6. package/package.json +1 -1
  7. package/themes/basic/ftable_basic.css +15 -6
  8. package/themes/basic/ftable_basic.min.css +1 -1
  9. package/themes/ftable_theme_base.less +17 -6
  10. package/themes/lightcolor/blue/ftable.css +15 -6
  11. package/themes/lightcolor/blue/ftable.min.css +1 -1
  12. package/themes/lightcolor/gray/ftable.css +15 -6
  13. package/themes/lightcolor/gray/ftable.min.css +1 -1
  14. package/themes/lightcolor/green/ftable.css +15 -6
  15. package/themes/lightcolor/green/ftable.min.css +1 -1
  16. package/themes/lightcolor/orange/ftable.css +15 -6
  17. package/themes/lightcolor/orange/ftable.min.css +1 -1
  18. package/themes/lightcolor/red/ftable.css +15 -6
  19. package/themes/lightcolor/red/ftable.min.css +1 -1
  20. package/themes/metro/blue/ftable.css +15 -6
  21. package/themes/metro/blue/ftable.min.css +1 -1
  22. package/themes/metro/brown/ftable.css +15 -6
  23. package/themes/metro/brown/ftable.min.css +1 -1
  24. package/themes/metro/crimson/ftable.css +15 -6
  25. package/themes/metro/crimson/ftable.min.css +1 -1
  26. package/themes/metro/darkgray/ftable.css +15 -6
  27. package/themes/metro/darkgray/ftable.min.css +1 -1
  28. package/themes/metro/darkorange/ftable.css +15 -6
  29. package/themes/metro/darkorange/ftable.min.css +1 -1
  30. package/themes/metro/green/ftable.css +15 -6
  31. package/themes/metro/green/ftable.min.css +1 -1
  32. package/themes/metro/lightgray/ftable.css +15 -6
  33. package/themes/metro/lightgray/ftable.min.css +1 -1
  34. package/themes/metro/pink/ftable.css +15 -6
  35. package/themes/metro/pink/ftable.min.css +1 -1
  36. package/themes/metro/purple/ftable.css +15 -6
  37. package/themes/metro/purple/ftable.min.css +1 -1
  38. package/themes/metro/red/ftable.css +15 -6
  39. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.js CHANGED
@@ -34,7 +34,8 @@
34
34
  printTable: '🖨️ Print',
35
35
  cloneRecord: 'Clone Record',
36
36
  resetTable: 'Reset table',
37
- resetTableConfirm: 'This will reset all columns, pagesize, sorting to their defaults. Do you want to continue?',
37
+ resetTableConfirm: 'This will reset column visibility, column widths and page size to their defaults. Do you want to continue?',
38
+ resetTableTooltip: 'Resets column visibility, column widths and page size to defaults. Sorting is not affected.',
38
39
  resetSearch: 'Reset'
39
40
  };
40
41
 
@@ -2018,7 +2019,8 @@ class FTable extends FTableEventEmitter {
2018
2019
  saveUserPreferences: true,
2019
2020
  saveUserPreferencesMethod: 'localStorage',
2020
2021
  defaultSorting: '',
2021
- tableReset: false,
2022
+ tableResetButton: false,
2023
+ sortingResetButton: false,
2022
2024
 
2023
2025
  // Paging
2024
2026
  paging: false,
@@ -2228,6 +2230,23 @@ class FTable extends FTableEventEmitter {
2228
2230
  this.createPageSizeSelector();
2229
2231
  }
2230
2232
 
2233
+ // Table reset button — resets column visibility/widths and pageSize (not sorting)
2234
+ if (this.options.tableResetButton) {
2235
+ const resetTableBtn = FTableDOMHelper.create('button', {
2236
+ className: 'ftable-toolbar-item ftable-table-reset-btn',
2237
+ textContent: this.options.messages.resetTable || 'Reset table',
2238
+ title: this.options.messages.resetTableTooltip || 'Resets column visibility, column widths and page size to defaults.',
2239
+ type: 'button',
2240
+ parent: this.elements.rightArea
2241
+ });
2242
+ resetTableBtn.addEventListener('click', (e) => {
2243
+ e.preventDefault();
2244
+ const msg = this.options.messages.resetTableConfirm;
2245
+ this.modals.resetTable.setContent(`<p>${msg}</p>`);
2246
+ this.modals.resetTable.show();
2247
+ });
2248
+ }
2249
+
2231
2250
  }
2232
2251
 
2233
2252
  createPageSizeSelector() {
@@ -3197,6 +3216,10 @@ class FTable extends FTableEventEmitter {
3197
3216
  this.createInfoModal();
3198
3217
  this.createLoadingModal();
3199
3218
 
3219
+ if (this.options.tableResetButton) {
3220
+ this.createResetTableModal();
3221
+ }
3222
+
3200
3223
  // Initialize them (create DOM) once
3201
3224
  Object.values(this.modals).forEach(modal => modal.create());
3202
3225
  }
@@ -3282,6 +3305,34 @@ class FTable extends FTableEventEmitter {
3282
3305
  });
3283
3306
  }
3284
3307
 
3308
+ createResetTableModal() {
3309
+ this.modals.resetTable = new FtableModal({
3310
+ parent: this.elements.mainContainer,
3311
+ title: this.options.messages.resetTable || 'Reset table',
3312
+ className: 'ftable-reset-table-modal',
3313
+ closeOnOverlayClick: this.options.closeOnOverlayClick,
3314
+ buttons: [
3315
+ {
3316
+ text: this.options.messages.cancel,
3317
+ className: 'ftable-dialog-cancelbutton',
3318
+ onClick: () => this.modals.resetTable.close()
3319
+ },
3320
+ {
3321
+ text: this.options.messages.yes,
3322
+ className: 'ftable-dialog-savebutton',
3323
+ onClick: () => {
3324
+ this.userPrefs.remove('column-settings');
3325
+ // Preserve current sorting, only reset column settings and pageSize
3326
+ this.userPrefs.set('table-state', JSON.stringify({
3327
+ sorting: this.state.sorting
3328
+ }));
3329
+ location.reload();
3330
+ }
3331
+ }
3332
+ ]
3333
+ });
3334
+ }
3335
+
3285
3336
  createErrorModal() {
3286
3337
  this.modals.error = new FtableModal({
3287
3338
  parent: this.elements.mainContainer,
@@ -3565,6 +3616,21 @@ class FTable extends FTableEventEmitter {
3565
3616
  });
3566
3617
  }
3567
3618
 
3619
+ // Sorting reset button — visible only when sorting differs from default
3620
+ if (this.options.sorting && this.options.sortingResetButton) {
3621
+ this.elements.sortingResetBtn = this.addToolbarButton({
3622
+ text: this.options.messages.resetSorting || 'Reset sorting',
3623
+ className: 'ftable-toolbar-item-sorting-reset',
3624
+ onClick: () => {
3625
+ this.state.sorting = [];
3626
+ this.updateSortingHeaders();
3627
+ this.load();
3628
+ this.saveState();
3629
+ }
3630
+ });
3631
+ FTableDOMHelper.hide(this.elements.sortingResetBtn); // hidden by default
3632
+ }
3633
+
3568
3634
  if (this.options.actions.createAction) {
3569
3635
  this.addToolbarButton({
3570
3636
  text: this.options.messages.addNewRecord,
@@ -4604,19 +4670,45 @@ class FTable extends FTableEventEmitter {
4604
4670
  }
4605
4671
 
4606
4672
  updateSortingHeaders() {
4607
- // Clear all sorting classes
4673
+ // Clear all sorting classes and remove any existing sort badges
4608
4674
  const headers = this.elements.table.querySelectorAll('.ftable-column-header-sortable');
4609
4675
  headers.forEach(header => {
4610
4676
  FTableDOMHelper.removeClass(header, 'ftable-column-header-sorted-asc ftable-column-header-sorted-desc');
4677
+ const existing = header.querySelector('.ftable-sort-badge');
4678
+ if (existing) existing.remove();
4611
4679
  });
4612
-
4613
- // Apply current sorting classes
4614
- this.state.sorting.forEach(sort => {
4680
+
4681
+ // Apply current sorting classes and sort number badges
4682
+ this.state.sorting.forEach((sort, index) => {
4615
4683
  const header = this.elements.table.querySelector(`[data-field-name="${sort.fieldName}"]`);
4616
- if (header) {
4617
- FTableDOMHelper.addClass(header, `ftable-column-header-sorted-${sort.direction.toLowerCase()}`);
4684
+ if (!header) return;
4685
+
4686
+ FTableDOMHelper.addClass(header, `ftable-column-header-sorted-${sort.direction.toLowerCase()}`);
4687
+
4688
+ // Sort number badge — only show when multisorting with more than 1 active sort
4689
+ if (this.options.multiSorting && this.state.sorting.length > 1) {
4690
+ const container = header.querySelector('.ftable-column-header-container');
4691
+ if (container) {
4692
+ FTableDOMHelper.create('span', {
4693
+ className: 'ftable-sort-badge',
4694
+ textContent: String(index + 1),
4695
+ parent: container
4696
+ });
4697
+ }
4618
4698
  }
4619
4699
  });
4700
+
4701
+ // Update visibility of the sorting reset toolbar button
4702
+ this._updateSortingResetButton();
4703
+ }
4704
+
4705
+ _updateSortingResetButton() {
4706
+ if (!this.elements.sortingResetBtn) return;
4707
+ if (this.state.sorting.length === 0) {
4708
+ FTableDOMHelper.hide(this.elements.sortingResetBtn);
4709
+ } else {
4710
+ FTableDOMHelper.show(this.elements.sortingResetBtn);
4711
+ }
4620
4712
  }
4621
4713
 
4622
4714
  // Paging Methods
@@ -5198,8 +5290,8 @@ class FTable extends FTableEventEmitter {
5198
5290
  // this.emit('columnVisibilityChanged', { field: field });
5199
5291
  }
5200
5292
 
5201
- // Responsive helpers
5202
5293
  /*
5294
+ // Responsive helpers
5203
5295
  makeResponsive() {
5204
5296
  // Add responsive classes and behavior
5205
5297
  FTableDOMHelper.addClass(this.elements.mainContainer, 'ftable-responsive');
@@ -5244,200 +5336,6 @@ class FTable extends FTableEventEmitter {
5244
5336
  });
5245
5337
  }
5246
5338
 
5247
- // Advanced search functionality
5248
- enableSearch(options = {}) {
5249
- const searchOptions = {
5250
- placeholder: 'Search...',
5251
- debounceMs: 300,
5252
- searchFields: this.columnList,
5253
- ...options
5254
- };
5255
-
5256
- const searchContainer = FTableDOMHelper.create('div', {
5257
- className: 'ftable-search-container',
5258
- parent: this.elements.toolbarDiv
5259
- });
5260
-
5261
- const searchInput = FTableDOMHelper.create('input', {
5262
- attributes: {
5263
- type: 'text',
5264
- placeholder: searchOptions.placeholder,
5265
- class: 'ftable-search-input'
5266
- },
5267
- parent: searchContainer
5268
- });
5269
-
5270
- // Debounced search
5271
- let searchTimeout;
5272
- searchInput.addEventListener('input', (e) => {
5273
- clearTimeout(searchTimeout);
5274
- searchTimeout = setTimeout(() => {
5275
- this.performSearch(e.target.value, searchOptions.searchFields);
5276
- }, searchOptions.debounceMs);
5277
- });
5278
-
5279
- return searchInput;
5280
- }
5281
-
5282
- async performSearch(query, searchFields) {
5283
- if (!query.trim()) {
5284
- return this.load(); // Clear search
5285
- }
5286
-
5287
- const searchParams = {
5288
- search: query,
5289
- searchFields: searchFields.join(',')
5290
- };
5291
-
5292
- return this.load(searchParams);
5293
- }
5294
-
5295
- // Keyboard shortcuts
5296
- enableKeyboardShortcuts() {
5297
- document.addEventListener('keydown', (e) => {
5298
- // Only handle shortcuts when table has focus or is active
5299
- if (!this.elements.mainContainer.contains(document.activeElement)) return;
5300
-
5301
- switch (e.key) {
5302
- case 'n':
5303
- if (e.ctrlKey && this.options.actions.createAction) {
5304
- e.preventDefault();
5305
- this.showAddRecordForm();
5306
- }
5307
- break;
5308
- case 'r':
5309
- if (e.ctrlKey) {
5310
- e.preventDefault();
5311
- this.reload();
5312
- }
5313
- break;
5314
- case 'Delete':
5315
- if (this.options.actions.deleteAction) {
5316
- const selectedRows = this.getSelectedRows();
5317
- if (selectedRows.length > 0) {
5318
- e.preventDefault();
5319
- this.bulkDelete();
5320
- }
5321
- }
5322
- break;
5323
- case 'a':
5324
- if (e.ctrlKey && this.options.selecting && this.options.multiselect) {
5325
- e.preventDefault();
5326
- this.toggleSelectAll(true);
5327
- }
5328
- break;
5329
- case 'Escape':
5330
- // Close any open modals
5331
- Object.values(this.modals).forEach(modal => {
5332
- if (modal.isOpen) modal.close();
5333
- });
5334
- break;
5335
- }
5336
- });
5337
- }
5338
-
5339
- // Real-time updates via WebSocket
5340
- enableRealTimeUpdates(websocketUrl) {
5341
- if (!websocketUrl) return;
5342
-
5343
- this.websocket = new WebSocket(websocketUrl);
5344
-
5345
- this.websocket.onmessage = (event) => {
5346
- try {
5347
- const data = JSON.parse(event.data);
5348
- this.handleRealTimeUpdate(data);
5349
- } catch (error) {
5350
- this.logger.error('Failed to parse WebSocket message', error);
5351
- }
5352
- };
5353
-
5354
- this.websocket.onerror = (error) => {
5355
- this.logger.error('WebSocket error', error);
5356
- };
5357
-
5358
- this.websocket.onclose = () => {
5359
- this.logger.info('WebSocket connection closed');
5360
- // Attempt to reconnect after delay
5361
- setTimeout(() => {
5362
- if (this.websocket.readyState === WebSocket.CLOSED) {
5363
- this.enableRealTimeUpdates(websocketUrl);
5364
- }
5365
- }, 5000);
5366
- };
5367
- }
5368
-
5369
- handleRealTimeUpdate(data) {
5370
- switch (data.type) {
5371
- case 'record_added':
5372
- this.addRecordToTable(data.record);
5373
- break;
5374
- case 'record_updated':
5375
- this.updateRecordInTable(data.record);
5376
- break;
5377
- case 'record_deleted':
5378
- this.removeRecordFromTable(data.recordKey);
5379
- break;
5380
- case 'refresh':
5381
- this.reload();
5382
- break;
5383
- }
5384
- }
5385
-
5386
- addRecordToTable(record) {
5387
- const row = this.createTableRow(record);
5388
-
5389
- // Add to beginning or end based on sorting
5390
- if (this.state.sorting.length > 0) {
5391
- // Would need to calculate correct position based on sort
5392
- this.elements.tableBody.appendChild(row);
5393
- } else {
5394
- this.elements.tableBody.appendChild(row);
5395
- }
5396
-
5397
- this.state.records.push(record);
5398
- this.removeNoDataRow();
5399
- this.refreshRowStyles();
5400
-
5401
- // Show animation
5402
- if (this.options.animationsEnabled) {
5403
- this.showRowAnimation(row, 'added');
5404
- }
5405
- }
5406
-
5407
- updateRecordInTable(record) {
5408
- const keyValue = this.getKeyValue(record);
5409
- const existingRow = this.getRowByKey(keyValue);
5410
-
5411
- if (existingRow) {
5412
- this.updateRowData(existingRow, record);
5413
-
5414
- if (this.options.animationsEnabled) {
5415
- this.showRowAnimation(existingRow, 'updated');
5416
- }
5417
- }
5418
- }
5419
-
5420
- removeRecordFromTable(keyValue) {
5421
- const row = this.getRowByKey(keyValue);
5422
- if (row) {
5423
- this.removeRowFromTable(row);
5424
-
5425
- // Remove from state
5426
- this.state.records = this.state.records.filter(r =>
5427
- this.getKeyValue(r) !== keyValue
5428
- );
5429
- }
5430
- }
5431
-
5432
- showRowAnimation(row, type) {
5433
- const animationClass = `ftable-row-${type}`;
5434
- FTableDOMHelper.addClass(row, animationClass);
5435
-
5436
- setTimeout(() => {
5437
- FTableDOMHelper.removeClass(row, animationClass);
5438
- }, 2000);
5439
- }
5440
-
5441
5339
  // Plugin system for extensions
5442
5340
  use(plugin, options = {}) {
5443
5341
  if (typeof plugin === 'function') {
@@ -5618,65 +5516,20 @@ class FTable extends FTableEventEmitter {
5618
5516
 
5619
5517
  const messages = this.options.messages || {};
5620
5518
 
5621
- // Get prefix/suffix if defined
5622
- const prefix = messages.sortingInfoPrefix ? `<span class="ftable-sorting-prefix">${messages.sortingInfoPrefix}</span> ` : '';
5623
- const suffix = messages.sortingInfoSuffix ? ` <span class="ftable-sorting-suffix">${messages.sortingInfoSuffix}</span>` : '';
5624
-
5625
5519
  if (this.state.sorting.length === 0) {
5626
5520
  container.innerHTML = messages.sortingInfoNone || '';
5627
5521
  return;
5628
5522
  }
5629
5523
 
5524
+ // Get prefix/suffix if defined
5525
+ const prefix = messages.sortingInfoPrefix ? `<span class="ftable-sorting-prefix">${messages.sortingInfoPrefix}</span> ` : '';
5526
+ const suffix = messages.sortingInfoSuffix ? ` <span class="ftable-sorting-suffix">${messages.sortingInfoSuffix}</span>` : '';
5527
+
5630
5528
  // Build sorted fields list with translated directions
5631
5529
  const sortingInfo = this.getSortingInfo();
5632
5530
 
5633
5531
  // Combine with prefix and suffix
5634
5532
  container.innerHTML = `${prefix}${sortingInfo}${suffix}`;
5635
-
5636
- // Add reset sorting button
5637
- if (this.state.sorting.length > 0) {
5638
- const resetSortBtn = document.createElement('button');
5639
- resetSortBtn.textContent = messages.resetSorting || 'Reset Sorting';
5640
- resetSortBtn.style.marginLeft = '10px';
5641
- resetSortBtn.classList.add('ftable-sorting-reset-btn');
5642
- resetSortBtn.addEventListener('click', (e) => {
5643
- e.preventDefault();
5644
- this.state.sorting = [];
5645
- this.updateSortingHeaders();
5646
- this.load();
5647
- this.saveState();
5648
- });
5649
- container.appendChild(resetSortBtn);
5650
- }
5651
-
5652
- // Add reset table button if enabled
5653
- if (this.options.tableReset) {
5654
- const resetTableBtn = document.createElement('button');
5655
- resetTableBtn.textContent = messages.resetTable || 'Reset Table';
5656
- resetTableBtn.style.marginLeft = '10px';
5657
- resetTableBtn.classList.add('ftable-table-reset-btn');
5658
- resetTableBtn.addEventListener('click', (e) => {
5659
- e.preventDefault();
5660
- const confirmMsg = messages.resetTableConfirm;
5661
- if (confirm(confirmMsg)) {
5662
- this.userPrefs.remove('column-settings');
5663
- this.userPrefs.remove('table-state');
5664
-
5665
- // Clear any in-memory state that might affect rendering
5666
- this.state.sorting = [];
5667
- this.state.pageSize = this.options.pageSize;
5668
-
5669
- // Reset field visibility to default
5670
- this.columnList.forEach(fieldName => {
5671
- const field = this.options.fields[fieldName];
5672
- // Reset to default: hidden only if explicitly set
5673
- field.visibility = field.visibility === 'fixed' ? 'fixed' : 'visible';
5674
- });
5675
- location.reload();
5676
- }
5677
- });
5678
- container.appendChild(resetTableBtn);
5679
- }
5680
5533
  }
5681
5534
 
5682
5535
  /**