loading...

How to create an autocomplete input with plain Javascript

stephenafamo profile image Stephen Afam-Osemene ・3 min read

Now, most people who have built websites have included forms. Regular text elements, number, select (dropdown), checkboxes and radio buttons.

The problem

A situation I've often encountered is where we need the user to choose from a very long list of options.
The easiest thing to do is to put all the options into a select element. However, this is impractical because...

  1. It can make for a very terrible user experience
  2. If all the options are huge (like in thousands) then there is no practical reason to load all these into the select option.

A solution

An auto complete form. We could have a huge list and show only those relevant to the user. We don't need to load all the results into the HTML, we can simply load the ones we need as the user types.

This is the technique used in many websites where there are a lot of options to choose from. e-commerce, marketplaces, classifieds, social media, e.t.c.

It may seem like the implementation is complex, especially with libraries like typeahead.js, however, it is actually rather simple.

An implementation

First, our html element

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>
        Demo auto complete
    </title>
</head>
<body>
    <h2>The form</h2>
    <form>
        <input type="text" name="name" id="name_input">Name
        <br/>
        <input type="submit">
    </form>
</body>
</html>

To create an autocomplete form, we will use the HTML5 datalist tag, so we modify it to look like this.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>
        Demo auto complete
    </title>
</head>
<body>
    <h2>The form</h2>
    <form>
        <input type="text" name="name" id="name_input" list="huge_list">Name
        <datalist id="huge_list">
        </datalist>
        <br/>
        <input type="submit">
    </form>
</body>
</html>

Javascript to autofill with AJAX

What we will do, is we will check whenever the user types into our field and then populate the datalist element with only what is necessary.
Comments have been added to the code

index.js

window.addEventListener("load", function(){

    // Add a keyup event listener to our input element
    var name_input = document.getElementById('name_input');
    name_input.addEventListener("keyup", function(event){hinter(event)});

    // create one global XHR object 
    // so we can abort old requests when a new one is make
    window.hinterXHR = new XMLHttpRequest();
});

// Autocomplete for form
function hinter(event) {

    // retireve the input element
    var input = event.target;

    // retrieve the datalist element
    var huge_list = document.getElementById('huge_list');

    // minimum number of characters before we start to generate suggestions
    var min_characters = 0;

    if (input.value.length < min_characters ) { 
        return;
    } else { 

        // abort any pending requests
        window.hinterXHR.abort();

        window.hinterXHR.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {

                // We're expecting a json response so we convert it to an object
                var response = JSON.parse( this.responseText ); 

                // clear any previously loaded options in the datalist
                huge_list.innerHTML = "";

                response.forEach(function(item) {
                    // Create a new <option> element.
                    var option = document.createElement('option');
                    option.value = item;

                    // attach the option to the datalist element
                    huge_list.appendChild(option);
                });
            }
        };

        window.hinterXHR.open("GET", "/query.php?query=" + input.value, true);
        window.hinterXHR.send()
    }
}

On the server, we will listen and return a JSON formatted array of values. An example PHP script is.

query.php

<?php
$query = $_GET['query'];

// These values may have been gotten from a database.
// We'll use a simple array just to show this example.
$values = ['Neo',
            'Ibiyemi',
            'Olayinka',
            'Jonathan',
            'Stephen', 
            'Fisayo', 
            'Gideon',
            'Mezie',
            'Oreoluwa', 
            'Jordan', 
            'Enkay', 
            'Michelle', 
            'Jessica'];

if ($query) {
    foreach ($values as $key => $value) {
        if (stripos($value, $query) === false) {
            unset($values[$key]);
        }
    }
}

echo json_encode(array_values($values));
?>

Validation

A problem with this method is that there is no default way to make sure that the input is from the list.
To solve this, we will set a custom function for the submit action, and prevent submission if the value of the input is not found in the list.

IN

index.html

change

<h2>The form</h2>
<form>
    <input type="text" name="name" id="name_input" list="huge_list">Name

to

<h2>The form</h2>
<form onsubmit="return validateForm()">
    <input type="text" name="name" id="name_input" list="huge_list">Name

IN

index.js

add

function validateForm(){

    // Get the input element
    var input = document.getElementById('name_input');
    // Get the datalist
    var huge_list = document.getElementById('huge_list');


    // If we find the input inside our list, we submit the form
    for (var element of huge_list.children) {
        if(element.value == input.value) {
            return true;
        }
    }

    // we send an error message
    alert("name input is invalid")
    return false;
}

That's all!!!

Now if the user tries to submit an invalid name, he will be shown an error message.

All sample code can be found in this GitHub repository

Discussion

pic
Editor guide
Collapse
udus97 profile image
Qudus

Hi, Afam
This is a great article; its good to see this implementation in Vanilla JS. But i'd like to know the reason why you are testing for this condition in your JS file

!isNaN(input.value)

Thanks.

Collapse
stephenafamo profile image
Stephen Afam-Osemene Author

Thanks for spotting that. There's actually no reason for that in the example.
It was specific code for a project.

I'll edit.

Collapse
mauricehayward profile image
Maurice Hayward

Thank you! I need to do an autocomplete form and I had no idea where to start! 🔑

Collapse
stephenafamo profile image
Stephen Afam-Osemene Author

You're welcome!!!

I'm really happy that this helped

Collapse
simo97 profile image
ADONIS SIMO

this saved my day... thanks a lot

Collapse
simo97 profile image
ADONIS SIMO

I've made this after reading you tutorial, github.com/simo97/Nefertiti,

it is moslty for the case you already has an existing app with , the goal here is to turn them into lightweight autocomplete input. No dependencies, plain vanilla js, based on as you teach me.

thanks it save me from a lot of refractoring on my current project.

Collapse
stephenafamo profile image
Stephen Afam-Osemene Author

That's great! Thanks for the feedback. I'll check your project out.

Collapse
danhodge profile image
danhodge

Great post - datalist is a really powerful addition to HTML5 that I was not aware of.

Collapse
sage911 profile image
Stefan Age

Thanks for this! I'm trying to write a React component that does the same, this will be a huge help.

Collapse
tganyan profile image
Tyler Anyan

This is exactly what I've been looking for, so thanks for posting! I am getting a host of issues, however, and I'm a bit of a newbie with some of this (especially php) so I'm not sure what the wrench in the gears is here...

In Chrome: Failed to load file:///C:/query.php?query=N: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

In Chrome with web server:
Uncaught SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse ()
at XMLHttpRequest.window.hinterXHR.onreadystatechange (index.js:35)

In Firefox: NS_ERROR_DOM_BAD_URI: Access to restricted URI denied
window.hinterXHR.send();

Collapse
mrkezii profile image
David Kezi

Awesome Awesome Post

Collapse
singhkumarhemant profile image
Hemant Kumar Singh

Thanks for this awesome article... it was really helpful

Collapse
asadskhan profile image
asadskhan

Hi, Afam
This is a great article.

Thanks

Collapse
spik3s profile image
Jacek 'Spikes' Wozniak

Hi! Great article!

It's worth to mention that this will not work as intended on iOS devices, the dropdown will not show.

Collapse
rodhzuniga profile image
ZunHer

great friend

how to make it work only with the initials of each word.