# \[Announce\] uibuilder - the next phase! Part 1

**URL:** <https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365>\
**Category:** Share Your Nodes\
**Tags:** uibuilder\
**Created:** [16 April 2022 10:58 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365 "2022-04-16T10:58:33Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [16 April 2022 10:58 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/1 "2022-04-16T10:58:34Z")

</div>

Hi all, couldn't resist sharing something quite exciting on this Bank Holiday weekend.

In the next phase of development for uibuilder, I'm focusing on the client library. At present, I'm creating an all-new library that is JavaScript module based using ES2019. This is helping me understand where we can go next. I'll be sharing the beta client shortly.

But for this announcement, I wanted to look at _creating dynamic UI's based on data sent from Node-RED_.

I am going to propose a new set of standardised data structures to use in messages that will automatically add/remove/update HTML elements on-page. This should work no matter what framework you are using.

Actually adding things to the page turns out to be remarkably easy (now that I've finally learned how to manipulate the DOM!). Using 2 new features of the new client: `onTopic` and `$`, these few lines will do a simple addition dynamically to the page:

```javascript
uibuilder.onTopic('addme', function(msg) {
    let compToAdd = msg._ui.components[0]
    
    // Create the new component
    let newEl = document.createElement(compToAdd.type)
    
    // Set the component content to the msg.payload
    newEl.innerHTML = msg.payload
    
    $(compToAdd.parent).appendChild(newEl)
})

```

Of course, this will be a custom function eventually so that you don't need to write any code at all 🕶

Here is the simple data you need to send from Node-RED:

```nohighlight
{
    "_ui": {
        // method can be add, remove, update or load
        "method": "add",

        // Optional. All components will be added to this in order. Ignored if component provides a parent.
        "parent": "html selector",
        
        // List of component instances to add to the page - results in 1 or more HTML custom elements being added.
        "components": [
            {
                // The reference name of the component (TBD: May need to be Class name rather than the element name. e.g. SyntaxHighlight rather than syntax-highlight)
                "type": "...",
                 // Optional. Overrides master parent.
                "parent": "html selector",
                // Optional. Each property will be applied to the element attributes
                "props": {
                    // ...
                }
            }

            // and others as desired. Each will be added in order.
        ]
    }
}

```

This will work on the current version of the uibuilderfe client as well of course if you adjust the code to use `onChange` instead of `onTopic` and use `document.querySelector(compToAdd.parent)` instead of `$(compToAdd.parent)`.

* * *

Hopefully, you can see that this opens the way to fully dynamic interfaces driven by data from Node-RED. I'm also going to make it possible to load a complete interface from a JSON file.

Adding an ability to dynamically load additional components is also coming. And of course, I still need to add a lot more code to cope with dynamic attributes, element removal and update.

But as a quick proof-of-concept, I'm really pleased with this.

Keep your eyes open for more.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [16 April 2022 14:45 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/2 "2022-04-16T14:45:35Z")

</div>

An update on the JSON spec based on further experimentation:

### load

```javascript
{
    "_ui": {
        "method": "load",
        "components": [
            "url1", "url2" // as needed
        ]
    }
}

```

## add

```javascript
{
    "_ui": {
        "method": "add",

        // Optional. All components will be added to this in order. Ignored if component provides a parent.
        "parent": "html selector",
        
        // List of component instances to add to the page - results in 1 or more HTML custom elements being added.
        "components": [
            {
                // The reference name of the component (TBD: May need to be Class name rather than the element name. e.g. SyntaxHighlight rather than syntax-highlight)
                "type": "...",
                // Optional. Overrides master parent. If no parent given here or in outer, will be added to <body> element
                "parent": "html selector",
                // HTML to add to slot - if not present, the contents of msg.payload will be used. 
                // This allows multi-components to have their own slot content
                "slot": "HTML to <i>add</i> to <sup>slot</sup> instead of <code>msg.payload</code>",
                // Optional. Each property will be applied to the element attributes
                "props": {
                    // Supplying this will make further updates or removals easier. MUST be unique for the page.
                    "id": "uniqueid"
                    // ... not recommended to include `onClick or similar event handlers, specify those in the events property below ...
                },
                "events": {
                    // Handler functions must already exist and be in a context reachable by the uibuilder library (e.g. window or index.js)
                    // If dynamically loading a script in the same msg, make sure it is specified first in the components list.
                    "click": "uibuilder.eventSend"
            }

            // and others as desired. Each will be added in order.
        ]
    }
}

