Front-end based on uibuilder: 1. component acc-switch.vue

acc-switch
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.

2 Likes

Nice! Thanks for sharing this with everyone.

One small change you might like to make to index.js, adding these lines at the top will get rid of all of the warnings in the rest of the code and make sure you get the most relevant warnings:

/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder, httpVueLoader */

Would you be happy if I included this in the uibuilder examples that can be imported? Also, can I add a copy to the WIKI?

Thanks for your hint. I've updated the flow above.

Sure, Feel free to use it.
I'm working on more components. I'll post them here first.

1 Like

Thanks!

Cool. Don't forget that you can use the WIKI to document examples as well which might be easier than relying on Discourse. Just let me know if you add anything and I can update the menu.