Simple Autocomplete With Pure HTML Javascript (Free Download!)

Welcome to a tutorial on how to create an autocomplete with pure HTML, CSS, and Javascript. Yes, there are many autocomplete plugins these days, but some of them introduce a lot of loading bloat with the use of third-party frameworks. So here it is, a sharing of my version of autocomplete, all done using pure Javascript, no third-party libraries required. Read on!

ⓘ I have included a zip file with all the example source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes Quickstart & Demo How It Works
Useful Bits & Links The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

HOW TO USE & DEMO

For you guys who don’t want to read through everything, here are a few “quick start” examples and demos.

 

EXAMPLE 1) SIMPLE AUTOCOMPLETE WITH AN ARRAY

1-array.html
<!-- (A) LOAD JS + CSS -->
<script src="autocomplete.js"></script>
<link rel="stylesheet" href="autocomplete.css">
 
<!-- (B) INPUT FIELD -->
<input type="text" id="demoA"/>
 
<script>
// (C) ATTACH AUTOCOMPLETE TO INPUT FIELD
ac.attach({
  target: document.getElementById("demoA"),
  data: ["Aaronn", "Baattyy", "Chaarles", "Dionn", "Elly"]
});
</script>
  1. Include the autocomplete CSS and Javascript. Duh.
  2. Define the <input> field.
  3. Lastly, use the ac.attach() function to initiate the autocomplete.
    • target HTML field to attach the autocomplete.
    • data an array of strings for the autocomplete. This can also be a URL or object – See examples below.

 

EXAMPLE 2) AJAX AUTOCOMPLETE

2a-server.html
<!-- (A) INPUT FIELD -->
<input type="text" id="demoB"/>
 
<script>
// (B) ATTACH AUTOCOMPLETE TO INPUT FIELD
ac.attach({
  // (B1) REQUIRED - TARGET FIELD + URL
  target: document.getElementById("demoB"),
  data: "2b-search.php",
 
  // (B2) OPTIONAL - POST DATA
  post: {
    keyA: "valueA",
    keyB: "valueB"
  },
 
  // (B3) OPTIONAL - DELAY + MIN CHARACTERS
  delay: 1000,
  min: 2
});
</script>

Want to do a server-side autocomplete? Simply pass the URL into data, and it will change into “AJAX mode”. Take note of the extra options here:

  • post Additional data you want to post to the server along with the AJAX request.
  • delay In milliseconds. How long to wait before running the autocomplete, default 500ms.
  • min The minimum number of characters to trigger autocomplete, default 2 characters.

As for the server-side, here is a dummy example in PHP:

2b-search.php
// (A) DUMMY DATA
$data = ["Aaronn", "Baattyy", "Chaarles", "Dionn", "Elly"];
 
// (B) SEARCH
$search = strtolower($_POST["search"]);
$results = [];
foreach ($data as $d) {
  if (strpos(strtolower($d), $search) !== false) { $results[] = $d; }
}
 
// (C) RESPOND
echo count($results)==0 ? null : json_encode($results);

In your own server-side implementation, just do your own database search and reply with a JSON encoded array.

 

 

EXAMPLE 3) AUTOCOMPLETE MULTIPLE FIELDS & ON SELECT

3-multiple.html
<!-- (A) AUTOCOMPLETE MULTIPLE FIELDS -->
Name: <input type="text" id="demoC"/><br>
Email: <input type="email" id="dEmail"/><br>
Age: <input type="text" id="dAge"/>
 
<script>
// (B) ATTACH AUTOCOMPLETE
ac.attach({
  // (B1) USE AN ARRAY OF OBJECTS AS SUGGESTIONS
  target: document.getElementById("demoC"),
  data: [
    {D: "Aaronn", dEmail: "aaronn@doe.com", dAge: 27 },
    {D: "Baattyy", dEmail: "baattyy@doe.com", dAge: 37 },
    {D: "Chaarles", dEmail: "chaarles@doe.com", dAge: 47 },
    {D: "Dionn", dEmail: "dionn@doe.com", dAge: 57 },
    {D: "Elly", dEmail: "elly@doe.com", dAge: 18 }
  ],
 
  // (B2) OPTIONAL, DO THIS ON SELECT
  select : (val, multi) => {
    console.log(val); // selected value
    console.log(multi); // attached multiple values
  }
});
</script>
Name:
Email:
Age:

How this works – Choose a suggested name, and autocomplete will also fill in the email and age. As you can see, we now pass in an array of objects for data now.

  • D Data for the “main autocomplete field”.
  • dEmail dAge ID of the extra fields to “cascade fill”.

P.S. This also works in AJAX mode. Just set the server-side script to return a JSON-encoded array of objects.

 

 

HOW IT WORKS

