So, I have a quick and dirty example using uibuilder and the (current) default jQuery template. Not terribly pretty but hopefully you can see the basics:
index.js
/*global document,$,window,uibuilder */
/*
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.
*/
'use strict'
function syntaxHighlight(json) {
json = JSON.stringify(json, undefined, 4)
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
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>'
})
}
$( document ).ready(function() {
// Keeping track of received topics
var topics = {}
// Turn on debugging for uibuilderfe (default is off)
uibuilder.debug(true)
uibuilder.onChange('msg', function(newVal){
//console.info('indexjs:msg: property msg has changed! ', newVal)
// keep track of inbound topics
if ( (newVal.topic !=='') && (newVal.topic !== undefined) ) {
var encodedTopic = encodeURIComponent(newVal.topic).replace(/\%/g,'')
// Existing topic?
if ( newVal.topic in topics ) {
// update count
topics[newVal.topic].count++
// update display
$('#t' + encodedTopic).html(syntaxHighlight(newVal))
} else { // new topic
// add to obj
topics[newVal.topic] = {'count': 0, 'data': newVal}
// add to list
$('#topicList').append('<li><a href="#t' + encodedTopic + '">' + newVal.topic + '</a></li>')
// add to display
$('#debug').append('<a href="#t' + encodedTopic + '"></a><h2>' + newVal.topic + '</h2><pre id="t' + encodedTopic + '">' + syntaxHighlight(newVal) + '</pre>')
}
}
})
}) // --- End of JQuery Ready --- //
// EOF
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, user-scalable=yes">
<title>uibuilder - Debug (jQuery version)</title>
<meta name="description" content="uibuilder - Debug (jQuery version)">
<link rel="icon" href="./images/node-blue.ico">
<link rel="stylesheet" href="./vendor/normalize.css/normalize.css">
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div id="app">
<div id="topicList"><ul></ul></div>
<div id="debug"></div>
</div>
<script src="/uibuilder/socket.io/socket.io.js"></script>
<script src="./vendor/jquery/dist/jquery.min.js"></script>
<script src="./uibuilderfe.min.js"></script>
<script src="./index.js"></script>
</body></html>
index.css
body {font-family: sans-serif;}
div, p, code { margin:0.3em; padding: 0.3em;}
pre .string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }
Example flow
[{"id":"df2a6744.0f2088","type":"uibuilder","z":"18cb249f.38bafb","name":"jDebug","topic":"","url":"jdebug","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"debugFE":false,"copyIndex":true,"x":410,"y":760,"wires":[[],[]]},{"id":"889953a1.0152e","type":"inject","z":"18cb249f.38bafb","name":"","topic":"devices/01/ab/02","payload":"{\"Oregon\":{\"location\":\"Patio\",\"source\":\"RFX\",\"sensors\":\"TH\",\"type\":\"Sensor\",\"description\":\"Oregon Scientific Temperature/Humidity Sensor\"},\"TH1/0x7D01\":{\"location\":\"Patio\",\"source\":\"RFX\",\"sensors\":\"TH\",\"type\":\"Sensor\",\"description\":\"Oregon Scientific Temperature/Humidity Sensor\"},\"SRF/UNO1\":{\"location\":\"Loft\",\"source\":\"SRF\",\"sensors\":\"THPL\",\"type\":\"Sensor\",\"description\":\"Arduino with Temperature, Humidity, Pressure and Light Sensors\"},\"ARD/UNO1\":{\"location\":\"Loft\",\"source\":\"SER\",\"sensors\":\"THPL\",\"type\":\"Sensor\",\"description\":\"Arduino with Temperature, Humidity, Pressure and Light Sensors\"},\"ARD/NANO1\":{\"location\":\"Loft\",\"source\":\"SER\",\"sensors\":\"THL\",\"type\":\"Sensor\",\"description\":\"Arduino with Temperature, Humidity and Light Sensors\"},\"HOMEEASY_EU/0x000081C0\":{\"location\":\"Landing\",\"source\":\"RFX\",\"sensors\":\"M\",\"type\":\"PIR\",\"description\":\"Home Easy PIR\",\"AKA\":\"AC/0x0000A9F8\"},\"LIGHTWAVERF/0xF4C95B\":{\"location\":\"Cellar Head\",\"source\":\"RFX\",\"sensors\":\"M\",\"type\":\"Mag\",\"description\":\"Siemens Magnetic Door Sensor\",\"AKA\":\"Freezer\"},\"LIGHTWAVERF/0xF4C36F\":{\"location\":\"Front Hall\",\"source\":\"RFX\",\"sensors\":\"M\",\"type\":\"Mag\",\"description\":\"Siemens Magnetic Door Sensor\"},\"LIGHTWAVERF/0xF2D8D7\":{\"location\":\"Landing\",\"source\":\"RFX\",\"sensors\":\"S\",\"type\":\"Remote\",\"description\":\"Siemens White 4 Channel Remote Control\"},\"LIGHTWAVERF/0xF30F69\":{\"location\":\"Loft\",\"source\":\"RFX\",\"sensors\":\"S\",\"type\":\"Remote\",\"description\":\"Siemens White 4 Channel Remote Control\"},\"LIGHTWAVERF/0xF55E26\":{\"location\":\"Loft\",\"source\":\"RFX\",\"sensors\":\"S\",\"type\":\"Remote\",\"description\":\"Siemens Black 4 Channel Remote Control\"},\"HOMEEASY_EU/0x00024404\":{\"location\":\"Front Hall\",\"source\":\"RFX\",\"sensors\":\"S\",\"type\":\"Remote\",\"description\":\"Home Easy dual remote control\",\"AKA\":\"AC/0x000247C4\"},\"AC/0x00C77BAA\":{\"location\":\"Front Hall\",\"source\":\"RFX\",\"sensors\":\"S\",\"type\":\"Bell\",\"description\":\"Nexa Doorbell, not in use\"},\"SELECT_PLUS/0x005AE0\":{\"location\":\"Front Hall\",\"source\":\"RFX\",\"sensors\":\"S\",\"type\":\"Bell\",\"description\":\"1ByOne Doorbell\"},\"SP01\":{\"location\":\"Loft Main\",\"source\":\"LAN\",\"sensors\":\"ES\",\"type\":\"Sensor, Smart Switch\",\"description\":\"Edimax SP2101W Smartswitch with energy monitor\"},\"D1M01\":{\"location\":\"NA\",\"source\":\"LAN\",\"sensors\":\"\",\"type\":\"Sensor\",\"description\":\"Wemos D1-mini\"},\"D1M02\":{\"location\":\"Rear Hall\",\"source\":\"LAN\",\"sensors\":\"THL\",\"type\":\"Sensor\",\"ipAddress\":\"192.168.1.152\",\"description\":\"Wemos D1-mini with Temperature, Humidity and Light\"},\"D1M03\":{\"location\":\"NA\",\"source\":\"LAN\",\"sensors\":\"\",\"type\":\"Sensor\",\"description\":\"Wemos D1-mini\"},\"D1M04\":{\"location\":\"Landing\",\"source\":\"LAN\",\"sensors\":\"TH\",\"type\":\"Sensor\",\"ipAddress\":\"192.168.1.187\",\"description\":\"Wemos D1-mini with Temperature, Humidity\"},\"D1M05\":{\"location\":\"Living Room\",\"source\":\"LAN\",\"sensors\":\"THL\",\"type\":\"Sensor\",\"ipAddress\":\"192.168.1.188\",\"description\":\"Wemos D1-mini with Temperature, Humidity and Light\"},\"Sonoff_073412\":{\"location\":\"Loft Main\",\"source\":\"LAN\",\"sensors\":\"S\",\"type\":\"Smart Switch\",\"description\":\"Sonoff Wi-Fi smart switch\"},\"POW1\":{\"location\":\"Patio\",\"source\":\"LAN\",\"sensors\":\"ES\",\"type\":\"Sensor, Smart Switch\",\"ipAddress\":\"192.168.1.159\",\"mac\":\"5c:cf:7f:20:dc:4d\",\"description\":\"Sonoff POW Wi-Fi smart switch\"},\"Pi2NR-LIVE\":{\"location\":\"Loft Main\",\"source\":\"LAN\",\"type\":\"Node-RED on Pi2\",\"ipAddress\":\"192.168.1.167\",\"description\":\"Node-RED on Pi2\"},\"Pi3NR-LIVE\":{\"location\":\"Loft Main\",\"source\":\"LAN\",\"type\":\"Node-RED on Pi3\",\"ipAddress\":\"192.168.1.169\",\"description\":\"Node-RED on Pi3\"}}","payloadType":"json","repeat":"5","crontab":"","once":true,"onceDelay":0.1,"x":250,"y":680,"wires":[["8c7b3d33.6e8aa"]]},{"id":"9d9fe6c2.35d2e8","type":"link in","z":"18cb249f.38bafb","name":"jDebug","links":["8c7b3d33.6e8aa"],"x":260,"y":760,"wires":[["df2a6744.0f2088"]]},{"id":"8c7b3d33.6e8aa","type":"link out","z":"18cb249f.38bafb","name":"To-jDebug","links":["9d9fe6c2.35d2e8"],"x":440,"y":680,"wires":[]},{"id":"d6b034cb.7ef188","type":"inject","z":"18cb249f.38bafb","name":"","topic":"devices.switches","payload":"{\"SWITCH09\":{\"id\":\"SWITCH09\",\"status\":\"Off\",\"lastUpdate\":\"2019-01-13T23:00:08.720Z\",\"room\":\"Loft Main\"},\"SWITCH02\":{\"id\":\"SWITCH02\",\"status\":\"On\",\"lastUpdate\":\"2019-01-13T13:01:22.383Z\",\"room\":\"Rear Hall\"},\"SWITCH03\":{\"id\":\"SWITCH03\",\"status\":\"On\",\"lastUpdate\":\"2019-01-13T23:37:57.831Z\",\"room\":\"Front Hall\"},\"SWITCH04\":{\"id\":\"SWITCH04\",\"status\":\"On\",\"lastUpdate\":\"2019-01-13T13:01:22.386Z\",\"room\":\"Landing\"},\"SWITCH05\":{\"id\":\"SWITCH05\",\"status\":\"On\",\"lastUpdate\":\"2019-01-13T23:37:57.833Z\",\"room\":\"NA\"},\"SWITCH06\":{\"id\":\"SWITCH06\",\"status\":\"On\",\"lastUpdate\":\"2019-01-13T13:01:22.386Z\",\"room\":\"NA\"},\"SWITCH01\":{\"id\":\"SWITCH01\",\"status\":\"On\",\"lastUpdate\":\"2019-01-13T23:37:57.834Z\",\"room\":\"Living Room\"},\"SWITCH07\":{\"id\":\"SWITCH07\",\"status\":\"Off\",\"lastUpdate\":\"2019-01-13T13:01:22.387Z\",\"room\":\"NA\"},\"SWITCH08\":{\"id\":\"SWITCH08\",\"status\":\"Off\",\"lastUpdate\":\"2019-01-13T13:01:22.388Z\",\"room\":\"NA\"},\"SWITCH10\":{\"id\":\"SWITCH10\",\"status\":\"OFF\",\"lastUpdate\":\"2019-01-13T23:30:01.176Z\",\"room\":\"Patio\"},\"SWITCH11\":{\"id\":\"SWITCH11\",\"status\":\"Off\",\"lastUpdate\":\"2019-01-13T13:01:22.390Z\",\"room\":\"Loft Main\"}}","payloadType":"json","repeat":"6","crontab":"","once":true,"onceDelay":"0.4","x":250,"y":640,"wires":[["8c7b3d33.6e8aa"]]}]
UPDATE: If anyone is interested, I also have another example using VueJS which is a bit nicer.