Skip to content

Simple Example using VueJS

Julian Knight edited this page Aug 1, 2018 · 2 revisions

Here is a quick example using UIbuilder which is an alternative UI for Node-RED.

It demonstrates the use of a popular front-end library, Vue.js, which can be a lot faster and easier to work with than Node-RED's built-in Dashboard UI once you reach the point of needing to customise the UI beyond the standard capabilities of Dashboard.

This is a really quick demo of what you can do with uibuilder. Vue is an alternative to the likes of Angular (used by Dashboard), REACT, etc.

Note that this example is a copy of the MoonJS example altered to work with Vue instead. Moon was developed as a lighter weight alternative to Vue.

Here are the steps to set up (details given below):

  1. Install Vue & uibuilder using npm
  2. Add reference for Vue to settings.js
  3. Restart Node-RED and add a uibuilder node with input and output nodes (see the flow for the simplest of examples)
  4. Amend the template html, js and css files as per the examples below
  5. Open the uibuilder URL and send msg's both ways to see it working.

Note that I will assume your userDir is at ~/.node-red which is the default.

1. Install Vue & uibuilder using npm

Open a terminal/command prompt, cd to ~/.node-red (most platforms including Windows PowerShell, %USERPROFILE%\.node-red for Windows cmd prompt).

npm install node-red-contrib-uibuilder vue --save

2. Add reference for Vue to settings.js

Add the following to your ~/.node-red/settings.js file:

    uibuilder: {
        userVendorPackages: ['vue'],
        debug: true  // see docs for other values here
    }

Don't forget to fix any preceding/trailing comma's to ensure that the JavaScript object is valid.

3. Restart Node-RED and add a uibuilder node with input and output nodes

After restarting Node-RED, import the flow given below and deploy. Note that the instance of the uibuilder node in the example flow has its URL set to vue.

4. Copy/amend the template files

You should now have a folder ~/.node-red/uibuilder/vue/src. You need to copy over the files shown below into that folder. You will be replacing the files that the deployment of the node has copied over for you.

5. Open the uibuilder URL

In your favourite browser, navigate to the moon url. If you are using default settings and running Node-RED on the same machine as the browser, this will be http://localhost:1880/vue.

Now you can use the button in the web page to send a message back to Node-RED, the data will appear in the debug output pane. Then you can use the Inject node to send a message to the browser.

Example Flow

Import this to Node-RED & deploy.

[
    {
        "id": "65c207ab.b53e08",
        "type": "debug",
        "z": "106ba95c.ff91e7",
        "name": "vuejs-debug",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 480,
        "y": 260,
        "wires": []
    },
    {
        "id": "2283ab5e.70d5a4",
        "type": "uibuilder",
        "z": "106ba95c.ff91e7",
        "name": "vuejs",
        "url": "vue",
        "fwdInMessages": false,
        "customFoldersReqd": true,
        "x": 270,
        "y": 260,
        "wires": [
            [
                "65c207ab.b53e08"
            ]
        ]
    },
    {
        "id": "eb50e622.116878",
        "type": "inject",
        "z": "106ba95c.ff91e7",
        "name": "",
        "topic": "uibuilder-moon",
        "payload": "{\"lib\":\"vue\", \"testNum\": 500}",
        "payloadType": "json",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 130,
        "y": 260,
        "wires": [
            [
                "2283ab5e.70d5a4"
            ]
        ]
    }
]

Example front-end files

These go in ~/.node-red/uibuilder/vue/src

index.html

<!doctype html>
<html lang="en">
  <!--
    This is the default, template html for uibuilder.
    It is only meant to demonstrate the use of JQuery 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">

    <link rel="icon" href="images/node-red.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 rel="stylesheet" href="vendor/normalize.css/normalize.css" media="all">
    <link rel="stylesheet" href="index.css" media="all">

