<nav class="navbar fixed-top navbar-expand-lg scrolling-navbar navbar-primary">
    <div class="container">
        <button class="navbar-toggler collapsed" type="button" data-toggle="collapse" data-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false">
            <span class="sr-only">Menu</span>
            <span class="navbar-toggler-icon" aria-hidden="true"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbar-collapse">
            <ul class="navbar-nav mr-auto smooth-scroll">
                <li class="nav-item">
                    <a href="#section-1" class="nav-link">Menu 1</a>
                </li>
                <li class="nav-item">
                    <a href="#section-2" class="nav-link">Menu 2</a>
                </li>
                <li class="nav-item">
                    <a href="#section-3" class="nav-link">Menu 3</a>
                </li>
                <li class="nav-item">
                    <a href="#section-4" class="nav-link">Menu 4</a>
                </li>
            </ul>
        </div>
        <div class="navbar__search">
            <button class="navbar__search-button" id="navbar-open-search">
                <span class="sr-only">Open zoekveld</span><span class="mdi mdi-magnify" aria-hidden="true"></span>
            </button>
            <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" aria-describedby="autocomplete-help-text"
                />
                <button class="autocomplete__search-button" aria-label="Zoekknop">
                    <span class="mdi mdi-magnify" aria-hidden="true"></span>
                </button>
                <button class="autocomplete__clear-button autocomplete__button--hide" aria-label="Invoer wissen" title="Invoer wissen">
                    <span class="mdi mdi-close" aria-hidden="true"></span>
                </button>
                <ul id="autocomplete-results" class="autocomplete__result-list" aria-label="Zoekresultaten" role="listbox">

                </ul>
                <span id="autocomplete-help-text" class="sr-only">
                    Als er zoekresultaten gevonden zijn kunt u de pijltjes toetsen omhoog en naar beneden gebruiken om een resultaat
                    te selecteren. Toets enter om te navigeren naar het geselecteerde resultaat. Met de escape toets kunt u de
                    invoer wissen.</span>
                <div id="autocomplete-search-results-announcer" aria-live="assertive" class="sr-only"></div>
            </form>

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

                </li>
            </script>
            <button class="navbar__search-close-button" id="navbar-close-search" aria-label="Sluit zoekveld">
                sluiten
            </button>
        </div>
    </div>
</nav>

Based on the fixed top version from MDB:
https://mdbootstrap.com/components/navbar/#basic-example

Using

Aside from the markup, some extra actions are needed.

  • Since the navbar is fixed top, it’s advisable to add some padding (with a minimum of 74 pixels) to the body element to not risk having content under the navbar which should be visible by default.

Active menu item

To set an active menu item, underneath HTML markup for a navigation item can be used where a class active is added to the active menu item and as and extra addition regarding accessibility the “span” can be used.

<li class="nav-item active">
    <a href="#" class="nav-link">Menu <span class="sr-only">(huidig)</span></a>
</li>

Close mobile menu when clicking outside or when navigate on the same page

Include the script below to enable automatic closing of the menu on a mobile when:

  • A user clicks somewhere outside the menu
  • A user clicks on a link in the menu that scrolls to content on the same page

When using the navbar to scroll to content on the same page make sure to add the class smooth-scroll to the navbar-nav element.

<script>
    var navbarToggler = $('.navbar-toggler');

    // close if clicked outside the navbar
    $(document).on('click', function (event) {
        if (
            navbarToggler.is(':visible') &&
            !navbarToggler.hasClass('collapsed') &&
            !$(event.target).closest('.navbar-toggler').length &&
            !$(event.target).closest('.navbar-collapse').length
        ) {
            navbarToggler.trigger('click');
        }
    });

    // close if there is a smooth scroll
    if (navbarToggler.is(':visible')) {
        $('.navbar-nav.smooth-scroll').each(function () {
            $('.nav-link', this).click(function (event) {
                if (!navbarToggler.hasClass('collapsed')) {
                    navbarToggler.trigger('click');
                }
            });
        });
    }

    var SCROLLING_NAVBAR_OFFSET_TOP = 50;
    $(window).off('scroll').on('scroll', function () {
        var $navbar = $('.navbar');
        if ($navbar.length) {
            if ($navbar.offset().top > SCROLLING_NAVBAR_OFFSET_TOP) {
                $('.scrolling-navbar').addClass('top-nav-collapse');
            } else {
                $('.scrolling-navbar').removeClass('top-nav-collapse');
            }
        }
    });
