<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>
        <a class="navbar-brand" href="/">
            <span class="sr-only">Hoofdpagina</span>
            <div class="navbar-brand-container">
                <img src="https://componenten.nijmegen.nl/v3/img/beeldmerklabelwit.svg" class="logo-labeled" alt="Logo Nijmegen">
                <img src="https://componenten.nijmegen.nl/v3/img/beeldmerkwit.svg" class="logo" aria-hidden="true" alt="">
            </div>
        </a>
        <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="searchresults-template.html" 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>

<main>
    <div class="container">
        <div class="row">
            <div class="col-lg-3 col-md-12">
                <section class="facets" id="facets" role="navigation">
                    <h1 class="facets__title">Filter results</h1>
                    <div class="facets__facets">
                    </div>
                </section>

                <script id="facets-group-template" type="text/template">
                    <div class="facets__group">
                        <h2 class="facets__group-title"></h2>
                        <ul class="facets__list">
                        </ul>
                    </div>
                </script>

                <script id="facets-item-template" type="text/template">
                    <li class="facets__item">
                        <a class="facets__link" href=""></a>
                    </li>
                </script>
            </div>
            <div class="col-lg-9 col-md-12">
                <div id="loading">Bezig met laden van de zoekresultaten..</div>
                <div id="searchresults">
                    <section class="search-results">
                        <h1 class="search-results__title">
                            Zoekresultaten voor "<span class="search-results__term" id="search-term"></span>"
                        </h1>
                        <p id="search-noresults" class="search-results__noresults">
                            Geen resultaten gevonden
                            <span id="search-didyoumean" class="search-results__didyoumean">
                                , resultaten voor "<span id="search-term-didyoumean"></span>" worden getoond.
                            </span>
                        </p>
                        <ul id="search-results" class="search-results__list"></ul>
                    </section>

                    <script id="search-results-item-template" type="text/template">
                        <li class="search-results__item">
                            <a href="" class="search-results__item-link">
                                <h2 class="search-results__item-title"></h2>
                            </a>
                            <div class="search-results__item-body">

                            </div>
                        </li>
                    </script>
                    <div class="search-results__pagination">
                        <nav aria-label="Pagina navigatie">
                            <ul class="pagination" id="pagination">
                                <li class="page-item page-item-previous">
                                    <a class="page-link" href="">
                                        <span aria-hidden="true">
                                            <span class="mdi mdi-chevron-left" aria-hidden="true"></span>
                                        </span>
                                        <span class="sr-only">Vorige</span>
                                    </a>
                                </li>
                                <li class="page-item page-item-next">
                                    <a class="page-link" href="">
                                        <span aria-hidden="true">
                                            <span class="mdi mdi-chevron-right" aria-hidden="true"></span>
                                        </span>
                                        <span class="sr-only">Volgende</span>
                                    </a>
                                </li>
                            </ul>
                        </nav>
                    </div>
                </div>
            </div>
        </div>
    </div>
</main>

<script id="pagination-item-template" type="text/template">
    <li class="page-item">
        <a class="page-link" href=""></a>
    </li>
</script>

<script id="pagination-item-current-template" type="text/template">
    <span class="sr-only">(huidig)</span>
</script>

<footer class="page-footer">
    <div class="footer-content">
        <div class="container">
            <div class="row">
                <div class="hidden-md-down col-lg-6">
                    <!-- Space for other content/links -->
                </div>
                <div class="col-md-12 col-lg-6">
                    <div class="row">
                        <div class="hidden-md-down col-lg-5">
                            <h2 class="title">Over deze site</h2>
                            <ul class="link-list">
                                <li><a href="https://www.nijmegen.nl/toegankelijkheid/">Toegankelijkheid</a></li>
                                <li><a href="https://www.nijmegen.nl/privacyverklaring/">Privacyverklaring</a></li>
                                <li><a href="https://www.nijmegen.nl/cookies/">Cookies</a></li>
                                <li><a href="https://www.nijmegen.nl/proclaimer/">Proclaimer</a></li>
                                <li><a href="https://www.nijmegen.nl/sitemap/">Sitemap</a></li>
                            </ul>
                        </div>

                        <div class="col-xs-12 col-md-6 col-lg-7">
                            <h2 class="title">Contactgegevens</h2>
                            <ul class="link-list contact-list">
                                <li>
                                    <span class="mdi mdi-map-marker" aria-hidden="true"></span>
                                    <span class="sr-only">Bekijk onze locatie:</span>
                                    <a href="https://www.google.nl/maps/place/...">
                                        Stadswinkel, Mariënburg 30
                                    </a>
                                </li>
                                <li>
                                    <span class="mdi mdi-phone" aria-hidden="true"></span>
                                    <span class="sr-only">Bel ons:</span>
                                    <a href="tel:14024">
                                        14 024
                                    </a>
                                </li>
                                <li>
                                    <span class="mdi mdi-email" aria-hidden="true"></span>
                                    <span class="sr-only">Mail ons:</span>
                                    <a href="mailto:gemeente@nijmegen.nl">
                                        gemeente@nijmegen.nl
                                    </a>
                                </li>
                                <li>
                                    <span class="mdi mdi-facebook-box" aria-hidden="true"></span>
                                    <span class="sr-only">Vind ons op facebook:</span>
                                    <a href="https://www.facebook.com/gemeentenijmegen">
                                        Gemeente Nijmegen
                                    </a>
                                </li>
                                <li>
                                    <span class="mdi mdi-twitter-box" aria-hidden="true"></span>
                                    <span class="sr-only">Volg ons op Twitter:</span>
                                    <a href="https://twitter.com/gem_nijmegen">
                                        @nijmegen
                                    </a>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="footer-copyright text-center">
        <div class="container-fluid">
            <img src="https://componenten.nijmegen.nl/v3/img/beeldmerkwit.svg" height="32" width="25" class="logo-labeled" alt="Logo Gemeente Nijmegen"> Gemeente Nijmegen
        </div>
    </div>
