[uibuilder] Easy toast notifications

One of the issues that some people find when switching to uibuilder from Dashboard is that they are faced with writing more HTML/CSS/JavaScript front-end code than they may be used to (or comfortable with).

I chose VueJS and bootstrap-vue as the default templates from v2 because I believe that they offer the most capability with the simplest learning curve and minimum "boilerplate" code.

Even so, many things could be simpler still and I've been thinking about and writing about this for some while, trying to get thoughts and designs together.

Well, it seems that inspiration has finally struck. and here is a quick and easy example of where I'm intending to go. Follow the other thread for design discussions but this article is something that anyone can easily add to their own uibuilder app.

It provides a really easy way to create "toast" pop-over notifications on any uibuilder web page that uses VueJS and bootstrap-vue. You simply send the configuration data and config from Node-RED in a standardised format that I'll be utilising for more extension components in the future (as I hope will other people also creating components).

Add the following function to your index.js file. It goes outside the var app1 = new Vue({...}) part. You don't need to understand how it works, you can simply copy/paste it across. But I'm happy to explain it if anyone wants.

/** Simple function to create a toast notification from an incoming msg
 * Requires a reference to a VueJS instance and a msg object from Node-RED.
 * Place inside the uibuilder.on('msg', ...) function inside your Vue app's
 * mounted section.
 * @see https://bootstrap-vue.org/docs/components/toast
 * @param {Vue} vueApp A reference to a VueJS app instance
 * @param {Object} msg A msg from Node-RED with appropriate formatting
 */
function makeToast(vueApp, msg) {
    // Make sure that we have Vue loaded with the $bvToast function
    // That lets us dynamically create a toast object directly in the virtual DOM
    if ( ! vueApp.$bvToast ) {
        console.warn('[uibuilder] bootstrap-vue toast component not available')
        return
    }

    // $createElement is a Vue function that lets you create Vue virtual DOM
    // elements. We use it here to let us render HTML in the toast.
    const h = vueApp.$createElement

    /** Toast options
     * @type {Object} toastOptions Optional metadata for the toast.
     * @param {String|vNodes|vNodes[]} [toastOptions.title] Optional title, may be HTML (vNode or array of vNodes)
     * @param {Boolean} [toastOptions.appendToast] Optional. Whether to show new toasts below previous ones still on-screen (true). Or to replace previous (false - default)
     * @param {Number} [toastOptions.autoHideDelay] Optional. Ms until toast is auto-hidden.
     * See the bootstrap-vue docs for more options
     */
    let toastOptions = {}

    /** Main content of the toast
     * @type {String|vNodes|vNodes[]}
     */
    let content = ''
    
    // Main body content
    if ( msg.payload ) content += msg.payload
    if ( msg._uib.options.content ) content += msg._uib.options.content
    // Assume that the input content is or could be HTML. create a virtual DOM element
    const vNodesContent = h(
        'p', {
            domProps: {
                innerHTML: content
            }
        }
    )

    if ( msg._uib.options ) toastOptions = Object.assign({}, msg._uib.options) // Need a copy here otherwise debug output breaks

    // The title is also allowed to have HTML
    if ( msg._uib.options.title ) toastOptions.title = h(
        'p', {
            domProps: {
                innerHTML: msg._uib.options.title
            }
        }
    )

    // Do we want new toasts to be shown at the bottom of the list (true) instead of the top (false - default)?
    if ( msg._uib.options.append ) toastOptions.appendToast = msg._uib.options.append

    // If set, number of ms until toast is auto-hidden
    if ( msg._uib.options.autoHideDelay ) toastOptions.autoHideDelay = msg._uib.options.autoHideDelay

    // Toast wont show anyway if content is empty, may as well warn user
    if ( content === '' ) {
        console.warn('[uibuilder] Toast content is blank. Not shown.')
        return
    }

    // Dynamically insert the toast to the virtual DOM
    // Will show at top-right of the HTML element that is the app root
    // unless you include a <b-toaster> element
    vueApp.$bvToast.toast(vNodesContent, toastOptions)

} // --- End of makeToast() --- //

Then in your uibuilder.onChange('msg', function(msg){ ... }) function, add this:

// Testing a notifications scheme - to be moved into uibuilderfe
if (msg._uib && msg._uib.componentRef && msg._uib.componentRef === 'globalNotification') {
    makeToast(vueApp, msg)
}

Now try sending some messages to uibuilder using the following format:

{
    "payload": "",
    "_uib": {
      "componentRef": "globalNotification",
      "options": {
            "title": "This is the <i>title</i>",  /* & $string($floor($random()*10)), */
            "content": "This is content <span style=\"color:red;\">in addition to</span> the payload",
            "append": true,
            "autoHideDelay": 1500,
            "variant": "info",
            "solid": true,
            "href": "https://bbc.co.uk",
            "toaster": "b-toaster-top-center",
            "noAutoHide": true
        }
    }
}

All of the options are optional. You can find the details at:

So there you have it. A very simple but comprehensive way to show user notifications.

I will, at some point most likely be including that within the uibuilderfe code so that you don't need to write any code at all! Just as easy as Dashboard and hopefully a lot more performant since you are only sending minimal data and not any code.

Check out the other thread where I also talk about new extension components that provide a lot more capability. I'm using a graphical gauge component as an example to work through the ideas. That will be really simple to use as well and should nicely demonstrate combining the power and flexibility of uibuilder with the simplicity found in Dashboard.

Let me know what you think.

3 Likes

Evening Julian,

seems you have been very busy :+1:
I have to admit that I had no chance yet to read through the material yet.
So I will ask uibuilder noob questions now ...

Do I need to create that file manually, or via the uibuilder node, or somehow else?
And would it be possible to create a ui-vuejs-toast node that:

  • injects this code automatically in the index.js file somehow.
  • is detected by a single central ui-vuejs-index node that collects to code automatically from all ui-vuejs-xxx nodes in the flow.

I mean that I - as a noob user - simply have to drag such a ui-vuejs-toast node on my flow (like I used to do with the AngularJs dashboard). Just to make sure that your magnificent work is accessible for all Node-RED users ...

Please forgive me if you have already explained me in the past that this is not possible, for some reason that I have forgotten ...

Bart

1 Like

The index.(html|css|js) files are created automatically if you are using the default settings for uibuilder. The index.js will have everything you need to add that function and the call.

I will most likely add the code direct to the uibuilderfe library (the front-end code that supports uibuilder) since the code required is quite small. No need for a separate module in this case.

However, for things like the gauge component, though the code is actually amazingly small, I will be putting that into its own module, something like uibuilder-vue-svggauge. I still need to do some further tweaks like adding a method to be able to query the required msg schema from Node-RED to make it easy for flow authors to discover the required data.

Noo, far to complex :slight_smile: - once the front-end uibuilder library has been updated, you won't need any front-end code to use the notification. All you will need to do is send the right msg.