Uibuilder cache node

I am testing the uibuilder cache node, to keep the displayed data over a page refresh. but it doesn't look working. Context data is not showing any cache. Where am I wrong?

From the docs:

If you connect the uibuilder node's #2 lower output port to the input, clients will automatically request a replay of the cache whenever they connect or reconnect.

Flow:

[{"id":"924053e5df27e960","type":"uibuilder","z":"1c508b727f857010","name":"","topic":"","url":"uitable","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"vue-simple","extTemplate":"","showfolder":false,"reload":false,"sourceFolder":"src","deployedVersion":"5.0.2","x":420,"y":580,"wires":[[],["d93ddef94972bcc1"]]},{"id":"66f588bceac2ca8e","type":"inject","z":"1c508b727f857010","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"devices","payload":"{\"aspiratore_camera\":{\"status\":\"Online\",\"name\":\"Aspiratore stanzino\",\"teleperiod\":60,\"fw\":\"11.1.0(sensors)\",\"uptime\":\"3T00:55:31\",\"ip\":\"192.168.1.63\",\"ssid\":\"Vodafone-saltydog\",\"signal\":64},\"deumidificatore\":{\"status\":\"Online\",\"name\":\"Deumidificatore\",\"teleperiod\":10,\"fw\":\"11.1.0(sensors)\",\"uptime\":\"3T06:09:19\",\"ip\":\"192.168.1.64\",\"ssid\":\"Vodafone-saltydog\",\"signal\":58},\"contact-1\":{\"status\":\"Online\",\"name\":\"Contatto porta\",\"teleperiod\":10,\"fw\":\"11.1.0(lite)\",\"uptime\":\"3T00:56:12\",\"ip\":\"192.168.1.66\",\"ssid\":\"Vodafone-saltydog\",\"signal\":56},\"irr-laterali\":{\"status\":\"Online\",\"name\":\"Irrigatori laterali\",\"teleperiod\":60,\"fw\":\"11.1.0(tasmota)\",\"uptime\":\"3T00:58:25\",\"ip\":\"192.168.1.61\",\"ssid\":\"Saltydog_1ext\",\"signal\":74},\"irr-centrale\":{\"status\":\"Online\",\"name\":\"Irrigatore centrale\",\"teleperiod\":60,\"fw\":\"11.1.0(tasmota)\",\"uptime\":\"3T00:58:04\",\"ip\":\"192.168.1.60\",\"ssid\":\"Saltydog_1ext\",\"signal\":80},\"ventilatore\":{\"status\":\"Online\",\"name\":\"Ventilatore\",\"teleperiod\":10,\"fw\":\"11.1.0(tasmota)\",\"uptime\":\"1T03:07:36\",\"ip\":\"192.168.1.62\",\"ssid\":\"Saltydog_1ext\",\"signal\":70},\"cancello\":{\"status\":\"Online\",\"name\":\"Apricancello\",\"teleperiod\":10,\"fw\":\"11.1.0(tasmota)\",\"uptime\":\"3T00:56:41\",\"ip\":\"192.168.1.67\",\"ssid\":\"Saltydog_1ext\",\"signal\":92},\"pala-soffitto\":{\"status\":\"Online\",\"name\":\"Pala soffitto\",\"teleperiod\":5,\"fw\":\"11.1.0(sensors)\",\"uptime\":\"3T00:59:53\",\"ip\":\"192.168.1.68\",\"ssid\":\"Saltydog_1ext\",\"signal\":100},\"ac-salone\":{\"status\":\"Online\",\"name\":\"AC Salone\",\"teleperiod\":10,\"fw\":\"11.1.0(ir)\",\"uptime\":\"3T00:57:35\",\"ip\":\"192.168.1.57\",\"ssid\":\"Saltydog_1ext\",\"signal\":100},\"striscialedsalone\":{\"status\":\"Online\",\"name\":\"Striscia Led Salone\",\"teleperiod\":10,\"fw\":\"11.1.0(tasmota)\",\"uptime\":\"3T00:55:10\",\"ip\":\"192.168.1.51\",\"ssid\":\"Saltydog_1ext\",\"signal\":100},\"PAB-plug\":{\"status\":\"Online\",\"name\":\"Interruttore PAB\",\"teleperiod\":60,\"fw\":\"11.1.0(tasmota)\",\"uptime\":\"1T03:45:23\",\"ip\":\"192.168.1.70\",\"ssid\":\"Saltydog_1ext\",\"signal\":100},\"luxmetro\":{\"status\":\"Online\",\"name\":\"Luxmetro\",\"teleperiod\":1,\"fw\":\"11.1.0(sensors)\",\"uptime\":\"3T01:00:46\",\"ip\":\"192.168.1.71\",\"ssid\":\"Vodafone-saltydog\",\"signal\":70},\"monitoraf\":{\"name\":\"Monitor Antifurto\",\"ip\":\"192.168.1.65\",\"signal\":92,\"ssid\":\"Saltydog_1ext\",\"status\":\"Online\",\"fw\":\"FM - 3.0.1\"}}","payloadType":"json","x":170,"y":520,"wires":[["d93ddef94972bcc1"]]},{"id":"d93ddef94972bcc1","type":"uib-cache","z":"1c508b727f857010","cacheall":false,"cacheKey":"topic","num":1,"storeName":"default","name":"","x":410,"y":520,"wires":[["924053e5df27e960"]]}]

