Problem with Template UI element / TypeError: Class extends value undefined is not a function or null / 2 Canvases

Hi guys,

has anyone ever run into this exception shown on a Browser console?
TypeError: Class extends value undefined is not a function or null
(javascript - TypeError: Class extends value undefined is not a function or null - Stack Overflow)

I've been trying to reliably display a candle stick chart on my dashboard. On my very first deploys the page loaded and displayed the chart properly. At some point it stopped working / for some reason, when I reload the page the chart is no more displayed and the above exception is thrown on the console.

The flow looks like this:


Here's the code from the selected nodes as shown above:

[{"id":"80001d10.90e61","type":"ui_template","z":"9ba23ed4.19e7f","g":"d2cdd39e.32eff","group":"5a4200da.62c9e","name":"Template Display CandleStick Chart","order":19,"width":"36","height":"14","format":"<center>\n    <H3>Candlestick Chart needs reloading.</H3>\n</center>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","x":640,"y":1160,"wires":[[]]},{"id":"7f1c8b09.7b4854","type":"ui_template","z":"9ba23ed4.19e7f","d":true,"g":"d2cdd39e.32eff","group":"5a4200da.62c9e","name":"Load JS libs","order":12,"width":0,"height":0,"format":"<script src=\"https://cdn.jsdelivr.net/npm/luxon@1.24.1\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@3.0.0-beta.9/dist/chart.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@0.2.1\"></script>\n<script src=\"../chartjs-chart-financial.js\" type=\"text/javascript\"></script>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","x":570,"y":1120,"wires":[[]]},{"id":"80a913cd.0158a","type":"template","z":"9ba23ed4.19e7f","g":"d2cdd39e.32eff","name":"CandleStick Chart","field":"template","fieldType":"msg","format":"javascript","syntax":"mustache","template":"<script src=\"https://cdn.jsdelivr.net/npm/luxon@1.24.1\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@3.0.0-beta.9/dist/chart.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@0.2.1\"></script>\n<script src=\"../chartjs-chart-financial.js\" type=\"text/javascript\"></script>\n\n<h1>Sample Candlestick Chart</h1>\n<!--p>See the <a href=\"https://github.com/chartjs/chartjs-chart-financial/tree/master/docs\">source for this example</a>, <a href=\"https://github.com/chartjs/chartjs-chart-financial\">README</a>, and <a href=\"https://www.chartjs.org/docs/\">Chart.js docs</a> for more details.</p-->\n\n<div style=\"width:1000px\">\n\t<canvas id=\"candleStickChart\"></canvas>\n</div>\n\n<div>\n\tBar Type:\n\t<select id=\"type\">\n\t\t<option value=\"candlestick\" selected>Candlestick</option>\n\t\t<option value=\"ohlc\">OHLC</option>\n\t</select>\n\tScale Type:\n\t<select id=\"scale-type\">\n\t\t<option value=\"linear\" selected>Linear</option>\n\t\t<option value=\"logarithmic\">Logarithmic</option>\n\t</select>\n\tColor Scheme:\n\t<select id=\"color-scheme\">\n\t\t<option value=\"muted\" selected>Muted</option>\n\t\t<option value=\"neon\">Neon</option>\n\t</select>\n\tBorder:\n\t<select id=\"border-color\">\n\t\t<option value=\"true\" selected>Yes</option>\n\t\t<option value=\"false\">No</option>\n\t</select>\n\t<button id=\"update\">Update</button>\n\t<button id=\"randomizeData\">Randomize Data</button>\n</div>\n\n<script>//*/\nvar barCount = 60;\nvar initialDateStr = '01 Apr 2017 00:00 Z';\n\nvar ctx = document.getElementById('candleStickChart').getContext('2d');\nctx.canvas.width = 1000;\nctx.canvas.height = 250;\n\n//if (chart)\n    //chart.destroy();\n\nvar chart = new Chart(ctx, {\n\ttype: 'candlestick',\n\tdata: {\n\t\tdatasets: [{\n\t\t\tlabel: 'CHRT - Chart.js Corporation',\n\t\t\tdata: getRandomData(initialDateStr, barCount)\n\t\t}]\n\t}\n});\n\nvar getRandomInt = function(max) {\n\treturn Math.floor(Math.random() * Math.floor(max));\n};\n\nfunction randomNumber(min, max) {\n\treturn Math.random() * (max - min) + min;\n}\n\nfunction randomBar(date, lastClose) {\n\tvar open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);\n\tvar close = randomNumber(open * 0.95, open * 1.05).toFixed(2);\n\tvar high = randomNumber(Math.max(open, close), Math.max(open, close) * 1.1).toFixed(2);\n\tvar low = randomNumber(Math.min(open, close) * 0.9, Math.min(open, close)).toFixed(2);\n\treturn {\n\t\tt: date.valueOf(),\n\t\to: open,\n\t\th: high,\n\t\tl: low,\n\t\tc: close\n\t};\n\n}\n\nfunction getRandomData(dateStr, count) {\n\tvar date = luxon.DateTime.fromRFC2822(dateStr);\n\tvar data = [randomBar(date, 30)];\n\twhile (data.length < count) {\n\t\tdate = date.plus({days: 1});\n\t\tif (date.weekday <= 5) {\n\t\t\tdata.push(randomBar(date, data[data.length - 1].c));\n\t\t}\n\t}\n\treturn data;\n}\n\nvar update = function() {\n\tvar dataset = chart.config.data.datasets[0];\n\n\t// candlestick vs ohlc\n\tvar type = document.getElementById('type').value;\n\tdataset.type = type;\n\n\t// linear vs log\n\tvar scaleType = document.getElementById('scale-type').value;\n\tchart.config.options.scales.y.type = scaleType;\n\n\t// color\n\tvar colorScheme = document.getElementById('color-scheme').value;\n\tif (colorScheme === 'neon') {\n\t\tdataset.color = {\n\t\t\tup: '#01ff01',\n\t\t\tdown: '#fe0000',\n\t\t\tunchanged: '#999'\n\t\t};\n\t} else {\n\t\tdelete dataset.color;\n\t}\n\n\tvar border = document.getElementById('border-color').value;\n\tvar defaultOpts = Chart.defaults.elements[type];\n\tif (border === 'true') {\n\t\tdataset.borderColor = defaultOpts.borderColor;\n\t} else {\n\t\tdataset.borderColor = {\n\t\t\tup: defaultOpts.color.up,\n\t\t\tdown: defaultOpts.color.down,\n\t\t\tunchanged: defaultOpts.color.up\n\t\t};\n\t}\n\n\tchart.update();\n};\n\ndocument.getElementById('update').addEventListener('click', update);\n\ndocument.getElementById('randomizeData').addEventListener('click', function() {\n\tchart.data.datasets.forEach(function(dataset) {\n\t\tdataset.data = getRandomData(initialDateStr, barCount);\n\t});\n\tupdate();\n});\n</script>","output":"str","x":370,"y":1160,"wires":[["80001d10.90e61","7f1c8b09.7b4854"]]},{"id":"176dfef5.4056c1","type":"ui_button","z":"9ba23ed4.19e7f","g":"d2cdd39e.32eff","name":"Load CandleSticks","group":"5a4200da.62c9e","order":18,"width":"5","height":1,"passthru":false,"label":"Load CandleSticks Chart","tooltip":"Loads the candle sticks chart","color":"","bgcolor":"","icon":"refresh","payload":"0","payloadType":"num","topic":"","topicType":"str","x":170,"y":1160,"wires":[["80a913cd.0158a"]]},{"id":"312bf216.43527e","type":"hidden-ui-load","z":"9ba23ed4.19e7f","g":"d2cdd39e.32eff","group":"3335b9df.9253a6","order":0,"name":"Load Page","x":140,"y":1120,"wires":[["7531d000.a298f","40f182b3.5c4d9c","bb299be3.5853e8","80a913cd.0158a"]]},{"id":"5a4200da.62c9e","type":"ui_group","name":"Symbol Analysis","tab":"a9b97a36.b1b538","order":2,"disp":true,"width":"36","collapse":false},{"id":"3335b9df.9253a6","type":"ui_group","name":"Table_w_Signals","tab":"5fd14a8a.e8efa4","order":2,"disp":false,"width":"30","collapse":false},{"id":"a9b97a36.b1b538","type":"ui_tab","name":"Charts","icon":"poll","order":5,"disabled":false,"hidden":false},{"id":"5fd14a8a.e8efa4","type":"ui_tab","name":"Signals","icon":"add_alert","order":1,"disabled":false,"hidden":false}]

