Getting started with node-red-contrib-uibuilder

2,929 views
Skip to first unread message

Colin Law

unread,
Sep 10, 2017, 5:26:50 AM9/10/17
to node...@googlegroups.com
Hi, I am giving node-red-contrib-uibuilder a go. I have got it
installed and showing the default index.html at
localhost:1880/uibuilder. However I am somewhat at a loss to know
exactly what to do to get it to show my data (not having much
experience with front-end development). Is there an example available
that does something simple like showing a value from the flow and
accepting input from a switch, for example? I just need something to
get me going, I think.

Colin

Julian Knight

unread,
Sep 10, 2017, 8:11:09 AM9/10/17
to Node-RED
Hi Colin. That is my node.

I don't think I've published an example other than the default included with the node. That uses JQuery to dynamically display data that is passed via the built-in websocket connection.

I also have a personal test using the RIOT front end library. I will try to find some time to publish that. Got to run to lunch just now, I'll see if I can put more together this afternoon.

Dave C-J

unread,
Sep 10, 2017, 10:37:43 AM9/10/17
to node...@googlegroups.com
Am liking the sound of RIOT... looks neat.

Julian Knight

unread,
Sep 10, 2017, 4:11:26 PM9/10/17
to Node-RED
Yes, it seems rather simpler than Vue and of course infinitely simpler than Angular! ;-)

I've only had a bit of a play with it but I chose it as a useful example of using uibuilder since you can simply add the module name to the list of modules and then hack on it fairly simply.

Colin, as promised, here is a quick hack for using RIOT:

In ~/.node-red/package.json (along with whatever you normally have):
"dependencies": {
   
"node-red-contrib-uibuilder": "*",
   
"riot": "^3.6.1"
 
}

In the module.exports part of your settings.js file, add this:

 uibuilder: {
   userVendorPackages
: ['riot'],
   debug
: true
 
}


Flow:
[{"id":"502557a7.e19678","type":"debug","z":"106ba95c.ff91e7","name":"","active":true,"console":"false","complete":"true","x":570,"y":100,"wires":[]},{"id":"c6005228.32f9c","type":"uibuilder","z":"106ba95c.ff91e7","name":"a","url":"riot/","fwdInMessages":true,"customFoldersReqd":true,"x":419.9461975097656,"y":100.18403625488281,"wires":[["502557a7.e19678"]]},{"id":"db7c70c3.20f8f","type":"change","z":"106ba95c.ff91e7","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{ \"ts\": $now(), \"cool\":\"very\"}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":260,"y":100,"wires":[["c6005228.32f9c"]]},{"id":"865a81d8.4e333","type":"inject","z":"106ba95c.ff91e7","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":100,"y":100,"wires":[["db7c70c3.20f8f"]]}]

The flow should create a ~/.node-red/uibuilder/riot folder. In the src subfolder, add index.css, index.html, index.js, with-tags.tag