</script>

For using the search in the navbar on a mobile device you need to include the following script:

<script>
    var navbarSearch = new NavbarSearch();
    $(document).ready(function () {
        navbarSearch.init();
    });

    function NavbarSearch() { }

    NavbarSearch.prototype.init = function () {
        $('#navbar-open-search').click(this.openSearch);
        $('.autocomplete').on('close', this.closeSearch);
        $('#navbar-close-search').click(this.closeSearch);
    };

    NavbarSearch.prototype.openSearch = function (event) {
        $('.navbar').addClass('search-open');
        $('.autocomplete__input').focus();
        event.stopPropagation();
        event.preventDefault();
    };

    NavbarSearch.prototype.closeSearch = function () {
        $('.navbar').removeClass('search-open');
        $('#navbar-open-search').focus();
    };
</script>

For the autocomplete search a real-time feed is provided by Pandosearch. This uses a combination of search suggestions and direct hits. To use this search functionality, JavaScript code is required before the closing “body” tag.

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

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

    Autocomplete.prototype.init = function (getResultsFn, defaultSearchUrl) {
        this.$resultsList = $('#autocomplete-results');
        this.resultItemTemplate = $.parseHTML($('#autocomplete-result-template').html());
        this.resultHeaderTemplate = $.parseHTML($('#autocomplete-section-header-template').html());
        this.getResults = getResultsFn;
        if (defaultSearchUrl) {
            this.defaultSearchUrl = defaultSearchUrl;
        }

        $(document).on('click', this.clickOutside.bind(this));

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

        $('.autocomplete .autocomplete__clear-button').click((function (event) {
            event.preventDefault();
            this.clearSearchField();
        }).bind(this));

        $('.autocomplete .autocomplete__search-button').click((function (event) {
            event.preventDefault();
            $('#suggest-search-query').focus();
        }).bind(this));
    };

    Autocomplete.prototype.clickOutside = function (event) {
        if (this.$resultsList.is(':visible') && !$(event.target).closest('.autocomplete').length && !$(event.target).closest('#navbar-open-search').length) {
            this.closeResults(true);
        }
    };

    Autocomplete.prototype.closeResults = function (triggerClose) {
        this.$resultsList.html('');
        this.$resultsList.hide();
        this.$resultsList.removeAttr('role');
        $('#suggest-search-query').removeAttr('aria-activedescendant');
        if (triggerClose) {
            $('.autocomplete').trigger('close');
        }
    };

    Autocomplete.prototype.clearSearchField = function () {
        this.searchResults = [];
        this.query = null;
        $('#suggest-search-query').val("");
        $('#suggest-search-query').focus();
        $('#autocomplete-search-results-announcer').text('');
        this.setSearchIcon();
        this.closeResults();
    };

    Autocomplete.prototype.setSearchIcon = function () {
        if ($('#suggest-search-query').val() != '') {
            $('.autocomplete .autocomplete__search-button').addClass('autocomplete__button--hide');
            $('.autocomplete .autocomplete__clear-button').removeClass('autocomplete__button--hide');
        } else {
            $('.autocomplete .autocomplete__search-button').removeClass('autocomplete__button--hide');
            $('.autocomplete .autocomplete__clear-button').addClass('autocomplete__button--hide');
        }
    };

    Autocomplete.prototype.inputFocusHandle = function () {
        if ($('#suggest-search-query').val() != '') {
            this.autocompleteSearch();
        }
    };

    Autocomplete.prototype.show = function (results) {
        this.searchResults = results;
        this.$resultsList.html('');
        this.$resultsList.show();
        this.$resultsList.attr('role', 'listbox');
        this.numberOfItems = results.filter(function (result) { return !result.header; }).length;
        this.currentItem = -1;

        var resultsCount = results.filter(function (result) {
            return !result.header;
        }).length;

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

        $('#autocomplete-search-results-announcer').text(
            this.query
                ? 'Er zijn ' + resultsCount + ' zoekresultaten gevonden voor de zoekterm: ' + this.query
                : ''
        );

        var itemNr = 0;
        var currentHeader = '';
        for (var key in results) {
            var result = results[key];
            if (result.header) {
                var resultHeader = $(this.resultHeaderTemplate).clone();
                resultHeader.text(result.header);
                this.$resultsList.append(resultHeader);
                currentHeader = result.header + ': ';
                continue;
            }
            var resultItem = $(this.resultItemTemplate).clone();
            $('.autocomplete__result-item-header', resultItem).text(currentHeader);
            $('.autocomplete__result-link', resultItem)
                .html(result.title)
                .attr('href', result.url)
                .attr('id', 'result-item-' + itemNr)
                .mouseenter(this.itemMouseHandle.bind(this))
                .prepend('<span class="sr-only">' + currentHeader + '</span>');
            itemNr++;
            if (result.class) {
                $('.autocomplete__result-link', resultItem).addClass(result.class);
            }
            this.$resultsList.append(resultItem);
        }

        this.searching = false;
    };

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

        if (this.currentItem < 0) {
            $('#suggest-search-query').attr('aria-activedescendant', '');
            return;
        }
        var selectedItem = $('#result-item-' + this.currentItem);
        selectedItem.addClass('active').attr('aria-selected', 'true');
        if (scrollToElement) {
            $(selectedItem)[0].scrollIntoView();
        }
        $('#suggest-search-query').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;
        if (event.target.value == '') {
            this.clearSearchField();
        } else {
            this.setSearchIcon();
        }
        switch (key) {
            case this.keyCodes.DOWN:
                if (event.type === 'keyup') {
                    break;
                }
                if (this.currentItem < this.numberOfItems - 1) {
                    this.currentItem++;
                    this.selectItem(true);
                    event.preventDefault();
                }
                break;
            case this.keyCodes.UP:
                if (event.type === 'keyup') {
                    break;
                }
                if (this.currentItem >= 0) {
                    this.currentItem--;
                    this.selectItem(true);
                    event.preventDefault();
                }
                break;
            case this.keyCodes.RETURN:
                if (this.currentItem < 0 && (!this.defaultSearchUrl || !this.query)) {
                    event.preventDefault();
                    break;
                }
                var link = this.defaultSearchUrl.replace('{query}', this.query);
                if (this.currentItem >= 0) {
                    link = $('#result-item-' + this.currentItem, this.$resultsList).attr('href');
                }
                window.location = link;
                event.preventDefault();
                break;
            case this.keyCodes.ESC:
                if (event.type === 'keyup') {
                    break;
                }
                this.clearSearchField();
                break;
            default:
                this.query = event.target.value;
                if (this.searching) {
                    break;
                }
                if (this.searchTimeoutId) {
                    clearTimeout(this.searchTimeoutId);
                }
                this.searchTimeoutId = setTimeout(this.autocompleteSearch.bind(this), this.searchTimeout);
        }
    };

    Autocomplete.prototype.autocompleteSearch = function () {
        this.searching = true;
        this.getResults(this.query, this.show.bind(this));
    };
