Uibuilder: to subscribe or not to subscribe?

@BartButenaers Can you send me a simple ui_template example (a hello world ideally) and possibly one little more complicated

Thanks

Oh, that is a pity :slightly_frowning_face:. Because that is being a LOT nowadays ...

Temporarily I don't work with Node-RED, but you can find a lot of examples (like e.g. here).
Thanks for at least having a look at it!!!!

Looking forward to working on this.

It is more that I am concerned we might be swapping one fairly closed case (the complex Angular v1 of Dashboard) for another fairly closed case (using a Google language and toolset). Rather that opting for a truly open case.

However, as always, what is important is that we agree on the interface. That way, everyone has the opportunity to work with it and produce innovative solutions.

Already said that I'm happy to do that.

What I've always meant is that you should not be reliant on the back-end sending code over dynamic interfaces (e.g. websockets). This simply isn't efficient and tends to prevent the standard efficiencies of http from being used.

Most of the code is static and therefore doesn't need to be sent over a dynamic interface but instead can be cached using web services working with the browser.

However, as in uibuilder's case, that does not prevent you sending code dynamically. You just shouldn't have to do it as standard.

This is easily converted to static code. But the scope variables would need to be converted in accordance with whatever front-end framework is in use.

And this is my worry with the Flutter approach - how many people know DART? Speaking for myself, there is no way that I have the time or energy to learn a new language just to deal with this case. Not after spending so many years working with JavaScript. Which I chose because I can work on both front- and back-end code in the same language and have it run on pretty much every platform.

If we can separate out the Dashboard extract code from the Flutter front-end, then it should be fairly straight-forward to integrate to different front-end libraries.

I was especially pleased to see how easy it was to work with Svelte for example (though it requires a build step so possibly not the best choice as a default for uibuilder, still it "just worked"). While I am certainly no Svelte expert, the concepts and the code were really easy to work with and result in a pure JavaScript output. The whole thing being efficient and fast. So I could see that being a good framework to have as an alternative whether or not uibuilder was used as an integration "partner". I think that VueJS would also be amenable but then I expect REACT would as well.

Totally agree here. This is the crux of any next-generation Dashboard for Node-RED. A standardised interface.

1 Like

Yes that is unfortunately also the case for me. One of the advantages of Node-RED for me is that I can use the same programming language in flow editor, backend and dashboard at this moment...

1 Like

I just looked at Svelte :slight_smile: seriously, I concur with the new lang/env hurdle (and just for clarity you will find dart quite javascript like but that is not the point)

Looking at SVELTE it looks interesting. Still getting my head around it. I might try to generate my example dashboards from the flutter case in Svelte. Possibly a good way to learn.

Would the Svelte intention be that for a zero-config dashboard replacement, that we would generate the dashboard on deploy and then compile it? I assume the compile for small dashboards would be quick - The compile step could be orchestrated so while not an ideal soln not a show stopper from my perspective

Some time ago there was a very nice discussion about a building a new dashboard. That discussion was initiated by @andrei-tatar, the author of the current AngularJs dashboard. Unfortunately the ideas from that duscussion have never resulted in a new dashboard. But I see that Andrei has mentioned also Svelte in that same discussion...

Another noob question: I assume we can never stick to a single language (Javascript) for all our tools (flow editor, backend, dashboard...) when we also want to have a native app for both Android and iOS?

Personally, I would rather generate the components or maybe a collection of common components and send configuration to produce the display. So that you don't need to compile just to add a component to the display.

Well that is the $64,000 question. Again, personally, I would prefer to TRY to stick to HTML/CSS/JavaScript for all UI's. This is the standard I've set for the organisations I am the head of architecture for.

It is true that you can't reach 100% on these things but I would rather people focus on the set of international standards that cover the largest base.

2 Likes

Can I have your entire flow please?

I'm finally finding some time to circle back to this. I've been doing some tests with uibuilder + vue + vuetify to get a feeling for what I can get out of the box vs. have to build. I'm not a UI whizz so I periodically need a refresher :slight_smile:

I'm trying to grok the various discussions about a new dashboard. It seems to me that the top dividing line is around how the content of the dashboard is defined. In the current dashboard the content of what you see is defined in node-red using node-red nodes. The alternative is what uibuilder steers towards (while remaining flexible) is to define what you see in html/js, the way "normal" web UIs are done.

There are clear advantages to both approaches and I'm really happy that uibuilder enables the second option. I'm now also understanding a bit of the difference between a component in vuetify (or bootstrap-vue for that matter) and a component in a dashboard attached to node-red. Vuetify components (and web components in general) are configured and driven using a combination of html tags and data values. Adding node-red to the mix introduces messages, so the question is how should messages reach components and what should they be able to do to them?

