Making a simple autocomplete search box
Autocomplete search box is a wonderful yet essential component for every website especially ecommerce websites. Despite how complex it sound, it’s pretty simple to implement your own autocomplete search box. I have had a chance to create my own autocomplete in my previous school project so in this article, I’m going to re-create it and share some lessions that I learnt during the time I implement it for my school project.
What is an autocomplete search box?
I assume that you already have some notion about the autocomplete search box, but if you somehow click on this article then autocomplete search box in a nutshell is a search box just like every other search box but usually combine with a dropdown suggestion box to suggest some possible inputs for user.
Prepare the tools
My complete setup for this demo is:
- Express.js to setup a simple server and response data as we type. This is not required and you can setup any kind of server as long as they can response the JSON data back.
- nodemon to automatically restart the server if something changed in the server code. This is also optional, you can restart the server manually if you like.
- Atom , I’ll use atom to edit my code. You can use any other text editor if you want, I usually use vscode but I’m having a big project opened in it so, I switched to Atom.
The plan
The true mechanism behind this search box is dead simple. Here are the steps:
- User type something in the search box
- The search box send it to the server using Ajax
- The server sort and filter the data according to the keyword then response it back
- The client receive that response and render it bellow the search box
Enough planing, let’s code!
To begin with our coding, we must first create the HTML structure for our search box. Here is the final result of our search box for better visualization.
As you can see, our search box include a text box with rounded corners and bellow it is a list that also have some rounded corners. But most importantly, is that the list and the text box is stuck together therefor, the top corners of the list is not rounded and the bottom corners of the text box is not rounded.
By having a wrapper outside, we can eliminate the sharp corners of both the text box and the list by setting the overflow
css property of the wrapper to hidden
. By using that way, we can keep the sharp corners at top of the list and at the bottom of the text box.
With a few minutes smashing the keyboard, this is the HTML structure that we need:
<div class="autocomplete-search-box">
<input type="text" class="search-box" placeholder="Enter your favorite EDM producer" /> <ul class="search-result"></ul>
</div>
As you can see, our HTML structure consists of 3 parts:
- The wrapper — the
div
tag with the classautocomplete-search-box
- The text box — the
input
tag with the classsearch-box
- The result list — the
ul
tag with the classsearch-result
Now, let’s add some makeup for our search box:
The server
Let’s step back from the client side for a bit and look into our server. As I said before, we’re going to use express.js to create our server and response back the JSON data.
To setup our express.js server, we must first install it, using:
npm install --save express
After that, to test our server, we can make it to response the html file that we created before using these code:
const express = require('express')
const path = require('path')
const app = express()app.use('/assets', express.static(__dirname))app.get('/', (req, res) => res.sendFile(path.join(__dirname + '/index.html')))app.listen(3000, () => console.log('Example app listening on port 3000!'))
Hmm…But what does this line do?
app.use('/assets', express.static(__dirname))
Because express only response to what has been configured, our script and style files being included inside the html file can’t be loaded. For example, when the browser interpret our html file to this line:
<link rel="stylesheet" href="index.css">
It automatically send a request to localhost:3000/index.css
which will be denied by express because we didn’t configure it in our server code.
For that reason, I setup the path /assets
and tell express to return files in the current directory if those file is requested through that path. Thus, we must change our code to:
<link rel="stylesheet" href="assets/index.css">
Search API
The first step in creating our search API is to setup a path (endpoint) for our API.
app.get('/api', (req, res) => {
const keyword = req.query.keyword
res.json(handleSearch(keyword))
})
The meaning of the code above is that, every GET
request being sent to our server to the /api
endpoint, we will take the GET
paramater or the query keyword
. Then pass it to the handleSearch
function and response the result from that function in JSON format using the json
function of express.js .
Search function
So how the handleSearch
function work?
Just like every other autocomplete search box, we need to sort our data according to the similarity to the keyword, higher similarity means closer to the #1 position on our list.
In this example, our data will be list of EDM producers
const producers = [
'Avicii',
'Martin Garrix',
'Don Diablo',
'Tiesto',
'Calvin Harris',
'Hardwell',
'Steve Aoki',
'David Guetta',
'Zedd',
'Afrojack',
'Dimitri Vegas & Like Mike',
'The chainsmokers',
'KSMHR',
'Oliver Heldens',
'R3hab',
'Lost Frequencies'
]
So how do we sort them according to the similarity to the keyword? The answer is we will use a built in function of JS array called sort to sort our data.
function handleSearch (keyword) {
let producers_copy = producers.slice()
producers_copy.sort((a, b) => {
return getSimilarity(b, keyword) - getSimilarity(a, keyword)
})
return producers_copy
}
Because we want the data with the higher similarity to go on top, we will need to sort them in a descending order, which is why I take the similarity of the second parameter of the sort function minus the first one.
But how do we get the similarity between our data with the keyword? Or more precise, between 2 strings?
There’s a simple way to do that by substracting the length of the string with the length of the string after being removed all the parts that contains the keyword.
function getSimilarity (data, keyword) {
data = data.toLowerCase()
keyword = keyword.toLowerCase()
return data.length - data.replace(new RegExp(keyword, 'g'), '').length
}
Although this is probably not the best solution but, for the sake of simlicity, I’ll use this solution. I’ll leave some methods that can be used to complete this task at the end of the article.
Communicate with the API
You can now communicate with the server by navigating to this endpoint
localhost:3000/api?keyword=<your keyword>
When you navigate to that end point you should expect a list of producers to show up like this:
By changing your keyword, you can see that our data is changing as we expected
localhost:3000/api?keyword=martin
Aha! We can now write a simple script to pass the input from the search box to the API using Jquery like this:
$('.search-box').keyup((e) => {
$.get('/api', { keyword: $(e.target).val() }).done(producers => {
console.log(producers)
})
})
Render the result!
We now have all the result that we want, the only thing left to do is to draw it in a result list bellow our search box.
function drawProducerList (producers) {
$('.autocomplete-search-box .search-result').html('')
for (let i = 0; i < producers.length; i++) {
$('.autocomplete-search-box .search-result').append(`<li>${producers[i]}</li>`)
}
}
The function above take a list of producers, loop through it and create a li
tag to append to the result list which is just a ul
tag. However, before all of that happen, the list must be clear out first so that old result will be discarded.
We must also change the console.log
command above to the drawProducerList
function to be able to render it.
$('.search-box').keyup((e) => {
$.get('/api', { keyword: $(e.target).val() }).done(producers => {
drawProducerList(producers)
})
})
Adding some final touches
To make our search box more complete, we should also filter out all the irrelevant data too. Lucky for us, JS also have a built in filter function that we can use:
producers_copy = producers_copy.filter(producer => {
return getSimilarity(producer, keyword) > 0
})
The filter function above only return data that has the similarity greater than 0 which also means all the data that have 0 similarity to the keyword will be kicked out of our result list.
Another aspect that we need to consider is the server. With the current way of communicating, the server will take in a significant amount of damage if there’re more than 1,000,000 records in the database. I’ve never made that mistake. At all…
Anyway, so how do we cope with this problem? We simply wait until the users typed what they want and eventually stop typing.
let typingTimeout = null
$('.search-box').keyup((e) => {
clearTimeout(typingTimeout)
typingTimeout = setTimeout(() => {
$.get('/api', { keyword: $(e.target).val() })
.done(producers => drawProducerList(producers))
}, 500)
})
Everytime the user typed something, we reset the timer typingTimeout
and after 500 ms without typing anything, the data will be transfer to the server. This method will help reducing the number of request being sent to the server and kill our server.
In conclusion
This is basically the search box that I implemented in my school project. I hope you guys found something useful and maybe get a raise using techniques in this articles 😄
Here is the list of “string similarity” methods that you can use to improve this search box: