@liedekef/ftable 1.1.11 → 1.1.13

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 (38) hide show
  1. package/ftable.esm.js +161 -81
  2. package/ftable.js +161 -81
  3. package/ftable.min.js +3 -3
  4. package/ftable.umd.js +161 -81
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +1 -0
  7. package/themes/basic/ftable_basic.min.css +1 -1
  8. package/themes/ftable_theme_base.less +1 -0
  9. package/themes/lightcolor/blue/ftable.css +1 -0
  10. package/themes/lightcolor/blue/ftable.min.css +1 -1
  11. package/themes/lightcolor/gray/ftable.css +1 -0
  12. package/themes/lightcolor/gray/ftable.min.css +1 -1
  13. package/themes/lightcolor/green/ftable.css +1 -0
  14. package/themes/lightcolor/green/ftable.min.css +1 -1
  15. package/themes/lightcolor/orange/ftable.css +1 -0
  16. package/themes/lightcolor/orange/ftable.min.css +1 -1
  17. package/themes/lightcolor/red/ftable.css +1 -0
  18. package/themes/lightcolor/red/ftable.min.css +1 -1
  19. package/themes/metro/blue/ftable.css +1 -0
  20. package/themes/metro/blue/ftable.min.css +1 -1
  21. package/themes/metro/brown/ftable.css +1 -0
  22. package/themes/metro/brown/ftable.min.css +1 -1
  23. package/themes/metro/crimson/ftable.css +1 -0
  24. package/themes/metro/crimson/ftable.min.css +1 -1
  25. package/themes/metro/darkgray/ftable.css +1 -0
  26. package/themes/metro/darkgray/ftable.min.css +1 -1
  27. package/themes/metro/darkorange/ftable.css +1 -0
  28. package/themes/metro/darkorange/ftable.min.css +1 -1
  29. package/themes/metro/green/ftable.css +1 -0
  30. package/themes/metro/green/ftable.min.css +1 -1
  31. package/themes/metro/lightgray/ftable.css +1 -0
  32. package/themes/metro/lightgray/ftable.min.css +1 -1
  33. package/themes/metro/pink/ftable.css +1 -0
  34. package/themes/metro/pink/ftable.min.css +1 -1
  35. package/themes/metro/purple/ftable.css +1 -0
  36. package/themes/metro/purple/ftable.min.css +1 -1
  37. package/themes/metro/red/ftable.css +1 -0
  38. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.js CHANGED
