Display Data from MySQL without uibuilder

I've been playing with uibuilder as time allows for the past couple months. Giving up on it... unfortunately. I like it but I can't get anywhere with it. The most I can get it to display is "Object object" or the entire string of last message received. I'm just done with the frustration I've encountered with uibuilder and don't have much more time to just play around. I need to finish this project.

I have a flow that reads data from MySQL. That part works fine. I want to take specific columns from the table and display them (auto refreshing data). I'm really just asking what other people are using.

Thank you,
Chris

Well, you could reach out to the author of uibuilder who, I believe, inhabits this forum - ahem - :wink:

What you should see, if using the default template as a starting example, is that every inbound and outbound msg is displayed on-screen as formatted JSON.

Seeing "Object object" just means that you've not got your head quite around how to handle JavaScript objects. The default template is there to demonstrate this.

If your inbound data is an array of objects (the array entries each being a row, the objects containing each column as a property and value pair). Then you can pass that directly to the b-table element that is provided by bootstrap-vue.

Of course, if you are struggling with VueJS rather than uibuilder, you could also switch to jQuery with one of the many table addins. Or indeed any other framework you fancy.


If you really want to give up on it, you will need to use the contributed ui_table node most likely. I've personally never had much luck with that but clearly other people are using it fine.

I'm not sure what I'm struggling with. I can get it to display the raw inbound message or "Object object." I try manipulating the object with javascript in index.js and end up with a blank screen. All I can get through searches is "read the docs" or clickbait sites. I've been through the docs and the default templates but can't wrap my head around this.

Hmm, maybe I need to add a table example for a future release. I'll add that to my to do list.

In the meantime, do you have some example data (in json format) that you can share? If so, I'll quickly knock together a table display demo.


Here is the docs for bootstrap-vue's table component: Table | Components | BootstrapVue (bootstrap-vue.org)

So you need a suitable Vue data variable to hold the table data. And then you update that variable from the incoming msg.payload - either updating the whole thing if your incoming msg contains the full table or updating the appropriate entry.

Sure.

Last Message Received = { "topic": "SELECT Asset, Error, Status, Alerts FROM Assets ORDER BY Asset", "payload": [ { "Asset": "Asset 1", "Error": null, "Status": null, "Alerts": null }, { "Asset": "Asset 2", "Error": "978", "Status": "47", "Alerts": null } ], "qos": 0, "retain": false, "_topic": "Asset 2", "_msgid": "7d22f798.22cd78" }

I can get the data to display just fine in ui-table. The second part of parsing my data is taking the values and looking up and display associated data. For example, I'd have a .csv file containing all error codes for a specific asset. I want to look up the error and status codes being sent by each asset so I can retrieve a short description to display. I just haven't gotten to that part yet since I haven't been able to manipulate the data.

Thank you,
Chris

I have an example of what you are trying to do below - been working on something similar.

Needs some error handling or logic tweaking as gaps in the error table cause warnings/crashes. Trigger the inject nodes in order and it should all work, assuming you have bootstrap and vue installed.

Array matching shamelessly stolen from here https://stackoverflow.com/questions/55587376/js-append-key-to-matching-array-object-values

Using one of the other answers may be clearer syntactically. The for each in the array is more understandable, but map is quick.

image

Flow

