-
-
Notifications
You must be signed in to change notification settings - Fork 85
Simple Button Acknowledgement Example
Here is a simple example showing how messages flow from Node-Red to the UIbuilder Vue webpage and back to Node-Red on a user interaction.
In this example the message from Node-Red is simulating an alarm by changing the state of a variable from 0 to 1. In the webpage it receives the change in state and changes a button from green to red. When the user acknowledges the alarm by clicking the button it changes the variable from 1 back to 0, changes the color from red back to green and sends an update message to Node-Red so it knows a user interaction has occurred.
Paste the following Node-Red flow into your page.
Note I have included some MQTT nodes to show how simple it is to interface MQTT to the UIbuilder, but you will need to make sure you have MQTT broker and server setup on your computer for it to work. Delete these flows if not required, you can still build and deploy but it will give you an error (it can be ignored)
[{"id":"1a7c5d0e.0fdc73","type":"uibuilder","z":"32910924.887cb6","name":"","topic":"","url":"uibuilder","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":1120,"y":1040,"wires":[["a39ed31d.bca6f"],[]]},{"id":"3403d92d.5c4176","type":"inject","z":"32910924.887cb6","name":"Alarm 1","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":920,"wires":[["fec3d20f.90bba"]]},{"id":"fec3d20f.90bba","type":"function","z":"32910924.887cb6","name":"alarm1StatusOn","func":"msg.topic = \"alarm1Status\";\nmsg.payload = 1;\n\nreturn msg;","outputs":1,"noerr":0,"x":720,"y":920,"wires":[["1a7c5d0e.0fdc73","eb57a4c3.9770b8"]]},{"id":"a39ed31d.bca6f","type":"debug","z":"32910924.887cb6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1310,"y":980,"wires":[]},{"id":"364bd0d0.f77b4","type":"comment","z":"32910924.887cb6","name":"Press to trigger alarm","info":"","x":360,"y":880,"wires":[]},{"id":"fe887baa.3db618","type":"inject","z":"32910924.887cb6","name":"Alarm 2","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":980,"wires":[["1a7b62b6.219fad"]]},{"id":"1a7b62b6.219fad","type":"function","z":"32910924.887cb6","name":"alarm2StatusOn","func":"msg.topic = \"alarm2Status\";\nmsg.payload = 1;\n\nreturn msg;","outputs":1,"noerr":0,"x":720,"y":980,"wires":[["1a7c5d0e.0fdc73","eb57a4c3.9770b8"]]},{"id":"eb57a4c3.9770b8","type":"debug","z":"32910924.887cb6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1310,"y":920,"wires":[]},{"id":"55baa5ee.7f793c","type":"comment","z":"32910924.887cb6","name":"Note the data format - msg.topic and msg.payload","info":"","x":730,"y":880,"wires":[]},{"id":"16d11a39.ea9dd6","type":"mqtt in","z":"32910924.887cb6","name":"","topic":"alarm1Status","qos":"2","datatype":"auto","broker":"","x":910,"y":1120,"wires":[["1a7c5d0e.0fdc73"]]},{"id":"a6317bd9.41d248","type":"comment","z":"32910924.887cb6","name":"You can feed MQTT node in directly - very cool","info":"","x":820,"y":1080,"wires":[]},{"id":"6a61d395.7b7b8c","type":"mqtt in","z":"32910924.887cb6","name":"","topic":"alarm2Status","qos":"2","datatype":"auto","broker":"","x":910,"y":1180,"wires":[["1a7c5d0e.0fdc73"]]},{"id":"909a0c6.28a6df","type":"mqtt out","z":"32910924.887cb6","name":"","topic":"alarm1Status","qos":"2","retain":"true","broker":"","x":710,"y":1120,"wires":[]},{"id":"11121be6.386bc4","type":"inject","z":"32910924.887cb6","name":"Alarm 1","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":1120,"wires":[["bed91829.4e2bf8"]]},{"id":"50cb6c28.fe4ef4","type":"inject","z":"32910924.887cb6","name":"Alarm 2","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":1180,"wires":[["a1aa55a8.870af8"]]},{"id":"2da46cbb.ce99e4","type":"mqtt out","z":"32910924.887cb6","name":"","topic":"alarm2Status","qos":"2","retain":"true","broker":"","x":710,"y":1180,"wires":[]},{"id":"bed91829.4e2bf8","type":"function","z":"32910924.887cb6","name":"alarm1StatusOn","func":"msg.payload = 1;\n\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":1120,"wires":[["909a0c6.28a6df"]]},{"id":"a1aa55a8.870af8","type":"function","z":"32910924.887cb6","name":"alarm2StatusOn","func":"msg.payload = 1;\n\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":1180,"wires":[["2da46cbb.ce99e4"]]},{"id":"92ddd40.ae8183","type":"comment","z":"32910924.887cb6","name":"You will need to download mqtt broker and set it up","info":"","x":830,"y":1220,"wires":[]},{"id":"2ff9727c.2f200e","type":"comment","z":"32910924.887cb6","name":"For debug viewer","info":"","x":1320,"y":880,"wires":[]}]
Add an instance of uibuilder to your flows and replace the index.html, index.css and index.js files with the code below.
<!doctype html>
<!-- *********** NOTE 1 - The following block almost a direct copy of from Julian's examples ************ -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>Alarm Buttons with Acknowledgment</title>
<meta name="description" content="Alarm Buttons with Acknowledgment">
<link rel="icon" href="./images/node-blue.ico">
<!-- 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="Alarm Buttons with Acknowledgment">
<!-- 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="Alarm Buttons with Acknowledgment">
<link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
<link type="text/css" rel="stylesheet" href="./index.css" media="all">
<!-- ************************** NOTE 1 - End of copied code block ******************************* -->
</head>
<body>
<div id="app" v-cloak> <!--v-cloak hides the webpage until all vue content has been loaded -->
<b-container id="app_container">
<p class="textWhiteBoldLeft20px">Alarm Acknowledgement Example</p>
<table>
<tr><td class="textWhiteCentered16px">Alarm State. Click to Cancel Alarm</td></tr>
<tr><td><b-button v-bind:variant="alarm1Style" v-on:click="acknowledgeAlarm1()">
<b-icon v-bind:icon="alarm1Icon"></b-icon> Alarm 1 <!-- note that is just forcing a space between icon and text -->
</b-button></td></tr>
<tr><td><b-button :variant="alarm2Style" @click="acknowledgeAlarm2()">
<b-icon :icon="alarm2Icon"></b-icon> Alarm 2
</b-button></td></tr>
</table>
<!-- ******************** NOTE 2 - comments on above two lines *******************************
table, tr, td - Table commands, this is just so I can get a light grey box to sit over darker bagground,
just becase I think it looks nice, there are probably other ways to do this
b-button - This is the bootstrap version of the standard html button, it includes some nice features
such as hover and focus pre setup. This is great if you want a nice looking button with
minimal CSS coding.
b-icon - So we can use the bootstrap icons. NOTE - you need to include the script bootstrap-vue-icons.js
if you want to use the icons, they are not included by default
v-bind: - This tells vue that it needs to bind this parameter (the button class) to a component (can
be data, computed, watch or method hooks). This is saying to vue to keep an eye on this
class because one of the compnents (data, computed, watch or method) is going to change its value.
NOTE - you dont have to write "v-bind:", VUE allows a short cut and you can just use ":" which
can be confusing if you are looking at code and dont understand the shortcuts.
v-on: - This tells VUE that on some action (in this case click) it needs to go to one of the
components and do something.
NOTE - you dont have to write "v-on:", VUE allows a short cut and you can just use "@" which
can be confusing if you are looking at code and dont understand the shortcuts.
-->
</b-container>
</div>
<!-- These MUST be in the right order. Note no leading / -->
<!-- REQUIRED: Socket.IO is loaded only once for all instances without this, you don't get a websocket connection -->
<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<!-- --- Vendor Libraries - Load in the right order --- -->
<script src="../uibuilder/vendor/vue/dist/vue.js"></script> <!-- dev version with component compiler -->
<!-- <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script> prod version with component compiler -->
<!-- <script src="../uibuilder/vendor/vue/dist/vue.runtime.min.js"></script> prod version without component compiler -->
<script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>
<script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue-icons.js"></script> <!-- ********* TIP!! - If you want to include the Vue Bootstrap Icons then you need to Include this!!!! -->
<!-- REQUIRED: Sets up Socket listeners and the msg object -->
<!-- <script src="./uibuilderfe.js"></script> //dev version -->
<script src="./uibuilderfe.min.js"></script> <!-- //prod version -->
<!-- You need this file, it tells VUE how to handle input and output messages -->
<script src="./index.js"></script>
</body>
</html>
/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
/*
Copyright (c) 2019 Julian Knight (Totally Information)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict'
/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */
// eslint-disable-next-line no-unused-vars
var app1 = new Vue({
el: '#app',
// ******************************************************
// DATA
// If you want vue to control or react to data then it
// MUST be declared here in the data section
// ******************************************************
data: {
alarm1Data : 0, //0 = no alarm green, 1 = alarm red. So we will set the initial alarm state to off so button will show green
alarm2Data : 0,
}, // --- End of data --- //
// ******************************************************
// COMPUTED
// Best way to think about this. Computed are functions
// that are trigged when some data changes. It requires
// no input from the user. The data MUST be declared in
// the data section or vue will ignore it. For example
// here we are setting the color of the button only based
// on the value of the vaiable alarmXData
// ******************************************************
computed: {
alarm1Style: function() { //this function will contol the button color using bootsrap inbuilt buttons
if (this.alarm1Data === 0) { //if alarm is off then...,
return "success" //....colour green - in bootstrap success is green style
}
else if (this.alarm1Data == 1) { //if alarm is on then...,
return "danger" //....colour red - in bootstrap warning is red style
}
},
alarm1Icon: function() {
if (this.alarm1Data === 0) {
return "check-circle"
}
else if (this.alarm1Data == 1) {
return "exclamation-circle-fill"
}
},
alarm2Style: function() {
if (this.alarm2Data === 0) {
return "success"
}
else if (this.alarm2Data == 1) {
return "danger"
}
},
alarm2Icon: function() {
if (this.alarm2Data === 0) {
return "check-circle"
}
else if (this.alarm2Data == 1) {
return "exclamation-circle-fill"
}
},
}, // --- End of computed --- //
// ******************************************************
// METHODS
// Best way to think about this. Methods are functions
// that the user (or something else) starts, for example,
// we are saying here on a button click change the data variable
// alarmXData back to a non alarm condition and send some data
// back to Node-Red so the system knows the user has had
// an interaction
// ******************************************************
methods: {
acknowledgeAlarm1: function(event) { //this function is called when the button is clicked. If button is pressed and....
console.log("Alarm 1 cancelled")
if (this.alarm1Data == 1) { //...if alarm is currently triggered then.....
this.alarm1Data = 0; //...cancel the alarm and....
uibuilder.send( { //...send message back to Node-Red saying this is what you did
'topic': "alarm1Acknowledge",
'payload': this.alarm1Data
} )
}
},
acknowledgeAlarm2: function(event) {
console.log("Alarm 2 cancelled")
if (this.alarm2Data == 1) {
this.alarm2Data = 0;
uibuilder.send( {
'topic': "alarm2Acknowledge",
'payload': this.alarm2Data
} )
}
},
}, // --- End of methods --- //
// ******************************************************
// MOUNTED
// This section will keep an eye out for incoming messages
// from Node-Red, you use the onChange method, then you
// can filter by the message topics to decide what actions
// you want to take
// ******************************************************
mounted: function(){
uibuilder.start()
var vueApp = this
// Example of retrieving data from uibuilder
vueApp.feVersion = uibuilder.get('version')
uibuilder.onChange('msg', function(msg){
if (msg.topic == "alarm1Status") {
vueApp.alarm1Data = parseInt(msg.payload)
console.log("Alarm 1 triggered") //if you open web developer console, you can see the message coming in from node-Red when you click the send button
//console.log(msg.topic)
//console.log(msg.payload)
}
else if (msg.topic == "alarm2Status") {
vueApp.alarm2Data = parseInt(msg.payload)
console.log("Alarm 2 triggered")
//console.log(msg.topic)
//console.log(msg.payload)
}
})
} // --- End of mounted hook --- //
}) // --- End of app1 --- //
// EOF
/* Cloak elements on initial load to hide the possible display of {{ ... }}
* Add to the app tag or to specific tags
* To display "loading...", change to the following:
* [v-cloak] > * { display:none }
* [v-cloak]::before { content: "loading…" }
*/
[v-cloak] { display: none; }
body {
background-color: rgb(51,51,51);
}
table, td, th {
background-color:rgb(91,91,91);
border: 5px solid rgb(91,91,91);
}
/* ----------- Setup text ----------- */
.textWhiteBoldLeft20px {
color: white;
font-size: 20px;
font-weight: bold;
text-align: left;
font-family: Calibri, 'Gill Sans', 'Gill Sans MT', 'Trebuchet MS', sans-serif;
}
.textWhiteCentered16px {
color: white;
font-size: 16px;
text-align: center;
font-family: Calibri, 'Gill Sans', 'Gill Sans MT', 'Trebuchet MS', sans-serif;
}
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)