Billy Moon
Billy Moon

Reputation: 58531

JavaScript autocomplete without external library

Is there a javascript autocomplete library that does not depend on any other libraries?

I am not using jQuery or the likes as I am making a mobile app that I need to keep extra light.

Upvotes: 50

Views: 69123

Answers (6)

Optimae
Optimae

Reputation: 1002

For anyone looking at this in 2017 onwards who needs a simple solution, you can use HTML5's built-in <datalist> tag instead of relying on JavaScript.

Example:

<datalist id="languages">
  <option value="HTML"></option>
  <option value="CSS"></option>
  <option value="JavaScript"></option>
  <option value="Java"></option>
  <option value="Ruby"></option>
  <option value="PHP"></option>
  <option value="Go"></option>
  <option value="Erlang"></option>
  <option value="Python"></option>
  <option value="C"></option>
  <option value="C#"></option>
  <option value="C++"></option>
</datalist>

<input type="text" list="languages">

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist

Upvotes: 32

agm1984
agm1984

Reputation: 17132

I was researching this the other night, and my solution was originally like the ES6 solution in here, like this:

return this.data.filter((option) => {
    return option.first_name
        .toString()
        .toLowerCase()
        .indexOf(this.searchTerms.toLowerCase()) >= 0
})

But the problem with that is it isn't robust enough to handle filtering nested data. You can see it is filtering this.data which has a data structure like:

[
    { first_name: 'Bob', },
    { first_name: 'Sally', },
]

You can see it filters based on this.searchTerms after lowercasing both the search term and option.first_name, but it is too rigid to search for option.user.first_name. My original attempt was to pass in the field to filter by, such as:

this.field = 'user.first_name';

But this involves savage, custom JavaScript to handle something like this.field.split('.') and dynamically generating the filter function.

Instead, I remembered an old library I've used before called fuse.js, and it is working well because it not only handles that case of arbitrary nesting on what I just called this.field but also handles fuzzy matching based on defined thresholds.

Check out here: https://fusejs.io/

[edit note]: I realize this question is looking for no-external-library, but I want to keep this post here as it provides adjacent value. It is not intended to be "the" solution.

Here is how I'm currently using it:

import Fuse from 'fuse.js';

const options = {
    threshold: 0.3,
    minMatchCharLength: 2,
    keys: [this.field],
};

const fuse = new Fuse(this.data, options);

this.filteredData = fuse.search(this.searchTerms);

You will have to read the Fuse docs to understand that better, but fundamentally, you can see that a new Fuse() object is created using the data to filter and the options.

The keys: [this.field] part is important because that's where you pass in the keys to search by, and you can pass in an array of them. For example, you could filter this.data by keys: ['user.first_name', 'user.friends.first_name'].

I am using this currently in Vue JS, so I have that above logic inside an instance watch function, so every time this.searchTerms changes, that logic runs and updates this.filteredData which is placed into my dropdown list in my autocomplete component.

Also I'm sorry I just realized this question specifically says without an external library, but I'll post this anyway because I find this question every time I make an ES6 autocomplete in Vue JS or React JS. I think it is very valuable to have strict or loose fuzzy matching and to support arbitrarily nested data. Based on Webpack bundle analyzer, fuse.js is 4.1kb gzipped, so it is quite small given that it can support "all" client-side filtering needs.

If you are limited in your ability to use external libraries, consider my first example piece of code. It works if your data structure is static, and you can easily change option.first_name to something like option[this.field] if you wish to variablize the searched field (ie: if your objects are always flat).

You could variablize the list to search as well. Try something like this:

const radicalFilter = ({ collection, field, searchTerms }) => {
    return collection.filter((option) => {
        return option[field]
            .toString()
            .toLowerCase()
            .indexOf(searchTerms.toLowerCase()) >= 0
    })
}

radicalFilter({
    collection: [{ first_name: 'Bob' }, { first_name: 'Sally' }],
    field: 'first_name',
    searchTerms: 'bob',
})

Based on my experiences over the past couple years, the above sample is very performant. I've used it to filter 10,000 records in a react-table component, and it didn't break a sweat. It doesn't create any extra intermediate data structures. It is simply just Array.prototype.filter() which takes your array and returns a new array with matched items.

Upvotes: 1

svnm
svnm

Reputation: 24308

Here is a basic JavaScript example, which could be modified into an autocomplete control:

var people = ['Steven', 'Sean', 'Stefan', 'Sam', 'Nathan'];

function matchPeople(input) {
  var reg = new RegExp(input.split('').join('\\w*').replace(/\W/, ""), 'i');
  return people.filter(function(person) {
    if (person.match(reg)) {
      return person;
    }
  });
}

function changeInput(val) {
  var autoCompleteResult = matchPeople(val);
  document.getElementById("result").innerHTML = autoCompleteResult;
}
<input type="text" onkeyup="changeInput(this.value)">
<div id="result"></div>

Upvotes: 35

ronaldtgi
ronaldtgi

Reputation: 727

ES2016 feature: Array.prototype.includes without external library.

function autoComplete(Arr, Input) {
    return Arr.filter(e =>e.toLowerCase().includes(Input.toLowerCase()));
}

Codepen Demo

Upvotes: 3

Ben
Ben

Reputation: 673

I did this once by sending a JSON request back to the server and using Python code to do the autocomplete. It Was a little slow, but it saved sending a ton of data across.

Upvotes: 0

Christophe
Christophe

Reputation: 28114

The core of an autocomplete script will be the ajax call to the dictionary of terms.

I assume your mobile application already includes an ajax function, so maybe you're better off just writing your autocomplete from scratch? Basically all you need in an input tag, a keyup event handler that triggers the ajax call, and a div to collect the response.

[Update] Based on the comments, some references from John Resig's blog:

http://ejohn.org/blog/revised-javascript-dictionary-search/

http://ejohn.org/blog/jquery-livesearch/

Upvotes: 9

Related Questions