So far so good? Let us now get into the details of the autocomplete script. This is for the guys who want to further customize it.

 

PART A) PROPERTIES

autocomplete.js
// (A) PROPERTIES
instances : [], // autocomplete instances
open : null, // instance that is currently open
  • ac.instances To contain all the autocomplete instances – Which target field, data array, settings, and options.
  • ac.open Since there are possibly multiple instances, we don’t want them to clash… Only one autocomplete instance can open at a time, and this flag will make sure of that.

 

PART B) INITIALIZING THE AUTOCOMPLETE

autocomplete.js
// (B) ATTACH AUTOCOMPLETE TO INPUT FIELD
// target : target field
// data : suggestion data (array), or url (string)
// post : optional, extra data to send to server
// delay : optional, delay before suggestion, default 500ms
// min : optional, minimum characters to fire suggestion, default 2
// select : optional, function to call on selecting an option
attach : (options) => {
  // (B1) NEW AUTOCOMPLETE INSTANCE
  ac.instances.push({
    target: options.target,
    data: options.data,
    post: options.post ? options.post : null,
    delay: options.delay ? options.delay : 500,
    min: options.min ? options.min : 2,
    select : options.select ? options.select : null,
    suggest: document.createElement("div"), // html suggestion box
    timer: null // autosuggest timer
  });
 
  // (B2) ATTACH AUTOCOMPLETE HTML
  let id = ac.instances.length-1,
      instance = ac.instances[id],
      parent = instance.target.parentElement,
      wrapper = document.createElement("div");
    
  instance.target.setAttribute("autocomplete", "off");
  parent.insertBefore(wrapper, instance.target);
  wrapper.classList.add("acWrap");
  wrapper.appendChild(instance.target);
  wrapper.appendChild(instance.suggest);
  instance.suggest.classList.add("acSuggest");

  // (B3) KEY PRESS LISTENER
  instance.target.addEventListener("keyup", (evt) => {
    // (B3-1) CLEAR OLD TIMER & SUGGESTION BOX
    if (instance.timer != null) { window.clearTimeout(instance.timer); }
    instance.suggest.innerHTML = "";
    instance.suggest.style.display = "none";

    // (B3-2) CREATE NEW TIMER - FETCH DATA FROM SERVER OR STRING
    if (this.value.length >= instance.min) {
      if (typeof instance.data == "string") {
        instance.timer = setTimeout(() => { ac.fetch(iid); }, instance.delay);
      } else {
        instance.timer = setTimeout(() => { ac.filter(iid); }, instance.delay);
      }
    }
  });
},
  • The attach() function is what we call to create an autocomplete instance.
  • Basically, what this function does are 3 things.
    • Create a new entry in the instances array to keep track.
    • Attach the necessary autocomplete HTML (will go through more below).
    • Lastly, attach key listeners to fire up the suggestions.

 

 

PART C & D) SUGGESTIONS DATA

autocomplete.js
// (C) DRAW SUGGESTIONS FROM ARRAY
filter : (id) => {
  // (C1) GET INSTANCE + DATA
  let instance = ac.instances[id],
      search = instance.target.value.toLowerCase(),
      multi = typeof instance.data[0]=="object",
      results = [];

  // (C2) FILTER APPLICABLE SUGGESTIONS
  for (let i of instance.data) {
    let entry = multi ? i.D : i ;
    if (entry.toLowerCase().indexOf(search) != -1) { results.push(i); }
  }

  // (C3) DRAW SUGGESTIONS
  ac.draw(id, results.length==0 ? null : results);
},
  
// (D) AJAX FETCH SUGGESTIONS FROM SERVER
fetch : (id) => {
  // (D1) INSTANCE & FORM DATA
  let instance = ac.instances[id],
      data = new FormData();
  data.append("search", instance.target.value);
  if (instance.post !== null) { for (let i in instance.post) {
    data.append(i, instance.post[i]);
  }}
 
  // (D2) FETCH
  fetch(instance.data, { method: "POST", body: data })
  .then((res) => {
    if (res.status != 200) { throw new Error("Bad Server Response"); }
    return res.json();
  })
  .then((res) => { ac.draw(id, res); })
  .catch((err) => { console.error(err); });
},
  • As you already know, there are 2 ways to drive the autocomplete – By providing an array of data or pointing it to a URL.
    • filter() searches through the array of strings and returns the results.
    • fetch() does an AJAX call to your specified URL – It sends a search parameter, along with post that you may have specified. It expects a JSON encoded array as a response.
  • Lastly, both filter() and fetch() will call draw() to render the “search results” in the HTML suggestion box.

 

PART E) DRAW HTML SUGGESTIONS

