Simple Autocomplete In Pure HTML JS (Free Download)

Welcome to a tutorial on how to create an autocomplete with pure HTML, CSS, and Javascript. Yes, there are autocomplete plugins all over the Internet. But some of them require third-party frameworks and introduce a lot of loading bloat. So here it is, a sharing of mine with zero third-party libraries. Read on!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Source code on GitHub Gist

Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

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

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. Load the autocomplete CSS and Javascript.
  2. Define the <input> field.
  3. Lastly, use ac.attach() to initiate the autocomplete.
    • target HTML field to attach the autocomplete.
    • data An array of data for the autocomplete. This can also be a URL or object – See the 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: { key: "value" }
});
</script>

Want to fetch suggestions from the server instead? Simply pass the URL into data, and it will change into “AJAX mode”. Take note that you can define post to send more data to the server. 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; }
}
echo count($results)==0 ? null : json_encode($results);

 

 

EXAMPLE 3) AUTOCOMPLETE MULTIPLE FIELDS

3-multiple.html
<!-- (A) AUTOCOMPLETE MULTIPLE FIELDS -->
<form id="myForm">
  <label for="dName">Name</label>
  <input type="text" id="demoC">
  <label for="dEmail">Email</label>
  <input type="email" id="dEmail">
  <label for="dAge">Age</label>
  <input type="text" id="dAge">
</form>
 
<script>
// (B) ATTACH AUTOCOMPLETE
ac.attach({
  // (B1) USE AN ARRAY OF OBJECTS AS SUGGESTIONS
  target: document.getElementById("demoC"),
  data: [
    {D: "诸葛猪哥", dEmail: "zhuge@doe.com", dAge: 67 },
    {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
  },
 
  // (B3) OPTIONAL - DELAY + MIN CHARACTERS
  delay: 1000,
  min: 2
});
</script>
Name:
Email:
Age:

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

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

Also, take note of the optional settings:

  • select Run this function when the user selects an option.
  • delay Delay before autocomplete is triggered. Defaults to 500 ms.
  • min Minimum characters for autocomplete to trigger. Defaults to 2.

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

 

 

EXAMPLE 4) OVERRIDE SELECTED VALUE

4-override.html
<!-- (B) INPUT FIELD -->
<input type="text" id="demoD">
 
<!-- (C) ATTACH AUTOCOMPLETE TO INPUT FIELD -->
<script>
ac.attach({
  target: document.getElementById("demoD"),
  data: [
    {
      D : "Joe Doe (joe@doe.com)",
      V : "joe@doe.com"
    },
    {
      D : "Joe Doe (joe123@doe.com)",
      V : "joe123@doe.com"
    }
  ]
});

Let’s say we have 2 users with the same name. To differentiate between them:

  • D We can set the “display name” to NAME (EMAIL).
  • V But on select, we only set the email as the “value”.

 

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
var ac = {
  // (A) PROPERTIES
  now : null, // current "focused instance"
  // ...
};
  • var ac This object contains all the mechanics to drive the autocomplete.
  • ac.now The “currently focused” autocomplete instance.

 

PART B) INITIALIZING THE AUTOCOMPLETE

autocomplete.js
// (B) ATTACH AUTOCOMPLETE
// 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 : inst => {
  // (B1) SET DEFAULTS
  if (inst.delay == undefined) { inst.delay = 500; }
  if (inst.min == undefined) { inst.min = 2; }
  inst.timer = null; // autosuggest timer
  inst.ajax = null; // ajax fetch abort controller
 
  // (B2) UPDATE HTML
  inst.target.setAttribute("autocomplete", "off");
  inst.hWrap = document.createElement("div"); // autocomplete wrapper
  inst.hList = document.createElement("ul"); // suggestion list
  inst.hWrap.className = "acWrap";
  inst.hList.className = "acSuggest";
  inst.hList.style.display = "none";
  inst.target.parentElement.insertBefore(inst.hWrap, inst.target);
  inst.hWrap.appendChild(inst.target);
  inst.hWrap.appendChild(inst.hList);

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

    // (B3-2) CREATE NEW TIMER - FETCH DATA FROM SERVER OR ARRAY
    if (inst.target.value.length >= inst.min) {
      if (typeof inst.data == "string") { inst.timer = setTimeout(() => ac.fetch(), inst.delay); }
      else { inst.timer = setTimeout(() => ac.filter(), inst.delay); }
    }
  });
},

You already know ac.attach(), this is used to create an autocomplete instance. All it does is pretty much insert the autocomplete HTML, and add keyup listener on the input field itself. If you are curious, here is how the HTML will be modified:

<div class="acWrap">
  <input type="text" autocomplete="off">
  <ul class="acSuggest">
    <li>OPTION</li> <li>OPTION</li> <li>...</li>
  </ul>
</div>

 

 

PART C & D) SUGGESTIONS DATA

autocomplete.js
// (C) FILTER ARRAY DATA
filter : () => {
  // (C1) SEARCH DATA
  let search = ac.now.target.value.toLowerCase(),
      complex = typeof ac.now.data[0]=="object",
      results = [];

  // (C2) FILTER & DRAW APPLICABLE SUGGESTIONS
  for (let row of ac.now.data) {
    let entry = complex ? row.D : row ;
    if (entry.toLowerCase().includes(search)) { results.push(row); }
  }
  ac.draw(results.length==0 ? null : results);
},
  
