Skip to content
Kazushige Sasaki edited this page Apr 6, 2018 · 2 revisions

Here is a quick example 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="uibuilderfe.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 msgCounter = {control: 0, sent: 0, data: 0},
 msg = {},
 timerid

uibuilder.debug(true)

// When JQuery is ready, update
$( document ).ready(function() {

 $('#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 Node-RED uibuilder template node sends a msg over Socket.IO...
 uibuilder.onChange('msg', function(recievedMsg) {

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

 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 (uibuilder.get('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
 uibuilder.onChange('ctrlMsg', function(recievedCtrlMsg) {
  
 // 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 (uibuilder.get('debug')) {
  sendMsg({payload: 'We got a control message from you, thanks'})
}

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

});

// ----- 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))
 
  uibuilder.send(msgToSend)
 } // --- End of Send Msg 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.

Clone this wiki locally