<form class="autocomplete autocomplete__form" method="GET" action="" role="search">
    <input class="autocomplete__input form-control" type="text" placeholder="Zoeken" id="suggest-search-query" name="q" autocomplete="off" aria-controls="autocomplete-results" aria-autocomplete="both" aria-label="Zoekveld" />
    <button class="autocomplete__search-button" aria-label="Zoekknop">
        <span class="mdi mdi-magnify" aria-hidden="true"></span>
    </button>
    <ul id="autocomplete-results" class="autocomplete__result-list" aria-label="Zoekresultaten">

    </ul>
</form>

<script id="autocomplete-result-template" type="text/template">
    <li class="autocomplete__result-item">
        <a href="" class="autocomplete__result-link" role="option" id="" tabindex="-1"></a>
    </li>
</script>

Autocomplete

This component will show the autocomplete results

Using

To start using this component, some JavaScript is needed to initialize it.
Underneath a jQuery example on how to initialize the autocomplete and the autocomplete script itself. Both scripts be placed in the Additional component(s) script section as documented in How to use.

You need to initialize the autocomplete class by passing a function that will fetch the search results. The function will get the search query as it’s first parameter and a callback as a second parameter. You need to call the callback to show the search results. Pass an array of objects with at least an url and title: [{url: 'link', title: 'the title'}].

You can also add the class property. That class is added to the item. When an item is a search suggestion instead of an actual result then you should add the class search to the item. The item will be renedered with a search icon on the right side to indicate that it is a search suggestion.

Example on how to implement:

<script>
    var autocomplete = new Autocomplete();

    $(document).ready(function () {
        autocomplete.init(getAutocompleteResults)
    })

    function getAutocompleteResults(query, callback) {
        var rawResults = [
            'Finibus' + query,
            query + 'elementum urna',
            'Blandit aliquet' + query,
            query
        ];
        var results = rawResults
            .slice(0, query.length)
            .map(function (result) {
                return {
                    title: result,
                    url: '?q=' + query
                }
            });
        if (results.length === 0) {
            callback(results);
            return;
        }
        results[0].class = 'search';
        callback(results);
    }
</script>

Autocomplete script:

<script>
    function Autocomplete() {
        this.$resultsList;
        this.resultItemTemplate;
        this.getResults;
        this.currentItem = -1;
        this.numberOfItems = 0;
        this.searchTimeoutId;
        this.searchTimeout = 500;
        this.query;
        this.searching = false;
        this.searchResults = [];
        this.closeTimeoutId;
        this.closeTimeout = 100;

        this.keyCodes = {
            RETURN: 13,
            ESC: 27,
            UP: 38,
            DOWN: 40,
        }
    }

    Autocomplete.prototype.init = function (getResultsFn) {
        this.$resultsList = $('#autocomplete-results');
        this.resultItemTemplate = $.parseHTML($('#autocomplete-result-template').html());
        this.getResults = getResultsFn;

        $('#suggest-search-query').on('keydown keyup', this.inputKeyHandle.bind(this));
        $('#suggest-search-query').blur(this.inputBlurHandle.bind(this));
        $('#suggest-search-query').focus(this.inputFocusHandle.bind(this));
    }

    Autocomplete.prototype.inputBlurHandle = function () {
        clearTimeout(this.closeTimeoutId);
        clearTimeout(this.searchTimeoutId);
        this.closeTimeoutId = setTimeout((function () {
            this.closeResults();
            $('.autocomplete').trigger('close');
            $('.autocomplete .autocomplete__search-button').show();
        }).bind(this), this.closeTimeout);
    }

    Autocomplete.prototype.closeResults = function () {
        this.$resultsList.html('');
        this.$resultsList.hide();
        this.$resultsList.removeAttr('role');
        this.$resultsList.removeAttr('aria-activedescendant');
    }

    Autocomplete.prototype.inputFocusHandle = function () {
        this.show(this.searchResults);
    }

    Autocomplete.prototype.show = function (results) {
        clearTimeout(this.closeTimeoutId);
        this.searchResults = results;
        this.$resultsList.html('');
        this.$resultsList.show();
        this.$resultsList.attr('role', 'listbox');
        this.numberOfItems = results.length;
        this.currentItem = -1;
        $('.autocomplete .autocomplete__search-button').hide();

        if (results.length === 0) {
            this.closeResults();
            this.searching = false;
            return;
        }

        for (var key in results) {
            var resultItem = $(this.resultItemTemplate).clone();
            var result = results[key];
            $('.autocomplete__result-link', resultItem)
                .html(result.title)
                .attr('href', result.url)
                .attr('id', 'result-item-' + key)
                .attr('aria-label', result.titleRaw || result.title)
                .mouseenter(this.itemMouseHandle.bind(this));

            if (result.class) {
                $('.autocomplete__result-link', resultItem).addClass(result.class);
            }
            this.$resultsList.append(resultItem);
        }

        this.searching = false;
    }

    Autocomplete.prototype.selectItem = function () {
        $('.autocomplete__result-link', this.$resultsList).removeClass('active').removeAttr('aria-selected');

        if (this.currentItem < 0) {
            return;
        }
        var selectedItem = $('.autocomplete__result-item', this.$resultsList)[this.currentItem];
        $('.autocomplete__result-link', selectedItem).addClass('active').attr('aria-selected', 'true');
        this.$resultsList.attr('aria-activedescendant', 'result-item-' + this.currentItem);
    }

    Autocomplete.prototype.itemMouseHandle = function (event) {
        var id = event.target.id.replace('result-item-', '');
        this.currentItem = parseInt(id);
        this.selectItem();
    }

    Autocomplete.prototype.inputKeyHandle = function (event) {
        var key = event.which || event.keyCode;
        switch (key) {
            case this.keyCodes.DOWN:
                if (event.type === 'keyup') {
                    break;
                }
                if (this.currentItem < this.numberOfItems - 1) {
                    this.currentItem++;
                    this.selectItem();
                    event.preventDefault();
                }
                break;
            case this.keyCodes.UP:
                if (event.type === 'keyup') {
                    break;
                }
                if (this.currentItem >= 0) {
                    this.currentItem--;
                    this.selectItem();
                    event.preventDefault();
                }
                break;
            case this.keyCodes.RETURN:
                if (this.currentItem < 0) {
                    break;
                }
                var link = $('#result-item-' + this.currentItem, this.$resultsList).attr('href');
                window.location = link;
                event.preventDefault();
                break;
            case this.keyCodes.ESC:
                if (event.type === 'keyup') {
                    break;
                }
                event.target.value = '';
                if (this.searchResults.length > 0) {
                    this.show([]);
                    event.preventDefault();
                    break;
                }
                $('.autocomplete').trigger('close');
                break;
            default:
                this.query = event.target.value;
                if (this.searching) {
                    break;
                }
                if (this.searchTimeoutId) {
                    clearTimeout(this.searchTimeoutId);
                }
                this.searchTimeoutId = setTimeout(this.search.bind(this), this.searchTimeout);
        }
    }

    Autocomplete.prototype.search = function () {
        this.searching = true;
        this.getResults(this.query, this.show.bind(this));
    }
</script>