```

## remove

```javascript
{
    "_ui": {
        "method": "remove",

        // List of component instances to remove from the page - use CSS Selector
        // - will remove the 1st match found so specify multiple times to remove more than one of same selector
        "components": [
            "selector1",
            "selector2"
            // and others as desired. Each will be removed in order.
        ]
    }
}

```

---

<div class="post-metadata">

**Author:** ![JayDickson](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/jaydickson/32/90592_2.png) [@JayDickson](https://discourse.nodered.org/u/JayDickson)\
**Post date:** [16 April 2022 17:44 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/3 "2022-04-16T17:44:43Z")

</div>

If I'm understanding you right, this would allow us to deliver a completely different page without a page refresh?

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [16 April 2022 22:15 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/4 "2022-04-16T22:15:19Z")

</div>

Yes indeed 😀

I can already add and remove elements on-page with just messages from Node-RED. I need to tackle element updates next. As mentioned, you actually don't need the new client to do this, you can do it all already using uibuilder v5 and just plain HTML/JavaScript.

Let me know if you are going to try though because it will prompt me to keep you up-to-date with the data schema which is obviously still evolving. Even this afternoon before going out for a family & friends meal, I expanded it to include event handling, both attributes and custom properties and got the removal function working. I also expanded the schema to allow nested components which means that you can specify a complete list or table using simple data. As in this example msg:

```javascript
{
    "payload": "This was dynamically added 😁",
    "_ui": {
        "method": "add",
        "parent": "#start",
        "components": [
            {
                "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"
                    }
                ]
            }
        ]
    },
    "topic": "addme"
}

```

---

<div class="post-metadata">

**Author:** ![bakman2](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/bakman2/32/6207_2.png) [@bakman2](https://discourse.nodered.org/u/bakman2)\
**Post date:** [17 April 2022 06:22 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/5 "2022-04-17T06:22:17Z")

</div>

Note that using `innerHTML` poses a security risk.

Per [mozilla](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML):

> **Warning:** If your project is one that will undergo any form of security review, using `innerHTML` most likely will result in your code being rejected.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [17 April 2022 10:41 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/6 "2022-04-17T10:41:56Z")

</div>

Indeed - but then wait till you see my use of `eval` in the code 😈

But all of the inputs need sanitising as with any user input. I will get to it 🙂

> When inserting user-supplied data you should always consider using [`Element.SetHTML()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML) instead, in order to sanitize the content before it is inserted

One thing that MDN is currently really letting themselves down on is pushing people towards features that are not yet supported or only supported by a few browsers. I got quite excited when I saw their recommendation but then realised that no browser actually supports that!

MDN are generally the definitive guide to JavaScript and the DOM but if they keep this up, they are going to end up being pretty useless.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [17 April 2022 10:43 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/7 "2022-04-17T10:43:24Z")

</div>

PS: If anyone things that they can help me find an alternative to the use of `eval`, please feel free to add an answer to my question on Stack Overflow.

> <https://stackoverflow.com/questions/71893727/how-to-bind-an-event-callback-passed-as-a-string-parameter/71894591#71894591>

---

<div class="post-metadata">

