selectify-1.0.3/js/selectify-dropdown-searchable.js

js/selectify-dropdown-searchable.js
/**
 * @file
 * Contains utility functions for Selectify module multi-select searchable dropdowns.
 *
 * Filename: selectify-dropdown-searchable.js
 * Website: https://www.flashwebcenter.com
 * Developer: Alaa Haddad https://www.alaahaddad.com.
 */
((Drupal, once) => {
  'use strict';
  const type = 'searchable';
  /**
   * Drupal behavior for custom multi-select searchable dropdowns.
   */
  Drupal.behaviors.selectifySelectSearchable = {
    attach: (context) => {
      once('selectifyMultiSelectSearchable', '.selectify-dropdown-searchable-widget', context).forEach((selectifySelect) => {
        setTimeout(() => {
          // Retrieve the target ID from the data attribute.
          const targetId = selectifySelect.getAttribute('data-target-id');
          if (!targetId) {
            console.error('Selectify: Missing data-target-id on dropdown element', selectifySelect);
            return;
          }
          // Locate the corresponding hidden <select> element by its ID.
          const nativeSelect = document.getElementById(targetId);
          if (!nativeSelect || nativeSelect.tagName !== 'SELECT') {
            console.error(`Selectify: No matching <select> found for data-target-id="${targetId}"`, selectifySelect);
            return;
          }
          initializeSelectifySearchable(selectifySelect, nativeSelect);
          let maxSelections = selectifySelect.getAttribute('data-max-selections');
          maxSelections = maxSelections === 'null' ? null : parseInt(maxSelections, 10);
          Drupal.selectify.handleSelectionLimit(
            type
            , selectifySelect
            , nativeSelect
            , maxSelections
            , '.selectify-available-one-option'
          );
          const dropdownMenu = selectifySelect.querySelector('.selectify-available-display');
          if (dropdownMenu) {
            // Generate a unique ID for the dropdown if not already set.
            const dropdownId = `${targetId}-dropdown`;
            dropdownMenu.setAttribute('id', dropdownId);
            // Set aria-controls on selectifySelect
            selectifySelect.setAttribute('aria-controls', dropdownId);
          }
          Drupal.selectify.injectAriaLabelledBy();
        }, 10);
      });
    }
  };
  /**
   * Initializes the searchable multi-select dropdown.
   */
  function initializeSelectifySearchable(selectifySelect, nativeSelect) {
    const selectedDisplay = selectifySelect.querySelector('.selectify-selected-display');
    const dropdownMenu = selectifySelect.querySelector('.selectify-available-display');
    const options = selectifySelect.querySelectorAll('.selectify-available-one-option');
    const clearAllButton = selectifySelect.querySelector('.selectify-clear-all');
    const dropdownArrow = selectifySelect.querySelector('.selectify-dorpdown-arrow');
    const searchInput = selectifySelect.querySelector('.multi-search-input');
    if (!nativeSelect || nativeSelect.tagName !== 'SELECT') {
      console.error('Selectify: Could not find the corresponding hidden <select> for', selectifySelect);
      return;
    }
    if (!selectedDisplay || !dropdownMenu || options.length === 0) {
      return;
    }
    // Toggle dropdown open/close.
    selectedDisplay.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      if (dropdownMenu.classList.contains('toggled')) {
        Drupal.selectify.closeDropdown(dropdownMenu, selectedDisplay);
      } else {
        Drupal.selectify.adjustDropdownHeight(selectifySelect, dropdownMenu);
        Drupal.selectify.openDropdown(dropdownMenu, selectedDisplay);
        // Focus the search input when the dropdown opens.
        if (searchInput) {
          setTimeout(() => searchInput.focus(), 100);
        }
      }
    });
    // Handle search filtering.
    if (searchInput) {
      searchInput.addEventListener('input', function() {
        const searchTerm = this.value.toLowerCase();
        let optionsArray = Array.from(selectifySelect.querySelectorAll('.selectify-available-one-option'));
        // Filter options based on search input.
        let filteredOptions = optionsArray.filter(option =>
          option.textContent.trim().toLowerCase().includes(searchTerm)
        );
        // Separate options based on type.
        let textOptions = [];
        let numberOptions = [];
        let emailOptions = [];
        let dateOptions = [];
        let entityOptions = [];
        let otherOptions = [];
        filteredOptions.forEach(option => {
          const optionText = option.textContent.trim();
          if (optionText.includes('@') && optionText.includes('.')) {
            emailOptions.push(option);
          } else if (!isNaN(parseFloat(optionText)) && isFinite(optionText)) {
            numberOptions.push(option);
          } else if (optionText.length >= 10 && optionText[4] === '-' && optionText[7] === '-') {
            dateOptions.push(option);
          } else if (optionText.startsWith("Node:") || optionText.startsWith("User:") || optionText.startsWith("Term:")) {
            entityOptions.push(option);
          } else {
            textOptions.push(option);
          }
        });
        // Sort only text alphabetically.
        textOptions.sort((a, b) => a.textContent.trim().localeCompare(b.textContent.trim()));
        // Hide all options before displaying the filtered ones.
        optionsArray.forEach(option => option.classList.add('s-hidden'));
        // Append sorted text, then original order for other types.
        [...textOptions, ...numberOptions, ...dateOptions, ...emailOptions, ...entityOptions, ...otherOptions].forEach(option => {
          option.classList.remove('s-hidden');
        });
      });
    }
    // Handle option selection.
    once('selectifySearchableEvents', dropdownMenu).forEach(() => {
      dropdownMenu.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        if (event.target.classList.contains('selectify-available-one-option')) {
          Drupal.selectify.toggleMultiSelectOption(type, nativeSelect, event.target, selectedDisplay, clearAllButton);
        }
      });
    });
    // Handle "Clear All" functionality.
    once('clearAllSearchable', clearAllButton).forEach(() => {
      clearAllButton.addEventListener('click', () => {
        // Unselect all options in the hidden <select> element.
        nativeSelect.querySelectorAll('option').forEach(option => (option.selected = false));
        // Remove all selected items from the display.
        const multiTagContainer = selectedDisplay.querySelector('.selectify-selected-options');
        if (multiTagContainer) {
          multiTagContainer.innerHTML = ''; // Clear selected items but keep the structure.
        }
        // Ensure dropdown items are fully reset
        dropdownMenu.querySelectorAll('.selectify-available-one-option').forEach(option => {
          option.classList.remove('s-selected', 's-hidden'); // Unhide and unselect
        });
        // Sync the hidden select field.
        Drupal.selectify.syncHiddenSelect(nativeSelect, []);
        // Update the UI to reflect the cleared selections.
        Drupal.selectify.updateSelectedDisplay(type, nativeSelect, selectedDisplay, clearAllButton);
        // Ensure changes are registered.
        setTimeout(() => {
          nativeSelect.dispatchEvent(new Event('change', { bubbles: true }));
          nativeSelect.dispatchEvent(new Event('input', { bubbles: true }));
        }, 0);

        // Return focus to the main trigger
        if (selectedDisplay && typeof selectedDisplay.focus === 'function') {
          setTimeout(() => selectedDisplay.focus(), 100);
        }
      });
    });
    // Add keyboard support for remove tag buttons
    selectedDisplay.querySelectorAll('.remove-tag').forEach(btn => {
      btn.addEventListener('keydown', (event) => {
        if (event.key === 'Enter' || event.key === ' ') {
          event.preventDefault();
          btn.click();
        }
      });
    });

    // Re-add keyboard support when tags are dynamically added
    const observer = new MutationObserver(() => {
      selectedDisplay.querySelectorAll('.remove-tag').forEach(btn => {
        if (!btn.hasAttribute('data-keyboard-enabled')) {
          btn.setAttribute('data-keyboard-enabled', 'true');
          btn.addEventListener('keydown', (event) => {
            if (event.key === 'Enter' || event.key === ' ') {
              event.preventDefault();
              btn.click();
            }
          });
        }
      });
    });
    observer.observe(selectedDisplay, { childList: true, subtree: true });

    // Close dropdown when clicking outside.
    document.addEventListener('click', (event) => {
      if (!selectifySelect.contains(event.target)) {
        Drupal.selectify.closeDropdown(dropdownMenu, selectedDisplay);
      }
    });
    // Add event delegation for remove tag buttons (ONE listener for all)
    once('selectifyRemoveTagDelegation', selectedDisplay).forEach(() => {
      selectedDisplay.addEventListener('click', function(event) {
        const removeButton = event.target.closest('.remove-tag');
        if (removeButton) {
          event.preventDefault();
          event.stopPropagation();
          Drupal.selectify.removeTag(type, nativeSelect, removeButton.parentElement, selectedDisplay, clearAllButton);
        }
      });
    });
    // Enable keyboard navigation.
    Drupal.selectify.handleKeyboardNavigation(selectifySelect, dropdownMenu, selectedDisplay, type);
    const selectedValues = Drupal.selectify.getSelectedValues(nativeSelect);
    Drupal.selectify.syncHiddenSelect(nativeSelect, selectedValues);
    Drupal.selectify.updateSelectedDisplay(type, nativeSelect, selectedDisplay, clearAllButton);
  }
})(Drupal, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc