-
-
Notifications
You must be signed in to change notification settings - Fork 88
Basic uibuilder RIOT example displaying values, switch and select box
Here is an example using RIOT with uibuilder to show values from MQTT (or elsewhere) and send on messages from a switch and select box.
In the dependencies section of ~/.node-red/package.json
add (along with whatever you normally have):
"dependencies": {
"node-red-contrib-uibuilder": "*",
"riot": "^3.7.3"
}
In the module.exports part of your settings.js
file, add this:
uibuilder: {
userVendorPackages: ['riot'],
debug: false
}
In ~/.node-red
run
npm install node-red-contrib-uibuilder riot
Restart node-red.
Copy the flow below and import it into a tab in node-red, and deploy.
Browse to <ip>:1880/home
you should see the default uibuilder welcome page, and deploying it should have created a ~/.node-red/uibuilder/home
folder, and in the src subfolder, it should have created index.css
, index.html
, index.js
and manifest.json
.
If you want a url other than home
then change it in the uibuilder node and re-deploy.
Flow:
[{"id":"7be939b5.af86c8","type":"uibuilder","z":"251597c3.8396e8","name":"","topic":"","url":"home","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"debugFE":false,"x":310.5,"y":163,"wires":[["dbee3905.275c7"]]},{"id":"c3d15ed.7ce93a","type":"debug","z":"251597c3.8396e8","name":"","active":true,"console":"false","complete":"payload","x":662,"y":76,"wires":[]},{"id":"e52b1aa4.1090e8","type":"inject","z":"251597c3.8396e8","name":"topic/one On","topic":"topic/one","payload":"On","payloadType":"str","repeat":"","crontab":"","once":false,"x":397,"y":354,"wires":[["738e30b9.71b4e"]]},{"id":"b42be742.a8c12","type":"inject","z":"251597c3.8396e8","name":"topic/one Off","topic":"topic/one","payload":"Off","payloadType":"str","repeat":"","crontab":"","once":false,"x":401,"y":385,"wires":[["738e30b9.71b4e"]]},{"id":"dbee3905.275c7","type":"switch","z":"251597c3.8396e8","name":"","property":"command","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","outputs":2,"x":448.5,"y":163,"wires":[["738e30b9.71b4e","c3d15ed.7ce93a"],["4f0391b0.b04868"]]},{"id":"4f0391b0.b04868","type":"function","z":"251597c3.8396e8","name":"Topic repeater","func":"// Given messages containing topic/payload values this saves the latest payload for each topic\n// and passes the message on.\n// If it receives a message with msg.command set to 'reload' it retransmits each saved pair as\n// individual messages.\n// If it receives a message with msg.command set to 'reset' it removes all saved data\nif (msg.command && msg.command === 'reload') {\n // a reload message so re-send all messages saved\n var keys = context.keys();\n for (var i = 0; i < keys.length; i++) {\n node.send({topic: keys[i], payload: context.get(keys[i])});\n }\n msg = null;\n} else if (msg.command && msg.command === 'reset') {\n // a reset command so remove all saved messages\n var keys = context.keys();\n for (var i = 0; i < keys.length; i++) {\n context.set(keys[i], undefined);\n }\n msg= null;\n} else {\n // a normal message so add/update the payload for this topic, provided there is a topic\n if (msg.topic) {\n context.set(msg.topic, msg.payload);\n }\n}\n// pass on the message unless it has been nulled\nreturn msg;\n","outputs":"1","noerr":0,"x":338.5,"y":265,"wires":[["7be939b5.af86c8"]]},{"id":"1bceb41a.73e694","type":"inject","z":"251597c3.8396e8","name":"topic/two 123.456","topic":"topic/two","payload":"123.456","payloadType":"num","repeat":"","crontab":"","once":false,"x":419,"y":440,"wires":[["738e30b9.71b4e"]]},{"id":"faab15e6.f82ec8","type":"inject","z":"251597c3.8396e8","name":"topic/two -73.5","topic":"topic/two","payload":"-73.5","payloadType":"num","repeat":"","crontab":"","once":false,"x":409,"y":472,"wires":[["738e30b9.71b4e"]]},{"id":"1c8cf52.85b7a0b","type":"link in","z":"251597c3.8396e8","name":"Simulated MQTT In","links":["738e30b9.71b4e"],"x":83.5,"y":264,"wires":[["4f0391b0.b04868"]]},{"id":"738e30b9.71b4e","type":"link out","z":"251597c3.8396e8","name":"Simulated MQTT Out","links":["1c8cf52.85b7a0b"],"x":737.5,"y":136,"wires":[]},{"id":"a4c7955b.911278","type":"comment","z":"251597c3.8396e8","name":"Simulated MQTT Out","info":"","x":664.5,"y":184,"wires":[]},{"id":"1911ff6b.e20249","type":"comment","z":"251597c3.8396e8","name":"Simulated MQTT In","info":"","x":95,"y":209,"wires":[]}]
Replace index.html with the following:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<!-- See https://goo.gl/OOhYW5 -->
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#3f51b5">
<!-- Used if adding to homescreen for Chrome on Android. Fallback for manifest.json -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="Node-RED UI Builder">
<!-- Used if adding to homescreen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Node-RED UI Builder">
<!-- Homescreen icons for Apple mobile use if required
<link rel="apple-touch-icon" href="/images/manifest/icon-48x48.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/manifest/icon-72x72.png">
<link rel="apple-touch-icon" sizes="96x96" href="/images/manifest/icon-96x96.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/manifest/icon-144x144.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/manifest/icon-192x192.png">
-->
<title>Node-RED UI Builder</title>
<meta name="description" content="Node-RED UI Builder">
<link rel="icon" href="images/node-blue.ico">
<!-- OPTIONAL: Normalize is used to make things the same across browsers. Index is for your styles -->
<link rel="stylesheet" href="vendor/normalize.css/normalize.css">
<link rel="stylesheet" href="index.css">
</head>
<body>
<!-- the riot application tag contains everything -->
<application></application>
<!-- Socket.IO is loaded only once for all instances -->
<script src="/uibuilder/socket.io/socket.io.js"></script>
<!-- include riot.js and the compiler Note no leading / -->
<script type="text/javascript" src="vendor/riot/riot+compiler.min.js"></script>
<script src="uibuilderfe.min.js"></script>
<script src="index.js"></script>
<!-- application and other tags are specified in external files -->
<script data-src="dashboard-tags.tag" type="riot/tag"></script>
<script data-src="application-tags.tag" type="riot/tag"></script>
</body>
</html>
Replace index.js with:
/*global document,window,uibuilder */
/**
* uibuilder: The main global object containing the following...
* Methods:
* .onChange(attribute, callbackFn) - listen for changes to attribute and execute callback when it changes
* .get(attribute) - Get any available attribute
* .set(attribute, value) - Set any available attribute (can't overwrite internal attributes)
* .msg - Shortcut to get the latest value of msg. Equivalent to uibuilder.get('msg')
* .send(msg) - Shortcut to send a msg back to Node-RED manually
* .debug(true/false) - Turn on/off debugging
* .uiDebug(type,msg) - Utility function: Send debug msg to console (type=[log,info,warn,error,dir])
* Attributes with change events (only accessible via .get method except for msg)
* .msg - Copy of the last msg sent from Node-RED over Socket.IO
* .sentMsg - Copy of the last msg sent by us to Node-RED
* .ctrlMsg - Copy of the last control msg received by us from Node-RED (Types: ['shutdown','server connected'])
* .msgsReceived - How many standard messages have we received
* .msgsSent - How many messages have we sent
* .msgsCtrl - How many control messages have we received
* .ioConnected - Is Socket.IO connected right now? (true/false)
* Attributes without change events
* (only accessible via .get method, reload page to get changes, don't change unless you know what you are doing)
* .debug - true/false, controls debug console logging output
* ---- You are not likely to need any of these ----
* .version - check the current version of the uibuilder code
* .ioChannels - List of the channel names in use [uiBuilderControl, uiBuilderClient, uiBuilder]
* .retryMs - starting retry ms period for manual socket reconnections workaround
* .retryFactor - starting delay factor for subsequent reconnect attempts
* .ioNamespace - Get the namespace from the current URL
* .ioPath - make sure client uses Socket.IO version from the uibuilder module (using path)
* .ioTransport - ['polling', 'websocket']
*/
// this is needed if you want to bundle everything up with webpack
if (typeof require != "undefined") {
var uibuilder = require('node-red-contrib-uibuilder/nodes/src/uibuilderfe.js')
var riot = require('riot')
require("./index.css")
require("./application-tags.tag")
}
// wait for the document to be loaded
window.onload = function() {
// mount all the tags
riot.mount('*')
// Catch new messages from node red
uibuilder.onChange('msg', function(newMsg){
// all we need to do is tell riot to update all the data
riot.update()
})
// Catch Socket.IO connect/disconnect events, connected will be true or false
uibuilder.onChange('ioConnected', function(connected){
if (connected) {
// Send a reload message to indicate a browser window
// has opened so all the topics will be reloaded.
// Since we are using riot we also have to do that when all the tags
// have been mounted, in the top level tag on mount (application-tags.tag)
// as here may be too early if it is the initial connect. It won't matter
// that this is done twice
uibuilder.send({command: 'reload', payload: 'Browser connected'})
}
riot.update()
})
}
Create the file application-tags.tag
which contains the application:
if (typeof require != "undefined") {
var uibuilder = require('node-red-contrib-uibuilder/nodes/src/uibuilderfe.js')
require("./dashboard-tags.tag")
}
// define the tag that contains everything
<application>
<div id="app">
<h4>{this.ioConnected}</h4>
<!-- a text value driven by a topic -->
<p>Text value, topic/one: <dblt-value topic="topic/one"></dblt-value></p>
<!-- a numeric value driven by a topic, displayed to two decimal places -->
<p>Numeric value, topic/two: <dblt-value topic="topic/two" dp="2"></dblt-value></p>
<!-- a switch with current value -->
<p>Switch, current value: <dblt-value topic="topic/three"></dblt-value> <dblt-switch topic="topic/three"></dblt-switch></p>
<!-- a dropdown selection, first define the values and text -->
<script>
this.mode_selections = [
{value: '0', text: 'Off'},
{value: '1', text: 'Intermittent'},
{value: '2', text: 'On'}
]
</script>
<div>Mode: <dblt-select topic = "topic/four" selections = {mode_selections}></dblt-select></div>
</div>
<script>
// this is done when riot.update() is called, we just have to refresh any data that
// is shown by the application tag
this.on('update', function() {
this.ioConnected = uibuilder.get("ioConnected") ? "Connected" : "Disconnected"
})
// include this in just the top level tag of the application
this.on('mount', function() {
// The application tab is mounted, so all its children should be, send a reload
// message to get node-red to send us the latest values
uibuilder.send({command: 'reload', payload: 'Browser connected'})
this.ioConnected = uibuilder.get("ioConnected") ? "Connected" : "Disconnected"
this.update()
})
</script>
</application>
And finally, dashboard-tags.tag
which contains the definition of the low level objects:
if (typeof require != "undefined") {
var uibuilder = require('node-red-contrib-uibuilder/nodes/src/uibuilderfe.js')
}
<!--
Given a topic this displays the current payload value for that topic
If dp is provided then the value is coerced to a number and then formatted with
the specified number of decimal places
-->
<dblt-value>
<span>{this.payload || "."}</span>
<script>
this.on('update', function() {
var msg = uibuilder.get("msg")
if (msg.topic == opts.topic) {
this.payload = opts.dp ? Number(msg.payload).toFixed(opts.dp) : msg.payload
}
})
</script>
</dblt-value>
<!--
Given a topic and an array of selections this displays a select box with the
text for the value passed in the payload as the current selection. If the user
changes the selection that the value is sent in a message with the topic.
If the extras option is provided then the sent message will containg the property extras
with the value given. This can be useful, for example, for specifying alternative MQTT servers.
The selections array can be setup and the tag invoked using something like
<script>
this.mode_selections = [
{value: '0', text: 'Off'},
{value: '1', text: 'Intermittent'},
{value: '2', text: 'On'}
]
</script>
<dblt-select topic = "some/topic" selections = {mode_selections}></dblt-select>
-->
<dblt-select>
<select value={selected} onchange={ selection_changed } >
<option each={ item in opts.selections } value={item.value}>{item.text}</option>
</select>
<script>
this.selected = ''
this.on('update', function() {
var msg = uibuilder.get("msg")
if (msg.topic == opts.topic) {
this.selected = msg.payload
}
})
this.selection_changed = function(e) {
this.selected = e.target.value
uibuilder.send({topic: opts.topic, payload: e.target.value, extras: opts.extras})
}
</script>
</dblt-select>
<!--
Given a topic this displays a switch showing the state from the payload value for that topic
The default expected is "On" and "Off" payload values.
If the switch is clicked then a message is sent with the specified topic and the payload
containing the new state.
Options on and off may be provided to provide alternative on/off strings
If the extras option is provided then the sent message will containg the property extras
with the value given. This can be useful, for example, for specifying alternative MQTT servers.
-->
<dblt-switch>
<label class="switch">
<input type="checkbox" checked="{this.checked}" onclick={switch_clicked}>
<span class="slider round"></span>
</input>
</label>
<script>
this.on_text = opts.on || "On"
this.off_text = opts.off || "Off"
this.on('update', function() {
var msg = uibuilder.get("msg")
if (msg.topic == opts.topic) {
this.checked = msg.payload === this.on_text
}
})
this.switch_clicked = function(e) {
this.checked = !this.checked
uibuilder.send({topic: opts.topic, payload: (this.checked ? this.on_text : this.off_text), extras: opts.extras})
}
</script>
<style>
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
}
/* Hide default HTML checkbox */
.switch input {display:none;}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 15px;
width: 15px;
left: 3px;
bottom: 3px;
background-color: white;
-webkit-transition: .2s;
transition: .2s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(15px);
-ms-transform: translateX(15px);
transform: translateX(15px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
</style>
</dblt-switch>
Refresh the browser you should be up and running.
I have been very surprised how much can be achieved with so little code, with uibuilder (thanks to Julian Knight) and riot doing all the hard work in the background.
One issue that had to be overcome is that when the browser is refreshed or initially connected (or when another browser is connected) that there is no history so the initial states of all the controls will be unknown. To overcome this, in index.js
when the socket connection is made it sends a message with msg.command set
. In the flow this is sensed by the switch node and is sent on to the Topic repeater function node. This node is designed to remember the latest payload values for all topics sent to it. When it sees the reload command it re-sends a message for each topic, which goes to the uibuilder node and populates the controls with the latest values. There is one final twist to this in that it is possible for the connection to be made before riot has fully instantiated all the tags, in which case the messages may arrive too early. To cope with this, the reload message is also sent from the on mount
handler in the top level tag (the application tag). On mount is called for the top level tag after all child tags have been instantiated so at that time all controls will be populated. I decided to include both calls as, if successful, the first call will populate them quicker and the fact that the messages will be sent twice is of little, if any, consequence.
If anyone who knows more about css than I do would like to provide the css for a fancier looking switch then please do.
This covers some experiments that allow tags to be added dynamically from the node-red flow by sending messages to the uibuilder node. This is not adding new types of tags, just adding instances of existing tags to the page at run time.
Edit 26 Oct 17 Added the ability to remove the tag from a placeholder, and also introduced a technique that populates the tag with the current value (if any) when it is added. The placeholder tag and flow below have been updated. The changes to the flow are in the Topic Repeater function node.
The feature is enabled using the placeholder node type, so the first thing to do is to add this to the file dashboard-tags.tag
.
<!--
This is a placeholder tag within which a tag of any type may be instantiated
at run time by sending the uibuilder node a message.
Configure the placeholder tag with a topic that the message will have and
in the message set that topic and the payload to and object containing the
key "tagtype" set to the type of tag to be instantiated. Any other attributes
in the payload will be passed on to the instantiated type.
For example include in the application
<placeholder topic="ph001"></placeholder>
then send it a message
{topic: "ph001", payload: {tagtype: "dblt-value", topic: "topic/one"}}
If another message with topic ph001 is passed to the uibuilder node then
the existing dynamic tag will be removed and a new one created as defined by
the new message.
To remove the tag from the placeholder pass tagtype as an empty string, or tagtype
not present.
-->
<placeholder>
<script>
var mounted = false
var tag = null
this.on('update', function() {
var msg = uibuilder.get("msg")
if (msg.topic == opts.topic) {
// if this is a reload operation, so as a result of browser refresh or possibly as a result of this or
// another tag being mounted by code below, then ignore it unless we are not currently mounted
if (msg.reloading && this.mounted) {
// this is a reloading message and we are mounted so ignore it
// did the logic this way as it is much easier to understand
} else {
if (this.mounted) {
this.root.removeChild(this.root.childNodes[0])
this.mounted = false
}
var payload = msg.payload
// if tagtype is empty string or not present then leave the placeholder empty
if (payload.tagtype) {
tag = document.createElement(payload.tagtype)
for (var k in payload) {
if (payload.hasOwnProperty(k)) {
if (payload[k] !== "tagtype") {
tag.setAttribute(k, payload[k])
}
}
}
this.root.appendChild(tag)
riot.mount(tag, msg.payload.tagtype)
this.mounted = true
uibuilder.send({command: 'reload', payload: 'Refreshing ' + opts.topic})
}
}
}
})
</script>
</placeholder>
The comments at the front explain (hopefully) reasonable well how to use it. To demonstrate its use add this to application-tags.tag
before the closure of the app div.
<p>Placeholder ph001:</p>
<placeholder topic="ph001"></placeholder>
<p>Placeholder ph002:</p>
<placeholder topic="ph002"></placeholder>
and use this flow
[{"id":"c9355873.32d038","type":"inject","z":"251597c3.8396e8","name":"ph001 clear","topic":"ph001","payload":"{\"tagtype\":\"\"}","payloadType":"json","repeat":"","crontab":"","once":false,"x":375,"y":562,"wires":[["738e30b9.71b4e"]]},{"id":"738e30b9.71b4e","type":"link out","z":"251597c3.8396e8","name":"Simulated MQTT Out","links":["1c8cf52.85b7a0b"],"x":737.5,"y":136,"wires":[]},{"id":"e52b1aa4.1090e8","type":"inject","z":"251597c3.8396e8","name":"topic/one On","topic":"topic/one","payload":"On","payloadType":"str","repeat":"3","crontab":"","once":false,"x":401,"y":309,"wires":[["738e30b9.71b4e"]]},{"id":"b42be742.a8c12","type":"inject","z":"251597c3.8396e8","name":"topic/one Off","topic":"topic/one","payload":"Off","payloadType":"str","repeat":"5","crontab":"","once":false,"x":405,"y":340,"wires":[["738e30b9.71b4e"]]},{"id":"dbee3905.275c7","type":"switch","z":"251597c3.8396e8","name":"","property":"command","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","outputs":2,"x":448.5,"y":163,"wires":[["738e30b9.71b4e"],["4f0391b0.b04868","c3d15ed.7ce93a"]]},{"id":"1bceb41a.73e694","type":"inject","z":"251597c3.8396e8","name":"topic/two 123.456","topic":"topic/two","payload":"123.456","payloadType":"num","repeat":"","crontab":"","once":false,"x":413,"y":395,"wires":[["738e30b9.71b4e"]]},{"id":"faab15e6.f82ec8","type":"inject","z":"251597c3.8396e8","name":"topic/two -73.5","topic":"topic/two","payload":"-73.5","payloadType":"num","repeat":"","crontab":"","once":false,"x":403,"y":427,"wires":[["738e30b9.71b4e"]]},{"id":"e35e46f3.62c5d8","type":"inject","z":"251597c3.8396e8","name":"ph001, dblt-value, topic/one","topic":"ph001","payload":"{\"tagtype\":\"dblt-value\",\"topic\":\"topic/one\"}","payloadType":"json","repeat":"","crontab":"","once":false,"x":426,"y":489,"wires":[["738e30b9.71b4e"]]},{"id":"d2fc81e1.c67a4","type":"inject","z":"251597c3.8396e8","name":"ph001, dblt-switch, topic/one","topic":"ph001","payload":"{\"tagtype\":\"dblt-switch\",\"topic\":\"topic/one\"}","payloadType":"json","repeat":"","crontab":"","once":false,"x":428,"y":521,"wires":[["738e30b9.71b4e"]]},{"id":"a425e584.4a747","type":"inject","z":"251597c3.8396e8","name":"ph002, dblt-value, topic/two","topic":"ph002","payload":"{\"tagtype\":\"dblt-value\",\"topic\":\"topic/two\",\"extras\":\"somestring\"}","payloadType":"json","repeat":"","crontab":"","once":false,"x":710,"y":485,"wires":[["738e30b9.71b4e"]]},{"id":"7be939b5.af86c8","type":"uibuilder","z":"251597c3.8396e8","name":"","topic":"","url":"home","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"debugFE":false,"x":310.5,"y":163,"wires":[["dbee3905.275c7"]]},{"id":"4f0391b0.b04868","type":"function","z":"251597c3.8396e8","name":"Topic repeater","func":"// Given messages containing topic/payload values this saves the latest payload for each topic\n// and passes the message on.\n// If it receives a message with msg.command set to 'reload' it retransmits each saved pair as\n// individual messages, except that if msg.exclude_topics is provided then it should contain\n// an array of topic strings and those topics will not be resent\n// If it receives a message with msg.command set to 'reset' it removes all saved data\nif (msg.command && msg.command === 'reload') {\n // a reload message so re-send all messages saved\n var keys = context.keys();\n for (var i = 0; i < keys.length; i++) {\n node.send({topic: keys[i], payload: context.get(keys[i]), reloading: true});\n }\n msg = null;\n} else if (msg.command && msg.command === 'reset') {\n // a reset command so remove all saved messages\n var keys = context.keys();\n for (var i = 0; i < keys.length; i++) {\n context.set(keys[i], undefined);\n }\n msg= null;\n} else {\n // a normal message so add/update the payload for this topic, provided there is a topic\n if (msg.topic) {\n context.set(msg.topic, msg.payload);\n }\n}\n// pass on the message unless it has been nulled\nreturn msg;\n","outputs":"1","noerr":0,"x":338.5,"y":265,"wires":[["7be939b5.af86c8"]]},{"id":"1c8cf52.85b7a0b","type":"link in","z":"251597c3.8396e8","name":"Simulated MQTT In","links":["738e30b9.71b4e"],"x":83.5,"y":264,"wires":[["4f0391b0.b04868"]]},{"id":"a4c7955b.911278","type":"comment","z":"251597c3.8396e8","name":"Simulated MQTT Out","info":"","x":664.5,"y":184,"wires":[]},{"id":"1911ff6b.e20249","type":"comment","z":"251597c3.8396e8","name":"Simulated MQTT In","info":"","x":95,"y":209,"wires":[]}]
This provides four extra inject nodes to the flow given earlier. The left hand three inject a dblt-value tag or a dblt-switch tag into placeholder ph001 and clear ph001, and the fourth injects a dblt-value into placeholder ph002
Colin Law
Please feel free to add comments to the page (clearly mark with your initials & please add a commit msg so we know what has changed). You can contact me in the Discourse forum, or raise an issue here in GitHub! I will make sure all comments & suggestions are represented here.
-
Walkthrough 🔗 Getting started
-
In Progress and To Do 🔗 What's coming up for uibuilder?
-
Awesome uibuilder Examples, tutorials, templates and references.
-
How To
- How to send data when a client connects or reloads the page
- Send messages to a specific client
- Cache & Replay Messages
- Cache without a helper node
- Use webpack to optimise front-end libraries and code
- How to contribute & coding standards
- How to use NGINX as a proxy for Node-RED
- How to manage packages manually
- How to upload a file from the browser to Node-RED
-
Vanilla HTML/JavaScript examples
-
VueJS general hints, tips and examples
- Load Vue (v2 or v3) components without a build step (modern browsers only)
- How to use webpack with VueJS (or other frameworks)
- Awesome VueJS - Tips, info & libraries for working with Vue
- Components that work
-
VueJS v3 hints, tips and examples
-
VueJS v2 hints, tips and examples
- Dynamically load .vue files without a build step (Vue v2)
- Really Simple Example (Quote of the Day)
- Example charts using Chartkick, Chart.js, Google
- Example Gauge using vue-svg-gauge
- Example charts using ApexCharts
- Example chart using Vue-ECharts
- Example: debug messages using uibuilder & Vue
- Example: knob/gauge widget for uibuilder & Vue
- Example: Embedded video player using VideoJS
- Simple Button Acknowledgement Example Thanks to ringmybell
- Using Vue-Router without a build step Thanks to AFelix
- Vue Canvas Knob Component Thanks to Klaus Zerbe
-
Examples for other frameworks (check version before trying)
- Basic jQuery example - Updated for uibuilder v6.1
- ReactJS with no build - updated for uibuilder v5/6
-
Examples for other frameworks (may not work, out-of-date)
-
Outdated Pages (Historic only)
- v1 Examples (these need updating to uibuilder v2/v3/v4/v5)