</script>
  • Pandosearch integration
<script>
    var suggestUrl = 'https://public.pandosearch.com/nijmegen.nl/suggest?size=5&q=';

    var autocomplete = new Autocomplete();
    $(document).ready(function () {
        autocomplete.init(getAutocompleteResults, '?q={query}');
    });

    function getAutocompleteResults(query, callback) {
        $.get(suggestUrl + query, function (rawResults) {
            // Get suggestions from raw search results
            var suggestions = rawResults.suggestions
                .map(function (suggestion) {
                    return {
                        title: suggestion.text,
                        url: '?q=' + suggestion.text,
                        class: 'search'
                    };
                });

            // Get hits from raw search results
            var hits = Array.isArray(rawResults.hits)
                ? rawResults.hits
                    .map(function (hit) {
                        return {
                            title: hit.title,
                            url: hit.url
                        };
                    })
                : [];
            var result = [];
            if (hits.length) {
                result = result.concat({ header: 'Zoekresultaten:' }, hits);
            }
            if (suggestions.length) {
                result = result.concat({ header: 'Zoeksuggesties:' }, suggestions);
            }
            callback(result);
        });
    }
</script>

Notes

  • Nijmegen has a collaboration with Pandosearch (https://www.pandosearch.com) for the search and search results implementation, as can be read in the Using section here. For an example of the search results, have a look at the search results template.