+import {htmlToDom} from '../services/dom.ts';
+import {debounce} from '../services/util.ts';
+import {KeyboardNavigationHandler} from '../services/keyboard-navigation.ts';
+import {Component} from './component';
+
/**
- * @extends {Component}
+ * Global (header) search box handling.
+ * Mainly to show live results preview.
*/
-import {htmlToDom} from "../services/dom";
-
-class GlobalSearch {
+export class GlobalSearch extends Component {
setup() {
this.container = this.$el;
this.input = this.$refs.input;
this.suggestions = this.$refs.suggestions;
this.suggestionResultsWrap = this.$refs.suggestionResults;
+ this.loadingWrap = this.$refs.loading;
+ this.button = this.$refs.button;
this.setupListeners();
}
setupListeners() {
- this.hideOnOuterEventListener = this.hideOnOuterEventListener.bind(this);
+ const updateSuggestionsDebounced = debounce(this.updateSuggestions.bind(this), 200, false);
+ // Handle search input changes
this.input.addEventListener('input', () => {
- const value = this.input.value;
+ const {value} = this.input;
if (value.length > 0) {
- this.updateSuggestions(value);
- } else {
+ this.loadingWrap.style.display = 'block';
+ this.suggestionResultsWrap.style.opacity = '0.5';
+ updateSuggestionsDebounced(value);
+ } else {
this.hideSuggestions();
}
});
+
+ // Allow double click to show auto-click suggestions
+ this.input.addEventListener('dblclick', () => {
+ this.input.setAttribute('autocomplete', 'on');
+ this.button.focus();
+ this.input.focus();
+ });
+
+ new KeyboardNavigationHandler(this.container, () => {
+ this.hideSuggestions();
+ });
}
+ /**
+ * @param {String} search
+ */
async updateSuggestions(search) {
- const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5});
- const resultDom = htmlToDom(results);
-
- const childrenToTrim = Array.from(resultDom.children).slice(9);
- for (const child of childrenToTrim) {
- child.remove();
+ const {data: results} = await window.$http.get('/search/suggest', {term: search});
+ if (!this.input.value) {
+ return;
}
+ const resultDom = htmlToDom(results);
+
this.suggestionResultsWrap.innerHTML = '';
+ this.suggestionResultsWrap.style.opacity = '1';
+ this.loadingWrap.style.display = 'none';
this.suggestionResultsWrap.append(resultDom);
if (!this.container.classList.contains('search-active')) {
this.showSuggestions();
showSuggestions() {
this.container.classList.add('search-active');
- document.addEventListener('click', this.hideOnOuterEventListener);
- document.addEventListener('focusin', this.hideOnOuterEventListener);
window.requestAnimationFrame(() => {
this.suggestions.classList.add('search-suggestions-animation');
- })
+ });
}
hideSuggestions() {
this.container.classList.remove('search-active');
this.suggestions.classList.remove('search-suggestions-animation');
this.suggestionResultsWrap.innerHTML = '';
- document.removeEventListener('click', this.hideOnOuterEventListener);
- document.removeEventListener('focusin', this.hideOnOuterEventListener);
}
- hideOnOuterEventListener(event) {
- if (!this.container.contains(event.target)) {
- this.hideSuggestions();
- }
- };
}
-
-export default GlobalSearch;
\ No newline at end of file