index.css:
h1 { color: #448}
h2
{ color: #585}

index.html:
<!doctype html>
<html>
<head>
 
<meta charset="utf-8">
 
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

 
<title>Node-RED UI Builder - RiotJS</title>
 
<meta name="description" content="Node-RED UI Builder - Testing RiotJS">

 
<link rel="icon" href="images/logo-red.png">

 
<!-- See https://goo.gl/OOhYW5 -->
 
<link rel="manifest" href="manifest.json">
 
<meta name="theme-color" content="#3f51b5">

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

 
<!-- Add 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
 <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">
 
<link rel="stylesheet" href="index.css">
 
<!-- <script src="js/vendor/modernizr-2.8.3.min.js"></script> -->
 
</head>
<body>
 
<h1>Testing RiotJS</h1>
 
 
<!-- inlined tag definition, Custom tags can be empty, HTML only or JavaScript only -->

 
<my-tag></my-tag><!-- mount point -->

 
<script type="riot/tag">
 
<my-tag>
 
<h2>(0) Some local stuff first</h2>
 <p>
 msgCounter (global JS variable): {msgCounter.data}
 </
p>
 
<p>This only updates when riot.update is called. We call that in <i>index.js</i> when we receive a new msg from SocketIO.</p>

 
<h2>(1) Next is an HTML-only tag, no JS allowed ...</h2>
 
<tag1>
 
<p> We get no local html here so this doesn't show up unless you use &lt;yield/> in your tag definition.</p>
 
</tag1>

 <h2>(2) Next up, a JavaScript only Riot tag (see the console log) ...</
h2>
 
<tag2>
 
<p>
 HTML and content is inside the tag
, the tag definition is only Javascript.
 
The next line is an attribute (tag2Msg) from the JavaScript:
 
</p>
 { tag2Msg }
 </
tag2>

 
<tag3 />

 
<h2>(4) Next, an external tag file (<i>with-tags.js</i>) ...</h2>
 
<example message="We are passing a message!"/>

 
this.on('update', function() {
 
// allows recalculation of context data before the update

 console
.log('my-tag was updated')
 console
.log(msgCounter.data)
 
//console.log(msg)
 
})
 
</my-tag>
 
 <tag1>
 <p>This is an HTML-only tag, no JS allowed. So the following will be empty:</
p>
 
<p>A message on mount: { opts.mymsg }</p>
 
<p>But using &lt;yield/> lets us show information inserted where the tag is mounted (see <i>my-tag</i> in the source):</p>
 
<div style="margin-left:20%;margin-right:20%;border:1px solid silver;text-align:center;">
 
<yield/>
 
</div>
 </
tag1>
 
 
<tag2>
 
// This is a JavaScript-only tag, no HTML allowed

 
this.tag2Msg = 'Welcome to tag2'

 console
.log('tag2, a JavaScript-only tag')

 
this.on('mount', function(){
 console
.log('tag2 was mounted!')
 
})
 
</tag2>
 
 <tag3>
 <h2 onclick={updateMe}>(3) Another tag ...</
h2>
 
<p></p>
 
<p>Parent opts.mymsg: { parent.opts.mymsg }</p>
 
<p>Global msgCounter.data: { msgCounter.data }</p>
 
<p>This mymsg: { mymsg }</p>

 
var self = this
 
this.mymsg = 'Poo'
 
this.on('mount', function(){
 
//self.update()
 console
.log('tag3 was mounted')
 
})
 
this.on('update', function() {
 
// allows recalculation of context data before the update
 console
.log('tag3 was updated')
 
})
 updateMe
(e) {
 console
.log('tag3: updateMe')
 self
.mymsg = 'You clicked milord?'
 console
.dir(e)
 
//console.log(parent.opts.msgCounter)
 
//self.update()
 
}
 
</tag3>
 
</script>
 
 
 
<!-- <example/> is specified on external file -->
 
<script data-src="with-tags.tag" type="riot/tag"></script>

 
<h2>(5) That's it for RiotJS content. See the Javascript file as well.</h2>
 
<p>
 There really isn't much point using anything other than JQuery unless
 you need more complex interactions with the page. Simple pages are best done
 just with JQuery.
 
</p>

 
<h1>Dynamic Data (via JQuery)</h1>
 
<p>Messages Received: <span id="msgsReceived"></span></p>
 
<p>Control Messages Received: <span id="msgsControl"></span></p>
 
<p>Messages Sent: <span id="msgsSent"></span></p>
 
<p>Last Message Received:</p>
 
<code id="showMsg"></code>
 
<p>Last Message Sent:</p>
 
<code id="showMsgSent"></code>

 
<!-- Socket.IO is loaded only once for all instances -->
 
<script src="/uibuilder/socket.io/socket.io.js"></script>
 
<!-- Note no leading / -->
 
<script src="vendor/jquery/dist/jquery.min.js"></script>

 
<!-- include riot.js and the compiler -->
 
<script type="text/javascript" src="vendor/riot/riot+compiler.min.js"></script>

 
<script src="index.js"></script>

 
<!-- mount normally -->
 
<script>
 
</script>

</body>
</html>

index.js
/*global document,$,window,io,riot */
/*
 Copyright (c) 2017 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.
*/


var debug = true,
 ioChannels
= {control: 'uiBuilderControl', client: 'uiBuilderClient', server: 'uiBuilder'},
 msgCounter
= {control: 0, sent: 0, data: 0},
 msg
= {},
 cookies
= [],
 ioNamespace
= '/' + readCookie('uibuilder-namespace'),
 socket
,
 retryMs
= 2000, // retry ms period for manual socket reconnections workaround
 timerid

// When JQuery is ready, update
$
( document ).ready(function() {
 debug
&& console.log('Document Ready: IO Namespace: ' + ioNamespace)

 
// Create the socket - make sure client uses Socket.IO version from the uibuilder module (using path)
 socket
= io(ioNamespace, {
 path
: '/uibuilder/socket.io',
 transports
: ['polling', 'websocket']
 
})

 $
('#msgsReceived').text(msgCounter.data)
 $
('#msgsControl').text(msgCounter.control)
 $
('#msgsSent').text(msgCounter.sent)
 $
('#showMsg').text(JSON.stringify(msg))

 riot
.mount('*', {'msg': msg, 'mymsg': 'Hello there'})


 
// When the socket is connected .................
 socket
.on('connect', function() {
 debug
&& console.log('SOCKET CONNECTED - Namespace: ' + ioNamespace)

 
// Reset any reconnect timers
 
if (timerid) {
 window
.clearTimeout(timerid)
 retryMs
= 2000
 timerid
= null
 
}

 
// When Node-RED uibuilder template node sends a msg over Socket.IO...
 socket
.on(ioChannels.server, function(recievedMsg) {
 debug
&& console.info('uibuilder:socket.connect:socket.on.data - msg received - Namespace: ' + ioNamespace)
 
//console.dir(wsMsg)

 
// Make sure that msg is an object & not null
 
if ( recievedMsg === null ) {
 recievedMsg
= {}
 
} else if ( typeof recievedMsg !== 'object' ) {
 recievedMsg
= { 'payload': recievedMsg }
 
}

 
// Save the msg for further processing
 msg
= recievedMsg

 
// Track how many messages have been recieved
 msgCounter
.data++
 $
('#msgsReceived').text(msgCounter.data)
 $
('#showMsg').text(JSON.stringify(msg))

 
// Note that, unlike more complex libraries, Riot.JS does
 
// NOT update data dynamically! You have to call update.
 riot
.update()

 
// TODO: Add a check for a pre-defined global function here
 
// to make it easier for users to add their own code
 
// to process reciept of new msg
 
// OR MAYBE use msg.prototype to add a function?

 
// Test auto-response
 
if (debug) {
 sendMsg
({payload: 'We got a message from you, thanks'})
 
}

 
}) // -- End of websocket recieve DATA msg from Node-RED -- //

 
// Recieve a CONTROL msg from Node-RED
 socket
.on(ioChannels.control, function(recievedCtrlMsg) {
 debug
&& console.info('uibuilder:socket.connect:socket.on.control - msg received - Namespace: ' + ioNamespace)
 
//console.dir(wsMsg)


 
// Make sure that msg is an object & not null
 
if ( recievedCtrlMsg === null ) {
 recievedCtrlMsg
= {}
 
} else if ( typeof recievedCtrlMsg !== 'object' ) {
 recievedCtrlMsg
= { 'payload': recievedCtrlMsg }
 
}

 msgCounter
.control++
 $
('#msgsControl').text(msgCounter.control)
 $
('#showMsg').text(JSON.stringify(recievedCtrlMsg))

 
switch(recievedCtrlMsg.type) {
 
case 'shutdown':
 
// We are shutting down
 
break
 
case 'connected':
 
// We are connected to the server
 
break
 
default:
 
// Anything else
 
}

 
// Test auto-response
 
if (debug) {
 sendMsg
({payload: 'We got a control message from you, thanks'})
 
}

 
}) // -- End of websocket recieve CONTROL msg from Node-RED -- //

 
}) // --- End of socket connection processing ---

 
// When the socket is disconnected ..............
 socket
.on('disconnect', function(reason) {
 
// reason === 'io server disconnect' - redeploy of Node instance
 
// reason === 'transport close' - Node-RED terminating
 debug
&& console.log('SOCKET DISCONNECTED - Namespace: ' + ioNamespace + ', Reason: ' + reason)

 
// A workaround for SIO's failure to reconnect after a NR redeploy of the node instance
 
if ( reason === 'io server disconnect' ) {
 
if (timerid) window.clearTimeout(timerid) // we only want one running at a time
 timerid
= window.setTimeout(function(){
 debug
&& console.log('Manual SIO reconnect attempt, timeout: ' + retryMs)
 socket
.connect() // Try to reconnect
 retryMs
= retryMs + 1000 // extend timer for next time round
 
}, retryMs)
 
}
 
}) // --- End of socket disconnect processing ---

 
/* We really don't need these, just for interest
 socket.on('connect_error', function(err) {
 debug && console.log('SOCKET CONNECT ERROR - Namespace: ' + ioNamespace + ', Reason: ' + err.message)
 //console.dir(err)
 }) // --- End of socket connect error processing ---
 socket.on('connect_timeout', function(data) {
 debug && console.log('SOCKET CONNECT TIMEOUT - Namespace: ' + ioNamespace)
 console.dir(data)
 }) // --- End of socket connect timeout processing ---
 socket.on('reconnect', function(attemptNum) {
 debug && console.log('SOCKET RECONNECTED - Namespace: ' + ioNamespace + ', Attempt #: ' + attemptNum)
 }) // --- End of socket reconnect processing ---
 socket.on('reconnect_attempt', function(attemptNum) {
 debug && console.log('SOCKET RECONNECT ATTEMPT - Namespace: ' + ioNamespace + ', Attempt #: ' + attemptNum)
 }) // --- End of socket reconnect_attempt processing ---
 socket.on('reconnecting', function(attemptNum) {
 debug && console.log('SOCKET RECONNECTING - Namespace: ' + ioNamespace + ', Attempt #: ' + attemptNum)
 }) // --- End of socket reconnecting processing ---
 socket.on('reconnect_error', function(err) {
 debug && console.log('SOCKET RECONNECT ERROR - Namespace: ' + ioNamespace + ', Reason: ' + err.message)
 //console.dir(err)
 }) // --- End of socket reconnect_error processing ---
 socket.on('reconnect_failed', function(data) {
 debug && console.log('SOCKET RECONNECT FAILED - Namespace: ' + ioNamespace)
 console.dir(data)
 }) // --- End of socket reconnect_failed processing ---
 socket.on('ping', function() {
 debug && console.log('SOCKET PING - Namespace: ' + ioNamespace)
 }) // --- End of socket ping processing ---
 socket.on('pong', function(data) {
 debug && console.log('SOCKET PONG - Namespace: ' + ioNamespace + ', Data: ' + data)
 }) // --- End of socket pong processing ---
 */

});

// ----- UTILITY FUNCTIONS ----- //

// send a msg back to Node-RED, NR will generally expect the msg to contain a payload topic
var sendMsg = function(msgToSend) {
 
// Track how many messages have been sent
 msgCounter
.sent++
 $
('#msgsSent').text(msgCounter.sent)
 $
('#showMsgSent').text(JSON.stringify(msgToSend))

 socket
.emit(ioChannels.client, msgToSend)
} // --- End of Send Msg Fn --- //

function readCookie(name,c,C,i){
 
// @see http://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript
 
if(cookies.length > 0){ return cookies[name]; }
 c
= document.cookie.split('; ')
 
// @ts-ignore
 cookies
= {}
 
for(i=c.length-1; i>=0; i--){
 C
= c[i].split('=')
 cookies
[C[0]] = C[1]
 
}
 
return cookies[name]
} // --- End of readCookie fn --- //

// ----------------------------- //

// EOF

And finally, with-tags.tag:
<example>
 
<h1 show={this.show_message} onclick={uppercase}>{ message }</h1>
 
<input onkeyup={show_text} ref="input_text"/>
 
<button onclick={toggle_message}> toggle message </button>
 
<p>Parent mymsg: { this.parent.opts.mymsg }</p>

 
<p>Parent Keys:</p>
 
<ul>
 
<li each={ item, i in aparent }>
 ({ i }) { item }
 
</li>
 
</ul>

 
<p>Parent Opts Keys:</p>
 
<ul>
 
<li each={ item, i in Object.keys(this.parent.opts) }>
 ({ i }) { item }
 
</li>
 
</ul>

 
<p>Parent Opts Msg Keys:</p>
 
<ul>
 
<li each={ item, i in Object.keys(this.parent.opts.msg) }>
 ({ i }) { item }
 
</li>
 
</ul>

 
<p>Parent Msg Type: { typeof this.parent.opts.msg }</p>
 
 
<div if={Object.keys(msg).length}>
 
<p>Global Msg:</p>
 
<ul if={Object.keys(msg.payload).length}>
 
<li each={ item, i in msg.payload }>
 { i }: { item }
 
</li>
 
</ul>
 
</div>

 
<script>
 
this.message = opts.message || 'Hello Riot'
 
this.show_message = true
 
this.mymsg = opts.mymsg || this.parent.opts.mymsg
 
this.pmsg = this.parent.opts.msg
 
this.aparent = Object.keys(this.parent)
 uppercase
() {
 
this.message = this.message.toUpperCase()
 
}
 
this.show_text = function(e) {
 
this.message = this.refs.input_text.value
 
}
 
this.toggle_message = function(e) {
 
this.show_message = !this.show_message
 
}
 console
.info('Riot: Loaded this from external file ./with-tags.tag')
 
</script>
</example>

Now you should have the same demo that I have installed.

The resulting page will be on the /riot path. Click the inject from NR admin and you will see data coming into the riot page.

Colin Law

unread,
Sep 10, 2017, 4:47:11 PM9/10/17
to node...@googlegroups.com
Julian, as is so often the case I am overwhelmed with your generosity
with your time and knowledge when it comes to helping others.

I am tied up tomorrow but will, hopefully, be trying out your example
on Tuesday.

Thanks again

Colin
> --
> http://nodered.org
>
> Join us on Slack to continue the conversation: http://nodered.org/slack
> ---
> You received this message because you are subscribed to the Google Groups
> "Node-RED" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to node-red+u...@googlegroups.com.
> To post to this group, send email to node...@googlegroups.com.
> Visit this group at https://groups.google.com/group/node-red.
> To view this discussion on the web, visit
> https://groups.google.com/d/msgid/node-red/4abc8b80-10fe-4ad0-b050-fadf9a06362d%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Julian Knight

unread,
Sep 11, 2017, 2:02:01 AM9/11/17
to Node-RED
No problem. It really isn't as much as it seems as I've already invested the time for myself ;-)  The programming side of things is my creative outlet to match the enterprise IT I do for a living.

I'm keen for feedback on uibuilder, I'm sure that there is much that can be improved.

Colin Law

unread,
Sep 11, 2017, 12:55:14 PM9/11/17
to node...@googlegroups.com
Well I got the riot example going with no problems. I had to run npm
install to install riot which I managed to work out.
It loads super fast on my phone which is good.
A question though, having experimented a bit, if I send it a message
with sensor value (and topic), from MQTT for example, I can display it
ok, based on the topic. However, if I send the node a message and then
open the page in the browser then it does not show the value till I
send the value again, which might not be for some time. Am I missing
something?

By the way there is a certain inconsistency in the spelling of receive
in the node and docs. Some of them are recieved :)