</footer>

Searchresults Template

A custom template created for Nijmegen to showcase the search results component from this library in one overall view.

Using

Aside from the markup, some extra actions are needed.

Pandosearch integration

For the search a real-time feed is provided by Pandosearch.

Example on how to implement:

<script>
    var facetsTranslations = {
        docType: 'Document type',
        page: 'Pagina',
        pdf: 'PDF bestand',
        category: 'Categorie',
        published: 'Publicatiedatum',
        today: 'Vandaag',
        older: 'Ouder',
        lastweek: 'Laatste week',
        lastmonth: 'Laatste maand',
        lastyear: 'Laatste jaar',
        lastday: 'Laatste dag',
    };

    var facetSortingOrder = {
        today: 1,
        lastday: 2,
        lastweek: 3,
        lastmonth: 4,
        lastyear: 5,
        older: 6,
    };

    var pandosearch = new Pandosearch(5);
    $(document).ready(function () {
        pandosearch.init(facetsTranslations, facetSortingOrder);
        pandosearch.showFacets();
        pandosearch.showHits();

        $(window).on('hashchange', function () {
            pandosearch.showHits();
        });

        $('#suggest-search-query').val(pandosearch.query);
        autocomplete.setSearchIcon();
    });
</script>

Pandosearch script:

<script>
    function Pandosearch(itemsPerPage) {
        this.baseSearchUrl = 'https://public.pandosearch.com/nijmegen.nl/search';
        this.searchUrl = null;
        this.searchParams = {
            page: 1,
            facetName: null,
            facetValue: null,
        };
        this.query = null;
        this.rawResults = null;
        this.itemsPerPage = itemsPerPage ? itemsPerPage : 5;
        this.totalHits = 0;
        this.searchResultsComponent = new SearchResults();
        this.$searchResultsContainer = $('#searchresults');
        this.$loadingContainer = $('#loading');
        this.$paginationContainer = $('#pagination');
        this.facets = null;
        this.facetsComponent = new Facets();
        this.$facetsContainer = $('#facets');
        this.facetsTranslations = null;
    }

    Pandosearch.prototype.init = function (facetsTranslations, facetsSortingOrder) {
        this.facetsTranslations = facetsTranslations ? facetsTranslations : {};
        this.facetsSortingOrder = facetsSortingOrder ? facetsSortingOrder : {};

        this.searchResultsComponent.init();
        this.facetsComponent.init(this.$facetsContainer, function () { $('.facets__title').click(); });

        this.toggleLoadingContainer(true);
    };

    Pandosearch.prototype.showHits = function () {
        this.searchParams = this.getParamsFromHash();
        this.buildSearchUrl();

        $.get(this.searchUrl, this.showHitsResult.bind(this));
    };

    Pandosearch.prototype.showFacets = function () {
        this.buildSearchUrl();

        $.get(this.searchUrl, this.showFacetsResult.bind(this));
    };

    Pandosearch.prototype.buildSearchUrl = function () {
        this.query = this.getUrlParameterByName('q');

        this.searchUrl = this.baseSearchUrl
            + '?size=' + this.itemsPerPage
            + '&page=' + this.searchParams.page
            + (this.searchParams.facetName && this.searchParams.facetValue
                ? '&facets[' + this.searchParams.facetName + ']=' + this.searchParams.facetValue
                : '')
            + '&q=' + this.query;
    };

    Pandosearch.prototype.showHitsResult = function (rawResults) {
        this.rawResults = rawResults;
        var hits = this.getHits();

        var didYouMeanTerm;
        if (!hits.length) {
            didYouMeanTerm = this.getDidYouMeanTerm();
            hits = didYouMeanTerm ? this.getDidYouMeanHits() : [];
        }
        this.searchResultsComponent.show(hits, this.query, didYouMeanTerm);

        if (hits.length) {
            var paginationUrl = '?q=' + this.query + this.buildHashUrlQuery(
                '{page}',
                this.searchParams.facetName,
                this.searchParams.facetValue
            );

            var pagination = new Pagination(this.getNumberOfPages(), paginationUrl, 5);
            pagination.init(this.$paginationContainer);
            pagination.show(parseInt(this.searchParams.page));
        } else {
            this.$paginationContainer.hide();
        }

        this.selectCurrentFacet();
        this.toggleLoadingContainer(false);
    };

    Pandosearch.prototype.showFacetsResult = function (rawResults) {
        this.rawResults = rawResults;

        this.facets = this.getFacets();
        this.facetsComponent.show(this.facets);
    };

    Pandosearch.prototype.getHits = function () {
        this.totalHits = this.rawResults.total;
        return Array.isArray(this.rawResults.hits)
            ? this.rawResults.hits
                .map(function (hit) {
                    return {
                        title: hit.fields.title ? hit.fields.title : '---',
                        body: hit.fields.description
                                ? hit.fields.description
                                : (hit.fields.body ? hit.fields.body : '---'),
                        url: hit.url
                    };
                })
            : [];
    };

    Pandosearch.prototype.getDidYouMeanTerm = function () {
        return this.rawResults.suggestions ? this.rawResults.suggestions.didyoumean.text : null;
    };

    Pandosearch.prototype.getDidYouMeanHits = function () {
        this.totalHits = this.rawResults.suggestions.didyoumean.result.total;
        return Array.isArray(this.rawResults.suggestions.didyoumean.result.hits)
            ? this.rawResults.suggestions.didyoumean.result.hits
                .map(function (hit) {
                    return {
                        title: hit.fields.title ? hit.fields.title : '---',
                        body: hit.fields.body ? hit.fields.body : '---',
                        url: hit.url
                    };
                })
            : [];
    };

    Pandosearch.prototype.getFacets = function () {
        var facets = [];
        var params = this.getParamsFromHash();
        for (var facetName in this.rawResults.facets) {
            var facetsByName = this.rawResults.facets[facetName]
                .map(function (facet) {
                    return {
                        key: facet.key,
                        title: this.translateFacetName(facet.display) + ' (' + facet.count + ')',
                        url: '?q=' + this.query + this.buildHashUrlQuery(1, facetName, facet.key),
                        active: params.facetName === facetName && params.facetValue === facet.key,
                        sort:
                            this.facetsSortingOrder[facet.display] != undefined
                                ? this.facetsSortingOrder[facet.display]
                                : 0
                    };
                }, this)
                .sort(function (facet_1, facet_2) {
                    return facet_1.sort - facet_2.sort;
                });
            if (facetsByName.length) {
                var facetData = {
                    name: facetName,
                    title: this.translateFacetName(facetName),
                    facets: facetsByName,
                };

                facets.push(facetData);
            }
        }

        return facets;
    };

    Pandosearch.prototype.selectCurrentFacet = function () {
        for (var groupKey in this.facets) {
            var facetGroup = this.facets[groupKey];
            for (var itemKey in facetGroup.facets) {
                var facetItem = facetGroup.facets[itemKey];

                if (
                    this.searchParams.facetName
                    && this.searchParams.facetValue
                    && facetGroup.name === this.searchParams.facetName
                    && facetItem.key === this.searchParams.facetValue
                ) {
                    facetItem.url = '?q=' + this.query + '#page=1';
                    facetItem.active = true;
                } else {
                    facetItem.url = '?q=' + this.query + this.buildHashUrlQuery(1, facetGroup.name, facetItem.key);
                    facetItem.active = false;
                }
            }
        }

        this.facetsComponent.show(this.facets);
    };

    Pandosearch.prototype.translateFacetName = function (facetName) {
        return this.facetsTranslations[facetName]
            ? this.facetsTranslations[facetName]
            : facetName;
    };

    Pandosearch.prototype.getNumberOfPages = function () {
        return Math.ceil(this.totalHits / this.itemsPerPage);
    };

    Pandosearch.prototype.getParamsFromHash = function () {
        var hash = window.location.hash.substr(1);
        if (!hash) {
            return this.searchParams;
        }

        return hash.split('&').reduce(function (result, item) {
            var parts = item.split('=');
            result[parts[0]] = parts[1];
            return result;
        }, {});
    };

    Pandosearch.prototype.toggleLoadingContainer = function (loading) {
        if (loading) {
            this.$searchResultsContainer.hide();
            this.$loadingContainer.show();
        } else {
            this.$searchResultsContainer.show();
            this.$loadingContainer.hide();
        }
    };

    Pandosearch.prototype.buildHashUrlQuery = function (page, facetName, facetValue) {
        var hashQuery = '#page=' + page;
        if (facetName && facetValue) {
            hashQuery += '&facetName=' + facetName + '&facetValue=' + facetValue;
        }
        return hashQuery;
    };

    Pandosearch.prototype.getUrlParameterByName = function (name) {
        var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
        return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
    };
</script>

Notes

  • Used components: see the “References” in the component library info tab
  • 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.