Skip to content

Adding New Core Widgets

A single widget consist of two key parts:

  1. A Node-RED node that will appear in the palette of the Node-RED Editor
  2. .vue and client-side code that renders the widget into a dashboard

You can explore our collection of core widgets here.

We are always open to Pull Requests and new ideas on widgets that can be added to the core Dashboard repository.

When adding a new widget to the core collection, you will need to follow the steps below to ensure that the widget is available in the Node-RED editor and renders correctly in the UI.

Checklist

When adding a new widget to Dashboard 2.0, you'll need to ensure that the following steps have been followed for that new widget to be recognised and included in a Dashboard 2.0 build:

  1. In /nodes/:
    • Add <widget>.html
    • Add <widget>.js
    • Add the in the node-red/nodes section in package.json
  2. In /ui/:
    • Add widgets/<widget>/<widget>.vue
    • Add widget to the index.js file in /ui/widgets

Example <widget.vue>

vue
<template>
    <div @click="onAction">
        {{ id }}
    </div>
</template>

<script>
    import { useDataTracker } from '../data-tracker.js'
    import { mapState } from 'vuex'

    export default {
        name: 'DBUIWidget',
        // we need to inject $socket so that we can send events to Node-RED
        inject: ['$socket'],
        props: {
            id: String,    // the id of the widget, as defined by Node-RED
            props: Object, // the properties for this widget defined in the Node-RED editor
            state: Object  // the state of this widget, e.g. enabled, visible
        },
        computed: {
            // map our data store such that we can get any data bound to this widget
            // received on input from Node-RED
            ...mapState('data', ['messages']), // provides access to `this.messages` where `this.messages[this.id]` is the stored msg for this widget
        },
        setup (props) {
            // Use our data-tracker, which setups up the basic event handling for us
            // including `on('msg-input')` and `on('widget-load')`
            useDataTracker(props.id)
        },
        methods: {
            onAction () {
                // we can send any data we need Node-RED through this (optional) message parameter
                const msg = {
                    payload: 'hello world'
                }
                // send an event to Node-RED to inform it that we've clicked this widget
                this.$socket.emit('widget-action', this.id, msg)
            }
        }
    }
</script>
  
<style scoped>
</style>

Data Tracker

The data tracker is a set of utility functions that help setup the standard event handlers for a core widget. It will setup the following events:

  • on('widget-load') - to handle any initial data that is sent to the widget when it is loaded
  • on('msg-input') - to handle any incoming data from Node-RED

It also provides flexibility to define custom event handlers for the widget if there is bespoke functionality required for a given node, for example in a ui-chart node, we have a collection of custom logic that handles the merging of data points and the rendering of the chart when a message is received.

The inputs for the useDataTracker (widgetId, onInput, onLoad, onDynamicProperties) function are used as follows:

  • widgetId - the unique ID of the widget
  • onInput - a function that will be called when a message is received from Node-RED through the on(msg-input) socket handler
  • onLoad - a function that will be called when the widget is loaded, and triggered by the widget-load event
  • onDynamicProperties - a function called as part of the on(msg-input) event, and is triggered before the default onInput function. This is a good entry point to check against any properties that have been included in the msg in order to set a dynamic property.

Dynamic Properties

Node-RED allows for definition of the underlying configuration for a node. For example, a ui-button would have properties such as label, color, icon, etc. It is often desired to have these properties be dynamic, meaning that they can be changed at runtime. Users expect to be able to control these generally by passing in msg.<property-name> to the node, which in turn, should update the property.

Design Pattern

This section will outline the architectural design pattern for developing dynamic properties into a widget.

Server-side, dynamic properties are stored in our state store, which is a mapping of the widget ID to the dynamic properties assigned to that widget. This is done so that we can ensure separation of the dynamic properties for a widget from the initial configuration defined, and stored, in Node-RED.

Before the ui-base node emits the ui-config, we merge the dynamic properties with the initial configuration, with the dynamic properties permitted to override the underlying configuration. As such, when the client receives a ui-config message, it will have the most up-to-date configuration for the widget, the merging of both static and dynamic properties.

Setting Dynamic Properties

Server-Side

In order to set a dynamic property in the server-side state store we can utilise the beforeSend event on the node. This event is triggered on any occasion that the server-side node is about to send a message to the client, including when a new input is received into a given node.

For this, we make the most of the state store's set function:

js
/**
    *
    * @param {*} base  - associated ui-base node
    * @param {*} node  - the Node-RED node object we're storing state for
    * @param {*} msg   - the full received msg (allows us to check for credentials/socketid constraints)
    * @param {*} prop  - the property we are setting on the node
    * @param {*} value - the value we are setting
*/
set (base, node, msg, prop, value) {
    if (canSaveInStore(base, node, msg)) {
        if (!state[node.id]) {
            state[node.id] = {}
        }
        state[node.id][prop] = value
    }
},

For example, in ui-dropdown:

javascript
const evts = {
    onChange: true,
    beforeSend: function (msg) {
        if (msg.options) {
            // dynamically set "options" property
            statestore.set(group.getBase(), node, msg, 'options', msg.options)
        }
        return msg
    }
}

// inform the dashboard  UI that we are adding this node
group.register(node, config, evts)

Client Side

Now that we have the server-side state updating, anytime we refresh, the full ui-config will already contain the dynamic properties.

We then need to ensure that the client is aware of these dynamic properties as they change. To do this, we can use the onDynamicProperties event available in the data tracker.

Updating Documentation

There are two important places to ensure documentation is updated when adding dynamic properties:

Online Documentation:

Each node will have a corresponding /docs/nodes/widgets/<node>.md file which allows for the definition of dynamic` table in the frontmatter, e.g:

yaml
dynamic:
    Options:
        payload: msg.options
        structure: ["Array<String>", "Array<{value: String}>", "Array<{value: String, label: String}>"]
    Class:
        payload: msg.class
        structure: ["String"]

You can then render this table into the documentation with:

md
## Dynamic Properties

<DynamicPropsTable/>

Editor Documentation:

Each node will have a corresponding /locales/<locale>/<node>.html file which should include a table of dynamic properties, e.g:

html
<h3>Dynamic Properties (Inputs)</h3>
<p>Any of the following can be appended to a <code>msg.</code> in order to override or set properties on this node at runtime.</p>
<dl class="message-properties">
    <dt class="optional">options <span class="property-type">array</span></dt>
    <dd>
        Change the options available in the dropdown at runtime
        <ul>
            <li><code>Array&lt;string&gt;</code></li>
            <li><code>Array&lt;{value: String}&gt;</code></li>
            <li><code>Array&lt;{value: String, label: String}&gt;</code></li>
        </ul>
    </dd>
    <dt class="optional">class <span class="property-type">string</span></dt>
    <dd>Add a CSS class, or more, to the Button at runtime.</dd>
</dl>

Debugging Dynamic Properties

Dashboard 2.0 comes with as Debug View that includes a specialist panel to monitor any dynamic properties assigned to a widget. This can be a very useful tool when checking whether the client is aware of any dynamic properties that have been sent.