[{"id":"3d16c93e.f3a486","type":"tab","label":"Flow 5","disabled":false,"info":""},{"id":"a97b78e7.a2d7e8","type":"uibuilder","z":"3d16c93e.f3a486","name":"","topic":"","url":"uibuilder","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"useSecurity":false,"sessionLength":432000,"tokenAutoExtend":false,"x":600,"y":260,"wires":[[],[]]},{"id":"1f125fb4.74435","type":"inject","z":"3d16c93e.f3a486","name":"First","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"items","payload":"[{\"Asset\":\"Asset 1\",\"Error\":\"123\",\"Status\":null,\"Alerts\":null},{\"Asset\":\"Asset 2\",\"Error\":\"978\",\"Status\":\"47\",\"Alerts\":null}]","payloadType":"json","x":370,"y":260,"wires":[["a97b78e7.a2d7e8"]]},{"id":"6b57b1c3.3579f","type":"inject","z":"3d16c93e.f3a486","name":"Second","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"errors","payload":"[{\"Error\":\"978\",\"Description\":\"Fire\"},{\"Error\":\"123\",\"Description\":\"Flames\"}]","payloadType":"json","x":370,"y":300,"wires":[["a97b78e7.a2d7e8"]]},{"id":"961d1962.7f0198","type":"inject","z":"3d16c93e.f3a486","name":"Third","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"updateTable","payload":"true","payloadType":"bool","x":370,"y":340,"wires":[["a97b78e7.a2d7e8"]]}]

Index .js

/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
/*
  Copyright (c) 2021 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'

// eslint-disable-next-line no-unused-vars
const app = new Vue({
    el: '#app',
    data: {
        items:[],
        errors:[],
        output:[],
    }, 
    created: function() {
        uibuilder.start(this);
    }, 
    mounted: function() {
        var app = this;  // Reference to `this` in case we need it for more complex functions
        uibuilder.onChange('msg', function (newVal) {
            vueApp.msgRecvd = newVal;
            if (newVal.topic == 'items') {
                vueApp.items = newVal.payload;
            }
            if (newVal.topic == 'errors') {
                vueApp.errors = newVal.payload;
            }
            if (newVal.topic == 'updateTable') {
                if (vueApp.items.length > 0 && vueApp.errors.length > 0) {
                        vueApp.output = vueApp.items.map(obj => {
                            var itemsRef = obj.Error;
                            var arr2Obj = vueApp.errors.find(tmp => tmp.Error === itemsRef);
                            if (arr2Obj) return { ...obj,  Description: arr2Obj.Description };
                    });
                }
            }
        });
    }

})

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, initial-scale=1">

    <title>Node-RED UI Builder - VueJS + bootstrap-vue default template</title>
    <meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue default template">

    <link rel="icon" href="./images/node-blue.ico">

    <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" />
    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

</head>
<body>
    <div id="app" v-cloak>
        <b-container id="app_container">
            <div>
                <b-table dark
                         striped
                         bordered
                         :hover="false"
                         :items="items"
                         responsive="sm"
                         text-variant="white"
                         style="margin-bottom: 0px;">
                </b-table>
                <br>
                <b-table dark
                         striped
                         bordered
                         :hover="false"
                         :items="errors"
                         responsive="sm"
                         text-variant="white"
                         style="margin-bottom: 0px;">
                </b-table>
                <br>
                <b-table dark
                         striped
                         bordered
                         :hover="false"
                         :items="output"
                         responsive="sm"
                         text-variant="white"
                         style="margin-bottom: 0px;">
                </b-table>
            </div>
        </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, use minified, production versions for speed -->
    <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> <!-- Dev version -->
    <!-- <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.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>
1 Like

Here is the uibuilder table display for your example data. I needed 1 line of html and 2 lines of JS. Note that virtually all of the html/js code is from the default template.

image

[
    {
        "id": "d95440ff.7a66b",
        "type": "inject",
        "z": "ff1a7711.244f48",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "query",
                "v": "SELECT Asset, Error, Status, Alerts FROM Rides ORDER BY Ride",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "Asset 2",
        "payload": "[{\"Asset\":\"Asset 1\",\"Error\":null,\"Status\":null,\"Alerts\":null},{\"Asset\":\"Asset 2\",\"Error\":\"978\",\"Status\":\"47\",\"Alerts\":null}]",
        "payloadType": "json",
        "x": 250,
        "y": 120,
        "wires": [
            [
                "c5f12bf1.cc19d8"
            ]
        ]
    },
    {
        "id": "c5f12bf1.cc19d8",
        "type": "uibuilder",
        "z": "ff1a7711.244f48",
        "name": "",
        "topic": "",
        "url": "tbl-test",
        "fwdInMessages": false,
        "allowScripts": false,
        "allowStyles": false,
        "copyIndex": true,
        "showfolder": false,
        "useSecurity": false,
        "sessionLength": 432000,
        "tokenAutoExtend": false,
        "x": 390,
        "y": 120,
        "wires": [
            [
                "d8c785cf.46fd48"
            ],
            [
                "807ed33b.10688"
            ]
        ]
    },
    {
        "id": "d8c785cf.46fd48",
        "type": "debug",
        "z": "ff1a7711.244f48",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 590,
        "y": 100,
        "wires": []
    },
    {
        "id": "807ed33b.10688",
        "type": "debug",
        "z": "ff1a7711.244f48",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 570,
        "y": 140,
        "wires": []
    }
]

index.html

<!doctype html>

    <title>Node-RED UI Builder - VueJS + bootstrap-vue default template</title>
    <meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue default template">

    <link rel="icon" href="./images/node-blue.ico">

    <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" />
    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

</head><body>

    <div id="app" v-cloak>
        <b-container id="app_container">
            <h1>Table Demo</h1>
            
            <h2>Table</h2></ht>
            <b-table striped hover :items="tblData"></b-table>
            
            <hr>
            
            <h2>Dynamic Data</h2>
            <div>
                <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>
            </div>

        </b-container>
    </div>

    <!-- Dont forget to use minified versions of libraries for production, non-min versions for development only -->
    <!-- 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>

index.js

/* 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: {
        
        tblData: [],
        
        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,

        isLoggedOn  : false,
        userId      : null,
        userPw      : null,
        inputId     : '',
    }, // --- 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 --- //

        doLogon: function() {
            uibuilder.logon( {
                'id': this.inputId,
            } )
        }, // --- End of doLogon --- //

        doLogoff: function() {
            uibuilder.logoff()
        }, // --- End of doLogon --- //

        // 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;')
            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: beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed, activated,deactivated, errorCaptured

    /** Called after the Vue app has been created. A good place to put startup code */
    created: function() {
        // Example of retrieving data from uibuilder
        this.feVersion = uibuilder.get('version')

        /** **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('/uib', '/uibuilder/vendor/socket.io') // change to use your paths/names
         * @param {Object=|string=} namespace Optional. Object containing ref to vueApp, Object containing settings, or String IO Namespace override. changes self.ioNamespace from the default.
         * @param {string=} ioPath Optional. changes self.ioPath from the default
         * @param {Object=} vueApp Optional. Reference to the VueJS instance. Used for Vue extensions.
         */
        uibuilder.start(this) // Single param passing vue app to allow Vue extensions to be used.

        //console.log(this)
    },

    /** Called once all Vue component instances have been loaded and the virtual DOM built */
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        var vueApp = this  // Reference to `this` in case we need it for more complex functions

        // Example of retrieving data from uibuilder
        vueApp.feVersion = uibuilder.get('version')

        // 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(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            vueApp.msgRecvd = msg
            vueApp.msgsReceived = uibuilder.get('msgsReceived')
            
            vueApp.tblData = msg.payload
        })

        //#region ---- Debug info, can be removed for live use ---- //

        /** You can use the following to help trace how messages flow back and forth.
         * You can then amend this processing to suite your requirements.
         */

        // 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(msg){
            //console.info('[indexjs:uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', msg)
            vueApp.msgCtrl = msg
            vueApp.msgsControl = uibuilder.get('msgsCtrl')
        })

        /** 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(msg){
            //console.info('[indexjs:uibuilder.onChange:sentMsg] msg sent to Node-RED server:', msg)
            vueApp.msgSent = msg
            vueApp.msgsSent = uibuilder.get('msgsSent')
        })

        /** If we send a control message to Node-RED, we can get a copy of it here */
        uibuilder.onChange('sentCtrlMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', msg)
            vueApp.msgCtrlSent = msg
            vueApp.msgsCtrlSent = uibuilder.get('msgsSentCtrl')
        })

        /** If Socket.IO connects/disconnects, we get true/false here */
        uibuilder.onChange('ioConnected', function(connected){
            //console.info('[indexjs:uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', connected)
            vueApp.socketConnectedState = connected
        })
        /** If Server Time Offset changes */
        uibuilder.onChange('serverTimeOffset', function(serverTimeOffset){
            //console.info('[indexjs:uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', serverTimeOffset)
            vueApp.serverTimeOffset = serverTimeOffset
        })

        /** If user is logged on/off */
        uibuilder.onChange('isAuthorised', function(isAuthorised){
            //console.info('[indexjs:uibuilder.onChange:isAuthorised] isAuthorised changed. User logged on?:', isAuthorised)
            //console.log('authData: ', uibuilder.get('authData'))
            //console.log('authTokenExpiry: ', uibuilder.get('authTokenExpiry'))
            vueApp.isLoggedOn = isAuthorised
        })

        //#endregion ---- Debug info, can be removed for live use ---- //

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

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

