UNPKG

search-index

Version:

A network resilient, persistent full-text search library for the browser and Node.js

127 lines (120 loc) 4.88 kB
/** * Options for suggestions * @typedef { Object } SuggestionsOptions * @memberof UI * @property { string } [ elementId="suggestions" ] - The id of the HTML element that contains the suggestions * @property { number } [ limit=20 ] - Max suggestions * @property { number } [ threshold=2 ] - Amount of keystrokes before suggestions are generated */ export class Autocomplete { constructor (searchboxEl, search, suggestions = {}) { this.autoCompleteFunction = suggestions.autoCompleteFunction this.currentFocus = -1 this.searchboxEl = searchboxEl this.search = search this.limit = suggestions.limit || 20 this.threshold = suggestions.threshold || 2 this.autocompleteListId = searchboxEl.id + '-autocomplete-list' this.suggestionsElementId = suggestions.elementId searchboxEl.addEventListener('input', this.searchBoxInputListener) searchboxEl.addEventListener('keydown', this.searchBoxKeydownListener) document.addEventListener('click', e => { this.closeAllLists(e.target) }) } closeAllLists = elmnt => { for (const item of document.getElementsByClassName('autocomplete-items')) { if (elmnt !== item && elmnt !== this.searchboxEl) { item.parentNode.removeChild(item) } } } searchBoxKeydownListener = e => { let x = document.getElementById(this.autocompleteListId) if (x) x = x.getElementsByTagName('div') if (e.keyCode === 40) { /* If the arrow DOWN key is pressed, increase the currentFocus variable: */ this.currentFocus++ /* and and make the current item more visible: */ this.addActive(x) } else if (e.keyCode === 38) { // up /* If the arrow UP key is pressed, decrease the currentFocus variable: */ this.currentFocus-- /* and and make the current item more visible: */ this.addActive(x) } else if (e.keyCode === 13 || e.keyCode === 9) { /* If the ENTER/TAB key is pressed, prevent the form from being submitted, */ e.preventDefault() if (this.currentFocus > -1) { /* and simulate a click on the "active" item: */ if (x) x[this.currentFocus].click() } else { // or simply close the suggestion list this.closeAllLists() } } else if (e.keyCode === 27) { /* if ESC pressed, close list */ this.closeAllLists() } } searchBoxInputListener = async e => { const suggestions = (await this.autoCompleteFunction(e.target.value)).slice( 0, this.limit ) let b const val = e.target.value /* close any already open lists of autocompleted values */ this.closeAllLists() if (!val) return false if (val.length < this.threshold) return false this.currentFocus = -1 /* create a DIV element that will contain the items (values): */ const a = document.createElement('DIV') a.setAttribute('id', this.autocompleteListId) a.setAttribute('class', 'autocomplete-items') a.setAttribute('style', 'position: absolute; background-color:cyan;') /* append the DIV element as a child of the autocomplete container: */ document.getElementById(this.suggestionsElementId).appendChild(a) /* for each item in the array... */ suggestions.forEach(sug => { /* check if the item starts with the same letters as the text field value: */ /* create a DIV element for each matching element: */ b = document.createElement('DIV') /* make the matching letters bold: */ b.innerHTML = '<strong>' + sug.substr(0, val.length) + '</strong>' b.innerHTML += sug.substr(val.length) /* insert a input field that will hold the current array item's value: */ b.innerHTML += "<input type='hidden' value='" + sug + "'>" /* execute a function when someone clicks on the item value (DIV element): */ b.addEventListener('click', e => { /* insert the value for the autocomplete text field: */ this.searchboxEl.value = e.target.getElementsByTagName('input')[0].value /* close the list of autocompleted values, (or any other open lists of autocompleted values: */ this.closeAllLists() this.search() }) a.appendChild(b) }) } addActive = x => { /* a function to classify an item as "active": */ if (!x) return false /* start by removing the "active" class on all items: */ this.removeActive(x) if (this.currentFocus >= x.length) this.currentFocus = 0 if (this.currentFocus < 0) this.currentFocus = x.length - 1 /* add class "autocomplete-active": */ x[this.currentFocus].classList.add('autocomplete-active') } removeActive = x => { /* a function to remove the "active" class from all autocomplete items: */ for (let i = 0; i < x.length; i++) { x[i].classList.remove('autocomplete-active') } } }