State Management
Dashboard 2.0 provides a data store within Node-RED such that it's possible to refresh your Dashboard clients and data is retained. This is particularly useful for widgets like ui-chart
where you may want to retain a history of data points, or for widgets like ui-text
where you want to retain the last value displayed.
This page details the different "stores" we have in place and what they're used for.
You can also check out the Events Architecture for a more detailed look at when these stores are used and how they interact with the rest of the Dashboard.
Client-Side (Dashboard)
An image depicting the three client-side vuex stores we have in Dashboard 2.0
Our client-side stores are built using VueX. These stores lose their data on a client refresh (but are re-populated by the server-side stores), and are just used to maintain a centralised, consistent view of the data across the entire Vue application as the user navigates around the Dashboard.
setup
store
This just stores the response from our initial /_setup
request. This object, in core, contains the SocketIO configuration to help the client connect to the server.
It is also possible for plugins to append to this object (see Adding Plugins) additional data that can be useful across the application.
ui
store
This store is where we store the full ui-config that details all of the pages, themes, groups and widgets to render on a Dashboard.
data
store
The client-side datastore is a map of widget id's to either:
- The last
msg
received by the widget - An array of
msg
objects, representing all knownmsg
objects received by the widget
In most cases, a widget only needs reference to the last message. In some cases, e.g. ui-chart
, the full history is required in order to render a history of data.
When a widget is first loaded, we emit a widget-load
event, which in the default onLoad
handler, will attempt to retrieve the last message received by the widget from the server-side datastore, and store it in the client-side data
store. You can read more about this in Events Architecture.
It is possible for a widget to access the mapped msg
object using:
<template>
<pre>this.messages[this.id]</pre>
</template>
<script>
export default {
computed: {
...mapState('data', ['messages'])
}
}
</script>
An example Widget.vue file that uses the data
store to access the last message received by the widget
This value is also updated automatically when a new message is received, as long as that widget is using the default handlers, again detailed in Events Architecture.
Server-Side (Node-RED)
An image depicting the two server-side stores we have in Dashboard 2.0
Our server-side stores maintain the "single source of truth". When any Dashboard client connects, the centralised data is sent to each client, and the client-side stores are populated with the relevant parts of this centralised store.
In our server-side architecture, we use two standalone stores:
datastore
: A map of each widget to the latestmsg
received by a respective node in the Editor.statestore
: A store for all dynamic properties set on widgets (e.g. visibility or setting a property at runtime). Often, these values are overrides of the base configuration found in thedatastore
.
Each time a function server-side wants to write into these stores, a check is done to ensure that any provided messages are permitted to be stored. An example of where this would get blocked is if msg._client.socketid
is specified and the relevant node type is setup to listen to socket constraints (by default, this is ui-control
and ui-notification
). In this case, we do not want to store that data in our centralised store since it's not relevant to all users of the Dashboard.
Importing Stores
Stores are imported into a node's .js
file with:
const store = require('<path>/<to>/store.js')
Data Store
The server-side datastore
is a centralised store for all messages received by widgets in the Editor. It is a simple key-value store, where the key is the widget's id, and the value is the message received by the widget. In some cases, e.g. ui-chart
instead of recording just the latest msg
received, we actually store a history.
datastore.save
When a widget receives a message, the default node.on('input')
handler will store the received message, mapped to the widget's id into the datastore using:
datastore.save(base, node, msg)
base
: Theui_base
node that the store is attached tonode
: The Node-RED node object we're storing state formsg
: The message that was received by the node
This will store the latest message received by the widget, which can be retrieved by that same widget on load using:
datastore.get
When a widget is initialised, it will attempt to retrieve the latest message from the datastore using:
datastore.get(node.id)
This ensures, on refresh of the client, or when new clients connect after data has been generated, that the state is presented consistently.
datastore.append
With .append
, we can store multiple messages against the same widget, representing a history of state, rather than a single point reference to the last value only.
datastore.append(base, node, msg)
base
: Theui_base
node that the store is attached tonode
: The Node-RED node object we're storing state formsg
: The message that was received by the node
This is used in ui-chart
to store the history of data points, where each data point could have been an individual message received by the widget.
datastore.clear
When a widget is removed from the Editor, we can clear the datastore of any messages stored against that widget using:
datastore.clear(node.id)
This ensures that we don't have any stale data in the datastore, and that we don't have any data stored against widgets that no longer exist in the Editor.
State Store
The statestore
is a centralised store for all dynamic properties set against widgets in the Editor. Dynamic Properties can be set through sending msg.<property>
payloads to a given node, e.g. for ui-dropdown
, we can send msg.options
to override the "Options" property at runtime.
At the top-level it is key-mapped to the Widget ID's, then each widget has a map, where each key is the property name, mapping to the value.
statestore.getAll
For a given widget ID, return all dynamic properties that have been set.
statestore.getAll(node.id)
statestore.getProperty
For a given widget ID, return the value of a particular property.
statestore.getProperty(node.id, property)
statestore.set
Given a widget ID and property, store the associated value in the appropriate mapping
statestore.set(base, node, msg, property, value)
base
: Theui_base
node that the store is attached tonode
: The Node-RED node object we're storing state formsg
: The message that was received by the nodeproperty
: The property name to storevalue
: The value to store against the property
statestore.reset
Remove all dynamic properties for a given Widget/Node.
statestore.reset(node.id)