The HTML + JS code from within that 'CandleStick Chart' template looks as follows:

<script src="https://cdn.jsdelivr.net/npm/luxon@1.24.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.0-beta.9/dist/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@0.2.1"></script>
<script src="../chartjs-chart-financial.js" type="text/javascript"></script>

<h1>Sample Candlestick Chart</h1>
<!--p>See the <a href="https://github.com/chartjs/chartjs-chart-financial/tree/master/docs">source for this example</a>, <a href="https://github.com/chartjs/chartjs-chart-financial">README</a>, and <a href="https://www.chartjs.org/docs/">Chart.js docs</a> for more details.</p-->

<div style="width:1000px">
	<canvas id="candleStickChart"></canvas>
</div>

<div>
	Bar Type:
	<select id="type">
		<option value="candlestick" selected>Candlestick</option>
		<option value="ohlc">OHLC</option>
	</select>
	Scale Type:
	<select id="scale-type">
		<option value="linear" selected>Linear</option>
		<option value="logarithmic">Logarithmic</option>
	</select>
	Color Scheme:
	<select id="color-scheme">
		<option value="muted" selected>Muted</option>
		<option value="neon">Neon</option>
	</select>
	Border:
	<select id="border-color">
		<option value="true" selected>Yes</option>
		<option value="false">No</option>
	</select>
	<button id="update">Update</button>
	<button id="randomizeData">Randomize Data</button>