In the context of uibuilder, or more precisely, in the context of a dashboard whose content and layout are defined in html/js I don't see the point of using messages to drive the looks of components. For example, I would argue that while messages obviously need to set the value of a gauge, I have no need to set the diameter of the gauge via a message from node-red because I already decided that I'm doing layout using html/js/css.

So looking at uibuilder-vuejs-component-extras/gauge at master · TotallyInformation/uibuilder-vuejs-component-extras · GitHub I would say that there's too much functionality there! :wink: A gauge should only have messages to set the value, maybe also to set min/max values, but that's it. The rest should come from the html/js/css. I believe that less is more, simpler is better, stay focused, etc. (Someone who really wants to double the size of a gauge if a certain threshold is reached can always write some javascript to make that happen.)

Something all this has clarified for me is that there really is a need for two different types of dashboards: one fully controlled through node-red nodes and one that uses std html/js/css to control the content of the page and only exchanges data with node-red.


As I think some more about the original "to subscribe or not to subscribe" topic I want to pick some more on uibuilder-vuejs-component-extras and specifically the gauge example. I don't think I want that! :slight_smile: (Please take all this as a discussion, as brainstorming, not as a value judgement! If you come to the opposite conclusion then we both win :slight_smile: , 'cause clarity is my goal, not imposing one approach.)

The reason I don't want the gauge in that repo is that I think it's beneficial to separate web components and widgets that talk to node red. Lemme explain. For dashboards one commonly uses card UIs, or at least UIs that have multiple rectangular containers on the page. Within each card one tends to find multiple web components, for example to display a couple of values and some input buttons/controls. At that point I think it's really beneficial to use the card abstraction to separate how the card content is constructed using the web components at hand from the messages that are exchanged with node-red. I.e., I'd like messages from node-red to reach a small snippet of code attached to the card and from there to plug the data into the right web components.

In general what I described isn't exactly what I want 'cause of nesting. Sometimes the visual card on the page should receive messages but sometimes the visual card contains a few sub-containers that should receive the messages. But that's a detail.

Continuing to explore...

1 Like

Thanks for that. I think that you have captured the issues and differences nicely :slight_smile:

That is indeed the nub. And why I am advocating for a set of standard schema's for common components. That way, it doesn't really matter what is delivering the dashboard, you should be able to plug and play.

Well, there are most certainly occasions when you want to control the look and feel of a dashboard as a whole or an individual component of it - from data. uibuilder is most useful as a data-driven UI builder. While you may mostly want to set the look and feel in code, this isn't always possible.

Take the example of sending an alert from Node-RED to your dashboard for example. You almost certainly don't want to either always have alerts looking the same or have to create multiple alert components - you want to control the look and feel from your data (maybe). On a simple level you can pre-define some standards - this is what bootstrap does. But that doesn't fit every need.

The trick is to get the right balance between flexibility and usability.

But why make someone do that if it is simple enough to add to the component?

OK, so I agree that there is a sensible balance to be had and much may depend on the component being used (assuming someone else wrote it). The author of the component has already made decisions about how controllable it is.

I'm still not entirely convinced that this actually needs >1 dashboard. I believe that a multi-level system is achievable. Anyone from an IBM mainframe background will be very familiar with this approach. The core of the system provides low-level functions and so can be used most flexibly. A wrapper around that core makes a simpler to use but potentially slightly less flexible approach. That could, in fact, result in multiple layers if needed.

That is what I was hoping for with uibuilder. uibuilder itself is the core, it just needs people with time and expertise to help with the next layer out. I put together the extras repo to try and illustrate what can be easily achieved. But what is really needed is something that would let users define a UI just through configuration and then data transfers. Just like Dashboard but brought up-to-date and more efficient.

And I agree - that some people want/need this - but not everyone. The advantage of the gauge example in extras is that you can wrap it at yet another layer of abstraction quite easily.

And here is where I think we may disagree - unless I've mistaken what you are saying. I never want to have to force Node-RED to send code to the front-end. That is inefficient and potentially unsafe. I want to allow it as an option certainly, but it should not, in my opinion, be required.

In my view, you should be able to tell your dashboard what components you want and in what order/grouping. And then you should only ever have to send data unless you need to change the look/feel via the data in which case, you send more configuration and then back to data. Configuration is data in this sense of course.

So for people who currently benefit most from Dashboard - a zero-code (from a user perspective) UI, that option is certainly needed. The common components for that should be defined in standard web-development front-end code but with sufficient parameterisation to allow remote control & composition via a standardised configuration schema. That way we can most easily leverage the massive investment and innovation that goes into that kind of front-end development. And make it relatively easy to replace something like Vue with whatever comes next - as it surely will.

