//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},
}
});