</div>

<script>//*/
var barCount = 60;
var initialDateStr = '01 Apr 2017 00:00 Z';

var ctx = document.getElementById('candleStickChart').getContext('2d');
ctx.canvas.width = 1000;
ctx.canvas.height = 250;

//if (chart)
    //chart.destroy();

var chart = new Chart(ctx, {
	type: 'candlestick',
	data: {
		datasets: [{
			label: 'CHRT - Chart.js Corporation',
			data: getRandomData(initialDateStr, barCount)
		}]
	}
});

var getRandomInt = function(max) {
	return Math.floor(Math.random() * Math.floor(max));
};

function randomNumber(min, max) {
	return Math.random() * (max - min) + min;
}

function randomBar(date, lastClose) {
	var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
	var close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
	var high = randomNumber(Math.max(open, close), Math.max(open, close) * 1.1).toFixed(2);
	var low = randomNumber(Math.min(open, close) * 0.9, Math.min(open, close)).toFixed(2);
	return {
		t: date.valueOf(),
		o: open,
		h: high,
		l: low,
		c: close
	};

}

function getRandomData(dateStr, count) {
	var date = luxon.DateTime.fromRFC2822(dateStr);
	var data = [randomBar(date, 30)];
	while (data.length < count) {
		date = date.plus({days: 1});
		if (date.weekday <= 5) {
			data.push(randomBar(date, data[data.length - 1].c));
		}
	}
	return data;
}

