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
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
<!-- (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>
- Include the autocomplete CSS and Javascript. Duh.
- Define the
<input>
field. - 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
<!-- (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:
// (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
<!-- (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>
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
// (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
// (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.
- Create a new entry in the
PART C & D) SUGGESTIONS DATA
// (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 asearch
parameter, along withpost
that you may have specified. It expects a JSON encoded array as a response.
- Lastly, both
filter()
andfetch()
will calldraw()
to render the “search results” in the HTML suggestion box.
PART E) DRAW HTML SUGGESTIONS
// (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 fromfetch()
orfilter()
.- 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
// (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
// (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 anotherselected
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
- Arrow Functions – CanIUse
- Spread In Objects – CanIUse
- Fetch – CanIUse
Will work fine in all modern “Grade A” browsers, not friendly on ancient browsers.
LINKS & REFERENCES
- Simple AJAX Examples – Code Boxx
- Autocomplete With PHP MYSQL – Code Boxx
- How to implement a Keyword Search in MySQL? – Stack Overflow
- MySQL LIKE: Querying Data based on a Specified Pattern – MySQLTutorial
- Example on CodePen – JS Autocomplete
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!
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 ?
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”.
There’s a typo in “autocomplete.js” file. Line 118: “entry = {…i};” should be “entry = […i];”.
Are you sure that is a typo? Change it to an array, try the autofill multiple fields and see for yourself.
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.
I am not upset by any means. Just report any potential bugs, it helps others as well. 🙂
1) Not sure which version of Firefox you are using, nor what changes you have done. But I have verified that it works (at the current latest version).
2) Spread in object is well supported by all modern browsers – https://caniuse.com/mdn-javascript_operators_spread_spread_in_object_literals
3) Console error messages offer more insights than “it does not work”.