</head>
<body>
    <div id="app">
        <h1>
            UIbuilder + Vue.js for Node-RED
        </h1>
        <p>
            This is a uibuilder test 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>Dynamic Data (via Vue)</h2>

        <p>
            Messages: Received={{msgsReceived}}, Sent: {{msgsSent}}
        </p><p>
            Control Messages Received: {{msgsControl}}
        </p>

        <pre v-html="hLastRcvd"></pre>
        <pre v-html="hLastSent"></pre>

        <h2>Simple input using Vue</h2>
        <p><button v-on:click="increment">Increment</button> Click Counter: {{counterBtn}}. Click on the button to increment, it sends the data back to Node-RED as well.</p>
        
    </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/socket.io/socket.io.js"></script>
    <!-- Note no leading / -->
    <!-- OPTIONAL: JQuery can be removed if not required -->
    <!-- <script src="vendor/jquery/dist/jquery.min.js"></script> -->

    <script src="vendor/vue/dist/vue.js"></script>    <!-- //dev version -->
    <!-- <script src="vendor/moonjs/dist/vue.runtime.min.js"></script>   //prod version -->

    <!-- 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>

index.js

const app1 = new Vue({
    el: "#app",
    data: {
        startMsg    : "Vue has started, waiting for messages",
        feVersion   : '',
        counterBtn  : 0,
        msgsReceived: 0,
        msgsControl : 0,
        msgsSent    : 0,
        msgRecvd    : '[Nothing]',
        msgSent     : '[Nothing]',
        msgCtrl     : '[Nothing]',
        inputText   : ''
    }, // --- End of data --- //
    computed: {
        hLastRcvd: function() {
            const msgRecvd = this.msgRecvd
            if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd
            else return 'Last Message Received = ' + this.syntaxHighlight(msgRecvd)
        },
        hLastSent: function() {
            const msgSent = this.msgSent
            if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent
            else return 'Last Message Sent = ' + this.syntaxHighlight(msgSent)
        },
        hMsgCtrl: function() {
            const msgCtrl = this.msgCtrl
            if (typeof msgCtrl === 'string') return 'Last Message Sent = ' + msgCtrl
            //else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgCtrl])
            else return 'Last Message Sent = ' + JSON.stringify(msgCtrl)
        }
    }, // --- End of computed --- //
    methods: {
        increment: function() {
            // Increment the count by one
            this.counterBtn = this.counterBtn + 1
            let topic = this.msgRecvd.topic || 'uibuilder/vue'
            uibuilder.send( { 'topic': topic, 'payload': { 'type': 'counterBtn', 'btnCount': this.counterBtn, 'message': this.inputText } } )
        },
        // return formatted HTML version of JSON object
        syntaxHighlight: function(json) {
            json = JSON.stringify(json, undefined, 4)
            json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
            return 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>'
            })
        } // --- End of syntaxHighlight --- //
    }, // --- End of methods --- //

    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        console.debug('app mounted - setting up uibuilder watchers')

        vueApp = this

        vueApp.feVersion = uibuilder.get('version')

        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
        // Note that you can also listen for 'msgsReceived' as they are updated at the same time
        // but newVal relates to the attribute being listened to.
        uibuilder.onChange('msg', function(newVal){
            console.info('property msg changed!', newVal)
            vueApp.msgRecvd = newVal
        })
        // As noted, we could get the msg here too
        uibuilder.onChange('msgsReceived', function(newVal){
            console.info('New msg sent FROM Node-RED over Socket.IO. Total Count: ', newVal)
            vueApp.msgsReceived = newVal
        })

        // If a message is sent back to Node-RED
        uibuilder.onChange('sentMsg', function(newVal){
            console.info('property sentMsg changed!', newVal)
            vueApp.msgSent = newVal
        })
        uibuilder.onChange('msgsSent', function(newVal){
            console.info('New msg sent TO Node-RED over Socket.IO. Total Count: ', newVal)
            vueApp.msgsSent = newVal
        })

        // If we receive a control message from Node-RED
        uibuilder.onChange('ctrlMsg', function(newVal){
            console.info('property msgCtrl changed!', newVal)
            vueApp.msgCtrl = newVal
        })
        uibuilder.onChange('msgsCtrl', function(newVal){
            console.info('New CONTROL msg sent FROM Node-RED over Socket.IO. Total Count: ', newVal)
            vueApp.msgsControl = newVal
        })

        // If Socket.IO connects/disconnects
        uibuilder.onChange('ioConnected', function(newVal){
            console.info('Socket.IO Connection Status Changed: ', newVal)
            vueApp.socketConnectedState = newVal
        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

index.css

body {font-family: sans-serif;}
div, p, code, pre { margin:0.3em; padding: 0.3em;}
pre .string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }
Clone this wiki locally