selectify-1.0.3/js/selectify-dual.js
js/selectify-dual.js
/**
* @file
* Contains utility functions for Selectify module multi-select dual list.
*
* Filename: selectify-dual.js
* Website: https://www.flashwebcenter.com
* Developer: Alaa Haddad https://www.alaahaddad.com.
*/
((Drupal, once) => {
'use strict';
const type = 'dual';
/**
* Drupal behavior for custom multi-select dual lists.
*/
Drupal.behaviors.selectifySelectDual = {
attach: (context) => {
once('selectifyMultiSelectDual', '.selectify-select-dual-list', 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 dual list 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;
}
initializeSelectifyDual(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 dual list multi-select.
*/
function initializeSelectifyDual(selectifySelect, nativeSelect) {
const selectedDisplay = selectifySelect.querySelector('.dual-selected');
const dropdownMenu = selectifySelect.querySelector('.dual-available');
const options = selectifySelect.querySelectorAll('.dual-available-option');
const clearAllButton = selectifySelect.querySelector('.dual-clear-all');
if (!nativeSelect || nativeSelect.tagName !== 'SELECT') {
console.error('Selectify: Could not find the corresponding hidden <select> for', selectifySelect);
return;
}
if (!selectedDisplay || options.length === 0) {
return;
}
// Click event to select an item.
once('selectifyDualEvents', 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);
}
});
});
// Click event to remove selected items.
selectedDisplay.addEventListener('click', (event) => {
if (event.target.classList.contains('selectify-selected-one-option')) {
event.preventDefault();
event.stopPropagation();
// Check if field is required and single-value
const isRequired = nativeSelect.hasAttribute('required');
const isMultiple = nativeSelect.getAttribute('data-multiple') === 'true';
const selectedCount = selectedDisplay.querySelectorAll('.selectify-selected-one-option').length;
// For required single-value fields, prevent removing the only selected item
if (isRequired && !isMultiple && selectedCount === 1) {
return; // Don't allow removal
}
const selectedItem = event.target;
const valueToRemove = selectedItem.getAttribute('data-value');
selectedItem.remove();
const correspondingOption = dropdownMenu.querySelector(`.selectify-available-one-option[data-value="${valueToRemove}"]`);
if (correspondingOption) {
correspondingOption.classList.remove('s-hidden', 's-selected');
}
const selectOption = nativeSelect.querySelector(`option[value="${valueToRemove}"]`);
if (selectOption) {
selectOption.selected = false;
}
// Collect all current selected values after the removal.
const selectedValues = [...selectedDisplay.querySelectorAll('.selectify-selected-one-option')].map(item => item.getAttribute('data-value'));
Drupal.selectify.syncHiddenSelect(nativeSelect, selectedValues);
Drupal.selectify.updateSelectedDisplay(type, nativeSelect, selectedDisplay, clearAllButton);
// Check if anything is left.
const dualInner = selectedDisplay.querySelector('.selectify-selected-options.dual-inner');
if (!dualInner || dualInner.children.length === 0) {
clearAllButton.classList.add('s-hidden');
}
nativeSelect.dispatchEvent(new Event('change'));
}
});
// "Clear All" Button Logic.
once('clearAllDual', clearAllButton).forEach(() => {
clearAllButton.addEventListener('click', () => {
nativeSelect.querySelectorAll('option').forEach(option => {
option.selected = false;
});
selectedDisplay.querySelectorAll('.selectify-selected-one-option').forEach(item => item.remove());
dropdownMenu.querySelectorAll('.selectify-available-one-option.selected').forEach(option => {
option.classList.remove('s-selected');
});
dropdownMenu.querySelectorAll('.selectify-available-one-option.s-hidden').forEach(option => {
option.classList.remove('s-hidden');
});
Drupal.selectify.syncHiddenSelect(nativeSelect, []);
Drupal.selectify.updateSelectedDisplay(type, nativeSelect, selectedDisplay, clearAllButton);
setTimeout(() => {
nativeSelect.dispatchEvent(new Event('change', { bubbles: true }));
nativeSelect.dispatchEvent(new Event('input', { bubbles: true }));
}, 0);
// Return focus to the dual available list
const availableList = selectifySelect.querySelector('.dual-available');
if (availableList && typeof availableList.focus === 'function') {
setTimeout(() => availableList.focus(), 100);
}
});
});
// 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);
