1window.combos = (function (combos) { 2 3 combos.searchBox = class SearchBox { 4 5 debounceInterval = 500; 6 debounceLeadingExecution = false; 7 searchResultContainer; 8 itemClass = `combo-search-box-item`; 9 10 /** 11 * 12 * @param idSelector - an element id to select the input box 13 * @param getSuggestedItems - the data function with a search term as argument. It should return an array of elements to add to the suggested list. 14 * @returns {combos.SearchBox} 15 */ 16 static create(idSelector, getSuggestedItems) { 17 return new SearchBox(idSelector, getSuggestedItems); 18 } 19 20 constructor(idSelector, getSuggestedItems) { 21 this.idSelector = idSelector; 22 this.getSuggesteditems = getSuggestedItems; 23 } 24 25 setDebounceInterval(debounceInterval) { 26 this.debounceInterval = debounceInterval; 27 return this; 28 } 29 30 /** 31 * Permits to pass a specific popper 32 * @param popper 33 * @returns {Window.combos.SearchBox} 34 */ 35 setPopper(popper) { 36 this.popper = popper; 37 return this; 38 } 39 40 getPopper() { 41 if (typeof this.popper != 'undefined') { 42 return this.popper; 43 } 44 if (typeof Popper != 'undefined') { 45 return Popper 46 } 47 throw Error("Popper was not found"); 48 } 49 50 init() { 51 52 let searchBoxInstance = this; 53 let elementSelected = document.getElementById(this.idSelector); 54 if (elementSelected === null) { 55 throw Error(`No element was found with the selector ${this.idSelector}`); 56 } 57 if (elementSelected instanceof HTMLInputElement) { 58 this.searchBoxElement = elementSelected; 59 } else { 60 throw Error(`No search box input element found with the selector ${this.idSelector}`); 61 } 62 63 this.searchResultContainer = document.createElement("ul"); 64 this.searchResultContainer.classList.add("dropdown-menu"); 65 this.searchBoxElement.insertAdjacentElement('afterend', this.searchResultContainer); 66 67 68 this.popperInstance = this.getPopper().createPopper( 69 this.searchBoxElement, 70 this.searchResultContainer, 71 { 72 placement: 'bottom', 73 modifiers: [ 74 { 75 name: 'offset', // to be below the box-shadow on focus 76 options: { 77 offset: [0, 4], 78 }, 79 }, 80 ] 81 } 82 ); 83 84 /** 85 * Build the list when typing 86 */ 87 this.searchBoxElement.addEventListener("input", 88 combos.debounce( 89 async function () { 90 let searchTerm = searchBoxInstance.searchBoxElement.value; 91 await searchBoxInstance.buildAutoCompletionList(searchTerm) 92 }, 93 searchBoxInstance.debounceInterval, 94 searchBoxInstance.debounceLeadingExecution 95 ) 96 ); 97 /** 98 * Build the list in focus if there is any value already 99 */ 100 this.searchBoxElement.addEventListener("focus", 101 async function () { 102 let searchTerm = searchBoxInstance.searchBoxElement.value; 103 await searchBoxInstance.buildAutoCompletionList(searchTerm) 104 } 105 ); 106 107 this.searchBoxElement.addEventListener("blur", function (event) { 108 searchBoxInstance.hideAutoComplete(event.relatedTarget); 109 }); 110 111 112 } 113 114 hideAutoComplete(relatedTarget) { 115 // Only if it's not an item of the list 116 // ie deleting the item will prevent click navigation from a page list suggestion 117 if (relatedTarget !== null && relatedTarget instanceof Element) { 118 // the target may be a link inside a list item 119 let closestLi = relatedTarget.closest(`li`); 120 if (closestLi != null && closestLi.classList.contains(this.itemClass)) { 121 return; 122 } 123 } 124 this.searchResultContainer.classList.remove("show"); 125 while (this.searchResultContainer.firstChild) { 126 this.searchResultContainer.firstChild.remove() 127 } 128 } 129 130 async buildAutoCompletionList(searchTerm) { 131 132 if (searchTerm.length < 3) { 133 return; 134 } 135 this.hideAutoComplete(); 136 let data = await this.getSuggesteditems(searchTerm); 137 this.searchResultContainer.classList.add("show"); 138 let searchBoxInstance = this; 139 for (let index in data) { 140 if (!data.hasOwnProperty(index)) { 141 continue; 142 } 143 let element = data[index]; 144 if (!(element instanceof HTMLElement)) { 145 throw Error("The suggested links data function should return HTML element"); 146 } 147 let li = document.createElement("li"); 148 li.classList.add("dropdown-item"); 149 li.classList.add(this.itemClass); 150 li.appendChild(element); 151 // Note: HTML element such as anchors are added in the tab order, no need to add tabindex - 1 152 element.addEventListener("blur", function (event) { 153 searchBoxInstance.hideAutoComplete(event.relatedTarget); 154 }); 155 156 this.searchResultContainer.appendChild(li); 157 } 158 159 await this.popperInstance.update(); 160 } 161 162 }; 163 164 return combos; 165 166}) 167(window.combos || {}); 168 169