**Author:** ![bakman2](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/bakman2/32/6207_2.png) [@bakman2](https://discourse.nodered.org/u/bakman2)\
**Post date:** [17 April 2022 12:46 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/8 "2022-04-17T12:46:15Z")

</div>

The “proper” way is to create each element separately using the [createElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement)/setAttribute methods and attach it to the child. Once done attach it to the DOM, then attach a listener for javascript interaction.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [17 April 2022 13:28 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/9 "2022-04-17T13:28:02Z")

</div>

That only works if you know the structure that is going to be added. To use that when sending the slot data from Node-RED, the slot content can be HTML of any kind (well there are some limits for certain element types). To do as you suggest would require the input text to be parsed into elements, the elements parsed into attributes, everything checked and then reassembled. Or I would have to force the input to either be restricted to text only which is very limiting or I would have to force the input to be de-structured already, equally limiting, in fact probably more-so.

The better thing to do is to use a library to sanitise the input. As MDN shows, there is a proposal for a sanitisation API for HTML but it is just an experiment right now.

Instead, what I've already done, is add an option that looks to see if `window.DOMPurify` exists. If it does, my library will use it to sanitise the input. Simple and effective.

The truth is, however, that many aspects of Node-RED and its contributed nodes rarely do that much sanitisation of user input and there is an inherent assumption that you add sanitisation to your flows. And therefore, the risk in this case, is minimal. But can be eliminated altogether by including the [DOMPurify](https://github.com/cure53/DOMPurify) library.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [17 April 2022 21:41 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/10 "2022-04-17T21:41:27Z")

</div>

A simple teaser from the work I've been doing. Here you can see a mostly dynamically created page (on the right) with buttons that have onClick handlers attached and a list and a table as examples of multi-level components.

Out of interest, on the left is the output from the dev tools console. You will note the more colourful log output (as a result of me finally working out the best way to create a custom log feature that still reports the correct originating line number).

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/4/6/46b6973a745f3f6ae9bcd16f3f41bcbd4d340ac0.png)

Here is an example of the message data that generates this kind of dynamic UI.

```json
{
    "method": "add",
    "parent": "#start",
    "components": [
        {
            "type": "button",
            "attributes": {
                "id": "fred",
                "style": "margin:1em;",
                "name": "Freddy",
                "data-return": "wow!"
            },
            "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"
            },
            "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"
                }
            ]
        }
    ]
}

```

This example shows loading via multiple methods (in this case both `add` but could have been a mix of load, add, remove, update. It also demonstrates how you can make changes in your front-end code using a simple command.

```auto
uibuilder.set('msg', {
    _ui: [
        {
            "method": "add",
            "components": [
                {
                    "type": "ol",
                    "parent": "#start",
                    "slot": "An ordered list",
                    "attributes": {
                        "id": "ol2",
                        "style": "display:block;margin:1em;border:1px solid silver;"
                    },
                    "components": [
                        {
                            "type": "li",
                            "slot": "A list entry"
                        },
                        {
                            "type": "li",
                            "slot": "Another list entry"
                        }
                    ]
                }
            ]
        },
        {
            "method": "add",
            "components": [
                {
                    "type": "table",
                    "parent": "#start",
                    "attributes": {
                        "id": "t1",
                        "style": ""
                    },
                    "components": [
                        {
                            "type": "tr",
                            "components": [
                                {
                                    "type": "th",
                                    "slot": "Col 1"
                                },
                                {
                                    "type": "th",
                                    "slot": "Col 2"
                                },
                            ],
                        },
                        {
                            "type": "tr",
                            "components": [
                                {
                                    "type": "td",
                                    "slot": "Cell 1.1"
                                },
                                {
                                    "type": "td",
                                    "slot": "Cell 1.2"
                                },
                            ],
                        },
                        {
                            "type": "tr",
                            "components": [
                                {
                                    "type": "td",
                                    "slot": "Cell 2.1"
                                },
                                {
                                    "type": "td",
                                    "slot": "Cell 2.2"
                                },
                            ],
                        },
                        {
                            "type": "caption",
                            "slot": "A <b>simple</b> table example"
                        },
                    ]
                }
            ]
        },
    ],
    "topic": "addme2"
})

```

And finally, here is an example, again from the index.js front-end code, showing how to pre-load a UI dynamically from a file delivered by web server (in this case, uibuider's web server):

```auto
uibuilder.loadui('./myui.json')
uibuilder.start()

```

These all show manually generated UI descriptors of course but since this is just JSON data, dynamically creating the output from, lets say, a Node-RED flow, would be trivial.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [17 April 2022 21:42 UTC](https://discourse.nodered.org/t/announce-uibuilder-the-next-phase-part-1/61365/11 "2022-04-17T21:42:12Z")

</div>

And here is the documentation so far for the new client library, it includes the documentation for the dynamic UI features.

# Documentation for uibuilder.module.js

## How to use

This version of the library _has_ to be used as a module.

### The quick guide

In `index.html`:

```html
<!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="./index.css" media="all">

    <script type="module" async src="./index.js"></script> 

</head><body class="uib">
    
    <!-- Your custom HTML -->
    
</body></html>

```

In `index.js`

```javascript
import {uibuilder} from './uibuilder.module.js'
uibuilder.start()

// ... your custom code ...

window.onload = (ev) => {
    // Put code in here if you need to delay it until everything is really loaded and ready.
    // You probably won't need this most of the time.
}

```

### More information

Because the library has to be loaded as a module, it no longer needs an IIFE wrapper. Modules are already isolated. This has greatly simplified the code.

The library consists of a new class `Uib`. That class is auto-instanciated on load. If loading via a script tag, the `window.uibuilder` global is set. However, it is best to load from your own module code. In doing so, you have the option to load both the raw class as well as the `uibuilder` instance. `import {Uib, uibuilder} from './uibuilder.module.js'`

It also adds `window.$` as long as it doesn't already exist (e.g. if you already loaded jQuery). `$` is bound to `document.querySelector` which means that you can use it as a shortcut to easily reference HTML entities in a similar way to a very simplisting jQuery. e.g. `$('#button1').innerHTML = 'boo!'`.

!\> Please note that this version requires a browser supporting ES2019. This is probably only an issue if you are stuck on Internet Explorer or on a version of Apple Safari \<15.1.

Because you should ideally be loading uibuilder in your own module code. For example `<script type="module" async src="./index.js"></script>` in your `index.html` `head` section and then `import {Uib, uibuilder} from './uibuilder.module.js'` in your `index.js` file. You can now choose to use a different name for the uibuilder library if you wish. For example `import {uibuilder as uib} from './uibuilder.module.js'` will give you a `uib` object instead. Use as `uib.start()`, etc. However, you should note that, at present, the global `uibuilder` object is actually still loaded so make sure that you only use one or the other copy. This is because it does not appear to be possible to detect whether a module has been loaded from a script tag in HTML or from an import statement in JavaScript. Really, only in the former case should the global be set and while `window.uibuilder` is checked for to ensure that it isn't loaded again, when using an `import`, you are in a different module context.

In addition, you could do just `import {Uib} from './uibuilder.module.js'` and then do `const uibuilder = new Uib()`. Not sure why you might want to do that but it is possible anyway.

## What is not yet working

There are some features from the old `uibuilderfe.js` library that haven't (yet) made it into this new library. It should be noted that some may never come back as they may have been superceded.

- Ability to load JavaScript and CSS from a msg send from Node-RED.

- `Toast` - the ability to show a pop-over `toast` message.

- VueJS specific features.

## Features

### 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

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](http://Socket.IO)).

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:

```javascript
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`](https://developer.mozilla.org/en-US/docs/Web/API/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](https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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.

### Internal 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.

At present, this log function is not available to your own code.

### 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 two current events are (other events may be added later):

- `uibuilder:stdMsgReceived`
- `uibuilder:propertyChanged`

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

```javascript
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`.

### 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 two useful things:

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

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

### 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 😲

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}})`.

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:

```javascript
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:

```javascript
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! 😎

### setStore, getStore, removeStore functions

Stores & retrieves information in the browser's localStorage if allowed. localStorage will survive tab, window and browser as well as machine shutdowns. 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

```javascript
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.

```javascript
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 added as a collection object to `msg.payload`. All custom properties are added as a collection to `msg.props`.

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

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

```

In index.js

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

```

#### VueJS/bootstrap-vue example

In index.html

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

```

In index.js VueJS app `methods` section

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

```

### Dynamic, data-driven HTML content

This version of the uibuilder front-end library supports the dynamic manipulation of your web pages. This is achieved either by loading a JSON file describing the layout and/or by sending messages from Node-RED via a uibuilder node that contain a `msg._ui` property.

Please see the next section for details.

## Dynamic content details

Dynamic, data-driven UI manipulation is supported directly by this uibuilder front-end library. You can either control the UI via messages sent from Node-RED as shown in the next section, or you can also load a UI from a web URL that returns JSON content in a similar format.

You can also manipulate the UI from within your own front-end code by simulating the receipt of node-red messages (`uibuilder.set('msg', {_ui: [{ ... }]})`).

It is best practice to always include a method-level parent (`_ui[n].parent`) even if you want to attach everything to the `<body>` tag (CSS Selector `body`).

### Initial load from JSON URL

This is optional but may be useful to pre-populate the dynamic UI.

It is triggered using the command `uibuilder.loadui(<URL>)` where `<URL>` is the URL that will return JSON formatted content in the format described here.

`uibuilder.loadui` can run before `uibuilder.start`. It is best to run it as early as possible.

A common way to provide an initial UI would be to create an `index.json` file in the same folder as your `index.html` file. You can then use `uibuilder.loadui('./index.json')` to get your initial UI on the page. A possible alternative might be to use uibuilder's instance API feature to dynamically create an API URL that returns the JSON. More commonly though, if wanting to dynamically generate the initial layout, would be to use a Node-RED flow that is triggered by a uibuilder client connection control message.

It is best practice to try and always include `id` attributes at least on every top-level component. That will enable you to easily and safely

### Dynamic changes via messages from Node-RED (or local set)

The receipt from Node-RED or local setting (`uibuilder.set('msg', {_ui: { ... }})`) of a `msg` object containing a `msg._ui` property object will trigger the uibuilder front-end library to make changes to the web page if it can.

Note that `msg._ui` can be either an Object (which only allows a single method call in the msg) or it can be an Array (which allows multiple method calls in a single msg).

Each method object may contain any number of component descriptors. Component descriptors can contain any number of sub-component descriptors. There is no theoretical limit to the nesting, however expect things to break spectacularly if you try to take things to extremes. If top-level components have no parent defined, they will use the parent at the method level, if that isn't defined, everything will be added to the `<body>` tag and a warning is issued. Sub-components will always be added to the parent component.

All methods and components are processed in the order they appear in the message.

#### Available methods

```js
msg._ui.method = 'load' || 'add' || 'remove' || 'update'

```

- `load`: Load a new UI component using `import()` so that it can be used.
- `add`: Add a UI component instance to the web page dynamically.
- `remove`: Remove a UI component instance from the web page dynamically.
- `update`: Update the settings/data of a UI component instance on the web page.

Other future possibilities: `reset`

### Method: load

The load method allows you to dynamically load external modules.

!\> You cannot use this feature to load web components that you manually put into your index.html file. That is because they will load too late. Only use this where you will dynamically add a component to the page.

!\> Please note that, at present, only ECMA modules (that use `export` not `exports`) can be dynamically loaded since this feature is primarily aimed at loading [web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components). This feature requires browser support for [Dynamic Imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports).

?\> Dynamic Imports happen asynchronously. While this isn't usually a problem, the load does not wait to complete so very occasionally with a particularly complex component or on a particularly slow network, it is possible that the load will not complete before its use. In that case, simply delay the components use or move the load to earlier in the processing.

```jsonc
{
    "_ui": {
        "method": "load",
        "components": [
            "url1", "url2" // as needed
        ]
    }
}

```

#### Example showing load in your own index.js

Note how this can and usually should be done _before_ calling `uibuilder.start()`.

```javascript
uibuilder.set('msg', {
    _ui: {
            "method": "load",
            "components": [
                "../uibuilder/vendor/@totallyinformation/web-components/components/definition-list.js",
                "../uibuilder/vendor/@totallyinformation/web-components/components/data-list.js",
            ]
    }
})

uibuilder.start()

```

### Method: add

The `add` method will add one or more HTML elements (components) to the page. Components are loaded in order and a component may also have nested components (which in turn can also do so, ...).

Each component can:

- Be attached to a specified parent element selected via a [CSS Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) statement (e.g. `#myelementid`, `.myclass`, `li.myclass`, `div[attr|=value]`, etc).

- Have attributes set. Remember that HTML attributes can only contain string data.

- Have custom properties set. This can contain any data that can be passed via JSON.

- Have the slot content filled with text or HTML.

- May specify functions to be called for specific HTML events (e.g. on click, mouseover, etc).

#### Example msg format

```jsonc
{
    "_ui": {
        // REQUIRED
        "method": "add",

        // Optional. All components will be added to this in order. Ignored if component provides a parent.
        "parent": "html selector",
        
        // List of component instances to add to the page - results in 1 or more HTML custom elements being added.
        "components": [
            {
                // REQUIRED. The reference name of the component (TBD: May need to be Class name rather than the element name. e.g. SyntaxHighlight rather than syntax-highlight)
                "type": "...",
                
                // Optional. Overrides master parent. If no parent given here or in outer, will be added to <body> element
                "parent": "html selector",
                
                // Optional. HTML to add to slot - if not present, the contents of msg.payload will be used. 
                // This allows multi-components to have their own slot content. 
                // However, the payload is not passed on to sub-components
                "slot": "HTML to <i>add</i> to <sup>slot</sup> instead of <code>msg.payload</code>",
                
                // Optional. Each property will be applied to the element attributes
                "attributes": {
                    // Supplying this will make further updates or removals easier. MUST be unique for the page.
                    "id": "uniqueid"
                    // ... not recommended to include `onClick or similar event handlers, specify those in the events property below ...
                },

                // Optional. properties to be added to the element. Unlike attributes, these can contain any data.
                // Where used, will be added under a single <element>._ui property to help avoid name clashes.
                "properties": {
                    // ...
                },

                // Optional. DOM Events to be added to the element
                "events": {
                    // Handler functions must already exist and be in a context reachable by the uibuilder library (e.g. window)
                    // This means that functions defined in index.js, if loaded as a module, will NOT be usable.
                    // If dynamically loading a script in the same msg, make sure it is specified first in the components list.
                    // If defining in index.js when loaded as a module, add a single window.xxxx object containing all of your callback fns
                    // All callback functions are passed a single event argument but an undeclared `event` variable is also
                    // available inside the callback functions.
                    "click": "uibuilder.eventSend"
                    // "click": "window.myCallbacks.buttonClick1"
                }

                // Optional. You can also NEST components which allows you to easily create lists and tables
                // "components": [...]
            }

            // and others as desired. Each will be added in order.
        ]
    }
}

