Open AllanOricil opened 1 week ago
The UI Base is the single parent object for a Dashboard.
In the constructor for each Widget, they get reference to the UI Base (normally via their assigned group/page). They call base.register, and pass themselves into that function call.
The Base then maps pages/groups/widgets/themes, and sends the relevant configurations when a Dashboard client connects
@joepavitt what do you think of my idea of making ui_base into a parent class called WidgetNode, and then widgets would extends this class? I think it is possible. Could you help me to see problems I would have trying to do it?
The UI Base has settings and configuration of its own. The widgets aren't necessarily extensions in a Class sense. They are different entities. There is only ever one Base. Having a Node an extension of Base would mean you suddenly have 100's of Dashboards/Based
If we consider Class hierarchy, there could be benefit in having a common WidgetNode
class that the nodes, in order to share feature sets, etc.
The base.register
function is the Widget's way of communicating to the Dashboard to say "Hey, I exist"
My use of the word "extends" here is likely misleading.
From a Dashboard Hierarchy we have:
That doesn't mean that there is Class Inheritance here though. Fundamentally, these are the Classes, then each node in a flow (or config node) in an instance of the respective class
@joepavitt
So every WidgetNode
class would have to have an instance of ui_base
.
Is this ok? I'm still not sure if "register/deregister" is called "per instance" of a node, or per type of node. If it by type, then I need to make those 2 methods static
export default class WidgetNode extends Node {
static #uiBase;
constructor(config){
super(config);
//initialize specific WidgetNode properties, such as state store
}
// specific WidgetNode methods, like register/ deregister
register(){
WidgetNode.#uiBase.register(this);
}
deregister(){
WidgetNode.#uiBase.deregister(this);
}
}
There are three types of reference, which depend on the "home" of the widget.
If it renders in a group (the most common, e.g Buttons, Charts), then the Widget has a group
property. But in turn, can then use group.getBase().register()
to register itself with the one true UI Base at the top level.
Some widgets render at the page-level, for example a UI Template might be assigned to a page to override CSS on that page. Similarly, there exists a page.getBase()
in order for the Widget to register to the Base too.
Finally, the simplest in this case, is a Widget that renders at the "base" level, I.e. independent on whichever page a user is one that Widget will always be there. An example of this is "UI Notification", where the user chooses the "Base" in the Node config editor, and so we can just call base.register
directly
Also worth noting, and I may be remembering this wrong, the nodes themselves dont need a register/deregister function.
The base.register()
is just called as part of the Node's constructor
Maybe they do need a deregister function? I can't quite remember how I handle that, as I'm not at a keyboard
@joepavitt
Maybe they do need a deregister function? I can't quite remember how I handle that, as I'm not at a keyboard
I believe that if a node can call this.deregister()
, maybe there are situations where the node would also call this.register()
afterward. Maybe to do a "hard reset" somewhere? Do you know if there is any Widget that does it?
The
base.register()
is just called as part of the Node's constructor
I can ensure this is called inside the parent's constructor, or in the mixin. This way devs won't need to manually write it. The same way I did for RED.nodes.registerType
or RED.nodes.createNode
https://github.com/AllanOricil/nrg/blob/main/templates/server/entrypoint.handlebars https://github.com/AllanOricil/node-red-node
@joepavitt What do you think of this model?
import { Node, Base, Theme, Page, Group } from "@allanoricil/node-red-node";
export default class WidgetNode extends Node {
scope: Base | Theme | Page | Group; // each instance of a widget can have its own ui scope
constructor(config){
//initialize specific WidgetNode properties, such as state store
}
// specific WidgetNode methods, like register/ deregister
register(){
this.scope.register(this);
}
deregister(){
this.scope.deregister(this);
}
}
import { WidgetNode } from "@allanoricil/node-red-node";
export default class ChildWidget extends WidgetNode {
constructor(config){
super(config);
this.scope = TO_BE_DEFINED; // dev configures which scope this node belongs to. The mixin will use it to call `this.register()` for his node
}
}
That could work, bare in mind though some widgets are permitted to be multiple, e.g. ui-template
can be switched between group, page and UI
That could work, bare in mind though some widgets are permitted to be multiple, e.g.
ui-template
can be switched between group, page and UI
How does a Widget switch between "ui scopes" (have to find a better name)?
It is defined in template node's configuration
It is defined in template node's configuration
@colinl every widget has those fields shown in that image? How can I get to that configuration form?
I started to have a better understanding after reading the config nodes
I think I can start modeling it into classes. I will try to do the base ones, and a button.
One last question.
@colinl @joepavitt
What is the schema for the "evts" object widgets pass when calling group.register(node, config, evts)
?
Also, why passing config
to register if you are already passing node
? I believe you can dereference config
from the node
, right?
const evts = {
beforeSend: async function (msg)
}
group.register(node, config, evts)
After modeling into classes, this is what I'm planning for the event handlers. Therefore, I must know its schema.
import { Node } from "@allanoricil/node-red-node";
export default Widget extends Node {
group;
constructor(config){
super(config);
this.group = config.group;
}
/*
* It can be implemented by a child class
* @abstract
*/
onBeforeSend(msg){}
// all other possible event handlers a widget can have
...
}
import { Widget } from "@allanoricil/node-red-node";
export default MyWidget extends Widget {
constructor(config){
super(config);
// set things that are specific for a given "instance", like initializing its own properties
this.myCoolAttribute = config.myCoolAttribute;
}
// since this is a widget, this event handler "could" be implemented
onBeforeSend(msg) {
...
}
// all other event handlers
...
}
Inside the mixin, I will do some sorcery js to make sure these "on" events are registered when calling group.register(node, config, evts)
.
@joepavitt @colinl could you help with the last comment?
Not me, I don't know the answer.
every widget has those fields shown in that image? How can I get to that configuration form?
Drop a Dashboard "template" node into the Editor, then double click it to configure it. Only the "Template" node as the option to switch between "parent scope" like this.
@AllanOricil https://dashboard.flowfuse.com/contributing/guides/registration.html#evts
@joepavitt
Is the onInput
from evts the same as the node's on input this.on("input", onInputHandler())
?
If not, is there a widget that use both? If there is, than I would need to change the evts event handler name to avoid conflicts
import { Widget } from "@allanoricil/node-red-node";
export default MyWidget extends Widget {
constructor(config){
super(config);
}
// same as node-red this.on("input", ())
onInput(msg){...}
// dashboard ui input event
onUIInput(msg){...}
// dashboard ui before send
onBeforeSend(msg){...}
}
Not quite, the one you've quoted there is the client-side handler (Vue Widget), the onInput
I linked to is the server-side handler (the Node-RED node)
Not quite, the one you've quoted there is the client-side handler (Vue Widget), the
onInput
I linked to is the server-side handler (the Node-RED node)
So all evts
handlers are sent to the client? I thought they executed on the server, because they are declared in the server js of a node.
No, they're separate. What a node does on input server-side (defined in the .js
file) will differ the the client-side behaviour of a widget when receiving a message (defined in the vue
file)
@joepavitt
Sorry, but I got confused a bit. Let me try to make a better question.
The doc says the evts handlers are server- side. Is the evts.onInput
the same as this.on("input")
inside the node's server-side js? Is there a node that make use of both? I need to see how they are used.
Sorry, I thought this.on("input", onInputHandler())
had been taken from a vue
file as it's very similar to our code there too.
Yes, if you checkout the node.register
function in ui-base.js
, you'll see that the on('input')
handler is setup for the relevant node and the evts.onInput
is passed there (if provided)
Sorry, I thought
this.on("input", onInputHandler())
had been taken from avue
file as it's very similar to our code there too.Yes, if you checkout the
node.register
function inui-base.js
, you'll see that theon('input')
handler is setup for the relevant node and theevts.onInput
is passed there (if provided)
I would need a new name for that handler to put it on the class alongside the node's this.on("input")
I can't have 2 onInput
methods in the class. What about onData
or onDashboardInput
or onUIInput
for the evts.onInput
? If the second one is chosen, then all other methods would also be prepended with either onDashboard
or onUI
not sure why the class needs that definition though? It needs evts
, which is then an object mapped to x functions/objets/booleans
not sure why the class needs that definition though? It needs
evts
, which is then an object mapped to x functions/objets/booleans
When one of those handlers is triggered, can't they access "instance" data? I can have multiple widget nodes, all with the same handler, but each with its own instance scope. Those events are part of the Widget, so I'm trying to encapsulate them normalized as individual class functions, instead of using an evts attribute. It looks cleaner in my opinion.
I read the dashboard 2 docs and understood that widgets are node-red nodes, and that they "extend" the ui_base config node, but I could not understand how exactly this happens. Somehow the widget's node has some additional methods called register/deregister. Probably has other, but I did not read everything yet. So, when/how exactly does a widget receive those 2 methods?
I'm planning to recreate it as a base class of Node class called, in the nrg project.