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},
caseInsensitive: {type: Boolean, default: false},
},
data() {
return {
displayValue: '',
displayingItems: [],
isLoading: false,
showSuggestions: false,
fetchSuggestionsDebounceTimer: null,
disableValueWatcher: false,
cursor: -1,
};
},
watch: {
value: {
handler(newValue) {
if (this.disableValueWatcher) return;
this.updateDisplayValue(newValue);
},
immediate: true
},
apiUrl: {
handler: 'fetchSuggestions',
immediate: true
},
},
methods: {
async updateDisplayValue(id) {
if (!id) {
this.displayValue = '';
return;
}
if (this.returnText && isNaN(id)) {
this.displayValue = id;
return;
}
if (this.apiUrl) {
const response = await axios.get(`${this.apiUrl}${this.apiUrl.includes('?') ? '&' : '?'}autocomplete=1&searchedID=${id}`);
if (response.data[0]) this.displayValue = response.data[0].text;
} else {
const selectedItem = this.items.find(item =>
this.caseInsensitive ? String(item.value).toLowerCase() === String(id).toLowerCase() : item.value === id
);
if (selectedItem) this.displayValue = selectedItem.text;
}
},
onInput() {
if (!this.apiUrl && this.items.length > 0) {
const lowerCaseInput = this.displayValue.toLowerCase();
const matchingItem = this.items.find(item => item.value.toLowerCase() === lowerCaseInput);
if (matchingItem) {
this.selectSuggestion(matchingItem);
return;
}
}
if (this.returnText) this.$emit('input', this.displayValue);
this.fetchSuggestions();
},
onFocus() {
this.showSuggestions = true;
this.fetchSuggestions();
},
onBlur() {
setTimeout(() => this.showSuggestions = false, 200);
},
fetchSuggestions() {
clearTimeout(this.fetchSuggestionsDebounceTimer);
if (this.displayValue.length < 3) {
this.displayingItems = [];
this.isLoading = false;
return;
}
this.fetchSuggestionsDebounceTimer = setTimeout(() => {
this.isLoading = true;
this.cursor = -1;
if (!this.apiUrl) {
const searchTerms = this.displayValue.toLowerCase().split(' ').filter(s => s);
this.displayingItems = this.items.filter(item => {
const itemText = item.text.toLowerCase();
return searchTerms.every(term => itemText.includes(term));
});
this.isLoading = false;
return;
}
axios.get(`${this.apiUrl}${this.apiUrl.includes('?') ? '&' : '?'}autocomplete=1&q=${encodeURIComponent(this.displayValue)}`)
.then(response => {
this.displayingItems = response.data?.status === 'error' ? [] : response.data;
if (this.displayingItems.length === 1 && this.displayingItems[0].text === this.displayValue) {
this.selectSuggestion(this.displayingItems[0]);
}
})
.finally(() => this.isLoading = false);
}, 300);
},
selectSuggestion(item) {
this.disableValueWatcher = true;
this.displayValue = item.text;
this.$emit('input', item.value);
this.showSuggestions = false;
this.$nextTick(() => this.disableValueWatcher = false);
},
selectOnEnter() {
if (this.cursor >= 0 && this.displayingItems[this.cursor])
this.selectSuggestion(this.displayingItems[this.cursor]);
},
navigate(direction) {
const itemCount = this.displayingItems.slice(0, 10).length;
if (!itemCount) return;
this.cursor = (this.cursor + direction + itemCount) % itemCount;
},
clear() {
this.displayValue = '';
this.$emit('input', '');
this.displayingItems = [];
}
}
});