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