JavaScript: autocomplete with json-server

JavaScript: autocomplete with json-server

We can use the json-server NPM module to implement a simple but effective autocomplete system in JavaScript.

We can use the json-server NPM module to implement a simple but effective autocomplete system in JavaScript.

Setup

sudo npm install json-server -g
json-server -w db.json
npm init -y
npm install express --save
node app.js

Serving static files

We can now create our simple ExpressJS app with the following code.

'use strict';

const path = require('path');
const express = require('express');
const app = express();

app.disable('x-powered-by');
app.use('/assets', express.static(path.join(__dirname, 'assets')));

app.get('/', (req, res) => {
   res.sendFile(__dirname + '/index.html');
});

app.listen(8080);

The index.html page

Our HTML code is made up of a simple form:

<form action="" method="get" novalidate id="autocomplete">
        <div class="form-group autocomplete-wrap">
            <input type="text" name="term" id="term" class="form-control" placeholder="Search for a user...">
        </div>
    </form>

The CSS code

We need to declare the parent element of the text input as relatively positioned so we can display the list of available completions just below it. This element is initially hidden.

.autocomplete-wrap {
    width: 100%;
    position: relative;
}

.autocomplete {
    width: 100%;
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 9999;
    background: #fff;
    border: 1px solid #ddd;
    display: none;
}

.autocomplete ul {
    margin: 0;
    padding: 0;
    list-style: none;
}

.autocomplete li {
    display: block;
    padding: 0.5em;
    color: #000;
    cursor: pointer;
}

.autocomplete li:hover {
    background: #f5f5f5;
}

The JavaScript code

In order to make the autocompletion work, we need to perform a query on the json-server API by searching a user.

The type of search we're going to run makes use of a LIKE query by passing field_like to the API's endpoint. In this case we're using the last_name property of each user model.

When you type more than three characters, we run the query, get the JSON results and parse them into an unordered list of items.

We use event delegation to handle the click on each list items. When an item is clicked, the input's value changes accordingly.

'use strict';


const request = async (url, queryString) => {

    const response = await fetch(url + '?' + queryString, {
        method: 'GET'
    });

    return response.ok ? response.json() : Promise.reject({error: 500});

};

const prepareLayout = () => {
    const wrap = document.querySelectorAll('.autocomplete-wrap');
    wrap.forEach(element => {
        let autocomplete = document.createElement('div');
        autocomplete.className = 'autocomplete';
        autocomplete.innerHTML = '<ul></ul>';

        element.appendChild(autocomplete);
    });
};

const getResults = async keyword => {
    try {
        const users = await request('http://localhost:3000/users/', 'last_name_like=' + encodeURIComponent(keyword));
        let html = '';
        if(users.length > 0) {
            users.forEach(user => {
                let str = `<li class="autocomplete-item">${user.first_name} ${user.last_name}</li>`;
                html += str;
            });
        }
        return html;
    } catch(err) {
        return err;
    }
};

const autocomplete =  input => {

    document.addEventListener('click', e => {
        const element = e.target;
        if(element.classList.contains('autocomplete-item')) {
            input.value = element.innerText;
        }
    }, false);

    input.addEventListener('keyup', async () => {
        let value = input.value;
        let autocomplete = document.querySelector('#autocomplete .autocomplete');
        autocomplete.style.display = 'none';
        if(value.length >= 3) {
            let wrap = autocomplete.querySelector('ul');

            try {
                const results = await getResults(value);

                wrap.innerHTML = results;
                autocomplete.style.display = 'block';
            } catch(err) {
                console.log(err);
            }
        }
    }, false);
};

document.addEventListener('DOMContentLoaded', () => {
    prepareLayout();
    autocomplete(document.querySelector('#term'));
});

You can see the following code on this GitHub repository.