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