Colin
> --
> http://nodered.org
>
> Join us on Slack to continue the conversation: http://nodered.org/slack
> ---
> You received this message because you are subscribed to the Google Groups
> "Node-RED" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to node-red+u...@googlegroups.com.
> To post to this group, send email to node...@googlegroups.com.
> Visit this group at https://groups.google.com/group/node-red.
> To view this discussion on the web, visit
> https://groups.google.com/d/msgid/node-red/6ed28be5-2b48-4515-bfc7-642b3f6291d8%40googlegroups.com.

Julian Knight

unread,
Sep 11, 2017, 2:22:02 PM9/11/17
to Node-RED


On Monday, 11 September 2017 17:55:14 UTC+1, Colin Law wrote:
Well I got the riot example going with no problems. I had to run npm
install to install riot which I managed to work out.
It loads super fast on my phone which is good.
A question though, having experimented a bit, if I send it a message
with sensor value (and topic), from MQTT for example, I can display it
ok, based on the topic. However, if I send the node a message and then
open the page in the browser then it does not show the value till I
send the value again, which might not be for some time.  Am I missing
something?

No, that's what you would expect. To do more, I would have to implement a store/forward mechanism within Node-RED and that would mean having to worry about what clients might connect when - otherwise I could never clear out previous messages. If you want to do that, it would be better to get the client page to connect directly to MQTT which supports such mechanisms via the retain flag. That isn't very hard and would be easy enough to add to the uibuilder page.

 
By the way there is a certain inconsistency in the spelling of receive
in the node and docs. Some of them are recieved :) 

