diff --git a/src/Filter/LookupFilter.php b/src/Filter/LookupFilter.php index 106e7dc..5d182ec 100644 --- a/src/Filter/LookupFilter.php +++ b/src/Filter/LookupFilter.php @@ -115,7 +115,7 @@ public function setLookupFields(array $lookupFields): self public function toArray(): array { if (empty($this->properties['autocompleteUrl'])) { - $this->properties['autocompleteUrl'] = Router::url($this->getAutocompleteRoute(), true); + $this->properties['autocompleteUrl'] = Router::url($this->getAutocompleteRoute()); } $props = parent::toArray(); diff --git a/templates/element/Search/v_search.php b/templates/element/Search/v_search.php index dbdd1e0..168ab31 100644 --- a/templates/element/Search/v_search.php +++ b/templates/element/Search/v_search.php @@ -11,6 +11,8 @@ var values = null; window._search.values = ; + + window._search.values = {}; diff --git a/templates/element/Search/v_templates.php b/templates/element/Search/v_templates.php index 185411e..0f87af1 100644 --- a/templates/element/Search/v_templates.php +++ b/templates/element/Search/v_templates.php @@ -336,7 +336,7 @@ display: block; padding: 0.3125rem 0; margin: 0.125rem 0 0; - font-size: 1rem; + font-size: 14px; text-align: left; list-style: none; background-color: var(--search-color-white); @@ -349,7 +349,7 @@ } .suggestions-list li { - padding: 0.1875rem 1.25rem; + padding: 0.2rem 1.25rem; clear: both; font-weight: 400; line-height: 1.5; @@ -363,6 +363,10 @@ text-decoration: none; background-color: var(--search-color-gray-100); } + +.suggestions-list .is-active { + background-color: var(--search-color-gray-200); +} diff --git a/webroot/js/main.js b/webroot/js/main.js index b4baa72..3d8a38c 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -238,12 +238,14 @@ const SearchApp = { }, mounted() { console.info("Search mounted!"); - const keys = Object.keys(this.values); - for (let filter of keys) { - this.add({filter: filter, ...this.values[filter]}); - } - if (keys.length == 0) { - this.add(); + if (this.values != null && this.values != undefined) { + const keys = Object.keys(this.values); + for (let filter of keys) { + this.add({filter: filter, ...this.values[filter]}); + } + if (keys.length == 0) { + this.add(); + } } const appRoot = document.getElementById(window._search.rootElemId); appRoot.addEventListener( @@ -708,6 +710,7 @@ const SearchLookupInput = { suggestions: [], showSuggestions: false, debounceTimeout: null, + arrowCounter: -1 }; }, computed: { @@ -732,52 +735,96 @@ const SearchLookupInput = { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { this.fetchSuggestions(); - }, 300); + }, 200); }, async fetchSuggestions() { - if (this.inputValue.length < 2) { + if (this.inputValue.length >= 2) { + try { + let query = this.query.replace(this.wildcard, this.inputValue) + let autocompleteUrl = this.autocompleteUrl + '?' + query; + if (!/^https?:\/\//i.test(autocompleteUrl)) { + autocompleteUrl = `${window.location.origin}${autocompleteUrl}`; + } + const url = new URL(autocompleteUrl); + const response = await fetch(url); + this.suggestions = await response.json(); + this.showSuggestions = true; + this.arrowCounter = -1; + } catch (error) { + console.error('Error fetching suggestions:', error); + } + } else { this.suggestions = []; this.showSuggestions = false; - return; - } - - let query = this.query.replace(this.wildcard, this.inputValue) - let autocompleteUrl = this.autocompleteUrl + '?' + query; - const url = new URL(autocompleteUrl); - - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('Network response was not ok'); - } - const data = await response.json(); - this.suggestions = data; - this.showSuggestions = true; - } catch (error) { - console.error('Error fetching suggestions:', error); } }, selectSuggestion(suggestion) { this.inputValue = suggestion[this.valueName]; this.selectedId = suggestion[this.idName]; this.showSuggestions = false; - this.$emit('change-value', { - index: this.index, - value: { id: this.selectedId, value: this.inputValue } - }); + this.$emit('change-value', { index: this.index, value: { id: this.selectedId, value: this.inputValue } }); + }, + onArrowDown(evt) { + if (this.showSuggestions) { + if (this.arrowCounter < this.suggestions.length - 1) { + this.arrowCounter++; + } + this.scrollToActive(); + evt.preventDefault(); + } + }, + onArrowUp(evt) { + if (this.showSuggestions) { + if (this.arrowCounter > 0) { + this.arrowCounter--; + } + this.scrollToActive(); + evt.preventDefault(); + } + }, + onEnter(event) { + if (this.showSuggestions && this.arrowCounter > -1 && this.arrowCounter < this.suggestions.length) { + event.preventDefault(); + this.selectSuggestion(this.suggestions[this.arrowCounter]); + } + }, + onEscape() { + if (this.showSuggestions) { + this.showSuggestions = false; + this.arrowCounter = -1; + this.$el.querySelector('input[type="text"]').focus(); + } + }, + scrollToActive() { + const activeItem = this.$el.querySelector('.is-active'); + if (activeItem) { + activeItem.scrollIntoView({ block: 'nearest' }); + } }, onBlur() { setTimeout(() => { this.showSuggestions = false; + this.arrowCounter = -1; }, 200); - }, + } }, mounted() { if (this.value) { - this.inputValue = this.value.value; - this.selectedId = this.value.id; + this.inputValue = this.value.value || ''; + this.selectedId = this.value.id || ''; } }, + watch: { + value(newValue) { + if (newValue) { + this.inputValue = newValue.value || ''; + this.selectedId = newValue.id || ''; + } else { + this.inputValue = ''; + this.selectedId = ''; + } + } + } }; const createMyApp = (root, callback) => {