// EOF
1 Like

I kind of get it now. One question... why is it that the table no longer displays if I delete the other content in b-container?

I guess a second question... Is it possible to format the table (change column sizes, etc.)? I'm guessing that would need to be done in CSS?

Probably because you have to reload the page and doing so resets all of the variables in the browser. You can easily work round this by pushing the data to local storage - though browsers normally put a fixed limit on the amount of data a page can store, 5MB I think. More than enough for most things.

Alternatively, you can add a "cache" in Node-RED. A caching example is provided with uibuilder. There is a copy in the WIKI as well. When uibuilder starts up, it sends out a number of control messages. Specifically, the second output of the uibuilder node outputs these messages. When a browser tab connects to Node-RED via the uibuilder.start function, a "CACHE REPLAY" message is output. You build a flow that listens for this and sends out the cached data so that every new tab for every client and whenever a tab is reloaded all get the cached data.

Absolutely, the details are in the link I shared. There are tons of ways and lots of clever things you can very easily do.

You can use CSS but you can also provide overrides in code. Bootstrap-vue also provides some standard colours and highlighting.

You can provide a second data variable to the b-table tag:

<b-table striped hover :items="itemsVar" :fields="fieldsVar"></b-table>

The fieldsVar has an entry for each field (column) in your table and each field definition gives you loads of controls. You can specify foreground and background formatting, format specific data types (numbers, dates, etc), change the HTML for the column cells - for example allowing an input tag to be used for entering data.

