| @@ -0,0 +1,35 @@ | |||
| { | |||
| "env": { | |||
| "node": false, | |||
| "browser": true, | |||
| "jquery": false, | |||
| "es6": false | |||
| }, | |||
| "globals": { | |||
| "Vue": true, | |||
| "uibuilder": true | |||
| }, | |||
| "extends": "eslint:recommended", | |||
| "rules": { | |||
| "no-console": 0, | |||
| "indent": [ | |||
| "warn", | |||
| 4, | |||
| {"SwitchCase": 1} | |||
| ], | |||
| "linebreak-style": [ | |||
| "error", | |||
| "unix" | |||
| ], | |||
| "quotes": [ | |||
| "warn", | |||
| "single" | |||
| ], | |||
| "semi": [ | |||
| "warn", | |||
| "never" | |||
| ], | |||
| "comma-dangle":"off", | |||
| "no-unused-vars": "warn" | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| /* 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; } | |||
| /* Colours for Syntax Highlighted pre's */ | |||
| .syntax-highlight {color:white;background-color:black;padding:5px 10px;} | |||
| .syntax-highlight > .key {color:#ffbf35} | |||
| .syntax-highlight > .string {color:#5dff39;} | |||
| .syntax-highlight > .number {color:#70aeff;} | |||
| .syntax-highlight > .boolean {color:#b993ff;} | |||
| @@ -0,0 +1,147 @@ | |||
| <!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" /> | |||
| <link type="text/css" rel="stylesheet" href="./index.css" media="all"> | |||
| </head> | |||
| <body> | |||
| <div id="disp_single1"></div> | |||
| <div id="app" v-cloak> | |||
| <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 id="btn_increment" pill variant="primary" v-on:click="increment">Increment</b-button> | |||
| Click Counter: <b>{{counterBtn}}</b>. | |||
| <p>Click on the button to increment the counter. It sends the data dynamically back to Node-RED as well.</p> | |||
| </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> | |||
| <pre v-html="hLastRcvd" class="syntax-highlight"></pre> | |||
| <pre v-html="hLastSent" class="syntax-highlight"></pre> | |||
| <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> | |||
| <pre v-html="hLastCtrlRcvd" class="syntax-highlight"></pre> | |||
| <pre v-html="hLastCtrlSent" class="syntax-highlight"></pre> | |||
| <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> | |||
| <!-- 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> | |||
| @@ -0,0 +1,197 @@ | |||
| /* 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 --- // | |||
| }) // --- End of app1 --- // | |||
| // EOF | |||
| @@ -0,0 +1,15 @@ | |||
| { | |||
| "name": "Node-RED UI Builder", | |||
| "short_name": "uibuilder", | |||
| "description": "A web template builder for Node-RED", | |||
| "display": "standalone", | |||
| "lang": "en-GB", | |||
| "icons": [ | |||
| { | |||
| "src": "./images/node-blue-192x192.png", | |||
| "sizes": "192x192", | |||
| "type": "image/png" | |||
| } | |||
| ], | |||
| "start_url": "." | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| CACHE MANIFEST | |||
| # 2019-02-02 19:48:57 | |||
| # WARNING: These files will never be delivered | |||
| # again until this file changes. If you are updating, | |||
| # make sure you change something in this file. | |||
| # The appcache isn't always good: https://alistapart.com/article/application-cache-is-a-douchebag | |||
| # Resources that will be delivered from the CACHE | |||
| # after first load. | |||
| CACHE: | |||
| # Required files | |||
| images/node-blue-192x192.png | |||
| images/node-blue.ico | |||
| /uibuilder/socket.io/socket.io.js | |||
| # Replace this with the non .min version if needed | |||
| uibuilderfe.min.js | |||
| # Specific for our app | |||
| index.css | |||
| index.js | |||
| # Local vendor libraries | |||
| vendor/normalize.css/normalize.css | |||
| # Useful utility libraries | |||
| #https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js | |||
| #https://unpkg.com/babel-polyfill@latest/dist/polyfill.min.js | |||
| # Resources that require the user to be online. | |||
| NETWORK: | |||
| * | |||
| # Optional section specifying fallback pages if a resource is inaccessible | |||
| # 1st URI is resource, 2nd is fallback if request fails or errors | |||
| # Both must from same origin as manifest. Can use prefixes as well as file uris | |||
| #FALLBACK: | |||
| # e.g. display offline.html if cache is empty and we are offline | |||
| #/ /offline.html | |||