autocomplete.js
// (E) DRAW AUTOSUGGESTION
draw : (id, results) => {
  // (E1) GET INSTANCE
  let instance = ac.instances[id];
  ac.open = id;

  // (E2) DRAW RESULTS
  if (results == null) { ac.close(); }
  else {
    instance.suggest.innerHTML = "";
    let multi = typeof results[0]=="object",
        list = document.createElement("ul"), row, entry;
    for (let i of results) { 
      row = document.createElement("li");
      row.innerHTML = multi ? i.D : i;
      if (multi) {
        entry = {...i};
        delete entry.D;
        row.dataset.multi = JSON.stringify(entry);
      }
      row.onclick = function () { ac.select(id, this); };
      list.appendChild(row);
    }
    instance.suggest.appendChild(list);
    instance.suggest.style.display = "block";
  }
},
  • draw() simply renders the list of suggestions as obtained from fetch() or filter().
  • Take note of the open flag here though – It contains “id” of the currently autocomplete box, and this is used to close this box whenever the user clicks on another field (or on anywhere else in the document).

 

 

PART F) SELECTING A SUGGESTION

autocomplete.js
// (F) ON SELECTING A SUGGESTION
select : (id, el) => {
  // (F1) SET VALUES
  let instance = ac.instances[id];
  instance.target.value = el.innerHTML;
  if (el.dataset.multi !== undefined) {
    let multi = JSON.parse(el.dataset.multi);
    for (let i in multi) {
      document.getElementById(i).value = multi[i];
    }
  }
 
   // (F2) CALL ON SELECT - IF DEFINED
  if (instance.select != null) {
    instance.select(el.innerHTML, multi?multi:null);
  }
 
  // (F3) CASE CLOSED
  ac.close();
},

select() is fired when the user clicks on a suggestion. This simply populates the target input field with the selected value and closes the suggestion box.

 

PART G & H) CLOSING

autocomplete.js
// (G) CLOSE AUTOCOMPLETE
close : () => { if (ac.open != null) {
  let instance = ac.instances[ac.open];
  instance.suggest.innerHTML = "";
  instance.suggest.style.display = "none";
  ac.open = null;
}},
  
// (H) CLOSE AUTOCOMPLETE IF USER CLICKS ANYWHERE OUTSIDE
checkclose : (evt) => { if (ac.open != null) {
  let instance = ac.instances[ac.open];
  if (instance.target.contains(evt.target)==false &&
      instance.suggest.contains(evt.target)==false) { ac.close(); }
}}

document.addEventListener("click", ac.checkclose);
  • Captain Obvious again, close() will close the currently open autocomplete box.
  • Lastly, remember that we will close the autocomplete box when the user clicks “anywhere else”? That is exactly what we do in checkclose().

 

EXTRA) AUTOSUGGEST HTML

This is the HTML that parts B & E will wrap the <input> field with – Go ahead and use this to customize the CSS if you want.

<div class="acWrap">
  <input type="text" id="TARGET-FIELD"/>
  <div class="acSuggest">
    <ul>
      <li>Suggestion A</li>
      <li>Suggestion B</li>
      <li>Suggestion C</li>
    </ul>
  </div>
</div>

 

USEFUL BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

HOW ABOUT ARROW KEYS TO SELECT SUGGESTIONS?

  • Update draw() to also attach key listeners. Introduce another selected flag to track the currently selected suggestion.
  • Check if the keypress is “up/down” – Change the selected suggestion.
  • Check if the keypress is “enter” – Fire select().
  • When the suggestion box is closed, detach the key listeners.

Good luck – This is not “simple” as you can see. Also, this is kind of a useless feature on mobile devices these days.

 

COMPATIBILITY CHECKS

Will work fine in all modern “Grade A” browsers, not friendly on ancient browsers.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

6 thoughts on “Simple Autocomplete With Pure HTML Javascript (Free Download!)”

  1. I have a question or I am asking for help O read your tutorial but still didn’t figure it out what if I have more then one input for example I want to search by email ba name or id etc… this script is now working foe that ?

    1. If you are trying to add autocomplete to multiple fields, just attach to multiple fields.
      ac.attach({ target: document.getElementById("EMAIL"), data:DATA });
      ac.attach({ target: document.getElementById("NAME"), data:DATA });

      If you are trying to autocomplete multiple fields from a single selection, see “EXAMPLE 3) AUTOCOMPLETE MULTIPLE FIELDS”.

  2. There’s a typo in “autocomplete.js” file. Line 118: “entry = {…i};” should be “entry = […i];”.

      1. In my browser (Firefox), 1-array.html and 2a-server.html don’t work without changing “entry = {…i};” into “entry = […i];”; but actually doing this change – as you noticed – 3-multiple.html doesn’t work. My comment was not meant to be a criticism of your work (which I really appreciate), but just an advisory.

Leave a Comment

Your email address will not be published.