//TODO: if autocomplete is focus and the input has not a single character still show "Bitte mindestens 3 Zeichen eingeben" //TODO: fix the fetchSuggestions function to not show a loading spinner when the input gets cleared //TODO: fix so we can use arrow keys to navigate the suggestions // TODO: Implement giving the option without the need of an API || need to use computed property to filter the items // TODO: Fix the weirdness with timeout and selecting the suggestion Vue.component('tt-autocomplete', { template: `
`, props: { value: {type: [String, Number]}, label: {type: String, required: false}, apiUrl: String, placeholder: {type: String, default: ''}, items: {type: Array, default: () => []}, sm: {type: Boolean, default: true}, row: {type: Boolean, default: false}, returnText: {type: Boolean, default: false}, emitDisplayValue: {type: Boolean, default: false}, caseInsensitive: {type: Boolean, default: false}, }, data() { return { window, displayingItems: [], oldDisplayValue: '', displayValue: '', isLoading: false, showSuggestions: false, cursor: -1, fetchSuggestionsDebounceTimer: null, disableIDFetch: false }; }, async mounted() { this.updateDisplayValue().then(); }, methods: { setOldDisplayValue(newValue, oldValue) { if (this.emitDisplayValue && newValue && typeof this.value !== 'number') { this.$emit('displayValue', newValue); } this.oldDisplayValue = oldValue; }, async updateDisplayValue(newValue, oldValue) { if (this.disableIDFetch) { this.disableIDFetch = false; return; } if (newValue) { this.$emit('input', newValue); this.value = newValue; } if (oldValue && !newValue) { this.$emit('input', ''); this.displayValue = ''; } if (this.value && this.apiUrl) { if (this.returnText && isNaN(this.value)) { this.displayValue = this.value; return; } const response = await axios.get(`${this.apiUrl}${this.apiUrl.includes('?') ? '&' : '?'}autocomplete=1&searchedID=${this.value}`); this.displayValue = response.data[0].text; } else if (this.value) { const selectedItem = this.caseInsensitive ? this.items.find(item => item.value.toLowerCase() === this.value.toLowerCase()) : this.items.find(item => item.value === this.value) ; this.displayValue = selectedItem ? selectedItem.text : this.displayValue; } else { if (this.returnText === false && !(typeof this.value === 'undefined' || this.value === '')) this.$emit('input', ''); this.displayValue = this.displayValue.replace(this.oldDisplayValue, ''); } }, onInput(event) { this.displayValue = event.target.value; if (this.returnText) this.$emit('input', this.displayValue); this.fetchSuggestions(); }, onFocus() { this.showSuggestions = true; }, onBlur() { setTimeout(() => { this.showSuggestions = false; }, 200); }, fetchSuggestions() { this.$set(this, 'displayingItems', []); if (this.displayValue.length < 3) return; if (!this.apiUrl) { const isNegated = this.displayValue.startsWith('!'); const substrings = (isNegated ? this.displayValue.slice(1) : this.displayValue).split(' ').map(s => s.toLowerCase()); this.displayingItems = this.items.filter(item => { let substringMatch = true; for (var k = 0, klen = substrings.length; k < klen; ++k) { if (!item.text.toLowerCase().includes(substrings[k])) { substringMatch = false; break; } } return (isNegated && !substringMatch) || (!isNegated && substringMatch); }) return } this.isLoading = true; clearTimeout(this.fetchSuggestionsDebounceTimer); this.fetchSuggestionsDebounceTimer = setTimeout(() => { setTimeout(async () => { if (this.displayValue.length < 3) { this.displayingItems = []; this.isLoading = false; return; } const response = await axios.get(`${this.apiUrl}${this.apiUrl.includes('?') ? '&' : '?'}autocomplete=1&q=${encodeURIComponent(this.displayValue)}`); if (response.data?.status === 'error') { this.displayingItems = []; } else { this.displayingItems = response.data if (response.data.length === 1 && response.data[0].text === this.displayValue) this.selectSuggestion(response.data[0]); } this.isLoading = false; this.fetchSuggestionsDebounceTimer = null; }, 100); }, 300); // Adjust the 300ms debounce time as needed }, selectSuggestion(item) { this.disableIDFetch = true; this.$emit('input', item.value); this.displayValue = item.text; this.showSuggestions = false; }, clear() { this.displayValue = ''; this.$emit('input', ''); } }, watch: { displayValue: {handler: 'setOldDisplayValue', immediate: true}, value: {handler: 'updateDisplayValue', immediate: true}, apiUrl: {handler: 'fetchSuggestions', immediate: true}, } });