Skip to content
Julian Knight edited this page Sep 2, 2019 · 4 revisions

UPDATE 2019-09-02: Updated for uibuilder v2.

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

It demonstrates the use of a lightweight front-end library, MoonJS, 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. MoonJS is a lightweight alternative to the likes of Angular (used by Dashboard), REACT, VueJS, etc. It is fast and really easy to use.

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

  1. Install uibuilder using the Node-RED pallete manager
  2. Add a uibuilder node & deploy
  3. Install moonjs & normalize.css using uibuilder's library manager
  4. Add input and output nodes (see the flow for the simplest of examples)
  5. Edit the index.[html,js,css] files as per the examples below
  6. 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.

Example Flow

Import this to Node-RED & deploy.

[
    {
        "id": "65c207ab.b53e08",
        "type": "debug",
        "z": "106ba95c.ff91e7",
        "name": "moonjs-debug",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 480,
        "y": 260,
        "wires": []
    },
    {
        "id": "2283ab5e.70d5a4",
        "type": "uibuilder",
        "z": "106ba95c.ff91e7",
        "name": "moonjs",
        "url": "moon",
        "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\":\"moon\", \"testNum\": 500}",
        "payloadType": "json",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 130,
        "y": 260,
        "wires": [
            [
                "2283ab5e.70d5a4"
            ]
        ]
    }
]

Example front-end files

index.html

<!doctype html>
<html lang="en"><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-blue.ico">

    <link rel="stylesheet" href="../uibuilder/vendor/normalize.css/normalize.css" media="all">
    <link rel="stylesheet" href="./index.css" media="all">

</head>
<body>
    <div id="app">
        <h1>
            UIbuilder + Moon for Node-RED
        </h1>
        <p>
            This is a uibuilder test using <a href="https://kbrsh.github.io/moon/">Moon.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 Moon)</h2>

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

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

        <h2>Simple input using Moon</h2>
        <p><button m-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/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/moonjs/dist/moon.min.js"></script>    <!-- //prod version -->
    <!-- <script src="../uibuilder/vendor/moonjs/dist/moon.js"></script>   //dev version -->
    <!-- <script src="./uibuilderfe.js"></script>   //dev version -->
    <script src="./uibuilderfe.min.js"></script> <!--    //prod version -->
    <!-- OPTIONAL: Put your custom code in here -->
    <script src="./index.js"></script>

</body>
</html>

index.js

/* jshint browser: true, esversion: 5, asi: true */
/* globals document,Moon,window,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.
*/
/**
 * This is based on the default template js for uibuilder.
 * It has been changed to remove JQuery but use MoonJS
 */

"use strict";

// Attach a Moon instance to html element with id "app"
const app1 = new Moon({
    el: "#app",
    data: {
        startMsg    : "Moon 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: {
            get: function() {
                const msgRecvd = this.get('msgRecvd')
                if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd
                else return 'Last Message Received = ' + this.callMethod('syntaxHighlight', [msgRecvd])
            }
        },
        hLastSent: {
            get: function() {
                const msgSent = this.get('msgSent')
                if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent
                else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgSent])
            }
        },
        hMsgCtrl: {
            get: function() {
                const msgCtrl = this.get('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.set('counterBtn', this.get('counterBtn') + 1)
            let topic = (this.get('msgRecvd')).topic || 'uibuilder/moon'
            uibuilder.send( { 'topic': topic, 'payload': { 'type': 'counterBtn', 'btnCount': this.get('counterBtn'), 'message': this.get('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 --- //
    hooks: { // Available hooks: init,mounted,updated,destroyed
        
        mounted: function(){
            console.debug('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
             * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
             * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names
             */
            uibuilder.start()

            this.set( '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)
                app1.set( '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)
                app1.set( 'msgsReceived', newVal)
            })


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


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

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

        } // --- End of mounted hook --- //
    } // --- End of hooks --- //
}) // --- 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