Flow:
[{"id":"31554538.d666ba","type":"uibuilder","z":"96248ad2.2c3df8","name":"","topic":"","url":"uiswitch","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":380,"y":200,"wires":[["151b60e8.47977f","3bf74e79.a02822"],["54f1123c.e47ecc"]]},{"id":"4b99c284.31f20c","type":"inject","z":"96248ad2.2c3df8","name":"true","topic":"","payload":"{\"name\":\"at_home\",\"service_name\":\"at_home\",\"characteristic\":\"On\",\"value\":true}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":180,"wires":[["53685025.b09be"]]},{"id":"3943ea87.e55ca6","type":"inject","z":"96248ad2.2c3df8","name":"false","topic":"","payload":"{\"name\":\"at_home\",\"service_name\":\"at_home\",\"characteristic\":\"On\",\"value\":false}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":220,"wires":[["53685025.b09be"]]},{"id":"5ff6e05a.f9eb9","type":"link in","z":"96248ad2.2c3df8","name":"home-replay","links":["151b60e8.47977f","54f1123c.e47ecc"],"x":135,"y":140,"wires":[["53685025.b09be"]]},{"id":"151b60e8.47977f","type":"link out","z":"96248ad2.2c3df8","name":"home-controls","links":["5ff6e05a.f9eb9"],"x":515,"y":140,"wires":[]},{"id":"54f1123c.e47ecc","type":"link out","z":"96248ad2.2c3df8","name":"","links":["ae0fd0a1.2f9f7","5ff6e05a.f9eb9"],"x":515,"y":220,"wires":[]},{"id":"53685025.b09be","type":"function","z":"96248ad2.2c3df8","name":"cache","func":"// saved context\nvar ui_msgs = context.get('ui_msgs') || {}\n\n// Replay cache if requested\nif (msg.hasOwnProperty('uibuilderCtrl') && msg.uibuilderCtrl === 'ready for content') {\n for (var name in ui_msgs) {\n node.send({\n \"topic\": msg.topic,\n \"payload\": ui_msgs[name],\n \"_socketId\": msg._socketId\n })\n }\n return null\n}\n\n// ignore cacheControl and uibuilder control messages\nif (msg.hasOwnProperty('cacheControl') || msg.hasOwnProperty('uibuilderCtrl')) return null\n\nif (msg.hasOwnProperty('payload') && msg.payload.hasOwnProperty('name')) {\n // Keep the last msg.payload by msg.payload.name\n ui_msgs[msg.payload.name] = msg.payload\n \n // save context for next time\n context.set('ui_msgs', ui_msgs)\n msg._socketId = null\n} else {\n node.warn(\"no payload or payload.name\")\n msg = null\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":250,"y":200,"wires":[["31554538.d666ba"]]},{"id":"3bf74e79.a02822","type":"debug","z":"96248ad2.2c3df8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":570,"y":180,"wires":[]},{"id":"c4a5c958.b120b8","type":"comment","z":"96248ad2.2c3df8","name":"index.html","info":"<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes\">\n\n <title>Node-RED UI Builder</title>\n <meta name=\"description\" content=\"Node-RED UI Builder - VueJS + bootstrap-vue version\">\n\n <link rel=\"icon\" href=\"./images/node-blue.ico\">\n\n <!-- See https://goo.gl/OOhYW5 -->\n <link rel=\"manifest\" href=\"./manifest.json\">\n <meta name=\"theme-color\" content=\"#3f51b5\">\n <link type=\"text/css\" rel=\"stylesheet\" href=\"../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css\" />\n <link type=\"text/css\" rel=\"stylesheet\" href=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css\" />\n \n <link rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n <style>\n [v-cloak] { display: none; }\n </style>\n</head>\n<body>\n <div id=\"app\" v-cloak>\n <b-container id=\"app_container\"> \n <b-card no-body>\n <b-tabs card small>\n <b-tab title=\"Main\" active>\n <b-card header=\"Control\" border-variant=\"primary\">\n <acc-switch name=\"at_home\" v-model=\"v_value.at_home\" badge></acc-switch>\n <acc-switch name=\"security_system\" v-model=\"v_value.security_system\"></acc-switch>\n <acc-switch name=\"heating\" v-model=\"v_value.heating\"></acc-switch>\n <acc-switch name=\"greenhouse\" v-model=\"v_value.greenhouse\"></acc-switch>\n </b-card>\n </b-tab>\n\n <b-tab title=\"Accessories\">\n <b-card header=\"Accessories\" border-variant=\"primary\">\n <b-card-text>an accessories list ...</b-card-text>\n </b-card>\n </b-tab>\n </b-tabs>\n \n <b-card v-if=debug header=\"Debug\" border-variant=\"info\">\n <b-card-text v-if=\"msg\">last msg: {{msg.payload}}</b-card-text>\n <b-card-text>accessories: {{accessories}}</b-card-text>\n <b-card-text>v_value: {{v_value}}</b-card-text>\n </b-card>\n </b-card>\n </b-container>\n </div>\n \n <script src=\"../uibuilder/vendor/socket.io/socket.io.js\"></script>\n\n <!-- Vendor Libraries - Load in the right order -->\n <script src=\"../uibuilder/vendor/vue/dist/vue.js\"></script> <!-- dev version with component compiler -->\n <!-- <script src=\"../uibuilder/vendor/vue/dist/vue.min.js\"></script> prod version with component compiler -->\n <!-- <script src=\"../uibuilder/vendor/vue/dist/vue.runtime.min.js\"></script> prod version without component compiler -->\n <script src=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js\"></script>\n <!-- Loading from CDN -->\n <!-- <script src=\"https://unpkg.com/http-vue-loader\"></script> -->\n <!-- Loading from npm installed version -->\n <script src=\"../uibuilder/vendor/http-vue-loader/src/httpVueLoader.js\"></script>\n \n <!-- REQUIRED: Sets up Socket listeners and the msg object -->\n <!-- <script src=\"./uibuilderfe.js\"></script> //dev version -->\n <script src=\"./uibuilderfe.min.js\"></script> <!-- //prod version -->\n <!-- OPTIONAL: You probably want this. Put your custom code here -->\n <script src=\"./index.js\"></script>\n</body>\n</html>\n\n","x":120,"y":80,"wires":[]},{"id":"579a4352.39dd8c","type":"comment","z":"96248ad2.2c3df8","name":"index.js","info":"/* jshint browser: true, esversion: 5, asi: true */\n/* globals Vue, uibuilder, httpVueLoader */\n\n'use strict'\n\nnew Vue({\n el: '#app',\n components: {\n 'acc-switch': httpVueLoader('acc-switch.vue'),\n },\n \n data: {\n msg: {},\n accessories: {},\n v_value: {},\n debug: false\n },\n \n methods: { \n \n },\n\n mounted: function() {\n //console.debug('[mounted] app mounted - setting up uibuilder watchers')\n uibuilder.start()\n \n uibuilder.onChange('msg', function(msg) {\n this.msg = msg\n \n if (msg.payload.value !== this.v_value[msg.payload.name]) {\n console.debug('[uibuilder.onChange]', msg.payload)\n Vue.set(this.accessories, msg.payload.name, msg.payload)\n Vue.set(this.v_value, msg.payload.name, msg.payload.value)\n }\n }.bind(this))\n }\n})\n","x":250,"y":80,"wires":[]},{"id":"4d7f73bd.33e0fc","type":"comment","z":"96248ad2.2c3df8","name":"acc-switch.vue","info":"<template>\n <div>\n <b-form-checkbox\n v-bind:checked=\"checked\"\n v-on:change.native=\"sw_onChange($event.target.checked)\"\n switch size=\"lg\"\n >{{name}}\n <b-badge v-if=\"badge\" variant=\"info\">{{checked? 'on': 'off'}}</b-badge>\n </b-form-checkbox>\n </div>\n</template>\n\n<script>\nmodule.exports = {\n model: {\n prop: 'checked',\n event: 'change'\n },\n props: {\n checked: Boolean,\n name: String,\n badge: {type: Boolean, default: false},\n },\n \n data: function() {\n return {\n \n }\n },\n\n methods: {\n sw_onChange: function(value) {\n console.debug('[sw_onChange]',this.name, value)\n uibuilder.send({\n \"topic\": \"sw_onChange\",\n \"payload\": {\n \"name\": this.name,\n \"service_name\": this.name,\n \"characteristic\":\"On\",\n \"value\": value\n }\n })\n }\n },\n}\n</script>","x":400,"y":80,"wires":[]}]
Requirements:
a) Install node-red-contrib-uibuilder
b) In the uibuilder Front-End Library Manager add http-vue-loader
c) Copy the code from the comment nodes (index.htm and index.js) and paste into the corresponding files.
d) Copy the code from the comment node acc-switch.vue and paste into a new file acc-switch.vue
.
I tried to build a short and simple example. Assuming some basic knowledge about uibuilder, Vue and Bootstrap-Vue the code should be self-explanatory.
Feedback welcome.