Testing now.

Tested using the "Simple VueJS" template which is what your flow uses - no edits made to the template files. It worked as expected. Clicking inject added an entry to the cache (as shown in the image above) and then reloading the page or opening a new tab with the page automatically receives the cache.

So, what could be wrong on my side? As said, I don't see any context variable added....
I have edited the "Simple VUE JS" code to reflect my needs and to draw the table.

Have you made any code changes to index.html or index.js? If so, can you share the code?

Also, attach a debug node to port #2 of the uibuilder node and set it to show everything. Lets make sure you are actually getting the control msg.

It should look at bit like this (this may be slightly different because I'm using the dev version of uibuilder):

image

The important bit is uibuilderCtrl: "client connect". Assuming I didn't mess anything up in v5.0.2 anyway.

This is from the debug node;

index.html:

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Dispositivi</title>
    <meta name="description" content="Dispositivi">

    <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" class="uib" v-cloak><!-- All UI code needs to be in here -->
        <b-container id="app_container"><!-- Wraps the bootstrap-vue formatting -->

            <h1>Dispositivi</h1>
            <div>
                <!--<li v-for="device in devices">@{{ device.name }}</li>-->
            </div>

            <pre id="msg" v-html="showLastReceivedMsg" class="syntax-highlight">Waiting for a message from Node-RED</pre>
            <div class="mt-5">
                <table id="devices">
                        <tr style="font-size:smaller">
                            <th>Device Name</th>
                            <th>MQTT topic</th>
                            <th>IP Address</th>
                            <th>WiFi AP</th>
                            <th>Strength</th>
                            <th>Telemetry</th>
                            <th>Firmware</th>
                            <th>Uptime</th>
                            <th>Status</th>
                            <th>Web UI</th>
                        </tr>
                <tr v-for="(v,k) in devices" style="font-size:smaller; border:1px dotted">
                            <td><b>{{v.name}}</b></td>
                            <td>{{k}}</td>
                            <td>{{v.ip}}</td>
                            <td>{{v.ssid}}</td>
                            <td>{{v.signal}}%</td>
                            <td>{{v.teleperiod}} min.</td>
                            <td>{{v.fw}}</td>
                            <td>{{v.uptime}}</td>
                            <td ng-class="{green: v.status=='Online', red: v.status=='Offline'}">{{v.status}}</td>
                            <td style="text-align:center"><a href="http://{{v.ip}}" target="_blank"><i class="fa fa-external-link" aria-hidden="true"></i></a></td>
                </tr>
                </table>
            </div>
        </b-container>
    </div>

    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->
    <script src="../uibuilder/vendor/socket.io/socket.io.js">/* REQUIRED: Socket.IO is loaded only once for all instances. Without this, you don't get a websocket connection */</script>
    <!-- Vendor Libraries - Load in the right order, use minified, production versions for speed -->
    <script src="../uibuilder/vendor/vue/dist/vue.min.js">/* prod version with component compiler */</script>
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.js">/* dev version with component compiler */</script> -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.runtime.min.js">/* prod version without component compiler */</script> -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.min.js">/* remove 'min.' to use dev version */</script> <!--  -->

    <script src="./uibuilderfe.min.js">/* REQUIRED: remove 'min.' to use dev version */</script>
    <script src="./index.js">/* OPTIONAL: Put your custom code here */</script>
    <!-- #endregion -->

</body></html>

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'

/** @see https://totallyinformation.github.io/node-red-contrib-uibuilder/#/front-end-library */

// eslint-disable-next-line no-unused-vars
const app = new Vue({
    el: '#app',
    data() { return {

        lastMsg    : '[Nothing]',
        devices    : '{}',

    }}, // --- End of data --- //

    computed: {

        // Show the last msg from Node-RED nicely formatted
        showLastReceivedMsg: function() {
            var lastMsg = this.lastMsg
            if (typeof lastMsg === 'string') return 'Last Message Received = ' + lastMsg
            if (lastMsg.topic === 'devices') {
                app.devices = lastMsg.payload
                return 
            }
            return 'Last Message Received = ' + this.syntaxHighlight(lastMsg)
        },

    }, // --- End of computed --- //

    methods: {

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

    /** Called after the Vue app has been created. A good place to put startup code */
    created: function() {

        uibuilder.start(this) // Single param passing vue app to allow Vue extensions to be used.

    }, // --- End of created hook --- //

    /** Called once all Vue component instances have been loaded and the virtual DOM built */
    mounted: function(){

        const app = this  // Reference to `this` in case we need it for more complex functions

        // If msg changes - msg is updated when a standard msg is received from Node-RED
        uibuilder.onChange('msg', function(msg){
            app.lastMsg = msg
        })

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

}) // --- End of app definition --- //

// EOF

index.css:

/* Load the defaults from `<userDir>/node_modules/node-red-contrib-uibuilder/front-end/src/uib-styles.css` */
@import url("./uib-styles.css");

/* Cloak elements on initial load to hide the possible display of {{ ... }} 
 * Add to the app tag or to specific tags
 * To display "loading...", change to the following:
 *    [v-cloak] > * { display:none }
 *    [v-cloak]::before { content: "loading…" }
 */
[v-cloak] { display: none; }

#devices {
  font-family: Arial, Helvetica, sans-serif;
  border-collapse: collapse;
  width: 100%;

}

#devices td, #devices th {
  border: 1px solid #ddd;
  padding: 8px;
}


#devices tr:nth-child(even){background-color: #f2f2f2;}

#devices tr:hover {background-color: #ddd;}

#devices th {
  padding-top: 12px;
  padding-bottom: 12px;
  text-align: left;
  background-color: #04AA6D;
  color: white;
}