That, of course, is deliberate to check whether you were really looking properly!! :)
Or maybe because I've only just installed a spell checker on vscode :( 

Colin Law

unread,
Sep 11, 2017, 4:03:45 PM9/11/17
to node...@googlegroups.com
Of course, connecting directly to MQTT from the page is the obvious
solution. It had not occurred to me.
More research required. How did we ever accomplish anything before the
internet was available?

Thanks

Colin
> --
> http://nodered.org
>
> Join us on Slack to continue the conversation: http://nodered.org/slack
> ---
> You received this message because you are subscribed to the Google Groups
> "Node-RED" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to node-red+u...@googlegroups.com.
> To post to this group, send email to node...@googlegroups.com.
> Visit this group at https://groups.google.com/group/node-red.
> To view this discussion on the web, visit
> https://groups.google.com/d/msgid/node-red/732cd4c8-deaa-4d30-b363-95e12fe0fbf0%40googlegroups.com.

Julian Knight

unread,
Sep 11, 2017, 5:11:33 PM9/11/17
to Node-RED
haha. Before Dashboard, and its predecessor UI was available, I played with such things more. I don't particularly like direct connections to MQTT because of the possibility of data leakage if you want to use the page over the Internet but it has its place. Just make sure you get a client library that supports auto-reconnection.

