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
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
<!-- (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>
- Load the autocomplete CSS and Javascript.
- Define the
<input>
field. - 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
<!-- (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:
// (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
<!-- (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>
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 to500
ms.min
Minimum characters for autocomplete to trigger. Defaults to2
.
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
<!-- (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” toNAME (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
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
// (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
// (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 asearch
parameter, along with anypost
that you may have specified. It expects a JSON-encoded array as a response.
- Lastly, both
ac.filter()
andac.fetch()
will callac.draw()
to render the “search results” in the HTML suggestion box.
PART E) DRAW HTML SUGGESTIONS
// (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
// (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
// (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
, functionfilter()
– Remove bothtoLowerCase()
.
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!
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
Thanks for sharing. Shall take a look when I have time…
EDIT : Fixed. Autocomplete will now work with non-English characters as well.
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”.