/** * The controller for a drop-down multi selector. * * Below is an implementation example. It shows the HTML and JS needed to create the component. * * HTML:
Select a product
JavaScript: new MmuiDropSelect({ id: "product_version", placeholder: "Select a product", onSelection: this.selectionHandler, }); */ /** * Dropdown Multi-select JavaScript * @deprecated Use MmuiDropChoiceComponent */ export class MmuiDropSelect { options = { id: null, onOpen: null, onClose: null, onSelection: null, placeholder: 'Select', enableSearchBox: true, searchBoxSelector: '#search-filter', isNested: false, // control if the dropdown is nested or not }; baseElement; button; buttonTitle; selectBody; searchFilter; constructor(options) { this.options = Object.assign({}, this.options, options); this.baseElement = document.getElementById(this.options.id); this.button = this.baseElement.querySelector('.btn'); this.button.addEventListener('click', this.onBtnClick); this.buttonTitle = this.baseElement.querySelector( '.mmui-drop-select-title' ); this.selectBody = this.baseElement.querySelector('.mmui-drop-select-body'); const checkboxes = this.getCheckboxes(); checkboxes.forEach((item) => { item.addEventListener('click', this.onSelection); }); if (this.options.enableSearchBox) { //searchBoxSelector should have prefix '#' this.searchFilter = this.baseElement.querySelector( this.options.searchBoxSelector ); this.searchFilter.addEventListener('input', this.onSearchInput); } this.setTitle(this.getSelectedNames()); this.close(); } open() { if (!this.isOpen()) { this.selectBody.classList.remove('d-none'); window.addEventListener('click', this.onBlur); } } close() { if (this.isOpen()) { this.selectBody.classList.add('d-none'); window.removeEventListener('click', this.onBlur); } } isOpen() { return !this.selectBody.classList.contains('d-none'); } setTitle(title) { if (Array.isArray(title)) { if (title.length == 0) { this.buttonTitle.innerHTML = this.options.placeholder; } else if (title.length == 1) { this.buttonTitle.innerHTML = title[0]; } else { this.buttonTitle.innerHTML = title[0] + ' + ' + (title.length - 1); } } else { this.buttonTitle.innerHTML = title; } } getCheckboxes() { return this.baseElement.querySelectorAll('input[type="checkbox"]'); } getSelectedValues() { const selectedValues = []; this.getCheckboxes().forEach(function (item) { if (item.checked) { selectedValues.push(item.value); } }); return selectedValues; } getSelectedNames() { const selectedNames = []; // if nested dropdown box, only count the children if (this.options.isNested) { this.getCheckboxes().forEach(function (item) { if (item.checked && !item.dataset.isparent) { selectedNames.push(item.getAttribute('data-display')); } }); } else { this.getCheckboxes().forEach(function (item) { if (item.checked) { selectedNames.push(item.getAttribute('data-display')); } }); } return selectedNames; } onBlur = (evt) => { const mX = evt.clientX; const mY = evt.clientY; const rect = this.selectBody.getBoundingClientRect(); const inRect = rect.left <= mX && mX <= rect.right && rect.top <= mY && mY <= rect.bottom; if (this.isOpen() && !inRect) { this.close(); } }; onBtnClick = (evt) => { evt.stopPropagation(); if (this.isOpen()) { this.close(); if (this.options.onClose) { this.options.onClose(); } } else { this.open(); if (this.options.onOpen) { this.options.onOpen(); } } }; getCheckBoxByValue(value) { let checkBox; this.getCheckboxes().forEach(function (item) { // suppose value is unique if (item.value == value) { checkBox = item; return false; } }); return checkBox; } onSelection = (evt) => { if (this.options.onSelection) { this.options.onSelection(this.getSelectedValues()); } // use this.options.isNested to determine if the parent and children checkbox will affect each other or not if (this.options.isNested) { // scenario 1: if parent is checked, check all children, if parent is unchecked, uncheck all children // attention: the value of child node should use parent's value as prefix, followed by '_' if (evt.target.dataset.isparent) { this.getCheckboxes().forEach(function (item) { if (item.value.startsWith(evt.target.value + '_')) { item.checked = evt.target.checked; } }); } // scenario 2: if children is selected, check its parent's all children to determine if parent should be checked // or indeterminate. If all children are unselected, unchecked the parent else { // get parent_id let parentId = evt.target.dataset.parentid, isAllChecked = true, isAllUnchecked = true; // there is a chance that a check box has no nested children, it will not have parentid attribute if (parentId != null) { this.getCheckboxes().forEach(function (item) { // find all siblings if (item.dataset.parentid == parentId) { isAllChecked = isAllChecked && item.checked; isAllUnchecked = isAllUnchecked && !item.checked; } }); // determine the parent status const parentCheckBox = this.getCheckBoxByValue(parentId); this.statusCheck(parentCheckBox, isAllChecked, isAllUnchecked); } } } // define what to display when boxes are selected this.setTitle(this.getSelectedNames()); }; onSearchInput = (evt) => { let query = evt.currentTarget.value, parentSelectors, inputChoices, inputChoice; inputChoices = this.baseElement.querySelectorAll('input:disabled'); for (inputChoice of inputChoices) { inputChoice.disabled = false; } if (query) { query = query.trim().toLowerCase(); if (query.length > 0) { let parent, itemName, choiceParentId, isPossibleMatch, nodeList = this.getCheckboxes(), parentChoicesMatchChildren = []; for (const item of nodeList) { if (item.disabled) { item.disabled = false; } itemName = item.dataset.display.toLowerCase(); isPossibleMatch = itemName.indexOf(query) >= 0; parent = item.parentElement; if (isPossibleMatch) { if (parent.classList.contains('d-none')) { parent.classList.remove('d-none'); } choiceParentId = item.dataset.parentid; if (choiceParentId) { parentChoicesMatchChildren.push(choiceParentId); } } else { if (!parent.classList.contains('d-none')) { parent.classList.add('d-none'); } } } let selectorArray = [], parentElmnts; for (choiceParentId of parentChoicesMatchChildren) { selectorArray.push(`div[data-id='${choiceParentId}']`); } if (selectorArray.length > 0) { parentSelectors = selectorArray.join(','); parentElmnts = this.baseElement.querySelectorAll(parentSelectors); for (parent of parentElmnts) { if (parent.classList.contains('d-none')) { parent.classList.remove('d-none'); parent.querySelector('input').disabled = true; } } } } } else { const nodeList = this.getCheckboxes(); for (const element of nodeList) { const parent = element.parentElement; if (parent.classList.contains('d-none')) { parent.classList.remove('d-none'); } } } }; filterBy(data) { // Select the relevant metric types const checkboxes = this.getCheckboxes(); for (let i = 0; i < checkboxes.length; i++) { const element = checkboxes[i]; if (data.length == 0) { const parent = element.parentElement; if (parent.classList.contains('d-none')) { parent.classList.remove('d-none'); } } else { const data_filter = element.getAttribute('data-filter'); const parent = element.parentElement; let hasIt = false; for (let j = 0; j < data.length; j++) { if (data_filter.indexOf(data[j]) >= 0) { hasIt = true; } } if (!hasIt) { if (!parent.classList.contains('d-none')) { parent.classList.add('d-none'); } element.checked = false; } else { if (parent.classList.contains('d-none')) { parent.classList.remove('d-none'); } } } } } /** * the parentCheckBoxStatus() only works for nested drop down box, * used case: * when the drop down value is submitted, instead of using web app framework's template language to check if the box * is checked or not, using this method to set the parent status to :indeterminate, checked, or unchecked */ parentCheckBoxStatus() { const nestedCheckBox = document.getElementById(this.options.id); const parentCheckBox = [].slice.call( nestedCheckBox.querySelectorAll("input[data-isparent='1']") ); // convert nodeoflist into array to get rid of the foreach error parentCheckBox.forEach((parentItem) => { const childCheckBox = parentItem.parentElement.nextElementSibling.querySelectorAll('input'); // next sibling is class=pl-5, all chidlren are div let isAllChecked = true, isAllUnchecked = true; childCheckBox.forEach(function (childItem) { isAllChecked = isAllChecked && childItem.checked; isAllUnchecked = isAllUnchecked && !childItem.checked; }); this.statusCheck(parentItem, isAllChecked, isAllUnchecked); }); } /** * a private function. created for reducing the code duplication * @param item: the checkbox which is a parent checkbox with children checkbox * @param isAllChecked: boolean, is all children checkbox all checked * @param isAllUnchecked: boolean, is all children checkbox all unchecked */ private statusCheck(item, isAllChecked, isAllUnchecked) { if (isAllChecked) { item.indeterminate = false; item.checked = true; } else if (isAllUnchecked) { item.indeterminate = false; item.checked = false; } else { item.indeterminate = true; } } }