Colin Law

unread,
Sep 11, 2017, 5:23:19 PM9/11/17
to node...@googlegroups.com
Date leakage is not an issue for me as it is on an internal network or
via VPN. Did you decide on a good mqtt client? I was looking at
https://eclipse.org/paho/clients/js/

Colin
> https://groups.google.com/d/msgid/node-red/d5c6762f-6caf-408a-ad74-882b3ee2a462%40googlegroups.com.

Julian Knight

unread,
Sep 12, 2017, 4:17:33 AM9/12/17
to Node-RED
While the paho client is the standard one, it has always lacked auto-reconnect which you certainly want.

There is a request for it but it isn't clear as to whether it's gone live yet: https://github.com/eclipse/paho.mqtt.javascript/issues/48
So you will want to check that out.

Colin Law

unread,
Sep 12, 2017, 4:21:48 AM9/12/17
to node...@googlegroups.com
On 12 September 2017 at 09:17, Julian Knight <j.kni...@gmail.com> wrote:
> While the paho client is the standard one, it has always lacked
> auto-reconnect which you certainly want.

Oh, on the page I linked to in the checklist at the top, it says
auto-reconnect is supported.
https://eclipse.org/paho/clients/js/

Colin

Colin Law

unread,
Sep 12, 2017, 6:45:09 AM9/12/17
to Node-RED