Having created couple of gauges for dashboard, I also found that the first thing which starts to annoy me is lack of simplicity. But it all depends of course.
Ready made components will force to have some kind of look and feel and there is not much you can do about it.

There are gauges on both dashboards, but they talk different story.

Or even more different story

But both are useful in the right context.

Thanks for the response! Good conversation :slight_smile:

Achievable: yes. Desirable: good question.

Let's take a gauge as an example. For my purposes I would need 3 layers of gauge:

  • A gauge that is a web component, it gets fully controlled through html/javascript, it gets used to build more complex widgets and node-red messages get dealt with at a higher level. An example is a gauge coupled with a selector knob allowing the user to select among 10 different values to be shown by the gauge.
  • A gauge that is a pure front-end widget that can be controlled via messages from node-red. It gets instantiated and configured via html/js but also listens to messages from node-red. The messages can set the data value and possibly also change the config. An example is the gauge in uibuilder-vuejs-component-extras.
  • A gauge that is a node-red node that gets fully controlled through node-red. It's like what the std dashboard has: you plop a node into your flow and a corresponding UI piece shows up somewhere.

This can clearly be designed in layers. The question is whether the result is usable. To me the sticking point is the layout. I guess something that could work is having a container for the UI components that are fully controlled through node-red. As an example, one could have a row of pure front-end widgets, followed by a row consisting of node-red controlled widgets, etc.

The other issue is finding time & will to build this stuff. It would have to be a team effort 'cause the person interested in the lower layers (like me) isn't going to be interested in building the whole node-red side of things, and vice-versa.

I believe you mis-read what I wrote :wink: . I didn't write "small snippet of code attached to the message" I wrote "attached to the card", i.e., that the card's javascript contain a handler that processes the message.

I think you misunderstood the point of my post. It's not about gauges that have a simplistic look. It's about gauges whose look gets controlled purely via html/js/css config and not through messages sent from node-red.

It is pretty common, that the request to add options to change the look of the widget on fly comes in at the second or third day after first announcement....

An issue I hit that is related to subscribe-or-not is the method of message delivery to components. In uibuilder-vuejs-component-extras the messages are delivered by modifying the properties of the components. One problem with this is that vue complains:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.

Another possible issue is that I'm not sure how frequently the on-change handlers get triggered. For example, if I have a simple chart component and multiple chart values come in back-to-back on the websocket, these values will set the value property of my chart in rapid succession. Does the chart component's handler that watches the value and appends it to the chart get called for each individual value or only once at the end?

I've switched to having the central dispatch call a handle(msg) {} method defined on the component. That seems safer to me although it requires a bit more code (which could be addressed by providing a helper function). If this dispatch code gets integrated into the core of uibuilder then perhaps it should first check whether there's a handle method and call that, and only modify props if there isn't such a method. Plus appease the warnings somehow...

Yes, not everything should be pre-built. Sometimes pure custom is the best way. If you have a standardised schema, you can still choose to use that with a fully custom component if you like, layering on any extra settings or data you want/need. That also enables people to easily start with a simple existing/standard component and then progress to something custom later. This is a major bugbear with many frameworks, the "falling of a cliff" problem where you get so far and then find yourself in a dead-end. The type of design outlined here should reduce that problem.

:slight_smile:

A node or just a configuration msg. Either way. By defining the configuration schema, you can have a node if you like or use something custom if you don't like.

Yes, this is the missing part of the jigsaw. It probably isn't that hard to reproduce something similar to the config that Dashboard does but that outputs a configuration that can be used by a set of standard front-end components. I'm not the worlds greatest coder by a very long way but even I can think of a couple of ways of doing that. I just don't have the time available to make it a reality I'm afraid. I wish I did.

Yes, that's what I had in mind. We could have a standardised front-end template (like we have now in uibuilder but better designed!) that creates the outer framework, the page layout if you will. It would certainly be a fairly standard controllable grid just as Dashboard is. And then you would define your cards to layer onto the grid. And the components would sit in the cards. All just like now. Except that, instead of having to write a whole node to define a component, it would be written using standard web tooling.

I already have something on the backlog that will make the template part easier. In the last release, I made it possible to switch between a limited set of templates. In a future release (hopefully the next but not sure yet), I will open that up so that you can supply a GitHub repository URL. That repository would contain the template folder structure complete with a package.json that defines what dev/build dependencies and scripts you might need. That means that anyone can create templates for uibuilder and they can be easily consumed. Hopefully you can see how that would fit in with the concepts outlined above.

Yup.

