Created
June 16, 2020 09:59
-
-
Save boisei0/93be49e46b3fef9cac752d4b587fced7 to your computer and use it in GitHub Desktop.
Super quick demo to show dynamic components on uibuilder v2; assumes `httpNodeRoot` is /.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{"id":"25ebb57d.b12ef2","type":"tab","label":"Uibuilder \"dynamic\" components","disabled":false,"info":""},{"id":"bc0ec216.f4792","type":"http in","z":"25ebb57d.b12ef2","name":"","url":"/uib-custom/components/JSONSyntaxHighlight.vue","method":"get","upload":false,"swaggerDoc":"","x":250,"y":80,"wires":[["b1e0f99a.6b8df"]]},{"id":"8bba772c.9eb008","type":"http response","z":"25ebb57d.b12ef2","name":"","statusCode":"","headers":{},"x":710,"y":80,"wires":[]},{"id":"b1e0f99a.6b8df","type":"template","z":"25ebb57d.b12ef2","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"plain","template":"<template>\n <pre class=\"syntax-highlight\" v-html=\"syntaxHighlight\">\n </pre>\n</template>\n\n<script>\nmodule.exports = {\n name: 'JSONSyntaxHighLight',\n data() {\n return {\n\n };\n },\n props: {\n prefix: {\n type: String,\n default: '',\n },\n json: {\n type: Object,\n required: true,\n },\n },\n computed: {\n // return formatted HTML version of JSON object\n syntaxHighlight: function() {\n if (typeof this.json === 'string') {\n // Return it unformatted but wrapped in the syntax-highlight pre.\n return this.prefix + this.json;\n }\n\n let jsonFormatted = JSON.stringify(this.json, undefined, 4);\n jsonFormatted = jsonFormatted.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n jsonFormatted = jsonFormatted.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {\n let cls = 'number';\n if (/^\"/.test(match)) {\n if (/:$/.test(match)) {\n cls = 'key';\n } else {\n cls = 'string';\n }\n } else if (/true|false/.test(match)) {\n cls = 'boolean';\n } else if (/null/.test(match)) {\n cls = 'null';\n }\n return '<span class=\"' + cls + '\">' + match + '</span>';\n });\n return this.prefix + jsonFormatted;\n }, // --- End of syntaxHighlight --- //\n }\n}\n</script>\n\n<style scoped>\n .syntax-highlight {\n color:white;\n background-color:black;\n padding:5px 10px;\n }\n .syntax-highlight > .key {\n color:#ffbf35\n }\n .syntax-highlight > .string {\n color:#5dff39;\n }\n .syntax-highlight > .number {\n color:#70aeff;\n }\n .syntax-highlight > .boolean {\n color:#b993ff;\n }\n</style>","output":"str","x":560,"y":80,"wires":[["8bba772c.9eb008"]]},{"id":"b23443f.849e2c","type":"uibuilder","z":"25ebb57d.b12ef2","name":"","topic":"","url":"dyn-components-test","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":200,"y":240,"wires":[[],[]]}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<!-- Note that adding an appcache really speeds things up after the first load | |
You need to amend the appcache file to meet your needs. | |
Don't forget to change the appcache file if you update ANY | |
of the files in it otherwise the old versions will ALWAYS be used. | |
<html lang="en" manifest="./uibuilder.appcache"> | |
--> | |
<html lang="en"> | |
<!-- | |
This is the default, template html for uibuilder. | |
It is meant to demonstrate the use of VueJS & bootstrap-vue to dynamically | |
update the ui based on incoming/outgoing messages from/to the | |
Node-RED server. | |
You will want to alter this to suite your own needs. To do so, | |
copy this file to <userDir>/uibuilder/<url>/src. | |
--> | |
<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>Node-RED UI Builder</title> | |
<meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue version"> | |
<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="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"> | |
--> | |
<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" /> | |
</head> | |
<body> | |
<div id="app"> | |
<b-container id="app_container"> | |
<b-img src="./images/node-blue-192x192.png" rounded left v-bind="imgProps" alt="Blue Node-RED" class="mt-1 mr-2"></b-img> | |
<h1> | |
UIbuilder + Vue.js + bootstrap-vue for Node-RED | |
</h1> | |
<p> | |
This is a uibuilder example using <a href="https://vuejs.org/">Vue.js</a> as a front-end library. | |
See the | |
<a | |
href="https://github.com/TotallyInformation/node-red-contrib-uibuilder">node-red-contrib-uibuilder</a> | |
README for details on how to use UIbuilder. | |
</p> | |
<h2>Simple input using Vue</h2> | |
<b-form class="border p-3 m-2"> | |
<p> | |
You can very simply create a form using Vue & bootstrap-vue. | |
The form sends data back to Node-RED. | |
Look at the <code>increment</code> method in <code>index.js</code> to see how easy this is. | |
</p> | |
<p> | |
<b-form-input v-model="inputText" type="text" placeholder="Enter some text to send to Node-RED"></b-form-input><br> | |
<b-form-checkbox v-model="inputChkBox"> | |
To tick or not to tick? That is the question | |
</b-form-checkbox><br /> | |
<b-button pill variant="primary" v-on:click="increment">Increment</b-button> | |
Click Counter: <b>{{counterBtn}}</b>. | |
</p> | |
<p>Click on the button to increment the counter. It sends the data dynamically back to Node-RED as well.</p> | |
</b-form> | |
<h2>Dynamic Data</h2> | |
<p>Uses Vue to dynamically update in response to messages from Node-RED.</p> | |
<p> | |
Check out the <code>mounted</code> function in <code>index.js</code> to See | |
how easy it is to update Vue data from Node-RED. | |
</p> | |
<b-card class="mt-3" header="Status" border-variant="info" header-bg-variant="info" header-text-variant="white" align="center" > | |
<p class="float-left">Socket.io Connection Status: <b>{{socketConnectedState}}</b></p> | |
<p class="float-right">Time offset between browser and server: <b>{{serverTimeOffset}}</b> hours</p> | |
</b-card> | |
<b-card class="mt-3" header="Normal Messages" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="left" > | |
<p> | |
Messages: Received=<b>{{msgsReceived}}</b>, Sent=<b>{{msgsSent}}</b> | |
</p> | |
<syntax-highlight prefix="Last Message Received = " :json="msgRecvd"></syntax-highlight> | |
<syntax-highlight prefix="Last Message Sent = " :json="msgSent"></syntax-highlight> | |
<p slot="footer" class="mb-0"> | |
The received message is from the input to the uibuilder node. | |
The send message will appear out of port #1 of the node. | |
</p> | |
</b-card> | |
<b-card class="mt-3" header="Control Messages" border-variant="secondary" header-bg-variant="secondary" header-text-variant="white" align="left" > | |
<p> | |
Control Messages: Received=<b>{{msgsControl}}</b>, Sent=<b>{{msgsCtrlSent}}</b> | |
</p> | |
<syntax-highlight prefix="Last Control Message Received = " :json="msgCtrl"></syntax-highlight> | |
<syntax-highlight prefix="Last Control Message Sent = " :json="msgCtrlSent"></syntax-highlight> | |
<p slot="footer" class="mb-0"> | |
Control messages always appear out of port #2 of the uibuilder node | |
whether they are from the server or the client. The <code>from</code> property | |
of the message tells you where it came from. | |
</p> | |
</b-card> | |
</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> | |
<!-- HTTP Vue Loader --> | |
<script src="../uibuilder/vendor/http-vue-loader/src/httpVueLoader.js"></script> | |
<!-- REQUIRED: Sets up Socket listeners and the msg object --> | |
<!-- <script src="./uibuilderfe.js"></script> //dev version --> | |
<script src="./uibuilderfe.min.js"></script> <!-- //prod version --> | |
<!-- OPTIONAL: You probably want this. Put your custom code here --> | |
<script src="./index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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: { | |
startMsg : 'Vue has started, waiting for messages', | |
feVersion : '', | |
counterBtn : 0, | |
inputText : null, | |
inputChkBox : false, | |
socketConnectedState : false, | |
serverTimeOffset : '[unknown]', | |
imgProps : { width: 75, height: 75 }, | |
msgRecvd : '[Nothing]', | |
msgsReceived: 0, | |
msgCtrl : '[Nothing]', | |
msgsControl : 0, | |
msgSent : '[Nothing]', | |
msgsSent : 0, | |
msgCtrlSent : '[Nothing]', | |
msgsCtrlSent: 0, | |
}, // --- End of data --- // | |
computed: { | |
hLastRcvd: function() { | |
var msgRecvd = this.msgRecvd | |
if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd | |
else return 'Last Message Received = ' + this.syntaxHighlight(msgRecvd) | |
}, | |
hLastSent: function() { | |
var msgSent = this.msgSent | |
if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent | |
else return 'Last Message Sent = ' + this.syntaxHighlight(msgSent) | |
}, | |
hLastCtrlRcvd: function() { | |
var msgCtrl = this.msgCtrl | |
if (typeof msgCtrl === 'string') return 'Last Control Message Received = ' + msgCtrl | |
else return 'Last Control Message Received = ' + this.syntaxHighlight(msgCtrl) | |
}, | |
hLastCtrlSent: function() { | |
var msgCtrlSent = this.msgCtrlSent | |
if (typeof msgCtrlSent === 'string') return 'Last Control Message Sent = ' + msgCtrlSent | |
//else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgCtrlSent]) | |
else return 'Last Control Message Sent = ' + this.syntaxHighlight(msgCtrlSent) | |
}, | |
}, // --- End of computed --- // | |
methods: { | |
increment: function(event) { | |
console.log('Button Pressed. Event DatA: ', event) | |
// Increment the count by one | |
this.counterBtn = this.counterBtn + 1 | |
var topic = this.msgRecvd.topic || 'uibuilder/vue' | |
uibuilder.send( { | |
'topic': topic, | |
'payload': { | |
'type': 'counterBtn', | |
'btnCount': this.counterBtn, | |
'message': this.inputText, | |
'inputChkBox': this.inputChkBox | |
} | |
} ) | |
}, // --- End of increment --- // | |
// return formatted HTML version of JSON object | |
syntaxHighlight: function(json) { | |
json = JSON.stringify(json, undefined, 4) | |
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') | |
json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { | |
var cls = 'number' | |
if (/^"/.test(match)) { | |
if (/:$/.test(match)) { | |
cls = 'key' | |
} else { | |
cls = 'string' | |
} | |
} else if (/true|false/.test(match)) { | |
cls = 'boolean' | |
} else if (/null/.test(match)) { | |
cls = 'null' | |
} | |
return '<span class="' + cls + '">' + match + '</span>' | |
}) | |
return json | |
}, // --- End of syntaxHighlight --- // | |
}, // --- End of methods --- // | |
// Available hooks: init,mounted,updated,destroyed | |
mounted: function(){ | |
//console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers') | |
/** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3 | |
* Pass the namespace and ioPath variables if hosting page is not in the instance root folder | |
* The namespace is the "url" you put in uibuilder's configuration in the Editor. | |
* e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages. | |
* e.g. uibuilder.start('uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names | |
*/ | |
uibuilder.start() | |
var vueApp = this | |
// Example of retrieving data from uibuilder | |
vueApp.feVersion = uibuilder.get('version') | |
/** You can use the following to help trace how messages flow back and forth. | |
* You can then amend this processing to suite your requirements. | |
*/ | |
//#region ---- Trace Received Messages ---- // | |
// If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO | |
// newVal relates to the attribute being listened to. | |
uibuilder.onChange('msg', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', newVal) | |
vueApp.msgRecvd = newVal | |
}) | |
// As we receive new messages, we get an updated count as well | |
uibuilder.onChange('msgsReceived', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange] Updated count of received msgs:', newVal) | |
vueApp.msgsReceived = newVal | |
}) | |
// If we receive a control message from Node-RED, we can get the new data here - we pass it to a Vue variable | |
uibuilder.onChange('ctrlMsg', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', newVal) | |
vueApp.msgCtrl = newVal | |
}) | |
// Updated count of control messages received | |
uibuilder.onChange('msgsCtrl', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:msgsCtrl] Updated count of received CONTROL msgs:', newVal) | |
vueApp.msgsControl = newVal | |
}) | |
//#endregion ---- End of Trace Received Messages ---- // | |
//#region ---- Trace Sent Messages ---- // | |
// You probably only need these to help you understand the order of processing // | |
// If a message is sent back to Node-RED, we can grab a copy here if we want to | |
uibuilder.onChange('sentMsg', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:sentMsg] msg sent to Node-RED server:', newVal) | |
vueApp.msgSent = newVal | |
}) | |
// Updated count of sent messages | |
uibuilder.onChange('msgsSent', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:msgsSent] Updated count of msgs sent:', newVal) | |
vueApp.msgsSent = newVal | |
}) | |
// If we send a control message to Node-RED, we can get a copy of it here | |
uibuilder.onChange('sentCtrlMsg', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', newVal) | |
vueApp.msgCtrlSent = newVal | |
}) | |
// And we can get an updated count | |
uibuilder.onChange('msgsSentCtrl', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:msgsSentCtrl] Updated count of CONTROL msgs sent:', newVal) | |
vueApp.msgsCtrlSent = newVal | |
}) | |
//#endregion ---- End of Trace Sent Messages ---- // | |
// If Socket.IO connects/disconnects, we get true/false here | |
uibuilder.onChange('ioConnected', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', newVal) | |
vueApp.socketConnectedState = newVal | |
}) | |
// If Server Time Offset changes | |
uibuilder.onChange('serverTimeOffset', function(newVal){ | |
//console.info('[indexjs:uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', newVal) | |
vueApp.serverTimeOffset = newVal | |
}) | |
}, // --- End of mounted hook --- // | |
components: { | |
'syntax-highlight': httpVueLoader('/uib-custom/components/JSONSyntaxHighlight.vue'), | |
} | |
}) // --- End of app1 --- // | |
// EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment