Added global search input debounce and loading indicator

This commit is contained in:
Dan Brown 2022-11-20 22:20:31 +00:00
parent 2c1f20969a
commit c617190905
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
4 changed files with 47 additions and 17 deletions

View File

@ -1,8 +1,9 @@
import {htmlToDom} from "../services/dom";
import {debounce} from "../services/util";
/** /**
* @extends {Component} * @extends {Component}
*/ */
import {htmlToDom} from "../services/dom";
class GlobalSearch { class GlobalSearch {
setup() { setup() {
@ -10,23 +11,43 @@ class GlobalSearch {
this.input = this.$refs.input; this.input = this.$refs.input;
this.suggestions = this.$refs.suggestions; this.suggestions = this.$refs.suggestions;
this.suggestionResultsWrap = this.$refs.suggestionResults; this.suggestionResultsWrap = this.$refs.suggestionResults;
this.loadingWrap = this.$refs.loading;
this.setupListeners(); this.setupListeners();
} }
setupListeners() { setupListeners() {
const updateSuggestionsDebounced = debounce(this.updateSuggestions.bind(this), 200, false);
// Handle search input changes
this.input.addEventListener('input', () => { this.input.addEventListener('input', () => {
const value = this.input.value; const value = this.input.value;
if (value.length > 0) { if (value.length > 0) {
this.updateSuggestions(value); this.loadingWrap.style.display = 'block';
this.suggestionResultsWrap.style.opacity = '0.5';
updateSuggestionsDebounced(value);
} else { } else {
this.hideSuggestions(); this.hideSuggestions();
} }
}); });
// Allow double click to show auto-click suggestions
this.input.addEventListener('dblclick', () => {
this.input.setAttribute('autocomplete', 'on');
this.input.blur();
this.input.focus();
})
} }
/**
* @param {String} search
*/
async updateSuggestions(search) { async updateSuggestions(search) {
const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5}); const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5});
if (!this.input.value) {
return;
}
const resultDom = htmlToDom(results); const resultDom = htmlToDom(results);
const childrenToTrim = Array.from(resultDom.children).slice(9); const childrenToTrim = Array.from(resultDom.children).slice(9);
@ -35,6 +56,8 @@ class GlobalSearch {
} }
this.suggestionResultsWrap.innerHTML = ''; this.suggestionResultsWrap.innerHTML = '';
this.suggestionResultsWrap.style.opacity = '1';
this.loadingWrap.style.display = 'none';
this.suggestionResultsWrap.append(resultDom); this.suggestionResultsWrap.append(resultDom);
if (!this.container.classList.contains('search-active')) { if (!this.container.classList.contains('search-active')) {
this.showSuggestions(); this.showSuggestions();

View File

@ -6,9 +6,9 @@
* N milliseconds. If `immediate` is passed, trigger the function on the * N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing. * leading edge, instead of the trailing.
* @attribution https://davidwalsh.name/javascript-debounce-function * @attribution https://davidwalsh.name/javascript-debounce-function
* @param func * @param {Function} func
* @param wait * @param {Number} wait
* @param immediate * @param {Boolean} immediate
* @returns {Function} * @returns {Function}
*/ */
export function debounce(func, wait, immediate) { export function debounce(func, wait, immediate) {

View File

@ -131,7 +131,7 @@ header .search-box {
right: 16px; right: 16px;
} }
svg { svg {
margin-block-end: 0; margin-inline-end: 0;
} }
} }
@ -163,17 +163,23 @@ header .search-box {
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
overflow: hidden; overflow: hidden;
} }
.global-search-loading {
position: absolute;
width: 100%;
}
} }
.search-active:focus-within .global-search-suggestions { header .search-box.search-active:focus-within {
display: block; .global-search-suggestions {
} display: block;
header .search-box.search-active input { }
background-color: #EEE; input {
color: #444; background-color: #EEE;
border-color: #DDD; color: #444;
} border-color: #DDD;
header .search-box.search-active #header-search-box-button { }
color: #444; #header-search-box-button {
color: #444;
}
} }
.logo { .logo {

View File

@ -30,6 +30,7 @@
aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}" aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}"
value="{{ $searchTerm ?? '' }}"> value="{{ $searchTerm ?? '' }}">
<div refs="global-search@suggestions" class="global-search-suggestions card"> <div refs="global-search@suggestions" class="global-search-suggestions card">
<div refs="global-search@loading" class="text-center px-m global-search-loading">@include('common.loading-icon')</div>
<div refs="global-search@suggestion-results" class="px-m"></div> <div refs="global-search@suggestion-results" class="px-m"></div>
<button class="text-button card-footer-link" type="submit">{{ trans('common.view_all') }}</button> <button class="text-button card-footer-link" type="submit">{{ trans('common.view_all') }}</button>
</div> </div>