@liedekef/ftable 1.1.48 → 1.1.49

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 +467 -11
  2. package/ftable.js +467 -11
  3. package/ftable.min.js +3 -3
  4. package/ftable.umd.js +467 -11
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +141 -0
  7. package/themes/basic/ftable_basic.min.css +1 -1
  8. package/themes/ftable_theme_base.less +166 -0
  9. package/themes/lightcolor/blue/ftable.css +141 -0
  10. package/themes/lightcolor/blue/ftable.min.css +1 -1
  11. package/themes/lightcolor/gray/ftable.css +141 -0
  12. package/themes/lightcolor/gray/ftable.min.css +1 -1
  13. package/themes/lightcolor/green/ftable.css +141 -0
  14. package/themes/lightcolor/green/ftable.min.css +1 -1
  15. package/themes/lightcolor/orange/ftable.css +141 -0
  16. package/themes/lightcolor/orange/ftable.min.css +1 -1
  17. package/themes/lightcolor/red/ftable.css +141 -0
  18. package/themes/lightcolor/red/ftable.min.css +1 -1
  19. package/themes/metro/blue/ftable.css +141 -0
  20. package/themes/metro/blue/ftable.min.css +1 -1
  21. package/themes/metro/brown/ftable.css +141 -0
  22. package/themes/metro/brown/ftable.min.css +1 -1
  23. package/themes/metro/crimson/ftable.css +141 -0
  24. package/themes/metro/crimson/ftable.min.css +1 -1
  25. package/themes/metro/darkgray/ftable.css +141 -0
  26. package/themes/metro/darkgray/ftable.min.css +1 -1
  27. package/themes/metro/darkorange/ftable.css +141 -0
  28. package/themes/metro/darkorange/ftable.min.css +1 -1
  29. package/themes/metro/green/ftable.css +141 -0
  30. package/themes/metro/green/ftable.min.css +1 -1
  31. package/themes/metro/lightgray/ftable.css +141 -0
  32. package/themes/metro/lightgray/ftable.min.css +1 -1
  33. package/themes/metro/pink/ftable.css +141 -0
  34. package/themes/metro/pink/ftable.min.css +1 -1
  35. package/themes/metro/purple/ftable.css +141 -0
  36. package/themes/metro/purple/ftable.min.css +1 -1
  37. package/themes/metro/red/ftable.css +141 -0
  38. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.esm.js CHANGED
