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