var update = function() {
	var dataset = chart.config.data.datasets[0];

	// candlestick vs ohlc
	var type = document.getElementById('type').value;
	dataset.type = type;

	// linear vs log
	var scaleType = document.getElementById('scale-type').value;
	chart.config.options.scales.y.type = scaleType;

	// color
	var colorScheme = document.getElementById('color-scheme').value;
	if (colorScheme === 'neon') {
		dataset.color = {
			up: '#01ff01',
			down: '#fe0000',
			unchanged: '#999'
		};
	} else {
		delete dataset.color;
	}

	var border = document.getElementById('border-color').value;
	var defaultOpts = Chart.defaults.elements[type];
	if (border === 'true') {
		dataset.borderColor = defaultOpts.borderColor;
	} else {
		dataset.borderColor = {
			up: defaultOpts.color.up,
			down: defaultOpts.color.down,
			unchanged: defaultOpts.color.up
		};
	//}

	chart.update();
};

document.getElementById('update').addEventListener('click', update);

document.getElementById('randomizeData').addEventListener('click', function() {
	chart.data.datasets.forEach(function(dataset) {
		dataset.data = getRandomData(initialDateStr, barCount);
	});
	update();
});
</script>

According to this thread on StackOverflow javascript - TypeError: Class extends value undefined is not a function or null - Stack Overflow the exception is triggered on a circularly importing, so I wonder how the heck is that happening?

Am I missing an important "feature" of the template UI element? It seems to be cashing stuff?
Also there're a few options in that template UI available. About the last option "Reload last value on refresh" I'm not sure if it helps or harms:

image

Why is the exception thrown on a page refresh of the UI dashboard? Should it not clear the cache and start with a fresh new slate?

On a side note, there's another bugger, which drives me crazy. I had a line chart on the dashboard which I disabled for now. The error with that - which I was trying to fix (before this Exception popped up) was a conflict with 2 canvases interfering with one another. I can't recall that exception again, but since the 2nd candlestick dashboard is not loading at all anymore, I stopped trying to fix this problem (A solution seems to be posted on SO javascript - Destroy chart.js bar graph to redraw other graph in same <canvas> - Stack Overflow).

As you can see, I also have a currently disabled 'Load JS Libs' template node which would (if enabled) place the JS includes at the start of the page. Not sure if this is the correct approach of if including the JS libraries in the template script is OK (as shown in the lengthy JS script for the candle sticks chart above).

Sorry for presenting you a problem while everything is still work in progress (with the intermingled "canvas with 2 charts" problem).
Maybe someone might share some experience and point me to what I'm probably doing wrong?

Best regards,
Marcel

Hi Marcel,

The errors could be due to a clash of Chart.js libraries.
I see that you are trying to import chart.js@3.0.0-beta.9 which i guess is needed from those financial charts but the Node-red Dashboard already at some point loads a version of Chart.js for its own use.
(as brought to my attention by @zenofmud in another post)

Now i have no idea in what order those libraries are being loaded or whether if indeed there is a clash
but maybe you could look into using Uibuilder that is a node created by @TotallyInformation that gives you the flexibilty to easily create your own custom dashboards.

I tested the financial example from Github with uibuilder and it loads fine.

Steps for using the example with Uibuilder.

  1. Install the node
  2. put a uibuilder node on your flow
  3. Edit it and give it a URL which is going to be endpoint for the files to be served
    image
  4. Copy the example files from Github to .node-red\uibuilder\financial\src
  5. visit http://<your-node-red-ip>:1880/financial

ps. when you get the main example working .. you have to edit the index.html in order to include the uibuilder library used to communicate with Node-red (sending and receiving msgs)

<!-- 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>
<!-- REQUIRED: Sets up Socket listeners and the msg object -->
    <script src="./uibuilderfe.js"></script> <!-- dev version -->
    <!-- <script src="./uibuilderfe.min.js"></script>     prod version -->

Info on how to receive and send msgs using this node can be found at the authors Wiki here

2 Likes

Thanks so much, @UnborN! I'll have a look tomorrow with a fresh mind.

Have a good weekend!

Cheers
Marcel

So amazing! I'm already knee-deep in @TotallyInformation's fantastic UiBuilder. Enjoying his plugin together with vue and the library apexcharts (https://apexcharts.com/). Got my first candlestick chart and many other chart types being properly displayed on one frontend page. And all that in just 1-2 hours. Thank you all!

2 Likes

That's great to hear and many thanks for the kind feedback.

As always, let me know if there is anything that needs improving :grin:

1 Like

Hey mate, thanks for checking in. Indeed I've got a brief question (after a quick search here on the forum before I dare to ask): Is it possible to setup a Vue project e.g. with Visual Studio Code or any other IDE so that we're able to debug our uibuilder/vue project in a browser frontend?

On the documentation pages of vue there's a nice section that describes how to setup a "hello world" vue project and how to Launch the Application from VS Code. The setup was super simple and I got the debugging working easily. However, I'm unsure which steps are required to setup an already existing src folder that has been created by your uibuilder. Do you happen to have some advice for me?

I think that you have to run Node-RED with the --inspect option, you should then get the option to connect to the debugger from your browser dev tools. You can also connect from within VScode.

However, that only works on actual javascript so it won't work with a .vue file natively. So it somewhat depends on how you are running your Vue code. It will probably work OK if using the http loader for example. In that case though, you don't need VScode, you can do it all from within the browser. Just put a debugger statement in your front-end code.

Don't forget to use the Vue dev tools extension as well. Note however that this only works when running Vue in dev mode. The easiest way to do that it to use the non-minified version of the Vue library. Indeed, for debugging, best to switch all of your libraries - including uibuilderfe - to the non-minified versions (the ones with .min in them).

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.