Announcing: Vanilla JavaScript front-end router for single-page apps

Nearly here! Just finalising the niceties.

A front-end router is used in single-page apps (SPA) to make it look to the users as though they are swapping web pages but without the slight delay that comes from actually having to load another page. The route content is simply a set of HTML/JavaScript/CSS that gets loaded and unloaded dynamically.

Node-RED Dashboard has always been a single-page app (SPA) whereas UIBUILDER has the choice of multi- or single-page.

However, UIBUILDER has always lacked a front-end router of its own with people either having to find and learn a 3rd-party router library or turn to a full-fat framework like VueJS or REACT with a router extension.

But no more! Thanks to some prompting and questioning by @fmarzocca, a new, lightweight router for SPA use is on its way. It will be incorporated into the next release of UIBUILDER (v6.7). Which shouldn't be too far away now.

This will be incorporated into the UIBUILDER front-end but will be kept as a separate library to keep the uibuilder library itself as small as feasible.

Though offered with UIBUILDER, the router library will actually be pure JavaScript and so could be taken out and used elsewhere. It has the usual permissive Apache 2.0 license to facilitate this.

The router library will let you combine route content both from in-page <template> tags AND from external HTML files. With external files supporting stylesheets and scripts as well as content.

Defining and executing the router will use some simple front-end code that will look something like this:

const routerConfig = {
    // Router templates created inside the routeContainer
    // If not provided, default div with ID uibroutecontainer is added as the last element of the body
    routeContainer: '#routecontainer',

    // Optionally, chose a default route to be displayed on load
    defaultRoute: 'route03',

    // Define the possible routes type=url for externals
    // Can be an object or an array but each entry must be an object containing {id,src,type}
    //   type can be anything but only `url` will be treated as an external template file.
    //   src is either a CSS selector for a <template> or a URL of an HTML file.
    //   id must match the href="#routeid" in any menu/link. and `<template id="routeid">` on any loaded template
    routes: [
        {id: 'route01', src: '#route01', type: 'dom'},
        {id: 'route02', src: '#route02', type: 'dom'},
        {id: 'route03', src: './fe-routes/route03a.html', type: 'url'},
        // Doesn't exist. Tests load error
        {id: 'route04', src: './fe-routes/dummy.html', type: 'url'},
    ],
}
const router = new UibRouter(routerConfig)

And switching to a route is as simple has creating a suitable link in your HTML:

    <ul id="routemenu">Route Menu
        <li><a href="#route01" onclick="router.doRoute(event)">#1</a></li>
        <li><a href="#route02">#2</a></li>
        <li><a href="#route03?doh=rei">#3</a></li>
    </ul>

With routes executed using, in this case, the click event on the link. In the example, that is defined in the HTML for route 1 but you can also do it in JavaScript if preferred:

const menuItems = $$('#routemenu > li > a') // gets array of all
menuItems.forEach( item => {
    item.addEventListener('click', (event) => {
        router.doRoute(event)
    })
} )

And as a bonus for anyone who bothered to read down to here. If you would like to make early use of the router, please leave a comment below and I will post some working code before the v6.7 release. :grinning:


Oh, and nearly forgot. Of course, there will be a no-code option for creating the router and routes as well. :mage: :exploding_head:

6 Likes

I would be interested to see how to make use of this

Like any url route mechanism: Router, Routing and Web Components | Frameworks | https://www.htmlelements.com/

1 Like

It is pretty straight forward. Though I need to make a couple of tweaks based on some feedback from @fmarzocca. You can define route content using a mix of template tags and/or external html files, simple links have a click handler that triggers the route handler. Routes are simply some template HTML that is inserted to a specific point in the display, replacing what was there before.

Hi again all, took a little longer than expected by we are finally there. v1.0.0 of the new front-end router library is now ready for use. There are still things I want to improve but I think that all of the basics are in place and it seems to be reasonably robust. Many thanks to @fmarzocca for patiently working through a number of issues and bugs.

Remember, that while this is currently going to be published as part of UIBUILDER, it is not dependent on it in any way. You are welcome to use it in your own projects and it will work just fine completely outside of Node-RED.

You can grab the current code and documentation via these links:

Source code: node-red-contrib-uibuilder/src/front-end-module/uibrouter.js at v6.7.0 · TotallyInformation/node-red-contrib-uibuilder (github.com)

IIFE minimised version: node-red-contrib-uibuilder/front-end/utils/uibrouter.iife.min.js at v6.7.0 · TotallyInformation/node-red-contrib-uibuilder (github.com) - there is also an ESM version if you prefer working with ES Modules.

and the map file: node-red-contrib-uibuilder/front-end/utils/uibrouter.iife.min.js.map at v6.7.0 · TotallyInformation/node-red-contrib-uibuilder (github.com) - makes debugging easier, you don't have to do anything with it other than have it in the same place as the main file.

Finally, here is the documentation: node-red-contrib-uibuilder/docs/client-docs/fe-router.md at v6.7.0 · TotallyInformation/node-red-contrib-uibuilder (github.com)

The documentation contains this simple example of use:

const routerConfig = {
    // OPTIONAL. Router templates created inside the routeContainer, specify an CSS selector
    // If not provided, default div with ID uibroutecontainer is added as the last element of the body
    routeContainer: '#routecontainer',

    // OPTIONAL. Chose a default route id to be displayed on load
    defaultRoute: 'route03',

    // OPTIONAL. If true, use CSS show/hide instead of removing/recreating route content
    // hide: true,

    // OPTIONAL. If true, templates are unloaded from the DOM after being accessed (only useful with hide: true)
    // unload: true, // NOTE: This is not working at present.

    // REQUIRED. Define the possible routes type=url for externals
    // Can be an object or an array but each entry must be an object containing {id,src,type}
    //   type can be anything but only `url` will be treated as an external template file.
    //   src is either a CSS selector for a <template> or a URL of an HTML file.
    //   id must match the href="#routeid" in any menu/link. and `<template id="routeid">` on any loaded template
    //      must be unique on the page
    routes: [
        // Two <template> tags as routes
        {id: 'route01', src: '#route01'},
        {id: 'route02', src: '#route02'},
        // File exists in sub-folder below index.js, is served by Node-RED/uibuilder
        {id: 'route03', src: './fe-routes/route03.html', type: 'url'},
        // Doesn't exist. Tests load error
        {id: 'route04', src: './fe-routes/dummy.html', type: 'url'},
    ],
}
const router = new UibRouter(routerConfig)

To get a menu, all you need are links in your HTML:

    <ul id="routemenu">Route Menu
        <li><a href="#route01">#1 (Internal template)</a></li>
        <li><a href="#route02">#2 (Internal template)</a></li>
        <li><a href="#route03?doh=rei">#3 (External template)</a></li>
        <li><a href="#route04">#4 (fails as the external route template doesn't exist)</a></li>
        <li><a href="#route05">#5 (External template)</a></li>
        <li><a href="#route06">#6 (Internal template)</a></li>
    </ul>

You can skin and format that however you like.

You can also manually trigger a route change in code with:

router.doRoute('routeid')

Or even trigger from some random click on an element with:

<div href="#route01" onclick="router.doRoute(event)">#1 via event handler</div>
2 Likes

Julian,
I must send you all my best feelings for this unvaluable piece of code, which gives UIBUILDER the merit of being defined as one of the best tools to create custom web interfaces for Node Red (and not only).
It was exciting to work with you through the issues. Now my SPA can really work without the need of any other fancy or complex framework!

Fabio

5 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.