Type ahead complete experience with VueJS
Asked Answered
M

1

7

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 ?

Monopolize answered 14/3, 2021 at 6:4 Comment(2)
You have a collision with the data property initialized as word and the key value you use, word in words. change it to w in words and use {{w}} not ${word}Win
Makes no difference... doesn't autocompleteMonopolize
P
4

Performance issue on Chrome

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:

Original answer:

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>

Edit: Is this only a Vue related issue? No.

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/

Primaveria answered 16/3, 2021 at 11:30 Comment(7)
IIUC you are just reducing the amount of results on the DOM? Perhaps this is a VueJS problem and worth looking at another approach?Monopolize
@Monopolize yes, I basically was just reducing the amount of results on the DOM. But it does not seem to be a VueJS problem - check the Edit on my answerPrimaveria
regarding using another approach - yes, definitely - the dynamic datalist support is not very good as I mentioned by pointing out the firefox bug. Apart from that I recommend that you keep the DOM as light as possible by instead keeping the results in memory and showing as needed.Primaveria
So using the select element t as opposed to datalist offers the best UX?Monopolize
No @hendry, the datalist conceptually has the best UX, it's just it's native implementations that are not good enough and so the best UX is to use a custom autocompletePrimaveria
I added a jsfiddle in the end with a working example fetching from the same API as your code on the OP, and using the polyfill code you provided on your OP! it works pretty well. If you need to make it work with vue, it is perfectly doable as well and I see no reason why it shouldn't work equally wellPrimaveria
Thanks for your effort with this ! I applied your suggestions upon dfts.dabase.comMonopolize

© 2022 - 2025 — McMap. All rights reserved.