simaec.net Web Publishing

Search Widget with Svelte

Presentation and navigation of a data set like a directory can be simplified by offering a reactive search widget. With Svelte, we implemented a solution which doesn't require server side processes. The concept works fine as long as the data set isn't very large.

Search Widget with Svelte has been a topic in our live stream on YT Search Widget with Svelte from 10/18/22.

The Problem

We maintain a data set of 160+ species identified in parks in and around Montreal and we want to provide website visitors a page where he/she can look up for species and then follow a link to visit the page with more details about a species.

At a certain size of the data set, a list or a collection of thumbnails stop being useful. Judge for yourself: List All Species. Instead we implemented a widget which allows searching the data set and which presents the options with thumbnails as visual clues: Search Species

Data Set as JSON Object

In order to keep the JSON object searched small, it only contains the data points searched and minimal data required to display a record. In the example of species search the data points searched are title and tags and the additional data points used for display a record in the data set are id_photo, path and presentation.


[
    {
        "id_photo": "khm-20221001-1230-0000185511", 
        "path": "flores-fabales-fabaceae-trifolium-pratense", 
        "title": "Red Clover (Trifolium pratense)", 
        "tags": "flowers, red", 
        "presentation": "focalpoint&fp-x=0.45&fp-y=0.43"
    }, 
    {
        "id_photo": "khm-20220730-1715-0000182560", 
        "path": "testudines-testudines-emydidae-chrysemys-picta",
        "title": "Painted Turtle (Chrysemys picta)", 
        "tags": "turtles", 
        "presentation": "focalpoint&fp-x=0.5&fp-y=0.45"
    }
]

For a data set of 160+ species, the resulting file is about 40k.

Widget Source Code

At the end, the adding an interactive UI built in Svelte requires two elements within a web page. The reference to the compiled javascript source code and a html container where the element is displayed within a page.

In this case, the javascript is called species.js (we search for species) and the trigger to include the element is named "species_search". This names are customizable. You are completely free to choose whatever you consider sound names for your project.


<head>
    ...
    <script defer src='/path/to/script/species.js'></script>
</head>
<body>
    ...
    <div class="species_search" data="fr"></div>
</body>

Having "defer" attribute within the script reference is critical! You want to be sure that all html code is loaded before initiating the app.

main.js

Our implementation of the app initializing code in "main.js" has three special characteristics.

  • Instead of an id, we want a class name triggering the app initialization. In some occasions you want to trigger the same app multiple times within a web page (webforms, interactive buttons, etc.)
  • We don't want to trigger a javascript error in case that a web page includes the script but the html container is missing (try/catch)
  • Providing the option to pass a parameter to the app within the html container using the attribute data. In this case the value is passed to the variable "lang" (one script for all 4 languages)

import App from './App.svelte';

try {
    var targets = document.querySelectorAll(".species_search");
    for (var i = 0; i < targets.length; i++) {
        var target = targets[i];
        var data = target.getAttribute('data');
        if (data && data !== '') {
            var app = new App({
                target: targets[i],
                props: { lang: data }
            });
        } else { var app = null}
    }
} catch (error) {
    var app = null;
}

export default app;

App.svelte

"App.svelte" is the main component of the search widget. It contains an javascript section and an html section. In essential, on mount the data set is loaded and sorted by species name and then displayed. With updating the search string, the records in the data set are filtered and then displayed again.

To deal with special characters in languages like French, German and Spanish, title and tags are normalized before search filtered. Special characters are also of importance when ordering the list of records.


// Comparison used for sort
Intl.Collator(lang).compare(a.title, b.title)

// Normalization of tags and title before testing if a search string is included
value.title.normalize('NFD').replace(/\p{Diacritic}/gu, "")
value.tags.normalize('NFD').replace(/\p{Diacritic}/gu, "")

The final version of the component is published on Github Search App.svelte.