Not necessarily, someone with the will and some time could do it - look at the original precursor to Dashboard, UI. Built by one person and fairly rapidly too.

Good :grinning: - as long as we agree that sending code from the back-end to the front-end is not a good idea.

look what happens when you go to sleep

I concur. This is what naively this function node does. It extracts from the flows the configuration of dashboard (layout and widget types) and that is then sent to the front end (in my case flutter but implementation is irrelevant ) for implementation.

This is what I am very keen to get documented (I have simply re ordered the flow json structure to be easier on the front end however I expect some thinking needs to go into it and it needs to be expanded.

let flows = msg.payload

let tabs = flows.filter(elm => elm.type == "ui_tab" )
let uigroups = flows.filter(elm => elm.type.substring(0,8) == "ui_group" )

let uiTabs = []

for (let i =0; i < tabs.length; i++){
    tab = tabs[i]
    let elms = uigroups.filter(elm => elm.tab == tab.id)
    elms = elms.sort((a, b) => a.order - b.order)
    console.log(`elms ${elms.length}`)
    let groups = []
   
    for (let j = 0; j < elms.length; j++){
        group = elms[j];
        let groupElms = flows.filter(elm => elm.group == group.id)
        let cleansedElms = []
      //  cleansedElms.push(groupElms);
        for (let k =0; k < groupElms.length; k++){
            elm = groupElms[k]
            elm.width = parseInt(elm.width)
            elm.height = parseInt(elm.height)
            if (elm.height == 0) elm.height = 1;
            if (elm.width == 0) elm.width = 1;
         //   elm.height = elm.height == 0 ? 1 : elm.height
            
            delete elm.wires
            cleansedElms.push(elm)
        }
        cleansedElms = cleansedElms.sort((a, b) => a.order - b.order)
        groups.push({id : group.id, group_details : group , elements : cleansedElms})
    }
    uiTabs.push({id : tab.id, tab_details: tab, groups : groups})
}


let control = {
    type : "control",
    scope : "tab_build",
    tabs : uiTabs
    
} 

msg.payload = flows
msg.control = control

Essentially what the flutter approach is, except and I acknowledge it is the reason it CANT be the answer is that to add new components types, you would need to develop the component implement and rebuild the app a non-starter, however for standard components, it works pretty well.

A big problem but as @TotallyInformation said the problem is probably not as large as it seems - and can be broken into bits. And as the saying goes to eat an elephant you eat 1 bit at a time

So how do we move forward on this? Do we want to jump on a call to agree to next steps ?

As far as I can tell, there are 2 set of things to standardize: the layout directives and the standard component messages.

By layout directives I mean the following:

  • assume the page contains a rectangular area that is to be filled with components
  • the layout directives name the components to place together with layout information
  • in a reimplementation of the std dashboard that rectangular area would be the entire web page minus the header
  • in a new dashboard, it could be just a portion of the page, the rest being filled with more custom stuff
  • in effect, the rectangular area is a component that receives a big layout message and proceeds to instantiate and lay out the appropriate components within its area
  • if we can define a common layout directive language then there could be multiple font-end implementations using different front-end frameworks (and native apps)

By standard component messages I mean the following:

  • assume the front-end contains implementations for the components/widgets available in the current dashboard (gauge, chart, button, etc.)
  • these components can be instantiated in multiple ways (e.g. using the layout directive stuff above or more custom methods)
  • once instantiated, each component responds to well-formed messages providing ideally the same functionality as the components in the std dashboard
  • if we can define a common set of messages for each of the std components then there could be multiple implementations that can all share the same node-red nodes that produce these messages
  • of course there could be more components beyond what the current std dashboard provides...

There are a few more things that could be standardized, such as control messages, but I believe the above is the bulk of the work.

One thing I'm assuming is that the ui_template node will not feature in the above, or if it does, there's no compatibility.

1 Like

Yes, I agree

For layout directives, the current dashboard gives a lot of this information. It is not perfect but a good start

For standard component messages, I am currently just passing on the message object and having the FE UI class interpret that. (topic, payload, color etc). I concur it needs to be standardized

BTW I am looking at an implementation of the template node with html/css. the flutter code supports this however i need to understand how it gets standardized

Great discussion, really starting to form some good ideas.

One I want to consider, see if there is demand for is the idea of sub-components - or groups of widgets. Should we be able to define a new component from a preformed group of widgets - eg some text, an indicator and a switch - that can then be re-used. (eg like a subflow) ? and thence could potentially be exported/shared etc. Is there any appetite for this ? Or would creating custom widgets be so easy that it wouldn't be necessary ?

Also yes I think we will absolutely need something like the ui_template as there will always be cases where the standard widgets don't go far enough but a full custom dashboard is way too much.