I think I have an alternative solution to the data reload on browser connect or refresh problem that is more in keeping with the node red paradigm.

Firstly add a line to index.js inside socket.on() to send a message indicating a connection has been established

sendMsg({command: 'reload', payload: 'Browser connected'})

Then use this flow to save topic/payload pairs passed to the uibuilder node and automatically reload them when a new browser connection is made

[
   
{
       
"id": "4752fa78.9d12dc",
       
"type": "comment",
       
"z": "a64c0170.a07c38",
       
"name": "Msg for uibuilder go in here ->",
       
"info": "",
       
"x": 130,
       
"y": 120,
       
"wires": []
   
},
   
{
       
"id": "3bc80526.35ff62",
       
"type": "function",
       
"z": "a64c0170.a07c38",
       
"name": "Topic repeater",
       
"func": "// Given messages containing topic/payload values this saves the latest payload for each topic\n// and passes the message on.\n// If it receives a message with msg.command set to 'reload' it retransmits each saved pair as\n// individual messages.\n// If it receives a message with msg.command set to 'reset' it removes all saved data\nif (msg.command && msg.command === 'reload') {\n    // a reload message so re-send all messages saved\n    var keys = context.keys();\n    for (var i = 0; i < keys.length; i++) {\n        node.send({topic: keys[i], payload: context.get(keys[i])});\n    }    \n    msg = null;\n} else if (msg.command && msg.command === 'reset') {\n    // a reset command so remove all saved messages\n    var keys = context.keys();\n    for (var i = 0; i < keys.length; i++) {\n        context.set(keys[i], undefined);\n    }\n    msg= null;\n} else {\n    // a normal message so add/update the payload for this topic, provided there is a topic\n    if (msg.topic) {\n        context.set(msg.topic, msg.payload);\n    }\n}\n// pass on the message unless it has been nulled\nreturn msg;\n",
       
"outputs": 1,
       
"noerr": 0,
       
"x": 380,
       
"y": 120,
       
"wires": [
           
[
               
"fb556f6b.d97658",
               
"fccca17e.d4a148"
           
]
       
]
   
},
   
{
       
"id": "fb556f6b.d97658",
       
"type": "uibuilder",
       
"z": "a64c0170.a07c38",
       
"name": "",
       
"url": "uibuilder",
       
"fwdInMessages": false,
       
"customFoldersReqd": true,
       
"x": 380,
       
"y": 60,
       
"wires": [
           
[
               
"902f597f.daef6"
           
]
       
]
   
},
   
{
       
"id": "902f597f.daef6",
       
"type": "switch",
       
"z": "a64c0170.a07c38",
       
"name": "",
       
"property": "command",
       
"propertyType": "msg",
       
"rules": [
           
{
               
"t": "null"
           
},
           
{
               
"t": "else"
           
}
       
],
       
"checkall": "true",
       
"outputs": 2,
       
"x": 510,
       
"y": 60,
       
"wires": [
           
[],
           
[
               
"3bc80526.35ff62"
           
]
       
]
   
},
   
{
       
"id": "427cb125.4eeb48",
       
"type": "comment",
       
"z": "a64c0170.a07c38",
       
"name": "Normal msgs on o/p 1",
       
"info": "",
       
"x": 680,
       
"y": 40,
       
"wires": []
   
}
]

I don't think this should add any significant overheads and seems to work well.
It won't work for things like charts, but they have to be dealt with separately anyway I think.

Colin

Colin Law

unread,
Sep 12, 2017, 10:34:27 AM9/12/17
to node...@googlegroups.com
On 12 September 2017 at 11:45, Colin Law <cla...@gmail.com> wrote:
>
> I think I have an alternative solution to the data reload on browser connect
> or refresh problem that is more in keeping with the node red paradigm.
>
> Firstly add a line to index.js inside socket.on() to send a message
> indicating a connection has been established
>
> sendMsg({command: 'reload', payload: 'Browser connected'})

I meant inside socket.on('connect'..) of course.

Colin

Julian Knight

unread,
Sep 14, 2017, 12:22:20 PM9/14/17
to Node-RED
That seems workable indeed and probably the best solution in some cases. Rather depends on payload size, NR service reliability (e.g. if NR is restarted between connections) and so on.

Nice thing about uibuilder is that you can do all this and more without too much in the way of boilerplate and framework code getting in the way.
Reply all
Reply to author
Forward
0 new messages