```

#### Example msgs for nested components

```jsonc
{
    "payload": "This was dynamically added 😁",
    "_ui": {
        "method": "add",
        "parent": "#start",
        "components": [
            {
                "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"
                    }
                ]
            }
        ]
    },
    "topic": "addme"
}

```

```jsonc
{
    "_ui": [
        {
            "method": "add",
            "components": [
                {
                    "type": "table",
                    "parent": "#start",
                    "attributes": {
                        "id": "t1"
                    },
                    "components": [
                        { // heading row
                            "type": "tr",
                            "components": [
                                { "type": "th", "slot": "Col 1" },
                                { "type": "th", "slot": "Col 2" },
                            ]
                        },
                        { // 1st data row
                            "type": "tr",
                            "components": [
                                { "type": "td", "slot": "Cell 1.1" },
                                { "type": "td", "slot": "Cell 1.2" },
                            ]
                        },
                        { // 2nd data row
                            "type": "tr",
                            "components": [
                                { "type": "td", "slot": "Cell 2.1" },
                                { "type": "td", "slot": "Cell 2.2" },
                            ]
                        },
                        { // a friendly caption heading
                            "type": "caption",
                            "slot": "A <b>simple</b> table example"
                        }
                    ]
                }
            ]
        }
    ]
}

```

### Method: remove

The remove method will remove the listed HTML elements from the page assuming they can be found. The search specifier as a [CSS Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) statement.

```jsonc
{
    "_ui": {
        "method": "remove",

        // List of component instances to remove from the page - use CSS Selector
        // - will remove the 1st match found so specify multiple times to remove more than one of same selector
        "components": [
            "selector1",
            "selector2"
            // and others as desired. Each will be removed in order.
        ]
    }
}

```

### Method: update

TBC