// (D) AJAX FETCH SUGGESTIONS FROM SERVER
fetch : () => {
  // (D1) FORM DATA
  let data = new FormData();
  data.append("search", ac.now.target.value);
  if (ac.now.post) {
    for (let [k,v] of Object.entries(ac.now.post)) { data.append(k,v); }
  }
 
  // (D2) FETCH
  ac.now.ajax = new AbortController();
  fetch(ac.now.data, { method:"POST", body:data, signal:ac.now.ajax.signal })
  .then(res => {
    ac.now.ajax = null;
    if (res.status != 200) { throw new Error("Bad Server Response"); }
    return res.json();
  })
  .then(res => ac.draw(res))
  .catch(err => console.error(err));
},
  • As you already know, there are 2 ways to drive autocomplete. By providing an array of data, or pointing it to a URL.
    • ac.filter() Searches through the array and returns the search results.
    • ac.fetch() Does an AJAX call to your specified URL. It sends a search parameter, along with any post that you may have specified. It expects a JSON-encoded array as a response.
  • Lastly, both ac.filter() and ac.fetch() will call ac.draw() to render the “search results” in the HTML suggestion box.

 

PART E) DRAW HTML SUGGESTIONS

autocomplete.js
// (E) DRAW AUTOSUGGESTION OPTIONS
draw : results => { if (results == null) { ac.close(); } else {
  // (E1) RESET SUGGESTIONS
  ac.now.hList.innerHTML = "";

  // (E2) DRAW OPTION
  let complex = typeof results[0]=="object";
  for (let row of results) {
    // (E2-1) SET "DISPLAY NAME"
    let li = document.createElement("li");
    li.innerHTML = complex ? row.D : row;
 
    // (E2-2) SET SUGGESTION DATA
    if (complex) {
      if (row.V) { li.dataset.val = row.V; }
      let entry = {} , multi = false;
      for (let [k,v] of Object.entries(row)) {
      if (k!="D" && k!="V") { entry[k] = v; multi = true; }
    }

    // (E2-3) ON SELECTING THIS OPTION
    li.onclick = () => ac.select(li);
    ac.now.hList.appendChild(li);
  }
  ac.now.hList.style.display = "block";
}},

Kind of self-explanatory, ac.draw() renders the list of suggestions as obtained from ac.fetch() or ac.filter().

 

 

PART F) SELECTING A SUGGESTION

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

When the user picks a suggestion, ac.select() is fired. This simply populates the input field with the selected value and closes the suggestion box.

 

PART G & H) CLOSING

autocomplete.js
// (G) CLOSE AUTOCOMPLETE
close : () => { if (ac.now != null) {
  if (ac.now.ajax != null) { ac.now.ajax.abort(); }
  if (ac.now.timer != null) { window.clearTimeout(ac.now.timer); }
  ac.now.hList.innerHTML = "";
  ac.now.hList.style.display = "none";
  ac.now = null;
}},
  
// (H) CLOSE AUTOCOMPLETE IF USER CLICKS ANYWHERE OUTSIDE
checkclose : evt => { if (ac.now!=null && ac.now.hWrap.contains(evt.target)==false) {
  ac.close();
}}

document.addEventListener("click", ac.checkclose);
  • Captain Obvious once again, ac.close() will close the currently open autocomplete box.
  • Lastly, ac.checkclose() is a small bit to close the autocomplete box when the user clicks anywhere outside of the currently active autocomplete box.

 

EXTRAS

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

 

USING ARROW KEYS TO SELECT SUGGESTIONS

  • Update ac.draw(), attach key listeners.
  • Introduce a selected flag to track/highlight the currently selected suggestion.
  • If the keypress is “up/down” – Change the selected suggestion.
  • If the keypress is “enter”, fire ac.select().
  • If the keypress is “escape”, fire ac.close().
  • When the suggestion box is closed, detach the key listeners accordingly.

Good luck – This is not as simple as some think. Also, this is a pretty useless feature on touch screens and mobile devices these days.

 

NON-ENGLISH & CASE SENSITIVE SEARCH

  • If you are working with non-English languages, remember to add <meta charset="utf8"> into the HTML <head>.
  • If you want a case-insensitive search, do an easy change in autocompete.js, function filter() – Remove both toLowerCase().

 

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!

8 thoughts on “Simple Autocomplete In Pure HTML JS (Free Download)”

  1. Hi again,
    here also don’t work with cyrillic names, as in previous example (Simple Autocomplete With PHP MySQL), and I cannot figure out why – maybe something with json_encode()?
    For example when I put “Иван” here https://codepen.io/code-boxx/pen/MWEQZyo, after the other names – autocomplete again don’t work.
    Please, help, because it’s very good example!

    P.S.
    $dbchar = “utf8mb4” and add into is not enought

    1. Thanks for sharing. Shall take a look when I have time…

      EDIT : Fixed. Autocomplete will now work with non-English characters as well.

  2. 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”.

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

    1. Are you sure that is a typo? Change it to an array, try the autofill multiple fields and see for yourself.

    2. 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. Required fields are marked *