I'm looking to create an input field that offers suggestions on completions like what VScode "Intellisense" (I think) or like dmenu does.
I have been using Vue JS and code like:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label>Lookup German word:
<input type="text" v-model.trim="word" v-on:keyup="signalChange" v-on:change="signalChange" list="words" autofocus>
</label>
<datalist id="words">
<option v-for="w in words">${w}</option>
</datalist>
Query: ${query} Results: ${words.length} Time taken: ${fetchtime} ms
</div>
<script>
const app = new Vue({
el:'#app',
delimiters: ['${', '}'],
data() {
return {
listId:'words',
word:'',
query:'',
words:[],
fetchtime: 0
}
},
methods: {
async signalChange(){
console.log(this.word)
if (this.word.length > 2 && this.word.slice(0,3).toLowerCase() != this.query) {
this.query = this.word.slice(0,3).toLowerCase()
let time1 = performance.now()
let response = await fetch('https://dfts.dabase.com/?q=' + this.query)
const words = await response.json()
let time2 = performance.now()
this.fetchtime = time2 - time1
this.listId="";
this.words = words
setTimeout(()=>this.listId="words");
}
}
}
})
</script>
Where signalChange
would fetch some completion results.
However the User Experience (UX) is non-intuitive. You have to backspace to see the completions after typing three characters like "for". I've tried a couple of browsers and the VueJS experience is pretty poor across the board. However it works ok without VueJS.
Is there something I am missing? Demo: https://dfts.dabase.com/
Perhaps I need to create my own dropdown HTML in VueJS like what happens in https://dl.dabase.com/?polyfill=true ?
There is a performance issue on Chrome reported here: Is this a Chrome UI performance bug related to input + datalist?
Applying the solution to your Vue code works fine for Chrome:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label>Lookup German word:
<input type="text" v-model.trim="word" v-on:keyup="signalChange" v-on:change="signalChange" list="words" autofocus>
</label>
<datalist v-bind:id="listId">
<option v-for="w in words">${w}</option>
</datalist>
Query: ${query} Results: ${words.length} Time taken: ${fetchtime} ms
</div>
<script>
const app = new Vue({
el:'#app',
delimiters: ['${', '}'],
data() {
return {
listId:'words',
word:'',
query:'',
words:[],
fetchtime: 0
}
},
methods: {
async signalChange(){
console.log(this.word)
if (this.word.length > 2 && this.word.slice(0,3).toLowerCase() != this.query) {
this.query = this.word.slice(0,3).toLowerCase()
let time1 = performance.now()
let response = await fetch('https://dfts.dabase.com/?q=' + this.query)
const words = await response.json()
let time2 = performance.now()
this.fetchtime = time2 - time1
this.listId="";
this.words = words
setTimeout(()=>this.listId="words");
}
}
}
})
</script>
Firefox still won't work properly with this, so refer to my original answer below about that:
I noticed a big lag when running your code, so I started fiddling a bit and it seems that the issue is generating the data-list options for a large amount of items.
Since you will be only showing a few results anyway, what can be done is to limit the amount of rendered options and then use filter to show further results when more characters are added.
This works fine on Chrome but still fails on Firefox (although there's a known issue in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1474137)
Check it out:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label>Lookup German word:
<input type="text" v-model="word" v-on:keyup="signalChange" list="words" autofocus>
</label>
<datalist id="words">
<option v-for="w in words">${w}</option>
</datalist> Query: ${query} Results: ${fetchedWords.length} Time taken: ${fetchtime} ms
</div>
<script>
new Vue({
el: '#app',
delimiters: ['${', '}'],
data() {
return {
word: '',
query: '',
words: [],
fetchedWords: [],
fetchtime: 0
}
},
methods: {
async signalChange() {
if (this.word.length > 2 && this.word.slice(0, 3).toLowerCase() != this.query) {
this.query = this.word.slice(0, 3).toLowerCase();
let response = await fetch('https://dfts.dabase.com/?q=' + this.query);
this.fetchedWords = (await response.json());
this.words = this.fetchedWords.slice(0, 10);
} else if (this.word.includes(this.query)) {
this.words = this.fetchedWords.filter(w => w.startsWith(this.word)).slice(0, 10);
} else {
this.words = [];
}
}
}
})
</script>
I created an equivalent implementation in pure JS+HTML. Used a performant way to minimize DOM creation time (created a fragment and only attach it once to the DOM as per How to populate a large datalist (~2000 items) from a dictionary) but it still takes a long time to become responsive. Once it does it works well, but on my machine it took almost a minute after inputting "was" to become responsive.
Here's the implementation in pure JS+HTML:
let word = '';
let query = '';
const input = document.querySelector('input');
const combo = document.getElementById('words');
input.onkeyup = function signalChange(e) {
word = e.target.value;
console.log(word)
if (word.length > 2 && word.slice(0, 3).toLowerCase() != query) {
query = word.slice(0, 3).toLowerCase();
fetch('https://dfts.dabase.com/?q=' + query)
.then(response => response.json())
.then(words => {
const frag = document.createDocumentFragment();
words.forEach(w => {
var option = document.createElement("OPTION");
option.textContent = w;
option.value = w;
frag.appendChild(option);
})
combo.appendChild(frag);
});
}
}
<div id="app">
<label>Lookup German word:
<input type="text" list="words" autofocus>
</label>
<datalist id="words"></datalist>
</div>
So, taking this into account and the limited experience in firefox due to bugs you should implement a custom autocomplete without the datalist.
For a good performance, if the list is very large you may want to keep the entire list out of the DOM anyway, and update it as the user changes the input or scrolls in the list.
Here's an example of an existing custom autocomplete working with the API from the OP's example: https://jsfiddle.net/ywrvhLa8/4/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With