You can also add synthetic fields that can be used to show calculated information or add buttons, icons, etc.

Table paging, sorting, filtering, .... anything you want is possible.

I expected it to not display initially, but completely expected it to load after it received data. Is that not how it works?

Yes, have you tried my example?

When you receive data, you assign it to the Vue data variable and Vue updates the display for you.

Your code works fine if I leave the other sample code in index.html. If I remove everything else in and only have...

<b-container id="app_container">
            <h1>Table Demo</h1>
            
            <h2>Table</h2></ht>
            <b-table striped hover :items="tblData"></b-table>
            <hr>
        </b-container>

...the table never displays. If I add...

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

...back in it works fine.

Open your dev tools in the browser and go to the console to see what errors you have created :slight_smile:

You will find that Vue is crashing because something is missing. You need to adjust the index.js file accordingly. Just rip out what you don't need.

You might also want to install the VueJS browser extension which lets you view some of the inner workings of Vue.

The warnings shown in the screenshot show up whether or not I include or delete the demo code in the template. The table code you provided doesn't work unless I leave the demo code in the template.

MWSnap 2021-04-14, 07_12_59

The error here is pretty clear. You have made the b-table element dependent on the Vue data variable called "tblData". But you actually haven't defined that in the data section of the Vue app in your index.js.

You must add tblData: [], into your data section.

In VueJS (and the same in things like REACT and Angular), you HAVE to pre-define your data variables otherwise the framework cannot use them to add reactivity to your HTML.

1 Like

OK. I see the line I missed and added it. Why does it work when I don't remove the sample code from index.html?

Very little has been clear with NodeJS and the documentation. Stuff I already know or somewhat know seems to be easy to figure out. The docs don't seem to clear much else up.

Thank you,
Chris

Thank you for the help. That part is working. Not on to my next headache... reading a specific line from a text file in a Javascript function. I've been playing with it for several hours. I'll probably be posting another topic later.

Front end or back end?

In the back end, Node-RED, use the file read node with it set to split lines to an array. Then in a function node, you can access msg.payload[line-number] if you know it or loop through the array or use the filter function to find the line you want.

I'm going to be reading the status of over 50 different assets and will be accessing two files for each. It seems like it would be more efficient to just read the specific line I want in the function at the time I need it.

let fs = global.get('fs')

for (index = 0; index < msg.payload.length; ++index) {
    dbError = '/home/pi/.node-red/uibuilder/uibuilder/src/db/' + msg.payload[index]['Asset'] + ' Errors.txt'

    line = (read specific line of file)
    msg.payload[index]['Error'] = line
    
    dbStatus = '/home/pi/.node-red/uibuilder/uibuilder/src/db/' + msg.payload[index]['Asset'] + ' Status.txt'

    line = (read specific line of file)
    msg.payload[index]['Status'] = line
}

return msg;