@@ -1374,10 +1374,10 @@ class FTableFormBuilder {
1374
1374
 
1375
1375
  // extra check for name and multiple
1376
1376
  let name = fieldName;
1377
+ let hasMultiple = false;
1378
+
1377
1379
  // Apply inputAttributes from field definition
1378
1380
  if (field.inputAttributes) {
1379
- let hasMultiple = false;
1380
-
1381
1381
  const parsed = this.parseInputAttributes(field.inputAttributes);
1382
1382
  Object.assign(attributes, parsed);
1383
1383
 
@@ -1387,6 +1387,13 @@ class FTableFormBuilder {
1387
1387
  name = `${fieldName}[]`;
1388
1388
  }
1389
1389
  }
1390
+
1391
+ // If multiple select, create custom UI
1392
+ if (hasMultiple) {
1393
+ return this.createCustomMultiSelect(fieldName, field, value, attributes, name);
1394
+ }
1395
+
1396
+ // Standard single select
1390
1397
  attributes.name = name;
1391
1398
 
1392
1399
  const select = FTableDOMHelper.create('select', {
@@ -1403,6 +1410,208 @@ class FTableFormBuilder {
1403
1410
  return select;
1404
1411
  }
1405
1412
 
1413
+ createCustomMultiSelect(fieldName, field, value, attributes, name) {
1414
+ // Create container
1415
+ const container = FTableDOMHelper.create('div', {
1416
+ className: 'ftable-multiselect-container',
1417
+ attributes: { 'data-field-name': fieldName }
1418
+ });
1419
+
1420
+ // Create hidden input to store selected values
1421
+ const hiddenInput = FTableDOMHelper.create('input', {
1422
+ type: 'hidden',
1423
+ name: name,
1424
+ id: `Edit-${fieldName}`,
1425
+ value: Array.isArray(value) ? value.join(',') : value || ''
1426
+ });
1427
+ container.appendChild(hiddenInput);
1428
+
1429
+ // Create display area
1430
+ const display = FTableDOMHelper.create('div', {
1431
+ className: 'ftable-multiselect-display',
1432
+ parent: container
1433
+ });
1434
+
1435
+ const selectedDisplay = FTableDOMHelper.create('div', {
1436
+ className: 'ftable-multiselect-selected',
1437
+ parent: display
1438
+ });
1439
+
1440
+ const placeholderText = field.placeholder || this.options.messages.multiSelectPlaceholder || 'Click to select options...';
1441
+ const placeholder = FTableDOMHelper.create('span', {
1442
+ className: 'ftable-multiselect-placeholder',
1443
+ textContent: placeholderText,
1444
+ parent: selectedDisplay
1445
+ });
1446
+
1447
+ // Create dropdown toggle button
1448
+ const toggleBtn = FTableDOMHelper.create('button', {
1449
+ type: 'button',
1450
+ className: 'ftable-multiselect-toggle',
1451
+ innerHTML: '▼',
1452
+ parent: display
1453
+ });
1454
+
1455
+ // Create options dropdown
1456
+ const dropdown = FTableDOMHelper.create('div', {
1457
+ className: 'ftable-multiselect-dropdown',
1458
+ parent: container,
1459
+ style: 'display: none;'
1460
+ });
1461
+
1462
+ // Store selected values and checkbox references
1463
+ const selectedValues = new Set(
1464
+ Array.isArray(value) ? value :
1465
+ value ? value.toString().split(',').filter(v => v) : []
1466
+ );
1467
+ const checkboxMap = new Map(); // Map of value -> checkbox element
1468
+
1469
+ // Function to update display
1470
+ const updateDisplay = () => {
1471
+ selectedDisplay.innerHTML = '';
1472
+
1473
+ if (selectedValues.size === 0) {
1474
+ placeholder.textContent = placeholderText;
1475
+ selectedDisplay.appendChild(placeholder);
1476
+ } else {
1477
+ const selectedArray = Array.from(selectedValues);
1478
+ const optionsMap = new Map();
1479
+
1480
+ // Build options map
1481
+ if (field.options) {
1482
+ const options = Array.isArray(field.options) ? field.options :
1483
+ Object.entries(field.options).map(([k, v]) => ({Value: k, DisplayText: v}));
1484
+
1485
+ options.forEach(opt => {
1486
+ const val = opt.Value !== undefined ? opt.Value :
1487
+ opt.value !== undefined ? opt.value : opt;
1488
+ const text = opt.DisplayText || opt.text || opt;
1489
+ optionsMap.set(val.toString(), text);
1490
+ });
1491
+ }
1492
+
1493
+ selectedArray.forEach(val => {
1494
+ const tag = FTableDOMHelper.create('span', {
1495
+ className: 'ftable-multiselect-tag',
1496
+ parent: selectedDisplay
1497
+ });
1498
+
1499
+ FTableDOMHelper.create('span', {
1500
+ className: 'ftable-multiselect-tag-text',
1501
+ textContent: optionsMap.get(val.toString()) || val,
1502
+ parent: tag
1503
+ });
1504
+
1505
+ const removeBtn = FTableDOMHelper.create('span', {
1506
+ className: 'ftable-multiselect-tag-remove',
1507
+ innerHTML: '×',
1508
+ parent: tag
1509
+ });
1510
+
1511
+ removeBtn.addEventListener('click', (e) => {
1512
+ e.stopPropagation();
1513
+ selectedValues.delete(val);
1514
+ // Update the checkbox state
1515
+ const checkbox = checkboxMap.get(val.toString());
1516
+ if (checkbox) {
1517
+ checkbox.checked = false;
1518
+ }
1519
+ updateDisplay();
1520
+ hiddenInput.value = Array.from(selectedValues).join(',');
1521
+ });
1522
+ });
1523
+ }
1524
+
1525
+ hiddenInput.value = Array.from(selectedValues).join(',');
1526
+ };
1527
+
1528
+ // Populate options
1529
+ const populateOptions = () => {
1530
+ if (!field.options) return;
1531
+
1532
+ const options = Array.isArray(field.options) ? field.options :
1533
+ Object.entries(field.options).map(([k, v]) => ({Value: k, DisplayText: v}));
1534
+
1535
+ options.forEach(option => {
1536
+ const optValue = option.Value !== undefined ? option.Value :
1537
+ option.value !== undefined ? option.value : option;
1538
+
1539
+ // Skip if value is empty
1540
+ if (optValue == null || optValue === '') {
1541
+ return; // This continues to the next iteration
1542
+ }
1543
+
1544
+ const optText = option.DisplayText || option.text || option;
1545
+
1546
+ const optionDiv = FTableDOMHelper.create('div', {
1547
+ className: 'ftable-multiselect-option',
1548
+ parent: dropdown
1549
+ });
1550
+
1551
+ const checkbox = FTableDOMHelper.create('input', {
1552
+ type: 'checkbox',
1553
+ className: 'ftable-multiselect-checkbox',
1554
+ checked: selectedValues.has(optValue.toString()),
1555
+ parent: optionDiv
1556
+ });
1557
+
1558
+ // Store checkbox reference
1559
+ checkboxMap.set(optValue.toString(), checkbox);
1560
+
1561
+ const label = FTableDOMHelper.create('label', {
1562
+ className: 'ftable-multiselect-label',
1563
+ textContent: optText,
1564
+ parent: optionDiv
1565
+ });
1566
+
1567
+ // Click anywhere on the option to toggle
1568
+ optionDiv.addEventListener('click', (e) => {
1569
+ e.stopPropagation();
1570
+
1571
+ if (selectedValues.has(optValue.toString())) {
1572
+ selectedValues.delete(optValue.toString());
1573
+ checkbox.checked = false;
1574
+ } else {
1575
+ selectedValues.add(optValue.toString());
1576
+ checkbox.checked = true;
1577
+ }
1578
+
1579
+ updateDisplay();
1580
+ });
1581
+ });
1582
+ };
1583
+
1584
+ // Toggle dropdown
1585
+ const toggleDropdown = (e) => {
1586
+ if (e) e.stopPropagation();
1587
+ const isVisible = dropdown.style.display !== 'none';
1588
+ dropdown.style.display = isVisible ? 'none' : 'block';
1589
+
1590
+ if (!isVisible) {
1591
+ // Close other dropdowns
1592
+ document.querySelectorAll('.ftable-multiselect-dropdown').forEach(dd => {
1593
+ if (dd !== dropdown) dd.style.display = 'none';
1594
+ });
1595
+ }
1596
+ };
1597
+
1598
+ display.addEventListener('click', toggleDropdown);
1599
+ toggleBtn.addEventListener('click', toggleDropdown);
1600
+
1601
+ // Close dropdown when clicking outside
1602
+ document.addEventListener('click', (e) => {
1603
+ if (!container.contains(e.target)) {
1604
+ dropdown.style.display = 'none';
1605
+ }
1606
+ });
1607
+
1608
+ // Initialize
1609
+ populateOptions();
1610
+ updateDisplay();
1611
+
1612
+ return container;
1613
+ }
1614
+
1406
1615
  createRadioGroup(fieldName, field, value) {
1407
1616
  const wrapper = FTableDOMHelper.create('div', {
1408
1617
  className: 'ftable-radio-group'
@@ -2308,12 +2517,19 @@ class FTable extends FTableEventEmitter {
2308
2517
  container.appendChild(input.datalistElement);
2309
2518
  }
2310
2519
 
2311
- if (input.tagName === 'SELECT') {
2312
- input.addEventListener('change', (e) => {
2520
+ // Handle event listeners - check if it's a custom multiselect container
2521
+ let targetElement = input;
2522
+ if (input.classList && input.classList.contains('ftable-multiselect-container') && input.hiddenSelect) {
2523
+ // It's a custom multiselect - attach listener to the hidden select
2524
+ targetElement = input.hiddenSelect;
2525
+ }
2526
+
2527
+ if (targetElement.tagName === 'SELECT') {
2528
+ targetElement.addEventListener('change', (e) => {
2313
2529
  this.handleSearchInputChange(e);
2314
2530
  });
2315
2531
  } else {
2316
- input.addEventListener('input', (e) => {
2532
+ targetElement.addEventListener('input', (e) => {
2317
2533
  this.handleSearchInputChange(e);
2318
2534
  });
2319
2535
  }
@@ -2378,12 +2594,6 @@ class FTable extends FTableEventEmitter {
2378
2594
  }
2379
2595
  attributes['data-field-name'] = name;
2380
2596
 
2381
- const select = FTableDOMHelper.create('select', {
2382
- attributes: attributes,
2383
- id: fieldSearchName,
2384
- className: 'ftable-toolbarsearch'
2385
- });
2386
-
2387
2597
  let optionsSource;
2388
2598
  if (isCheckboxValues && field.values) {
2389
2599
  optionsSource = Object.entries(field.values).map(([value, displayText]) => ({
@@ -2394,6 +2604,24 @@ class FTable extends FTableEventEmitter {
2394
2604
  optionsSource = await this.formBuilder.getFieldOptions(fieldName);
2395
2605
  }
2396
2606
 
2607
+ // If multiple, create custom UI
2608
+ if (hasMultiple) {
2609
+ return this.createCustomMultiSelectForSearch(
2610
+ fieldSearchName,
2611
+ fieldName,
2612
+ field,
2613
+ optionsSource,
2614
+ attributes
2615
+ );
2616
+ }
2617
+
2618
+ // Standard single select
2619
+ const select = FTableDOMHelper.create('select', {
2620
+ attributes: attributes,
2621
+ id: fieldSearchName,
2622
+ className: 'ftable-toolbarsearch'
2623
+ });
2624
+
2397
2625
  // Add empty option only if first option is not already empty
2398
2626
  const hasEmptyFirst = optionsSource?.length > 0 &&
2399
2627
  (optionsSource[0].Value === '' ||
@@ -2430,6 +2658,226 @@ class FTable extends FTableEventEmitter {
2430
2658
  return select;
2431
2659
  }
2432
2660
 
2661
+ createCustomMultiSelectForSearch(fieldSearchName, fieldName, field, optionsSource, attributes) {
2662
+ // Create container
2663
+ const container = FTableDOMHelper.create('div', {
2664
+ className: 'ftable-multiselect-container ftable-multiselect-search ftable-toolbarsearch',
2665
+ attributes: { 'data-field-name': attributes['data-field-name'] }
2666
+ });
2667
+
2668
+ // Create hidden select to maintain compatibility with existing search logic
2669
+ const hiddenSelect = FTableDOMHelper.create('select', {
2670
+ id: fieldSearchName,
2671
+ multiple: true,
2672
+ style: 'display: none;',
2673
+ attributes: attributes
2674
+ });
2675
+ container.appendChild(hiddenSelect);
2676
+
2677
+ // Expose hidden select for external access (needed for event listeners and reset)
2678
+ container.hiddenSelect = hiddenSelect;
2679
+
2680
+ // Create display area
2681
+ const display = FTableDOMHelper.create('div', {
2682
+ className: 'ftable-multiselect-display',
2683
+ parent: container
2684
+ });
2685
+
2686
+ const selectedDisplay = FTableDOMHelper.create('div', {
2687
+ className: 'ftable-multiselect-selected',
2688
+ parent: display
2689
+ });
2690
+
2691
+ const placeholderText = field.searchPlaceholder || field.placeholder || this.options.messages.multiSelectPlaceholder || 'Click to select options...';
2692
+ const placeholder = FTableDOMHelper.create('span', {
2693
+ className: 'ftable-multiselect-placeholder',
2694
+ textContent: placeholderText,
2695
+ parent: selectedDisplay
2696
+ });
2697
+
2698
+ // Create dropdown toggle button
2699
+ const toggleBtn = FTableDOMHelper.create('button', {
2700
+ type: 'button',
2701
+ className: 'ftable-multiselect-toggle',
2702
+ innerHTML: '▼',
2703
+ parent: display
2704
+ });
2705
+
2706
+ // Create options dropdown
2707
+ const dropdown = FTableDOMHelper.create('div', {
2708
+ className: 'ftable-multiselect-dropdown',
2709
+ parent: container,
2710
+ style: 'display: none;'
2711
+ });
2712
+
2713
+ // Store selected values and checkbox references
2714
+ const selectedValues = new Set();
2715
+ const checkboxMap = new Map(); // Map of value -> checkbox element
2716
+
2717
+ // Function to update display and hidden select
2718
+ const updateDisplay = () => {
2719
+ selectedDisplay.innerHTML = '';
2720
+
2721
+ // Update hidden select
2722
+ Array.from(hiddenSelect.options).forEach(opt => {
2723
+ opt.selected = selectedValues.has(opt.value);
2724
+ });
2725
+
2726
+ // Trigger change event on hidden select for search functionality
2727
+ hiddenSelect.dispatchEvent(new Event('change', { bubbles: true }));
2728
+
2729
+ if (selectedValues.size === 0) {
2730
+ placeholder.textContent = placeholderText;
2731
+ selectedDisplay.appendChild(placeholder);
2732
+ } else {
2733
+ const selectedArray = Array.from(selectedValues);
2734
+ const optionsMap = new Map();
2735
+
2736
+ // Build options map
2737
+ if (optionsSource && Array.isArray(optionsSource)) {
2738
+ optionsSource.forEach(opt => {
2739
+ const val = opt.Value !== undefined ? opt.Value :
2740
+ opt.value !== undefined ? opt.value : opt;
2741
+ const text = opt.DisplayText || opt.text || opt;
2742
+ optionsMap.set(val.toString(), text);
2743
+ });
2744
+ }
2745
+
2746
+ selectedArray.forEach(val => {
2747
+ const tag = FTableDOMHelper.create('span', {
2748
+ className: 'ftable-multiselect-tag',
2749
+ parent: selectedDisplay
2750
+ });
2751
+
2752
+ FTableDOMHelper.create('span', {
2753
+ className: 'ftable-multiselect-tag-text',
2754
+ textContent: optionsMap.get(val.toString()) || val,
2755
+ parent: tag
2756
+ });
2757
+
2758
+ const removeBtn = FTableDOMHelper.create('span', {
2759
+ className: 'ftable-multiselect-tag-remove',
2760
+ innerHTML: '×',
2761
+ parent: tag
2762
+ });
2763
+
2764
+ removeBtn.addEventListener('click', (e) => {
2765
+ e.stopPropagation();
2766
+ selectedValues.delete(val);
2767
+ // Update the checkbox state
2768
+ const checkbox = checkboxMap.get(val.toString());
2769
+ if (checkbox) {
2770
+ checkbox.checked = false;
2771
+ }
2772
+ updateDisplay();
2773
+ });
2774
+ });
2775
+ }
2776
+ };
2777
+
2778
+ // Add reset method to container
2779
+ container.resetMultiSelect = () => {
2780
+ selectedValues.clear();
2781
+ checkboxMap.forEach(checkbox => {
2782
+ checkbox.checked = false;
2783
+ });
2784
+ updateDisplay();
2785
+ };
2786
+
2787
+ // Populate options in both hidden select and dropdown
2788
+ const populateOptions = () => {
2789
+ if (!optionsSource) return;
2790
+
2791
+ const options = Array.isArray(optionsSource) ? optionsSource :
2792
+ Object.entries(optionsSource).map(([k, v]) => ({Value: k, DisplayText: v}));
2793
+
2794
+ options.forEach(option => {
2795
+ const optValue = option.Value !== undefined ? option.Value :
2796
+ option.value !== undefined ? option.value : option;
2797
+
2798
+ // Skip if value is empty
2799
+ if (optValue == null || optValue === '') {
2800
+ return; // This continues to the next iteration
2801
+ }
2802
+
2803
+ const optText = option.DisplayText || option.text || option;
2804
+
2805
+ // Add to hidden select
2806
+ FTableDOMHelper.create('option', {
2807
+ value: optValue,
2808
+ textContent: optText,
2809
+ parent: hiddenSelect
2810
+ });
2811
+
2812
+ // Add to visual dropdown
2813
+ const optionDiv = FTableDOMHelper.create('div', {
2814
+ className: 'ftable-multiselect-option',
2815
+ parent: dropdown
2816
+ });
2817
+
2818
+ const checkbox = FTableDOMHelper.create('input', {
2819
+ type: 'checkbox',
2820
+ className: 'ftable-multiselect-checkbox',
2821
+ parent: optionDiv
2822
+ });
2823
+
2824
+ // Store checkbox reference
2825
+ checkboxMap.set(optValue.toString(), checkbox);
2826
+
2827
+ const label = FTableDOMHelper.create('label', {
2828
+ className: 'ftable-multiselect-label',
2829
+ textContent: optText,
2830
+ parent: optionDiv
2831
+ });
2832
+
2833
+ // Click anywhere on the option to toggle
2834
+ optionDiv.addEventListener('click', (e) => {
2835
+ e.stopPropagation();
2836
+
2837
+ if (selectedValues.has(optValue.toString())) {
2838
+ selectedValues.delete(optValue.toString());
2839
+ checkbox.checked = false;
2840
+ } else {
2841
+ selectedValues.add(optValue.toString());
2842
+ checkbox.checked = true;
2843
+ }
2844
+
2845
+ updateDisplay();
2846
+ });
2847
+ });
2848
+ };
2849
+
2850
+ // Toggle dropdown
2851
+ const toggleDropdown = (e) => {
2852
+ if (e) e.stopPropagation();
2853
+ const isVisible = dropdown.style.display !== 'none';
2854
+ dropdown.style.display = isVisible ? 'none' : 'block';
2855
+
2856
+ if (!isVisible) {
2857
+ // Close other dropdowns
2858
+ document.querySelectorAll('.ftable-multiselect-dropdown').forEach(dd => {
2859
+ if (dd !== dropdown) dd.style.display = 'none';
2860
+ });
2861
+ }
2862
+ };
2863
+
2864
+ display.addEventListener('click', toggleDropdown);
2865
+ toggleBtn.addEventListener('click', toggleDropdown);
2866
+
2867
+ // Close dropdown when clicking outside
2868
+ document.addEventListener('click', (e) => {
2869
+ if (!container.contains(e.target)) {
2870
+ dropdown.style.display = 'none';
2871
+ }
2872
+ });
2873
+
2874
+ // Initialize
2875
+ populateOptions();
2876
+ updateDisplay();
2877
+
2878
+ return container;
2879
+ }
2880
+
2433
2881
  async createDatalistForSearch(fieldName, field) {
2434
2882
  const fieldSearchName = 'ftable-toolbarsearch-' + fieldName;
2435
2883
 
@@ -2520,6 +2968,14 @@ class FTable extends FTableEventEmitter {
2520
2968
  }
2521
2969
  });
2522
2970
 
2971
+ // Clear custom multiselect containers
2972
+ const multiSelectContainers = this.elements.table.querySelectorAll('.ftable-multiselect-container');
2973
+ multiSelectContainers.forEach(container => {
2974
+ if (typeof container.resetMultiSelect === 'function') {
2975
+ container.resetMultiSelect();
2976
+ }
2977
+ });
2978
+
2523
2979
  // Reload data without search parameters
2524
2980
  this.load();
2525
2981
  }