Hi folks,
I would like to discuss here how we can assist @tve to build an alternative for the current AngularJs UI nodes in his FlexDash dashboard. For people that don't like the concept of UI nodes, please just ignore this discussion because you can already use FlexDash without UI nodes
So this discussion is NOT about why we would like to have UI nodes, but about how we can implement this...
The trigger for this discussion was the following quote from Thorsten in another discussion:
To avoid that Thorsten had to reverse engineer some of the current UI nodes, I have tried to help him by summarizing how I think the UI nodes in the current AngularJs nodes work. Of course I am not a dashboard specialist, so if anybody has extra input please share it here. We have NOT discussed yet what would be the best way to implement this into Flexdash. I will leave that discussion to Thorsten and others in the community that are familiar with modern web technologies...
Here is a summary of the stuff that I have shared yesterday with Thorsten in a PM.
Contrib UI nodes vs standard UI nodes
I have mainly explained the contrib ui nodes (instead of the standard ui nodes like e.g. a button) because most probably in Flexdash ALL ui nodes will be separate contrib ui nodes. Which means that all ui nodes will be developed in the same way, unlike the current standard ui nodes that are very much integrated into the dashboard code. Although that should not mean that a FlexDash cannot be shipped by default with some standard ui nodes included...
Internal flow a contrib ui node
This is how a custom ui node works internally:
Note that there is also a convert and convertBack function, but I have not used those yet so not sure where the are positioned in this diagram...
-
At startup the Node-RED registry creates the server side node (just as it does for non-ui nodes). At this point a node developer can call some server side startup code. In case of a UI node we call ui.addWidget on the dashboard, and we pass a bunch of parameters to that function. One of those parameters is a callback function 'HTML' that returns the html string. So the html string is currenly created once dynamically as soon as the flow is started.
But of course if FlexDash wants to create that html in another way (like the existing FlexDash widgets currently do), be my guest!! Although it might be useful to take this into account somehow: currently the dashboard ui-template node allows users to enter their html/javascript/css in the config screen, and then it is send to the client. So it would be useful if you would be able to find a FD alternative for that...
Most other input parameters (of the ui.addWidget function) are related to message replaying and that kind of stuff (see further). Not sure if you need it...
-
When an input message arrives on the ui node, the dashboard framework has implemented the on-'input' event handler (so that is not maintained by the ui-node developer like we use to do with non-ui nodes). That handler makes sure that e.g. the beforeEmit function is being called...
-
In the beforeEmit function we can e.g. do server-side validation of the input message. Or we can adjust the message, before it is being send to the frontend.
One big disadvantage currently is that we cannot stop an invalid message being send to the frontend.
-
When a dashboard page is opened, the dashboard framework will call our initController function on the frontend size. In that function we can do all stuff of initialization, e.g. specify callback functions for $scope.init (called when the AngularJs scope is initialized) and $scope.watch (called when input msg object arrives).
-
When a new input message arrives, it is being stored in the AngularJs scope.msg.
-
In our AngularJs frontend code, we have to watch for changes in the $scope.msg object. So as soon as the input object changes, our watch function will be called. There we can do all kind of frontend stuff, e.g. update the DOM tree of our ui widget.
-
At certain conditions (e.g. when an element in the DOM tree is being clicked), we can construct an output message object and send it to the server with $scope.send.
-
When the output message arrives on the server, our beforeSend function is being called. There we can modify the output message, or stop the output message from being send.
-
At the end our output message is send to the next nodes in the flow.
Message resending
In the current dashboard there is also a message resending mechanism:
-
When we inject an input message, all open dashboards clients will be updated based on the information in that message. Because the watch function in all the clients will be called, which will update their DOM.
-
When a new dashboard client is opened, the dashboard will resend the last message to that new client. That way the new client is synced with the existing clients. E.g. when you have a ui-led node and you inject a message to turn the LED to red, then - when a new dashboard is opened - it will also receive that last message, which means the LED will also be red on that new dashboard client.
-
When the browser page is refreshed, the last message is being resend also (but now within the frontend). So the replayed message comes from somewhere in the frontend, not sure how it works.
Although this message resending is very useful, it has given me already lots of headaches:
-
Only the last message is being replayed. But the state of a ui node is most of the time constructed incrementally, by a sequence of multiple input messages. For example my ui-svg allows you to draw a floorplan. Suppose you inject a message to start flickering the icon of camera 1. Afterwards you inject a message to start flickering the icon of camera 2. When a new dashboard is opened, only the last message will be resend. Which means that on the new dashboard client, only icon of camera2 will be flickering (instead of both camera icons like in the other clients)...
-
Suppose I want to see my video recordings from yesterday till today. So I enter two dates in my dashboard (in two date fields) and I press the search button. Then the search results will be displayed on all my dashboards that are currently open. But my partner might be looking at the same moment for other recordings, which will be overwritten by mine on her screen...
-
This message replaying might also cause high load on the current charts, when too much data is being send...
So I am not sure what Flexdash should do with message replaying. It is very hard to get it right in all circumstances, and there are a series of parameters that you can adjust. It would be very nice if there would be a better mechanism possible, so that a ui node developer doesn't have to invent the same logic over and over again. But not sure how that could be done...
An example node
If you want to have some more technical information, you might have a look at the node-red-node-ui-mylittleuinode. When - at the time being - Hitachi had implemented a pull request to allow us to create custom ui nodes (using the above flow), we have implemented that mylittleuinode as an example for people that want to implement their own ui node. So it contains a lot of comments...
Files of a contrib ui node
- The .html file contains the config screen code, i.e. everything that is needed to construct the config screen when you double click the node in the flow editor).
- The .js file contains both the server side stuff of the node (i.e. beforeEmit, beforeSend...) but also the frontend stuff (i.e. initController, scope.watch...). This is VERY confusing, when you start developing ui nodes. But I assume the contrib ui nodes in FlexDash will separate server side and client side stuff in separate files.
Standard UI nodes
I don't know much about the standard UI nodes, but here are some pointers if you need to analyze e.g. the code of the current ui-control node:
- The client side code of all those nodes is inside the main.js file.
- The server side code is separated per node, e.g. in the ui_ui_control.js
- The config screen code is also separated per node, e.g. in the ui_ui_control.html file.
How is the config passed to the dashboard
In the html file of a ui node, a developer can specify what Node-RED has to show on the config screen and in the node info panel. For example we want to allow the user to configure the button color.
This works the same way for non-ui and ui nodes:
- The user double clicks on the ui node in the flow, and Node-RED will display the node's config screen (based on the html file).
- The user can read the documentation in the info panel on the right, and change e.g. the color on the config screen.
- When the user hits the "Deploy" button, Node-RED will send the updated config of the node to the server.
- Node-RED will restart the flows (to use the updated config), and call the node's server-side code (in the js file). That is the part of the code - seen my diagram above - where a UI node needs to call the ui.addWidget(....) function offered by the AngularJs dashboard:
So Node-RED calls our function at startup and at every deploy, and then we pass our html string (and other parameters) to the dashboard...// Node-RED calls this function at startup and after every deploy function FDButtonNode(nodeConfig) { var node = this; // Get a reference to the dashboard framework var ui = RED.require("node-red-dashboard")(RED); RED.nodes.createNode(this, config); // Generate the html string, by calling our HTML function (which simply returns a string) var html = HTML(config); var done = ui.addWidget({ node: node, order: config.order, group: config.group, width: config.width, height: config.height, format: html, ... }); } // This is called when Node-RED starts. We register our fd node in the Node-RED node registry. // That way Node-RED can call our FDButtonNode function at startup and at every redeploy RED.nodes.registerType("`fd-button", FDButtonNode);
Special UI nodes
If all ui nodes would be contrib nodes, then all ui nodes would be implemented using the same internal skeleton. However there are two ui nodes that are a bit different from the other ui nodes:
- A ui_template node: on the config screen of this node, users can enter html/css/javascript. Which means instead of generating this via the HTML function, this would arrive in your server side code via
config.html
. - A ui_control node: this node has to cooperate with the dashboard more closely compared to the other nodes. It has a two-way communication with the dashboard frontend:
- If something changes on the frontend (tab switch, resize, ...) an output message will be produced.
- If an input message is injected (e.g. tab switch) then the frontend will be updated.
Like Thorsten already mentioned, it will be hard (or impossible?) to create a wrapper so the old AngularJs code from the old UI template nodes can be used within the new Vue.js ui template nodes...
The config screen
The config screen of a UI node is almost similar to that of a non-UI node. The main difference is that two widgets have been provided, which UI node developers can use on their config screen (i.e. in the .html file). Those widgets allow the user to adjust:
- The size of the ui element.
- The tab and group where the ui element should be visualized
Note: at deploy time, those parameters are passed to the server side widget via config.width
, config.height
, config.order
and config.group
. The node developer then needs to pass those parameters to the addWidget function, so the dashboard can use them to adjust the layout.
Not sure how an alternative would fit into the FlexDash concept...
Layout
I have not discussed this yet with Thorsten. But the current AngularJs dashboard has a tree view in the right sidebar, which also offers a layout editor.
Again not sure how this would fit into the FlexDash concept...
Perhaps a third-party layout manager can somehow be integrated into Node-RED. I am not following these kind of technologies, so cannot give any advise...