here are the files. I'll try to look for the logs after I come home from work
Files
bar.json
{
"description": "A basic stacked bar chart example.",
"width": 500,
"height": 200,
"padding": 5,
"data": [
{
"name": "table",
"transform": [
{
"type": "stack",
"groupby": ["x"],
"sort": {"field": "c"},
"field": "y"
}
]
}
],
"scales": [
{
"name": "x",
"type": "band",
"range": "width",
"domain": {"data": "table", "field": "x"}
},
{
"name": "y",
"type": "linear",
"range": "height",
"nice": true, "zero": true,
"domain": {"data": "table", "field": "y1"}
},
{
"name": "color",
"type": "ordinal",
"range": "category",
"domain": {"data": "table", "field": "c"}
}
],
"axes": [
{"orient": "bottom", "scale": "x", "zindex": 1},
{"orient": "left", "scale": "y", "zindex": 1}
],
"marks": [
{
"type": "rect",
"from": {"data": "table"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "x"},
"width": {"scale": "x", "band": 1, "offset": -1},
"y": {"scale": "y", "field": "y0"},
"y2": {"scale": "y", "field": "y1"},
"fill": {"scale": "color", "field": "c"}
},
"update": {
"fillOpacity": {"value": 1}
},
"hover": {
"fillOpacity": {"value": 0.5}
}
}
}
]
}
bar2.json
{
"description": "A basic stacked bar chart example.",
"width": 500,
"height": 200,
"padding": 5,
"data": [
{
"name": "table2",
"transform": [
{
"type": "stack",
"groupby": ["x"],
"sort": {"field": "c"},
"field": "y"
}
]
}
],
"scales": [
{
"name": "x",
"type": "band",
"range": "width",
"domain": {"data": "table2", "field": "x"}
},
{
"name": "y",
"type": "linear",
"range": "height",
"nice": true, "zero": true,
"domain": {"data": "table2", "field": "y1"}
},
{
"name": "color",
"type": "ordinal",
"range": "category",
"domain": {"data": "table2", "field": "c"}
}
],
"axes": [
{"orient": "bottom", "scale": "x", "zindex": 1},
{"orient": "left", "scale": "y", "zindex": 1}
],
"marks": [
{
"type": "rect",
"from": {"data": "table2"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "x"},
"width": {"scale": "x", "band": 1, "offset": -1},
"y": {"scale": "y", "field": "y0"},
"y2": {"scale": "y", "field": "y1"},
"fill": {"scale": "color", "field": "c"}
},
"update": {
"fillOpacity": {"value": 1}
},
"hover": {
"fillOpacity": {"value": 0.5}
}
}
}
]
}
index.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; }
/* Colours for Syntax Highlighted pre's */
body{
background: rgb(0, 0, 0);
max-width: 1250px;
margin: 20px auto;
}
h1{
color: red;
}
.vega1{
margin: 10;
position: absolute;
top: 50%;
-ms-transform: translateY(0%);
transform: translateY(0%);
}
.syntax-highlight {color:white;background-color:rgb(184, 9, 9);padding:5px 10px;}
.syntax-highlight > .key {color:#ffbf35}
.syntax-highlight > .string {color:#5dff39;}
.syntax-highlight > .number {color:#70aeff;}
.syntax-highlight > .boolean {color:#b993ff;}
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 id="view" class="vega1"></div>
<div id="view2" class="vega1"></div>
<b-card class="mt-3" header="Normal Messages" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="left" >
<b-button pill variant="primary" v-on:click="dataLoad">Load</b-button>
<b-button pill variant="secondary" v-on:click="dataReset">Reset</b-button><br />
<br />
<pre id="msg" v-html="showLastReceivedMsg" class="syntax-highlight">Waiting for a message from Node-RED</pre>
<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-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 -->
<!--
<script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
-->
<script src="http://localhost:1880/vega.js"></script>
<script src="http://localhost:1880/vega-embed.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) 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 */
/** Reference the component (removes need for a build step with import) */
//Vue.component('v-chart', VueECharts)
/** Reference the apexchart component (removes need for a build step) */
//Vue.component('apexchart', VueApexCharts)
// eslint-disable-next-line no-unused-vars
const app = new Vue({
el: '#app',
data() { return {
startMsg : 'Vue has started, waiting for messages',
feVersion : '',
counterBtn : 0,
inputText : null,
inputChkBox : false,
socketConnectedState : false,
serverTimeOffset : '[unknown]',
imgProps : { width: 75, height: 75 },
lastMsg : '[Nothing]',
msgR_NR: '[Nothing]',
msgsReceivedNR: 0,
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: {
/*hNR_Rcvd: function() {
var msgR_NR = this.msgR_NR
if (typeof msgR_NR === 'string') return 'Last Message Received = ' + msgR_NR
else return 'Last Message Received = ' + this.syntaxHighlight(msgR_NR)
},*/
showLastReceivedMsg: function() {
var lastMsg = this.lastMsg
if (typeof lastMsg === 'string') return 'Last Message Received = ' + lastMsg
else return 'Last Message Received = ' + this.syntaxHighlight(lastMsg)
},
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: {
dataReset: function() {
console.log('Reset.')
/*uibuilder.send( {
topic: 'load'
})*/
},
dataLoad: function() {
console.log('Button Pressed.')
uibuilder.send( {
topic: 'load'
})
},
/*
contextLoad: function() {
uibuilder.send({topic:'load_content1'})
},
contextSave: function() {
console.log('Save: ', this.content1)
uibuilder.send( {
'topic': 'save_content1',
'payload': {
'type': 'save',
'btnCount': this.content1
}
} )
},
*/
// Called from the increment button - sends a msg to Node-RED
/*
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 --- //
*/
// REALLY Simple method to return DOM events back to Node-RED. See the 2nd b-button on the default html
doEvent: uibuilder.eventSend,
// return formatted HTML version of JSON object
syntaxHighlight: function(json) {
json = JSON.stringify(json, undefined, 4)
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
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)
}, // --- End of created hook --- //
/** Called once all Vue component instances have been loaded and the virtual DOM built */
mounted: function(){
var app = this // Reference to `this` in case we need it for more complex functions
const origData = [
{"x": 0, "y": 0, "c": 0}, {"x": 0, "y": 0, "c": 1},
{"x": 1, "y": 0, "c": 0}, {"x": 1, "y": 0, "c": 1},
{"x": 2, "y": 0, "c": 0}, {"x": 2, "y": 0, "c": 1},
{"x": 3, "y": 0, "c": 0}, {"x": 3, "y": 0, "c": 1},
{"x": 4, "y": 0, "c": 0}, {"x": 4, "y": 0, "c": 1},
{"x": 5, "y": 0, "c": 0}, {"x": 5, "y": 0, "c": 1},
{"x": 6, "y": 0, "c": 0}, {"x": 6, "y": 0, "c": 1},
{"x": 7, "y": 0, "c": 0}, {"x": 7, "y": 0, "c": 1},
{"x": 8, "y": 0, "c": 0}, {"x": 8, "y": 0, "c": 1},
{"x": 9, "y": 0, "c": 0}, {"x": 9, "y": 0, "c": 1}
]
const origData2 = [
{"x": 0, "y": 58, "c": 0}, {"x": 0, "y": 55, "c": 1},
{"x": 1, "y": 43, "c": 0}, {"x": 1, "y": 91, "c": 1},
{"x": 2, "y": 81, "c": 0}, {"x": 2, "y": 53, "c": 1},
{"x": 3, "y": 19, "c": 0}, {"x": 3, "y": 87, "c": 1},
{"x": 4, "y": 52, "c": 0}, {"x": 4, "y": 48, "c": 1},
{"x": 5, "y": 24, "c": 0}, {"x": 5, "y": 49, "c": 1},
{"x": 6, "y": 87, "c": 0}, {"x": 6, "y": 66, "c": 1},
{"x": 7, "y": 17, "c": 0}, {"x": 7, "y": 87, "c": 1},
{"x": 8, "y": 18, "c": 0}, {"x": 8, "y": 16, "c": 1},
{"x": 9, "y": 49, "c": 0}, {"x": 9, "y": 15, "c": 1}
]
const vlJson = "bar.json"
// Embed visualization and save view as window.view:
vegaEmbed('#view', vlJson).then( res => {
res.view
.insert('table', origData)
.run()
// Save the view to window.view for later use
window.view = res.view
})
vegaEmbed('#view2', vlJson).then( res => {
res.view
.insert('table', origData2)
.run()
// Save the view to window.view for later use
window.view = res.view
})
//console.log(vega.changeset())
/*
uibuilder.onChange('msg', function (newVal) {
// We are assuming that msg.payload is an array like [datenum, value]
// Add new element
vueApp.areaChartData.push( new Array( (new Date(newVal.payload[0])), newVal.payload[1] ) )
// If data array > 1000 points, keep it at that length by losing the first point
if ( vueApp.areaChartData.length > 10 ) vueApp.areaChartData.shift()
//console.log(vueApp.areaChartData)
})
uibuilder.onChange('msg', function (newVal) {
if ( typeof newVal.payload === 'number' ){
// Add new element
vueApp.dseries.push(newVal.payload)
// Lose the first element
vueApp.dseries.shift()
//console.log(vueApp.dseries)
}
})
*/
/*
// Listen for incoming messages from Node-RED
uibuilder.onChange('msg', function(msg){
console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
// dump the msg as text to the "msg" html element
const eMsg = document.getElementById('msg')
eMsg.innerHTML = window.syntaxHighlight(msg)
var changeSet = vega
.changeset()
.insert(msg.payload)
// .remove(function (t) {
// return t.x < minimumX
// })
window.view.change('myData', changeSet).run().resize()
// Changeset needs to remove everything first, then insert new data
// let changeset = vega.changeset().remove(() => true).insert(data);
// For some reason source_0 is the default dataset name
// view.change('source_0', changeset).run()
})
*/
uibuilder.onChange('msg', function(msg){
console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
app.msgRecvd = msg
app.msgsReceived = uibuilder.get('msgsReceived')
app.lastMsg = msg
})
//#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)
app.msgCtrl = msg
app.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)
app.msgSent = msg
app.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)
app.msgCtrlSent = msg
app.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)
app.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)
app.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'))
app.isLoggedOn = isAuthorised
})
//#endregion ---- Debug info, can be removed for live use ---- //
}, // --- End of mounted hook --- //
}) // --- End of app1 --- //
// EOF