@@ -162,7 +162,11 @@ class FTableDOMHelper {
162
162
  if (options.className) {
163
163
  element.className = options.className;
164
164
  }
165
-
165
+
166
+ if (options.style) {
167
+ element.style.cssText = options.style;
168
+ }
169
+
166
170
  if (options.attributes) {
167
171
  Object.entries(options.attributes).forEach(([key, value]) => {
168
172
  element.setAttribute(key, value);
@@ -1498,6 +1502,50 @@ class FTable extends FTableEventEmitter {
1498
1502
 
1499
1503
  // Add essential CSS if not already present
1500
1504
  //this.addEssentialCSS();
1505
+
1506
+ // now make sure all tables have a % width
1507
+ this.initColumnWidths();
1508
+ }
1509
+
1510
+ initColumnWidths() {
1511
+ const visibleFields = this.columnList.filter(fieldName => {
1512
+ const field = this.options.fields[fieldName];
1513
+ return field.visibility !== 'hidden';
1514
+ });
1515
+
1516
+ const count = visibleFields.length;
1517
+ visibleFields.forEach(fieldName => {
1518
+ const field = this.options.fields[fieldName];
1519
+ // Use configured width or equal distribution
1520
+ //field.width = field.width || `${(100 / count).toFixed(2)}%`;
1521
+ field.width = field.width || `${(100 / count)}%`;
1522
+ });
1523
+ }
1524
+
1525
+ normalizeColumnWidths() {
1526
+ const container = this.elements.mainContainer;
1527
+ const visibleHeaders = this.columnList
1528
+ .map(fieldName => ({
1529
+ th: this.elements.table.querySelector(`[data-field-name="${fieldName}"]`),
1530
+ field: this.options.fields[fieldName]
1531
+ }))
1532
+ .filter(item => item.th && item.field.visibility !== 'hidden');
1533
+
1534
+ if (visibleHeaders.length === 0) return;
1535
+
1536
+ const totalContainerWidth = container.offsetWidth;
1537
+ let totalPercent = 0;
1538
+
1539
+ visibleHeaders.forEach(item => {
1540
+ const widthPct = (item.th.offsetWidth / totalContainerWidth) * 100;
1541
+ //item.field.width = `${widthPct.toFixed(2)}%`;
1542
+ item.field.width = `${widthPct}%`;
1543
+ item.th.style.width = item.field.width;
1544
+ totalPercent += widthPct;
1545
+ });
1546
+
1547
+ // Optional: adjust for rounding drift
1548
+ // (not critical, but can help)
1501
1549
  }
1502
1550
 
1503
1551
  parseDefaultSorting(sortStr) {
@@ -1660,25 +1708,13 @@ class FTable extends FTableEventEmitter {
1660
1708
  field.edit = true;
1661
1709
  field.type = 'hidden';
1662
1710
  }
1663
- if (!field.hasOwnProperty('visibility')) {
1664
- field.visibility = 'hidden';
1665
- }
1711
+ field.visibility = field.visibility ?? 'visible';
1666
1712
  } else {
1667
- if (field.create === undefined) {
1668
- field.create = true;
1669
- }
1670
- if (field.edit === undefined) {
1671
- field.edit = true;
1672
- }
1673
- if (field.list === undefined) {
1674
- field.list = true;
1675
- }
1676
- if (field.sorting === undefined) {
1677
- field.sorting = true;
1678
- }
1679
- if (!field.hasOwnProperty('visibility')) {
1680
- field.visibility = 'visible';
1681
- }
1713
+ field.create = field.create ?? true;
1714
+ field.edit = field.edit ?? true;
1715
+ field.list = field.list ?? true;
1716
+ field.sorting = field.sorting ?? true;
1717
+ field.visibility = field.visibility ?? 'visible';
1682
1718
  }
1683
1719
  });
1684
1720
 
@@ -1691,7 +1727,7 @@ class FTable extends FTableEventEmitter {
1691
1727
  // Find key field
1692
1728
  this.keyField = this.fieldList.find(name => this.options.fields[name].key === true);
1693
1729
  if (!this.keyField) {
1694
- this.logger.warn('No key field defined');
1730
+ this.logger.info('No key field defined');
1695
1731
  }
1696
1732
  }
1697
1733
 
@@ -1867,7 +1903,7 @@ class FTable extends FTableEventEmitter {
1867
1903
  }
1868
1904
 
1869
1905
  // Hide column if needed
1870
- if (field.visibility === 'hidden') {
1906
+ if (field.visibility === 'hidden' || field.visibility === 'separator') {
1871
1907
  FTableDOMHelper.hide(th);
1872
1908
  }
1873
1909
  });
@@ -1910,7 +1946,10 @@ class FTable extends FTableEventEmitter {
1910
1946
 
1911
1947
  // Add empty cell for selecting column if enabled
1912
1948
  if (this.options.selecting && this.options.selectingCheckboxes) {
1913
- FTableDOMHelper.create('th', { parent: searchRow });
1949
+ FTableDOMHelper.create('th', {
1950
+ className: 'ftable-toolbarsearch-column-header',
1951
+ parent: searchRow
1952
+ });
1914
1953
  }
1915
1954
 
1916
1955
  // Add search input cells for data columns
@@ -2045,7 +2084,7 @@ class FTable extends FTableEventEmitter {
2045
2084
  }
2046
2085
 
2047
2086
  // Hide search cell if column is hidden
2048
- if (field.visibility === 'hidden') {
2087
+ if (field.visibility === 'hidden' || field.visibility === 'separator') {
2049
2088
  FTableDOMHelper.hide(th);
2050
2089
  }
2051
2090
  }
@@ -2053,7 +2092,7 @@ class FTable extends FTableEventEmitter {
2053
2092
  if (this.options.toolbarsearch && this.options.toolbarreset) {
2054
2093
  // Add reset button cell
2055
2094
  const resetTh = FTableDOMHelper.create('th', {
2056
- className: 'ftable-toolbarsearch-reset',
2095
+ className: 'ftable-toolbarsearch-column-header ftable-toolbarsearch-reset',
2057
2096
  parent: searchRow
2058
2097
  });
2059
2098
 
@@ -2170,8 +2209,18 @@ class FTable extends FTableEventEmitter {
2170
2209
  this.load();
2171
2210
  }
2172
2211
 
2212
+ getNextVisibleHeader(th) {
2213
+ const headers = Array.from(this.elements.table.querySelectorAll('thead th:not(.ftable-command-column-header, .ftable-toolbarsearch-column-header)'));
2214
+ const index = headers.indexOf(th);
2215
+ for (let i = index + 1; i < headers.length; i++) {
2216
+ if (headers[i].offsetParent !== null) { // visible
2217
+ return headers[i];
2218
+ }
2219
+ }
2220
+ return null;
2221
+ }
2222
+
2173
2223
  makeColumnResizable(th, container) {
2174
- // Create resize bar if it doesn't exist
2175
2224
  if (!this.elements.resizeBar) {
2176
2225
  this.elements.resizeBar = FTableDOMHelper.create('div', {
2177
2226
  className: 'ftable-column-resize-bar',
@@ -2188,59 +2237,88 @@ class FTable extends FTableEventEmitter {
2188
2237
  let isResizing = false;
2189
2238
  let startX = 0;
2190
2239
  let startWidth = 0;
2240
+ let containerRect;
2241
+ let nextTh = null;
2242
+ let nextStartWidth = 0;
2243
+ let nextField = null;
2191
2244
 
2192
2245
  resizeHandler.addEventListener('mousedown', (e) => {
2193
2246
  e.preventDefault();
2194
2247
  e.stopPropagation();
2195
-
2248
+
2196
2249
  isResizing = true;
2250
+
2251
+ // Capture layout
2252
+ containerRect = this.elements.mainContainer.getBoundingClientRect();
2197
2253
  startX = e.clientX;
2198
2254
  startWidth = th.offsetWidth;
2199
-
2200
- // Show resize bar
2201
- const rect = th.getBoundingClientRect();
2202
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2203
-
2204
- this.elements.resizeBar.style.left = (rect.right - containerRect.left) + 'px';
2205
- this.elements.resizeBar.style.top = (rect.top - containerRect.top) + 'px';
2255
+
2256
+ // Find next visible column
2257
+ nextTh = this.getNextVisibleHeader(th);
2258
+ if (nextTh) {
2259
+ nextStartWidth = nextTh.offsetWidth;
2260
+ const fieldName = nextTh.dataset.fieldName;
2261
+ nextField = this.options.fields[fieldName];
2262
+ } else {
2263
+ return;
2264
+ }
2265
+
2266
+ // Position resize bar
2267
+ const thRect = th.getBoundingClientRect();
2268
+ this.elements.resizeBar.style.left = (thRect.right - containerRect.left) + 'px';
2269
+ this.elements.resizeBar.style.top = (thRect.top - containerRect.top) + 'px';
2206
2270
  this.elements.resizeBar.style.height = this.elements.table.offsetHeight + 'px';
2271
+
2207
2272
  FTableDOMHelper.show(this.elements.resizeBar);
2208
-
2273
+
2209
2274
  document.addEventListener('mousemove', handleMouseMove);
2210
2275
  document.addEventListener('mouseup', handleMouseUp);
2211
2276
  });
2212
2277
 
2213
2278
  const handleMouseMove = (e) => {
2214
2279
  if (!isResizing) return;
2215
-
2216
- const diff = e.clientX - startX;
2217
- const newWidth = Math.max(50, startWidth + diff); // Minimum 50px width
2218
-
2219
- // Update resize bar position
2220
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2280
+
2281
+ // Move resize bar with mouse
2221
2282
  this.elements.resizeBar.style.left = (e.clientX - containerRect.left) + 'px';
2222
2283
  };
2223
2284
 
2224
2285
  const handleMouseUp = (e) => {
2225
2286
  if (!isResizing) return;
2226
-
2227
2287
  isResizing = false;
2228
- const diff = e.clientX - startX;
2229
- const newWidth = Math.max(50, startWidth + diff);
2230
-
2231
- // Apply new width
2232
- th.style.width = newWidth + 'px';
2233
-
2234
- // Hide resize bar
2235
- FTableDOMHelper.hide(this.elements.resizeBar);
2236
-
2237
- document.removeEventListener('mousemove', handleMouseMove);
2238
- document.removeEventListener('mouseup', handleMouseUp);
2239
-
2240
- // Save column width preference if enabled
2288
+
2289
+ const diff = e.clientX - startX; // px
2290
+ const totalWidth = containerRect.width;
2291
+
2292
+ // Current column new width in px
2293
+ const newCurrentPx = Math.max(50, startWidth + diff);
2294
+ const newCurrentPct = (newCurrentPx / totalWidth) * 100;
2295
+
2296
+ // Next column adjustment
2297
+ if (nextTh) {
2298
+ const newNextPx = Math.max(50, nextStartWidth - diff); // opposite delta
2299
+ const newNextPct = (newNextPx / totalWidth) * 100;
2300
+
2301
+ // Apply to next
2302
+ nextField.width = `${newNextPct.toFixed(2)}%`;
2303
+ nextTh.style.width = nextField.width;
2304
+ }
2305
+
2306
+ // Apply to current
2307
+ const field = this.options.fields[th.dataset.fieldName];
2308
+ field.width = `${newCurrentPct.toFixed(2)}%`;
2309
+ th.style.width = field.width;
2310
+
2311
+ // Final normalization (optional, but safe)
2312
+ this.normalizeColumnWidths();
2313
+
2314
+ // Save
2241
2315
  if (this.options.saveUserPreferences) {
2242
2316
  this.saveColumnSettings();
2243
2317
  }
2318
+
2319
+ FTableDOMHelper.hide(this.elements.resizeBar);
2320
+ document.removeEventListener('mousemove', handleMouseMove);
2321
+ document.removeEventListener('mouseup', handleMouseUp);
2244
2322
  };
2245
2323
  }
2246
2324
 
@@ -2471,8 +2549,8 @@ class FTable extends FTableEventEmitter {
2471
2549
  this.subscribeOptionEvents();
2472
2550
 
2473
2551
  // Add toolbar buttons
2474
- this.createToolbarButtons();
2475
2552
  this.createCustomToolbarItems();
2553
+ this.createToolbarButtons();
2476
2554
 
2477
2555
  // Keyboard shortcuts
2478
2556
  this.bindKeyboardEvents();
@@ -2569,6 +2647,7 @@ class FTable extends FTableEventEmitter {
2569
2647
  const field = this.options.fields[fieldName];
2570
2648
  const isVisible = field.visibility !== 'hidden';
2571
2649
  const isFixed = field.visibility === 'fixed';
2650
+ const isSeparator = field.visibility === 'separator';
2572
2651
  const isSorted = this.isFieldSorted(fieldName);
2573
2652
 
2574
2653
  const listItem = FTableDOMHelper.create('li', {
@@ -2581,24 +2660,32 @@ class FTable extends FTableEventEmitter {
2581
2660
  parent: listItem
2582
2661
  });
2583
2662
 
2584
- const checkbox = FTableDOMHelper.create('input', {
2585
- attributes: {
2586
- type: 'checkbox',
2587
- id: `column-${fieldName}`
2588
- },
2589
- parent: label
2590
- });
2591
-
2592
- checkbox.checked = isVisible;
2663
+ if (!isSeparator) {
2664
+ const checkbox = FTableDOMHelper.create('input', {
2665
+ attributes: {
2666
+ type: 'checkbox',
2667
+ id: `column-${fieldName}`
2668
+ },
2669
+ parent: label
2670
+ });
2671
+ checkbox.checked = isVisible;
2593
2672
 
2594
- // Disable checkbox if column is fixed or currently sorted
2595
- if (isFixed || (isSorted && isVisible)) {
2596
- checkbox.disabled = true;
2597
- listItem.style.opacity = '0.6';
2673
+ // Disable checkbox if column is fixed or currently sorted
2674
+ if (isFixed || (isSorted && isVisible)) {
2675
+ checkbox.disabled = true;
2676
+ listItem.style.opacity = '0.6';
2677
+ }
2678
+ // Handle checkbox change
2679
+ if (!checkbox.disabled) {
2680
+ checkbox.addEventListener('change', () => {
2681
+ this.setColumnVisibility(fieldName, checkbox.checked);
2682
+ });
2683
+ }
2598
2684
  }
2599
2685
 
2600
2686
  const labelText = FTableDOMHelper.create('span', {
2601
2687
  text: field.title || fieldName,
2688
+ style: isSeparator ? 'font-weight: bold;' : null,
2602
2689
  parent: label
2603
2690
  });
2604
2691
 
@@ -2613,12 +2700,6 @@ class FTable extends FTableEventEmitter {
2613
2700
  sortIndicator.style.color = '#666';
2614
2701
  }
2615
2702
 
2616
- // Handle checkbox change
2617
- if (!checkbox.disabled) {
2618
- checkbox.addEventListener('change', () => {
2619
- this.setColumnVisibility(fieldName, checkbox.checked);
2620
- });
2621
- }
2622
2703
  });
2623
2704
  }
2624
2705
 
@@ -2726,7 +2807,7 @@ class FTable extends FTableEventEmitter {
2726
2807
  if (!this.options.toolbar || !this.options.toolbar.items) return;
2727
2808
 
2728
2809
  this.options.toolbar.items.forEach(item => {
2729
- const button = FTableDOMHelper.create('button', {
2810
+ const button = FTableDOMHelper.create('span', {
2730
2811
  className: `ftable-toolbar-item ftable-toolbar-item-custom ${item.buttonClass || ''}`,
2731
2812
  parent: this.elements.toolbarDiv
2732
2813
  });
@@ -2754,6 +2835,7 @@ class FTable extends FTableEventEmitter {
2754
2835
  if (item.text) {
2755
2836
  FTableDOMHelper.create('span', {
2756
2837
  text: item.text,
2838
+ className: `ftable-toolbar-item-text ftable-toolbar-item-custom-text ${item.buttonTextClass || ''}`,
2757
2839
  parent: button
2758
2840
  });
2759
2841
  }
@@ -2823,6 +2905,7 @@ class FTable extends FTableEventEmitter {
2823
2905
  // Update sorting display
2824
2906
  this.renderSortingInfo();
2825
2907
 
2908
+ this.normalizeColumnWidths();
2826
2909
  }
2827
2910
 
2828
2911
  buildLoadParams() {
@@ -3512,7 +3595,7 @@ class FTable extends FTableEventEmitter {
3512
3595
  if (this.options.selectOnRowClick !== false) {
3513
3596
  row.addEventListener('click', (e) => {
3514
3597
  // input elements can't select the row, nor norowselectonclick class
3515
- if (!['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(e.target.tagName) &&
3598
+ if (!['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA', 'A'].includes(e.target.tagName) &&
3516
3599
  !e.target.classList.contains('norowselectonclick')) {
3517
3600
  this.toggleRowSelection(row);
3518
3601
  }
@@ -3905,11 +3988,8 @@ class FTable extends FTableEventEmitter {
3905
3988
  }
3906
3989
 
3907
3990
  // Public API Methods
3908
- reload(hard = false) {
3909
- if (hard) {
3910
- // Clear list cache
3911
- this.clearListCache();
3912
- }
3991
+ reload() {
3992
+ this.clearListCache();
3913
3993
  return this.load();
3914
3994
  }
3915
3995