@liedekef/ftable 1.0.0 → 1.1.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.
package/ftable.umd.js CHANGED
@@ -654,10 +654,6 @@ class FTableFormBuilder {
654
654
  }
655
655
  });
656
656
 
657
- /*if (this.options.formCreated) {
658
- this.options.formCreated(form, formType, record);
659
- }*/
660
-
661
657
  // Set up dependency listeners after all fields are created
662
658
  this.setupDependencyListeners(form);
663
659
 
@@ -1336,7 +1332,7 @@ class FTable extends FTableEventEmitter {
1336
1332
 
1337
1333
  this.options = this.mergeOptions(options);
1338
1334
  this.logger = new FTableLogger(this.options.logLevel);
1339
- this.userPrefs = new FTableUserPreferences('', this.options.saveFTableUserPreferencesMethod);
1335
+ this.userPrefs = new FTableUserPreferences('', this.options.saveUserPreferencesMethod);
1340
1336
  this.formBuilder = new FTableFormBuilder(this.options, this);
1341
1337
 
1342
1338
  this.state = {
@@ -1352,6 +1348,7 @@ class FTable extends FTableEventEmitter {
1352
1348
  this.elements = {};
1353
1349
  this.modals = {};
1354
1350
  this.searchTimeout = null; // For debouncing
1351
+ this.lastSortEvent = null;
1355
1352
  this._recalculatedOnce = false;
1356
1353
 
1357
1354
  // store it on the DOM too, so people can access it
@@ -1369,23 +1366,28 @@ class FTable extends FTableEventEmitter {
1369
1366
  animationsEnabled: true,
1370
1367
  loadingAnimationDelay: 1000,
1371
1368
  defaultDateFormat: 'yyyy-mm-dd',
1372
- saveFTableUserPreferences: true,
1373
- saveFTableUserPreferencesMethod: 'localStorage',
1369
+ saveUserPreferences: true,
1370
+ saveUserPreferencesMethod: 'localStorage',
1374
1371
  defaultSorting: '',
1375
1372
 
1376
1373
  // Paging
1377
1374
  paging: false,
1375
+ pageList: 'normal',
1378
1376
  pageSize: 10,
1379
1377
  gotoPageArea: 'combobox',
1380
1378
 
1381
1379
  // Sorting
1382
1380
  sorting: false,
1383
1381
  multiSorting: false,
1382
+ multiSortingCtrlKey: true,
1384
1383
 
1385
1384
  // Selection
1386
1385
  selecting: false,
1387
1386
  multiselect: false,
1388
1387
 
1388
+ // child tables
1389
+ openChildAsAccordion: false,
1390
+
1389
1391
  // Toolbar search
1390
1392
  toolbarsearch: false, // Enable/disable toolbar search row
1391
1393
  toolbarreset: true, // Show reset button
@@ -1622,6 +1624,9 @@ class FTable extends FTableEventEmitter {
1622
1624
  if (field.list === undefined) {
1623
1625
  field.list = true;
1624
1626
  }
1627
+ if (field.sorting === undefined) {
1628
+ field.sorting = true;
1629
+ }
1625
1630
  if (!field.hasOwnProperty('visibility')) {
1626
1631
  field.visibility = 'visible';
1627
1632
  }
@@ -1749,9 +1754,9 @@ class FTable extends FTableEventEmitter {
1749
1754
  });
1750
1755
 
1751
1756
  // Add selecting column if enabled
1752
- if (this.options.selecting) {
1757
+ if (this.options.selecting && this.options.selectingCheckboxes) {
1753
1758
  const selectHeader = FTableDOMHelper.create('th', {
1754
- className: 'ftable-column-header ftable-column-header-select',
1759
+ className: `ftable-column-header ftable-column-header-select`,
1755
1760
  parent: headerRow
1756
1761
  });
1757
1762
 
@@ -1771,7 +1776,7 @@ class FTable extends FTableEventEmitter {
1771
1776
  this.columnList.forEach(fieldName => {
1772
1777
  const field = this.options.fields[fieldName];
1773
1778
  const th = FTableDOMHelper.create('th', {
1774
- className: 'ftable-column-header',
1779
+ className: `ftable-column-header ${field.listClass || ''} ${field.listClassHeader || ''}`,
1775
1780
  attributes: { 'data-field-name': fieldName },
1776
1781
  parent: headerRow
1777
1782
  });
@@ -1786,6 +1791,10 @@ class FTable extends FTableEventEmitter {
1786
1791
  parent: th
1787
1792
  });
1788
1793
 
1794
+ if (field.tooltip) {
1795
+ container.setAttribute('title', field.tooltip);
1796
+ }
1797
+
1789
1798
  FTableDOMHelper.create('span', {
1790
1799
  className: 'ftable-column-header-text',
1791
1800
  text: field.title || fieldName,
@@ -1795,7 +1804,12 @@ class FTable extends FTableEventEmitter {
1795
1804
  // Make sortable if enabled
1796
1805
  if (this.options.sorting && field.sorting !== false) {
1797
1806
  FTableDOMHelper.addClass(th, 'ftable-column-header-sortable');
1798
- th.addEventListener('click', () => this.sortByColumn(fieldName));
1807
+ th.addEventListener('click', (e) => {
1808
+ e.preventDefault();
1809
+ // Store event for multiSortingCtrlKey logic
1810
+ this.lastSortEvent = e;
1811
+ this.sortByColumn(fieldName);
1812
+ });
1799
1813
  }
1800
1814
 
1801
1815
  // Add resize handler if column resizing is enabled
@@ -1846,7 +1860,7 @@ class FTable extends FTableEventEmitter {
1846
1860
  });
1847
1861
 
1848
1862
  // Add empty cell for selecting column if enabled
1849
- if (this.options.selecting) {
1863
+ if (this.options.selecting && this.options.selectingCheckboxes) {
1850
1864
  FTableDOMHelper.create('th', { parent: searchRow });
1851
1865
  }
1852
1866
 
@@ -2132,14 +2146,14 @@ class FTable extends FTableEventEmitter {
2132
2146
  document.removeEventListener('mouseup', handleMouseUp);
2133
2147
 
2134
2148
  // Save column width preference if enabled
2135
- if (this.options.saveFTableUserPreferences) {
2149
+ if (this.options.saveUserPreferences) {
2136
2150
  this.saveColumnSettings();
2137
2151
  }
2138
2152
  };
2139
2153
  }
2140
2154
 
2141
2155
  saveColumnSettings() {
2142
- if (!this.options.saveFTableUserPreferences) return;
2156
+ if (!this.options.saveUserPreferences) return;
2143
2157
 
2144
2158
  const settings = {};
2145
2159
  this.columnList.forEach(fieldName => {
@@ -2157,7 +2171,7 @@ class FTable extends FTableEventEmitter {
2157
2171
  }
2158
2172
 
2159
2173
  saveState() {
2160
- if (!this.options.saveFTableUserPreferences) return;
2174
+ if (!this.options.saveUserPreferences) return;
2161
2175
 
2162
2176
  const state = {
2163
2177
  sorting: this.state.sorting,
@@ -2168,7 +2182,7 @@ class FTable extends FTableEventEmitter {
2168
2182
  }
2169
2183
 
2170
2184
  loadColumnSettings() {
2171
- if (!this.options.saveFTableUserPreferences) return;
2185
+ if (!this.options.saveUserPreferences) return;
2172
2186
 
2173
2187
  const settingsJson = this.userPrefs.get('column-settings');
2174
2188
  if (!settingsJson) return;
@@ -2188,7 +2202,7 @@ class FTable extends FTableEventEmitter {
2188
2202
  }
2189
2203
 
2190
2204
  loadState() {
2191
- if (!this.options.saveFTableUserPreferences) return;
2205
+ if (!this.options.saveUserPreferences) return;
2192
2206
 
2193
2207
  const stateJson = this.userPrefs.get('table-state');
2194
2208
  if (!stateJson) return;
@@ -2267,9 +2281,6 @@ class FTable extends FTableEventEmitter {
2267
2281
  onClick: () => {
2268
2282
  this.modals.addRecord.close();
2269
2283
  this.emit('formClosed', { form: this.currentForm, formType: 'create', record: null });
2270
- /*if (this.options.formClosed) {
2271
- this.options.formClosed(this.currentForm, 'create', null);
2272
- }*/
2273
2284
  }
2274
2285
  },
2275
2286
  {
@@ -2622,6 +2633,11 @@ class FTable extends FTableEventEmitter {
2622
2633
  parent: this.elements.toolbarDiv
2623
2634
  });
2624
2635
 
2636
+ // Add title/tooltip if provided
2637
+ if (item.tooltip) {
2638
+ button.setAttribute('title', item.tooltip);
2639
+ }
2640
+
2625
2641
  // Add icon if provided
2626
2642
  if (item.icon) {
2627
2643
  const img = FTableDOMHelper.create('img', {
@@ -2684,12 +2700,12 @@ class FTable extends FTableEventEmitter {
2684
2700
  }
2685
2701
 
2686
2702
  setupFTableUserPreferences() {
2687
- if (this.options.saveFTableUserPreferences) {
2703
+ if (this.options.saveUserPreferences) {
2688
2704
  const prefix = this.userPrefs.generatePrefix(
2689
2705
  this.options.tableId || '',
2690
2706
  this.fieldList
2691
2707
  );
2692
- this.userPrefs = new FTableUserPreferences(prefix, this.options.saveFTableUserPreferencesMethod);
2708
+ this.userPrefs = new FTableUserPreferences(prefix, this.options.saveUserPreferencesMethod);
2693
2709
 
2694
2710
  // Load saved column settings
2695
2711
  this.loadState();
@@ -2861,7 +2877,7 @@ class FTable extends FTableEventEmitter {
2861
2877
  row.recordData = record;
2862
2878
 
2863
2879
  // Add selecting checkbox if enabled
2864
- if (this.options.selecting) {
2880
+ if (this.options.selecting && this.options.selectingCheckboxes) {
2865
2881
  this.addSelectingCell(row);
2866
2882
  }
2867
2883
 
@@ -3086,10 +3102,7 @@ class FTable extends FTableEventEmitter {
3086
3102
  this.modals.addRecord.close();
3087
3103
 
3088
3104
  // Call formClosed
3089
- // this.emit('formClosed', { form: this.currentForm, formType: 'create', record: null });
3090
- if (this.options.formClosed) {
3091
- this.options.formClosed(this.currentForm, 'create', null);
3092
- }
3105
+ this.emit('formClosed', { form: this.currentForm, formType: 'create', record: null });
3093
3106
 
3094
3107
  if (result.Message) {
3095
3108
  this.showInfo(result.Message);
@@ -3136,17 +3149,14 @@ class FTable extends FTableEventEmitter {
3136
3149
  this.modals.editRecord.close();
3137
3150
 
3138
3151
  // Call formClosed
3139
- // this.emit('formClosed', { form: this.currentForm, formType: 'edit', record: result.Record || formData });
3140
- if (this.options.formClosed) {
3141
- this.options.formClosed(this.currentForm, 'edit', this.currentEditingRow.recordData);
3142
- }
3152
+ this.emit('formClosed', { form: this.currentForm, formType: 'edit', record: this.currentEditingRow.recordData });
3143
3153
 
3144
3154
  // Update the row with new data
3145
3155
  this.updateRowData(this.currentEditingRow, result.Record || formData);
3146
3156
  if (result.Message) {
3147
3157
  this.showInfo(result.Message);
3148
3158
  }
3149
- this.emit('recordUpdated', { record: result.Record || formData });
3159
+ this.emit('recordUpdated', { record: result.Record || formData, row: this.currentEditingRow });
3150
3160
  } else {
3151
3161
  this.showError(result.Message || 'Update failed');
3152
3162
  }
@@ -3452,25 +3462,42 @@ class FTable extends FTableEventEmitter {
3452
3462
 
3453
3463
  // Sorting Methods
3454
3464
  sortByColumn(fieldName) {
3465
+ const field = this.options.fields[fieldName];
3466
+
3467
+ if (!field || field.sorting === false) return;
3468
+
3455
3469
  const existingSortIndex = this.state.sorting.findIndex(s => s.fieldName === fieldName);
3456
-
3457
- if (!this.options.multiSorting) {
3458
- this.state.sorting = [];
3459
- }
3460
-
3470
+ let isSorted = true;
3471
+ let newDirection = 'ASC';
3461
3472
  if (existingSortIndex >= 0) {
3462
- const currentSort = this.state.sorting[existingSortIndex];
3463
- if (currentSort.direction === 'ASC') {
3464
- currentSort.direction = 'DESC';
3473
+ const wasAsc = this.state.sorting[existingSortIndex].direction === 'ASC';
3474
+ if (wasAsc) {
3475
+ newDirection = 'DESC';
3476
+ this.state.sorting[existingSortIndex].direction = newDirection;
3465
3477
  } else {
3466
- this.state.sorting.splice(existingSortIndex, 1);
3478
+ this.state.sorting.splice(existingSortIndex,1);
3479
+ isSorted = false;
3467
3480
  }
3468
3481
  } else {
3469
- this.state.sorting.push({ fieldName, direction: 'ASC' });
3482
+ this.state.sorting.push({ fieldName, direction: newDirection });
3470
3483
  }
3471
-
3484
+
3485
+ // Handle multiSortingCtrlKey: did user press Ctrl/Cmd?
3486
+ const isCtrlPressed = this.lastSortEvent?.ctrlKey || this.lastSortEvent?.metaKey; // metaKey for Mac
3487
+
3488
+ if (this.options.multiSorting) {
3489
+ // If multiSorting is enabled, respect multiSortingCtrlKey
3490
+ if (this.options.multiSortingCtrlKey && !isCtrlPressed) {
3491
+ // Not using Ctrl → treat as single sort (clear others)
3492
+ this.state.sorting = isSorted ? [{ fieldName, direction: newDirection }] : [];
3493
+ }
3494
+ } else {
3495
+ // If multiSorting is disabled, always clear other sorts
3496
+ this.state.sorting = isSorted ? [{ fieldName, direction: newDirection }] : [];
3497
+ }
3498
+
3472
3499
  this.updateSortingHeaders();
3473
- this.load(); // Reload with new sorting
3500
+ this.load();
3474
3501
  this.saveState();
3475
3502
  }
3476
3503
 
@@ -3529,27 +3556,29 @@ class FTable extends FTableEventEmitter {
3529
3556
  this.createPageButton('‹', this.state.currentPage - 1, this.state.currentPage === 1, 'ftable-page-number-previous');
3530
3557
 
3531
3558
  // Page numbers
3532
- const pageNumbers = this.calculatePageNumbers(totalPages);
3533
- let lastNumber = 0;
3534
-
3535
- pageNumbers.forEach(pageNum => {
3536
- if (pageNum - lastNumber > 1) {
3537
- FTableDOMHelper.create('span', {
3538
- className: 'ftable-page-number-space',
3539
- text: '...',
3540
- parent: this.elements.pagingListArea
3541
- });
3542
- }
3543
-
3544
- this.createPageButton(
3545
- pageNum.toString(),
3546
- pageNum,
3547
- false,
3548
- pageNum === this.state.currentPage ? 'ftable-page-number ftable-page-number-active' : 'ftable-page-number'
3549
- );
3550
-
3551
- lastNumber = pageNum;
3552
- });
3559
+ if (this.options.pageList == 'normal') {
3560
+ const pageNumbers = this.calculatePageNumbers(totalPages);
3561
+ let lastNumber = 0;
3562
+
3563
+ pageNumbers.forEach(pageNum => {
3564
+ if (pageNum - lastNumber > 1) {
3565
+ FTableDOMHelper.create('span', {
3566
+ className: 'ftable-page-number-space',
3567
+ text: '...',
3568
+ parent: this.elements.pagingListArea
3569
+ });
3570
+ }
3571
+
3572
+ this.createPageButton(
3573
+ pageNum.toString(),
3574
+ pageNum,
3575
+ false,
3576
+ pageNum === this.state.currentPage ? 'ftable-page-number ftable-page-number-active' : 'ftable-page-number'
3577
+ );
3578
+
3579
+ lastNumber = pageNum;
3580
+ });
3581
+ }
3553
3582
 
3554
3583
  // Next and Last buttons
3555
3584
  this.createPageButton('›', this.state.currentPage + 1, this.state.currentPage >= totalPages, 'ftable-page-number-next');
@@ -3957,7 +3986,7 @@ class FTable extends FTableEventEmitter {
3957
3986
  if (columnIndex >= 0) {
3958
3987
  // Calculate actual column index (accounting for selecting column)
3959
3988
  let actualIndex = columnIndex + 1; // CSS nth-child is 1-based
3960
- if (this.options.selecting) {
3989
+ if (this.options.selecting && this.options.selectingCheckboxes) {
3961
3990
  actualIndex += 1; // Account for selecting column
3962
3991
  }
3963
3992
 
@@ -3974,7 +4003,7 @@ class FTable extends FTableEventEmitter {
3974
4003
  }
3975
4004
 
3976
4005
  // Save column settings
3977
- if (this.options.saveFTableUserPreferences) {
4006
+ if (this.options.saveUserPreferences) {
3978
4007
  this.saveColumnSettings();
3979
4008
  this.saveState(); // sorting might affect state
3980
4009
  }
@@ -4172,27 +4201,6 @@ class FTable extends FTableEventEmitter {
4172
4201
  });
4173
4202
  }
4174
4203
 
4175
- // Performance optimization for large datasets
4176
- enableVirtualScrolling(options = {}) {
4177
- const virtualOptions = {
4178
- rowHeight: 40,
4179
- overscan: 5,
4180
- ...options
4181
- };
4182
-
4183
- // This would implement virtual scrolling for performance with large datasets
4184
- // Simplified version - full implementation would be more complex
4185
- this.virtualScrolling = {
4186
- enabled: true,
4187
- ...virtualOptions,
4188
- visibleRange: { start: 0, end: 0 },
4189
- scrollContainer: null
4190
- };
4191
-
4192
- // Replace table body with virtual scroll container
4193
- // Implementation would calculate visible rows and only render those
4194
- }
4195
-
4196
4204
  // Real-time updates via WebSocket
4197
4205
  enableRealTimeUpdates(websocketUrl) {
4198
4206
  if (!websocketUrl) return;
@@ -4316,6 +4324,15 @@ class FTable extends FTableEventEmitter {
4316
4324
  return this;
4317
4325
  }
4318
4326
 
4327
+ editRecordByKey(keyValue) {
4328
+ const row = this.getRowByKey(keyValue);
4329
+ if (row) {
4330
+ this.editRecord(row);
4331
+ } else {
4332
+ this.showError(`Record with key '${keyValue}' not found`);
4333
+ }
4334
+ }
4335
+
4319
4336
  async editRecordViaAjax(recordId, url, params = {}) {
4320
4337
  try {
4321
4338
  // Get the actual key field name (e.g., 'asset_id', 'user_id', etc.)
@@ -4355,6 +4372,10 @@ class FTable extends FTableEventEmitter {
4355
4372
  }
4356
4373
 
4357
4374
  openChildTable(parentRow, childOptions, onInit) {
4375
+ // Close any open child tables if accordion mode
4376
+ if (this.options.openChildAsAccordion) {
4377
+ this.closeAllChildTables();
4378
+ }
4358
4379
  // Prevent multiple child tables
4359
4380
  this.closeChildTable(parentRow);
4360
4381
 
@@ -4423,6 +4444,31 @@ class FTable extends FTableEventEmitter {
4423
4444
  }
4424
4445
  }
4425
4446
 
4447
+ closeAllChildTables() {
4448
+ Object.values(this.elements.tableRows).forEach(row => {
4449
+ if (row.childTable) {
4450
+ this.closeChildTable(row);
4451
+ }
4452
+ });
4453
+ }
4454
+
4455
+ getSortingInfo() {
4456
+ // Build sorted fields list with translated directions
4457
+ const messages = this.options.messages || {};
4458
+ const sortingInfo = this.state.sorting.map(s => {
4459
+ const field = this.options.fields[s.fieldName];
4460
+ const title = field?.title || s.fieldName;
4461
+
4462
+ // Translate direction
4463
+ const directionText = s.direction === 'ASC'
4464
+ ? (messages.ascending || 'ascending')
4465
+ : (messages.descending || 'descending');
4466
+
4467
+ return `${title} (${directionText})`;
4468
+ }).join(', ');
4469
+ return sortingInfo;
4470
+ }
4471
+
4426
4472
  renderSortingInfo() {
4427
4473
  if (!this.options.sortingInfoSelector || !this.options.sorting) return;
4428
4474
 
@@ -4444,20 +4490,10 @@ class FTable extends FTableEventEmitter {
4444
4490
  }
4445
4491
 
4446
4492
  // Build sorted fields list with translated directions
4447
- const sortedItems = this.state.sorting.map(s => {
4448
- const field = this.options.fields[s.fieldName];
4449
- const title = field?.title || s.fieldName;
4450
-
4451
- // Translate direction
4452
- const directionText = s.direction === 'ASC'
4453
- ? (messages.ascending || 'ascending')
4454
- : (messages.descending || 'descending');
4455
-
4456
- return `${title} (${directionText})`;
4457
- }).join(', ');
4493
+ const sortingInfo = this.getSortingInfo();
4458
4494
 
4459
4495
  // Combine with prefix and suffix
4460
- container.innerHTML = `${prefix}${sortedItems}${suffix}`;
4496
+ container.innerHTML = `${prefix}${sortingInfo}${suffix}`;
4461
4497
 
4462
4498
  // Add reset sorting button
4463
4499
  if (this.state.sorting.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liedekef/ftable",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Modern, lightweight, jQuery-free CRUD table for dynamic AJAX-powered tables.",
5
5
  "main": "ftable.js",
6
6
  "module": "ftable.esm.js",