[New Web Components] A new set of pure web components that work with or without uibuilder & a new uibuilder client library!

OK, after some time playing with these, I think they are ready to share with the world! Though please note that you should certainly treat these as EXPERIMENTAL for now because I'm still learning and undoubtedly I'll need to make more breaking changes.

BUT, they work, if a little simplistically. :smile_cat:

What are they? You ask! Well, they are a set of modern web components. Which is to say, when you load the files, you get new HTML tags to play with. Those tags are designed to help you build data-driven web pages (of course, hence I'm relating these to uibuilder). They should, if I get them right, GREATLY simplify building a web UI that can be driven from Node-RED.

If you are truly adventurous, you can dig into the documentation for the new ECMA module version of the uibuilder front-end library and try using the configuration driven UI builder feature as well.

But for now, you can, at least, very easily try out the components along with the new client and the shiny new stylesheet that automatically or manually adjusts to light/dark layouts.

To use the components, you will firstly need node-red-contrib-uibuilder installed. Then use uibuilder's library manager to install totallyinformation/web-components which will install the library direct from GitHub.

Now add a uibuilder node using the "blank" template. Deploy. Then change the index.html file to be something like this:

<!doctype html>
<html lang="en"><head>
    <meta charset="utf-8">
    <title>Web Component Test: simple-container and simple-card</title>

    <style>
        @import url("./uib-brand.css");
        /* Can override the base hue and the accent colours and other things too */
        /* :root {
            --brand-hue: 200;
            --accent-offset: 30;
        } */
    </style>

    <script type="module" async >
        // These assume you are using Node-RED/uibuilder
        import '../uibuilder/vendor/@totallyinformation/web-components/libs/uibuilder.module.js'  // Adds `uibuilder` and `$` to globals
        import '../uibuilder/vendor/@totallyinformation/web-components/components/simple-container.js'
        import '../uibuilder/vendor/@totallyinformation/web-components/components/simple-card.js'
        import '../uibuilder/vendor/@totallyinformation/web-components/components/uib-theme-changer.js'
        import '../uibuilder/vendor/@totallyinformation/web-components/components/labelled-value.js'
        import '../uibuilder/vendor/@totallyinformation/web-components/components/button-send.js'
        import '../uibuilder/vendor/@totallyinformation/web-components/components/container-br.js'

        // An EventListener added here cannot pick up the initial setup but will pick up later events
        // However, uibuilder and $ are available.
        uibuilder.start()

    </script>

</head><body>
    <uib-theme-changer></uib-theme-changer>
    <h1>Demonstrating Web Components</h1>
    <p>This example uses <code>&lt;simple-container</code> and <code>&lt;simple-card></code>.</p>
    <p>
        The container component uses a flex row layout. The card component has header and footer slots.
    </p>

    <simple-container>
        <simple-card>
            Hello, in a card<br>
            <labelled-value label="My Value" value="42">&deg;</labelled-value>
            <span slot="header">Some other header</span>
            <span slot="footer">Some footer</span>
        </simple-card>
        <simple-card name="This Is a name">
            Another card
            <labelled-value label="My Other Value" value="Fourty Two">&deg;</labelled-value>
        </simple-card>
        <simple-card id="card3" data-something="Hello">
            Card #3
        </simple-card>
        <container-br></container-br>
        <div>
            <button-send data-something="Something interesting">Send Me to Somewhere 😀</button-send>
        </div>
    </simple-container>

    <script>
        // Event Listeners set in module scripts wont get set up early enough to pick up the initial component load events
        // BUT an ordinary script like this WILL trigger early enough.
        // HOWEVER, this triggers so early that if you load uibuilder in a module script, it
        //   wont be available here immediately. You would have to wait for ~300 ms.

        // document.addEventListener('simple-card:connected', e => {
        //     console.log('>> (document) EVENT simple-card:connected >>', e.target, e.detail)
        // })

        // This CAN pick up the initial setup event
        // document.addEventListener('simple-card:attribChanged', e => {
        //     console.log('>> (document) EVENT simple-card:attribChanged >>', e.target, e.detail)
        // })

        // Things set up in uibuilder won't be available here for ~300ms
        // setTimeout(function(){
        //     console.log('HELLO', window.$)
        //     $('#card3').setAttribute("name", "helloButton")
        // }, 300)

        // We can also listen at the component instance level:
        // const sc = document.getElementsByTagName('simple-card')
        // for (const i of sc) {
        //     // When the component constructor is run
        //     // i.addEventListener('simple-card:construction', e => {
        //     //     console.log('>> EVENT simple-card:construction >>', e)
        //     // })
            
        //     // When the component instance is connected to the DOM
        //     // i.addEventListener('simple-card:connected', e => {
        //     //     console.log('>> EVENT simple-card:connected >>', e)
        //     // })

        //     // When the component instance is disconnected from the DOM
        //     // i.addEventListener('simple-card:disconnected', e => {
        //     //     console.log('>> EVENT simple-card:disconnected >>', e)
        //     // })

        // }
    </script>
</body></html>

Open the new web page and marvel at how easy it is to create something nice with so little effort!


Not everything works brilliantly as yet and the documentation is lagging behind for sure but hopefully it gives you an idea of what I'm thinking about for another very easy way of building data-driven web apps via Node-RED.

I'll do an example with the config-driven settings soon as well.

Components available now:

Name Description
button-send A pre-defined button that fires an event and sends a uibuilder msg when clicked. Includes attribs in the sent data.
container-br Like <br> for flex layouts. Forces a new row in a simple-container (or any other flex row container)
data-list Data-driven UL/OL. Takes a JSON or JavaScript object or array of objects and outputs a formatted list.
definition-list Similar to data-list but outputs a DL instead.
html-include Dynamically load external HTML content very easily without needing an iFrame.
labelled-value Text output with a label.
simple-card A card container with optional header and footer.
simple-container A UI container for easy, automated layout of contained elements (specifically cards).
syntax-highlight A simple, easy to use JSON object highlight element. Auto settings for different types of uibuilder messages or manually pass the data.
uib-theme-changer This only works with the uib-brand stylesheet or something crafted to be like it. Switch between light/dark/auto schemes, shift the base hue, contrast ratio, and 2 accent colours.

With more to come, here is the list of things I'm thinking of. There will certainly be more.

Name Description
simple-input An input tag with a label. With option to be part of a form (button submission) or to give output on lost focus.
simple-button A button that triggers a custom document event and sends to uibuilder (if available). Automatically includes attrib and prop data, id, name, etc. Includes basic formatting and a slot for text (which allows some HMTL formatting).
visual-log Creates a log element on-page that expands to a set number of lines then drops from the start
simple-table Takes a JSON or JavaScript object or array and displays the contents in a simple table format.
data-table Similar to simple-table but with more options such as nested tables and multiple headers/bodies. Probably also with CRUD controls.
data-card A data-driven card with headers, footers, etc. Also acts as a container for other HTML as part of a grid view.
grid-view A controllable grid layout.
chart-* Various different chart outputs using different libraries. Chart.js, Uplot, EChart
on-off A dedicated on/off button.
simple-dialog Simple wrapper for a dialog box with options for modal, button list
simple-pie Web Components using UnknownHTMLElements for better semantic HTML - DEV Community
nav-bar
3 Likes

OK, so I found I already had an example flow for the config-driven UI.

image

Connect this to the input of the html from the previous post.

[{"id":"6844f9b7621e5068","type":"inject","z":"2c47031326e8fd01","name":"addme","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":175,"y":520,"wires":[["4eda63494d97856f"]],"l":false},{"id":"4eda63494d97856f","type":"change","z":"2c47031326e8fd01","name":"addme","rules":[{"t":"set","p":"_ui","pt":"msg","to":"{\"method\":\"add\",\"parent\":\"#start\",\"components\":[{\"type\":\"button\",\"xparent\":\"#start\",\"attributes\":{\"id\":\"fred\",\"style\":\"margin:1em;\",\"name\":\"Freddy\",\"data-return\":\"wow!\",\"type\":\"button\"},\"properties\":{\"nice\":{\"lets\":\"have\",\"a\":\"property\"}},\"events\":{\"click\":\"uibuilder.eventSend\"}},{\"type\":\"button\",\"parent\":\"#start\",\"slot\":\"Ano<sub>ther</sub> <code>Button</code>\",\"attributes\":{\"id\":\"jim\",\"style\":\"display:block;margin:1em;\",\"name\":\"Jimmy\",\"data-return\":\"OK\",\"type\":\"button\"},\"events\":{\"click\":\"window.myCallbacks.mycb\"}},{\"type\":\"ol\",\"parent\":\"#start\",\"slot\":\"A list\",\"attributes\":{\"id\":\"ol1\",\"style\":\"display:block;margin:1em;border:1px solid silver;\"},\"components\":[{\"type\":\"li\",\"slot\":\"A list entry\"},{\"type\":\"li\",\"slot\":\"Another list entry\"}]}]}","tot":"json"},{"t":"set","p":"payload","pt":"msg","to":"This was dynamically added 😁","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"addme","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":520,"wires":[["f4f634ab483e31c2"]]},{"id":"91dcfe4db2a80161","type":"inject","z":"2c47031326e8fd01","name":"addme","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":175,"y":560,"wires":[["39cd040645756976"]],"l":false},{"id":"39cd040645756976","type":"change","z":"2c47031326e8fd01","name":"addme no parent","rules":[{"t":"set","p":"_ui","pt":"msg","to":"{\"method\":\"add\",\"components\":[{\"type\":\"p\",\"slot\":\"Bonjour.\",\"attributes\":{\"id\":\"p1\",\"style\":\"margin:1em;\",\"data-return\":\"wow!\"},\"events\":{\"click\":\"uibuilder.eventSend\"}}]}","tot":"json"},{"t":"set","p":"payload","pt":"msg","to":"This was dynamically added 😁","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"addme","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":560,"wires":[["f4f634ab483e31c2"]]},{"id":"cd1c09bd9281bea0","type":"inject","z":"2c47031326e8fd01","name":"addme","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":175,"y":600,"wires":[["0ed537fd0aafb36a"]],"l":false},{"id":"0ed537fd0aafb36a","type":"change","z":"2c47031326e8fd01","name":"removeme","rules":[{"t":"set","p":"_ui","pt":"msg","to":"{\"method\":\"remove\",\"components\":[\"#fred\",\"button#jim\",\"#ol1\",\"#p1\",\"#start\"]}","tot":"json"},{"t":"set","p":"topic","pt":"msg","to":"removeme","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":600,"wires":[["f4f634ab483e31c2"]]}]

You can also pre-load a whole UI from a JSON file as well. See the docs.

Here is the config for the top one of those change nodes:

It contains this JSON:

{
    "method": "add",
    "parent": "#start",
    "components": [
        {
            "type": "button",
            "xparent": "#start",
            "attributes": {
                "id": "fred",
                "style": "margin:1em;",
                "name": "Freddy",
                "data-return": "wow!",
                "type": "button"
            },
            "properties": {
                "nice": {
                    "lets": "have",
                    "a": "property"
                }
            },
            "events": {
                "click": "uibuilder.eventSend"
            }
        },
        {
            "type": "button",
            "parent": "#start",
            "slot": "Ano<sub>ther</sub> <code>Button</code>",
            "attributes": {
                "id": "jim",
                "style": "display:block;margin:1em;",
                "name": "Jimmy",
                "data-return": "OK",
                "type": "button"
            },
            "events": {
                "click": "window.myCallbacks.mycb"
            }
        },
        {
            "type": "ol",
            "parent": "#start",
            "slot": "A list",
            "attributes": {
                "id": "ol1",
                "style": "display:block;margin:1em;border:1px solid silver;"
            },
            "components": [
                {
                    "type": "li",
                    "slot": "A list entry"
                },
                {
                    "type": "li",
                    "slot": "Another list entry"
                }
            ]
        }
    ]
}
3 Likes

Pushed a few updates and fixes since the last post.

Several of the components are now wired up for accepting updates from Node-RED via uibuilder using a standardised and very simple data schema. Notably the labelled-value (like the text node for Dashboard), button-send (really easy to use button that sends data back to node-red), and simple-card (nice simple container with optional header and footer and now also with several colours following the bootstrap-vue variant attribute style).

With those, you can send data with a msg containing something like:

"_ui": {
    "method": "update",
    "id": "card3",
    "variant": "success",
    "header": "New <b>Header</b>.",
    "footer": "Some footer text"
},
"payload": "Updated <i>card</i> text"

Which would update a <simple-card id="card3"></simple-card> tag on your page.

As with the previous example, this relies on the new front-end library libs/uibuilder.module.js, the new stylesheet, and a modern browser supporting ES2019+ (which is just about everything since early 2019). The HTML given in the first post will work for this example.

To update the library in node-red, simply use uibuilder's library manager and add the totallyinformation/web-components package again, it will overwrite the existing install with the new one.


1 Like

Some more updates pushed. This time mostly to the new uibuilder client library which is really coming along well. If you have a chance to try it, please do!

Coding for the front-end is easier than ever with the new client. Your code might be as small as:

<!doctype html>
<html lang="en"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>TotallyInformation - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - TotallyInformation">
    <link rel="icon" href="./images/node-blue.ico">

    <link type="text/css" rel="stylesheet" href="./uib-brand.css" media="all">
    <script type="module" async src="./uibuilder.module.js"></script>
</head><body></body></html>

Yup, no actual HTML and no JavaScript! If you don't need to send data back to Node-RED and you push the UI using the new UI action messages. :exploding_head: :man_mage:

As a taster, here are the features listed in the draft documentation.


Features

Dynamic, data-driven HTML content

This feature allows you to dynamically create a UI or part of a UI using just configuration information either send in messages from Node-RED or loaded from a JSON file (or a combination of both).

See the detail section on Dynamic, data-driven HTML content for details.

Exposes global uibuilder and $

For ease of use, both uibuilder and $ objects are added to the global window context unless they already exist there.

start function

You should hardly ever need to manually run this now. Try without first. See the details below.

The start function is what kick-starts the uibuilder front-end library into action. It attempts to make a connection to Node-RED and exchanges the initial control messages.

It:

  • Attempts to use some cookie values passed from Node-RED by uibuilder in order to work out how to connect the websocket (actually uses Socket.IO).
  • Starts the communications with Node-RED/uibuilder node using Socket.IO. This also issues 1 or more document custom events (see Event Handling below).
  • An event handler is created for incoming messages from Node-RED. It checks for reload and UI requests and deals with them automatically.
  • Automatically loads the default stylesheet if you haven't loaded your own.

Normally, you will not have to pass any options to this function (unlike the equivalent function in the older uibuilderfe.js library before uibuilder v5). However, see the troubleshooting section if you are having problems connecting correctly.

If you do need the options, there is now only a single argument with only two possible properties:

uibuilder.start({
    ioNamespace: '/components-html', // Will be the uibuilder instance URL prefixed with a leading /
    ioPath: '/uibuilder/vendor/socket.io', // Actual path may be altered if httpNodeRoot is set in Node-RED settings
})

$ function

uibuilder adds the global $ function when loaded if it can (it won't do it if $ is already present, such as if jQuery has been loaded before uibuilder). This is for convenience.

The $ function acts in a similar way to the version provided by jQuery. It is actually bound to document.querySelector which lets you get a reference to an HTML element using a CSS selector.

Note that this function will only ever return a single element which is differnt to jQuery. You can always redefine it to querySelectorAll using window.$ = document.querySelectorAll.bind(document) should you need to.

If multiple elements match the selection, the element returned will be the first one found.

Example. With the HTML <button id="button1">Press me</button> and the JavaScript $('#button1').innerHTML = 'boo!'. The label on the button will change from "Press me" to "Boo!".

See the MDN documentation on CSS query selectors for details on selecting elements.

onChange/cancelChange functions

The onChange function will be familiar if you have used previous versions of the uibuilderfe.js library. However, it works in a very different way now. The most important change is that it now returns a reference value that can be used to cancel the listener if you need to.

Here are some useful examples:

let ocRef = uibuilder.onChange('msg', function(msg) {
    console.log('>> onChange `msg` >>', this, msg)
    // ... do something useful with the msg here ...
})
let ocRefPing = uibuilder.onChange('ping', function(data) {
    console.log('>> onChange `ping` >>', data)
    // ... do something useful with the msg here ...
})
uibuilder.onChange('ioConnected', function(isConnected) {
    console.log('>> onChange `ioConnected` >>', isConnected)
    // ... do something useful with the msg here ...
})
// ... or anything else that is changed using the `set` function ...

The cancelChange function lets you turn off the event responders:

uibuilder.cancelChange('msg', ocRef)
uibuilder.cancelChange('ping', ocRefPing)

onTopic/cancelTopic functions

This is a convenience function pair that lets you take action when a message from Node-RED contains a specific topic. It may save you some awkward coding where you find yourself using onChange to listen for msg changes but then have to have a long-winded if or switch statement around the msg.topic. That is no longer necessary. Instead just use several different onTopic functions.

For example, a message from Node-RED such as {topic: 'mytopic', payload: 42} could be actioned using the following code:

let otRef = uibuilder.onTopic('mytopic', function(msg) {
    console.log('>> onTopic `mytopic` >>', this, msg)
    // ... do something useful with the msg here ...
})

Note that the onTopic function returns a reference value. For the most part, this is not required. However, if for some reason, you need to be able to cancel the listener, you can do so with:

uibuilder.cancelTopic('mytopic', otRef)

It is also worth noting that, as written above, you will see that the console message shows 2 copies of the msg. That is because the value of this within the callback function is also set to the msg. Obviously, this is not accessible if you use an arrow function as with:

let otRef = uibuilder.onTopic('mytopic', (msg) => {
    console.log('>> onTopic `mytopic` >>', this, msg)
    // ... do something useful with the msg here ...
})

Because this now points to the parent and not to the callback function. You could use a bound function if you really wanted the correct this when using an arrow function but at present, there is no real value in doing that as the content of this is identical to the msg argument. That may change in future releases.

Conditional logging

Internal logging is much improved over previous versions of this library. There is now a dedicated internal log function which adds colour highlighting to browsers that support it in the dev tools console. That includes all Chromium-based browsers and Firefox.

You can alter the amount of information that the uibuilder library outputs to the console by changing the logLevel with uibuilder.logLevel = 4 where the number should be between 0 and 5. you can set that at any time in your code, however it will generally be most useful set before calling uibuilder.start().

The default level is set to 2 (info). The levels are: 0 'error', 1 'warn', 2 'info', 3 'log', 4 'debug', 5 'trace'.

Changing the log level outputs an info note to the console telling you what the level is.

The log function is also available to your own code as uibuilder.log(level, prefix, ...outputs).

document-level events

In previous versions of the library, a custom event feature was used. In this version, we make use of custom DOM events on the document global object.

Each event name starts with uibuilder: to avoid name clashes.

The current events are (other events may be added later):

  • uibuilder:stdMsgReceived - triggered when a non-control msg arrives from Node-RED
  • uibuilder:propertyChanged - triggered when any uibuilder managed property is changed
  • uibuilder:msg:topic:${msg.topic} - triggered when an incoming msg contains a msg.topic property allowing specific topics to be monitored
  • uibuilder:msg:_ui - triggered when an incoming msg contains a msg._ui property (used for UI automation using web components)
  • uibuilder:msg:_ui:${action.method}${action.id ? :${action.id} : ''} - triggered when the incoming msg contains msg._ui.add, msg._ui.update, or msg._ui.remove for UI automation. For the update action, the msg._ui.id property links the msg to a specific on-page element by its HTML id attribute.
  • uibuilder:socket:connected - when Socket.IO successfully connects to the matching uibuilder node in Node-RED
  • uibuilder:socket:disconnected - when Socket.IO disconnects from the matching uibuilder node in Node-RED

You can watch for these events in your own code using something like:

document.addEventListener('uibuilder:propertyChanged', function (evt) {
    console.log('>> EVENT uibuilder:propertyChanged >>', evt.detail)
})

In each case, evt.detail contains the relevant custom data.

In general, you should not need to use these events. There are more focused features that are easier to use such as onChange and onTopic.

Of particular note are the _ui events. These are used by uibuilder-aware web components for UI automation.

setPing function

setPing accesses a special endpoint (URL) provided by uibuilder. That endpoint returns a single value which really isn't of any use to your code. However, it does do several useful things:

  1. It tells the server that your browser tab is alive.

    This may be useful when working either with a reverse Proxy server or with uibuilder's ExpressJS middleware for authentication and/or authorisation.

    Because most communication with uibuilder happens over websockets, telling the server whether a client is still active or whether the client's session has expired is challenging. A ping such as this may be sufficient for the proxy or your custom middleware code to continue to refresh any required security tokens, etc.

  2. It returns the timespan (in milliseconds) of the round-trip time.

    This can help with understanding networking and client device or Node-RED issues.

  3. It returns the uibuilder/Node-RED HTTP headers.

    Normally, the web server headers cannot be accessed by your custom JavaScript code. However, the ping function uses the Fetch feature available to modern browsers which does return the headers.

    You can watch for ping responses as follows:

     uibuilder.setPing(2000) // repeat every 2 sec. Re-issue with ping(0) to turn off repeat.
     uibuilder.onChange('ping', function(data) {
         console.log('>> PING RESPONSE >>', data)
     })
     // Output:
     //    pinger {success: true, status: 201, headers: Array(6)}
    
     // Turn off the repeating ping with
     uibuilder.setPing(0)
    

    The headers are included in the data object.

  4. It returns the full originating URL.

    While not normally needed, it can be useful in order to work out whether Node-RED's httpNode URL path has been changed.

set function

the uibuilder.set() function is now more flexible than in uibuilderfe.js. You can now set anything that doesn't start with _ or #.

Please note that there may be some rough edges still in reguard to what should and shouldn't be set. Please try to avoid setting an internal variable or function or bad things may happen :astonished:

This means that you can simulate an incoming message from Node-RED with something like uibuilder.set('msg', {topic:'uibuilder', payload:42}).

One interesting possibility is getting your page to auto-reload using uibuilder.set('msg', {_uib:{reload:true}}). Perhaps even more useful is the ability to very easily alter your UI on the page by using the dynamic UI feature (detailed below) uibuilder.set('msg', {_ui:[{method:'add', ...}, {method:'remove', ....}]}).

Using the set function triggers an event uibuilder:propertyChanged which is attached to the document object. This means that you have two different ways to watch for variables changing.

This will listen for a specific variable changing:

uibuilder.onChange('myvar', (myvar) => {
    console.log('>> MYVAR HAS CHANGED >>', myvar)
})
// ...
uibuilder.set('myvar', 42)
// Outputs:
//     >> MYVAR HAS CHANGED >> 42

Whereas this will listen for anything changing:

document.addEventListener('uibuilder:propertyChanged', function (evt) {
    // evt.detail contains the information on what has changed and what the new value is
    console.log('>> EVENT uibuilder:propertyChanged >>', evt.detail)
})
// ...
uibuilder.set('myvar', 42)
// Outputs:
//     >> EVENT uibuilder:propertyChanged >> {prop: 'myvar', value: 42}

Page auto-reload

By sending a message such as {_uib:{reload:true}} from Node-RED, you can make your page reload itself. This is already used by the uibuilder file editor. But you can add a flow in Node-RED that consists of a watch node followed by a set node that will create this message and send it into your uibuilder node. This will get your page to auto-reload when you make changes to the front-end code using an editor such as VSCode. This is what a dev server does in one of the many front-end frameworks that have build steps. You don't need a build step though and you don't need a dev server! :sunglasses:

setStore, getStore, removeStore functions

Stores & retrieves information in the browser's localStorage if allowed. localStorage will survive page reloads as well as tab, window, browser, and machine restarts. However, whether storage is allowed and how much is decided by the browser (the user) and so it may not be available or may be full.

Applies an internal prefix of 'uib_'. Returns true if it succeded, otherwise returns false. If the data to store is an object or array, it will stringify the data.

Example

uibuilder.setStore('fred', 42)
console.log(uibuilder.getStore('fred'))

To remove an item from local storage, use removeStore('fred').

send function

The send function sends a message from the browser to the Node-RED server via uibuilder.

uibuilder.send({payload:'Hello'})

There is an optional second parameter that specifies an originating uib-send node. Where present, it will return a message back to the sender node. To make use of the sender id, capture it from an incoming message.

eventSend function

Takes an suitable event object as an argument and returns a message to Node-RED containing the event details along with any data that was included in data-* attributes and any custom properties on the source element.

data-* attributes are all automatically added as a collection object to msg.payload.

All custom properties under <element>._ui are automatically added as a collection to msg.uiprops.

Note: Only the <element>._ui property is considered for custom properties. This is used by the data-driven UI feature. If you are adding your own custom properties to an element, please attach it to <element>._ui to avoid namespace clashes.

Plain html/javascript example.

In index.html

<button id="button1" data-life="42"></button>

In index.js

$('#button1').onclick = (evt) => { uibuilder.eventSend(evt) }

VueJS/bootstrap-vue example

In index.html

<b-button id="myButton1" @click="doEvent" data-something="hello"></b-button>

In index.js VueJS app methods section

    // ...
    methods: {
        doEvent: uibuilder.eventSend,
    },
    // ...

Auto-loading of the Socket.IO client

In previous versions of the front-end library, you had to load the correct Socket.IO library yourself in your index.html. This is no longer the case as the correct client is now loaded for you. This is one of the benefits of working as an ECMA module.

Auto-loading of the uibuilder default stylesheet

In previous versions of the front-end library, you had to provide your own CSS code and had to make sure that you imported (or loaded) the uibuilder default stylesheet (uib-styles.css).

In this version, if you haven't loaded any other stylesheets, the library will automatically load the new default stylesheet (uib-brand.css).

Note that using this auto-load feature will result in a flash of an unformated page before the styles are loaded and applied. So it is still recommended to load the stylesheet manually in the head section of your HTML. That will avoid the unstyled flash.

If you are trying to load the default CSS and can't find the correct URL, try removing the style link from your HTML and check what the client loads in the browsers dev tools network tab.

1 Like