.green {color:#04AA6D;}
.red {color:red;}

That works perfectly for me. The table re-displays if you reload the page or open a new copy.

What do you see on the page? Anything at all? Any errors in the browser console? Do you have Vue v2 installed and not v3?

Also, bootstrap-vue has a <b-table> component you can use or a lite version if you only need a simple table. Saves you messing with looping, headers, etc.

This is what I see when inject the data:

and this is what I see when refresh the page:

No errors in console:

...and no data in context:
Schermata 2022-06-17 alle 10.53.52

Yes, using Vue v2. I tried with b-table (I'm not very good yet with Vue) but I didn't know how to iterate through elements.

EDIT: Tested with Safari, Chrome and Firefox on Mac

I'm an idiot! I should have tried your code on my live server not on my dev PC! You are absolutely correct, it isn't working - my apologies. Let me think about how to fix this.

Right, I must have broken the cache node at some point and didn't realise.

As a workaround until I can get an update out, please add the following 2 nodes onto output port #2 on the flow that goes back to your cache node.

[{"id":"4381b6b5a5ec4ac2","type":"switch","z":"f748d0e6cc78a2ba","g":"124607d308a0f6a6","name":"","property":"uibuilderCtrl","propertyType":"msg","rules":[{"t":"eq","v":"client connect","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":550,"y":620,"wires":[["574fee9ab4e6bf8c"]]},{"id":"574fee9ab4e6bf8c","type":"change","z":"f748d0e6cc78a2ba","g":"124607d308a0f6a6","name":"","rules":[{"t":"set","p":"cacheControl","pt":"msg","to":"REPLAY","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":620,"wires":[["c6306e9378b9c1f2","445a19cff9f8da1f"]]}]

Here is the Cache Example in the uibuilder examples library adjusted with the additional 2 nodes:

I know things are working in the dev version which will be released before long as uibuilder v5.1.

Sorry about the confusion. I do normally try to avoid such silly mistakes!

1 Like

Pls, no apologies!!!

Thank you!

I confirm it is working now with the 2 additional nodes, bet where are the data stored? I don't see anything in the context.

It uses the standard Node-RED context store. You have to select the node, go to the context panel and refresh the entries for the "Node" section.

image

So if you want your cache to survive a Node-RED restart, you would need to add a file or db based store in Node-RED's settings.js and change the store in the node.

As with any cache, just take some care as to how much data you are storing. Node-RED will be keeping the data in memory for the default memory and file stores.

1 Like

As the table data I am displaying is saved into a global variable, I modified the flow in order not to use any cache (and save memory space) but detecting the uibuilderCtrl client connect message and use it to re-inject data into uibuilder node. The trick was to delete msg.uibuilderCtrl before re-injecting into the node.

[{"id":"ad658b5b35b05eb3","type":"change","z":"1c508b727f857010","name":"get global","rules":[{"t":"set","p":"payload","pt":"msg","to":"#:(saltyStore)::devices","tot":"global"},{"t":"set","p":"topic","pt":"msg","to":"devices","tot":"str"},{"t":"delete","p":"uibuilderCtrl","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":200,"y":720,"wires":[["924053e5df27e960"]]},{"id":"4381b6b5a5ec4ac2","type":"switch","z":"1c508b727f857010","name":"","property":"uibuilderCtrl","propertyType":"msg","rules":[{"t":"eq","v":"client connect","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":290,"y":800,"wires":[["ad658b5b35b05eb3"]]},{"id":"924053e5df27e960","type":"uibuilder","z":"1c508b727f857010","name":"","topic":"","url":"uitable","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"vue-simple","extTemplate":"","showfolder":false,"reload":false,"sourceFolder":"src","deployedVersion":"5.0.2","credentials":{},"x":420,"y":720,"wires":[["76d9f0655a8cdf11"],["4381b6b5a5ec4ac2"]]}]

Yes, uibuilder ignores any msg with uibuilderCtrl as a property to prevent loops.

One thing of note. You may find that you trigger the cache replay more often that really needed under uibuilder v5.0.x. That is because I never worked out a good way for the client to inform the server when a socket.io connection was genuinely new or whether it was a reconnection for some other reason (such as your client device going into sleep mode and then coming out). For uibuilder v5.1, I've fixed that and there will be an additional property msg.connections. Only if that is zero will uib-cache replay the cache.