Adding a "Dialog" node

I recently needed to indtroduce a dialog box to one of my dashboards, and tried to use the "Notification" node for this, but it didn't seem to be a good fit. What was missing to me was 1) a way to interrupt the flow of a message to ask for confirmation 2) a second output from which the original message would emit if (and only if) the dialog response was "Ok" 3) a way to enter and compare a password with a stored hash 4) the ability to substitute a custom template for the dialog. I thought through my requirements, and explored the "Notification" node's source code, and came to the conclusion that a separate node which only deals with md-dialog might be preferable to making changes to the existing node (I get the impression this node was first only intended to deal with md-toast messages, and that the md-dialog functionality was added as an afterthought).

At first I thought I'd build this as a "contrib" node, but it seems the required classes are not exported; particularly the ui.add method is missing from the scope, which is needed to create these dialogs. Yes, I've seen other solutions, with a hidden node in a dashboard group, but this doesn't seem right to me. So I gritted my teeth and jumped in by cloning node-red-dashboard and making my additions there. This is still very much work in progress, but I've come to a point where I can share a working draft of my ideas and get some feedback.

The "Dialog" node (copied from the help file):

Inputs

payload string
The msg.payload is not used by the dialog node, but will be passed through to the second output under certain conditions (see Output #2 below).

title string
Title of the dialog.

text string
Text message to display in the dialog.

html string
HTML formatted message to display in the dialog. (not yet implemented!)

values varies
The initial value in a "prompt" dialog,or an object with name:value pairs if the dialog has multiple fields (see "Custom fields" below).

fields object
An object describing the fields to show in the dialog (see "Custom fields" below).

Output #1

payload varies
The msg.payload on output #1 can be different types:

  • true, if the dialog is an "alert" dialog.
  • true, if the dialog is a "confirm" dialog, and primary action is taken (e.g. "Ok").
  • the entered value, if the dialog is a "prompt" and primary action is taken (e.g. "Ok").
  • an object containing the entered values, if the dialog is a "prompt" containing multiple fields, and primary action is taken (e.g. "Ok").
  • true, if this is a "passcode" dialog, the correct passcode was entered, and primary action is taken (e.g. "Ok")
  • false, in all other cases.

Output #2

The second output will output the incoming message, but only if the primary action (e.g. "Ok") was chosen. Furthermore, the incoming message will be dropped if the dialog is a prompt dialog and no value(s) have been entered, or if it is a passcode dialog and an incorrect passcode has been entered.

Details

Shows msg.text as a popup dialog

Sending a blank payload will remove any active dialog without sending any data.

Basic usage

Four different dialog types are available:

Alert
A basic alert box which displays a message and can be dismissed with a button.

Confirm
Displays a message and asks the user to confirm (primary action) or abort (secondary action).

Prompt
Asks for user text input.

Passcode
Asks for a passcode. A master passcode can be supplied either as a local plain text cred, or as an SHA-256 hashed local, flow or global string.

Custom fields

Custom fields can be provided for a "prompt" dialog by supplying a msg.fields parameter describing the fields. The initial value of a field can be set with msg.values. The fields supported by the default template include:

Text input

msg.fields = [{
    "type": "text",
    "name": "myText",
    "label": "My text input"
}]

msg.values = {
  "myText": "My initial value"
}

Password input

msg.fields = [{
    "type": "password",
    "name": "myPass",
    "label": "My password input"
}]

msg.values = {
  "myPass": "My initial password"
}

Select box

msg.fields = [{
    "type": "select",
    "name": "mySelect",
    "label": "A select box",
    "options": [{
            "name": "Option One",
            "value": 111 
        },{ 
            "name": "Option Two",
            "value": 222
    }]
}]

msg.values = {
  "mySelect": 222
}

Radio buttons

msg.fields = [{
    "type": "radio",
    "name": "myRadio",
    "label": "A radio button group",
    "options": [{
            "label": "Radio One",
            "value": "R1" 
        },{ 
            "label": "Radio Two",
            "value": "R2"
    }]
}]

msg.values = {
    "myRadio": "R2"
}

Checkboxes
T.b.d.

Date input

msg.fields = [{
    "type": "date",
    "name": "myDate",
    "label": "A date input"
}]

msg.values = {
    "myDate": new Date().toLocaleDateString()
}

Note that it is not currently possible to specify which fields are "required", or change the action buttons, without supplying a custom template (see below).

Custom templates

A custom dialog template can be substituted by specifying a "File" to use, or by entering a "Custom" template directly in the configuration window. Certain Angular elements are required to make a fully functioning template:

<md-dialog>
Container element for the entire dialog.

<md-dialog-content>
Container element for the dialog title, text and field(s).

{{ dialog.title }}
Contents of the msg.title parameter

{{::dialog.textContent}}
Contents of the msg.text parameter

ng-bind-html="::dialog.mdHtmlContent | trusted"
Binds the contents of the msg.html parameter to an Angular container (e.g. a <div>)

<md-dialog-actions>
Container element for the action buttons

<md-button ng-click="dialog.abort()">{{ dialog.cancel }}</md-button>
Action button to dismiss the dialog (e.g. "Cancel").

<md-button ng-click="dialog.hide()">{{ dialog.ok }}</md-button>
Action button to confirm the dialog (e.g. "Ok")

Some screenshots:

Screenshot_2021-02-19_00-29-17 Screenshot_2021-02-19_00-42-39

Screenshot_2021-02-19_00-50-12

Unfinished bits:

  • The default template renders a bit weirdly with certain fields
  • The default template logic needs improving, it's not very elegant
  • Add support for htmlContent (in addition to textContent)
  • Make title, textContent and htmlContent settable in the config
  • Give fields a "required" flag and enforce it
  • Add support for field validator functions
  • Bugs! There are some - and maybe you can help me find more!
  • Tests!?

Look forward to hearing what people - and dashboard core team in particular - think about this new node. Just please be gentle; I've spent a fair amount of time on this.

Edit: The relevant files are

src/main.js                           the mdDialog magic happens here
nodes/ui_dialog.html                  the node config window
nodes/ui_dialog.js                    the node logic
nodes/ui_dialog.md                    my messy dev notes (will be removed)
src/partials/dialog.html              the default template
nodes/locales/en-US/ui_dialog.json    i18n strings
nodes/locales/en-US/ui_dialog.html    help text
3 Likes

Hi - the way the extra nodes work is they are added via the index.js code - so you can see that addWidget is a wrapper around add - so you should be able to get to it that way. Hopefully that would help.

Can't resist the temptation to remind people that uibuilder has a simple "toast" feature that lets you send a msg that will appear as a popup on your UI without needing any front-end code :wink:

It has the full capabilities of bootstrap-vue's <b-toast> component which draws on bootstrap. So it can contain any HTML and Vue componentry including a form.

bootstrap-vue includes a number of easy to use alerts and dialogs:

  • b-alert - simple alert with powerful features
  • b-toast - flexibly positioned popup/popover
  • b-modal: Very easy to trigger - <b-button v-b-modal.my-modal>Show Modal</b-button>
1 Like

That’s great .... once you have built the rest of the dashboard :wink:

4 Likes

Indeed. Any more thoughts about the demise of Angular v1? :grimacing:

I'm still trying to think of ways to make life easier for people to create ui's with uibuilder. But what is really needed is for someone to create an point-and-click integrated UI designer. Beyond my skills I'm afraid.

However, I'll continue to work on ways to interact more easily and to reduce the amount of code needed.

1 Like

Toast != Dialog

  • A toast notification informs the user that something has taken place - a dialog informs the user that something is about to take place.
  • A toast disappears on its own after a pre-set time - a dialog waits for user input.
  • A toast typically contains no interactive widgets - a dialog can have many.
  • A toast typically does not generate any events - a dialog always generates events (even if it is just a "dismissed" event).
  • A toast is never modal - a dialog typically is.

They really are two very different things.

Are you suggesting I/we add a new method there for creating widgets that don't belong to a dashboard group? If so that might be a better solution, if this would allow me to turn the node into a "contrib" (and others to do similar nodes). But I'm also hooking into the configuration phase of Node-RED, to configure the mdDialogProvider and add the "passcode" preset - in fact I think one more preset may be needed, for building "remote control" style dialogs (i.e. a collection of widgets which feed straight to the output in real time). Maybe this is possible to do also from a "contrib" node?

Note: I have renamed msg.locals.fields to msg.fields and msg.initialValue to msg.values on the flow side.

The difference is somewhat arbitrary when looking at Vue/Bootstrap at least. A toast is simple a UI message.

Not necessarily. As long as the toast allows interaction it can be used either way.

Not correct I'm afraid. Maybe when toasts first appeared as a separate concept. But not now, at least not with Bootstrap anyway. Toasts can be retained and need manual cancel.

Typically - but it allows arbitrary HTML content anyway and is programmable.

Maybe tell that to the Node-RED Editor Notification widget :wink: It is very similar to the Bootstrap Toast but can be modal.

"is never" - that's a more reasonable thought. Actually, I've already made a note to add b-alert and b-modal to the next release of uibuilder :grin:

To a web designer perhaps, to a person using Node-RED with Dashboard or uibuilder? Not so much. Since there are lots of such people doing so because they don't have web design experience.

A toast is simplified dialog, as is an alert. Each has useful concepts but from a user perspective, they are simply ways to grab attention. Optionally with interactions.


Anyway, sorry for side-tracking your post, not meaning to detract from it and it is doubtless a great addition to Dashboard.

Damned with faint praise :laughing:

I'm sure us developers used to care about such subtleties. I still do! That's all academic though - I built this node to meet specific and previously unmet requirements, not to replace or compete with the "toast" functionality. I would certainly prefer if I could package it as a stand-alone node - as I'm sure the dashboard core devs would. Maybe that is possible somehow, but I'll need your help!

Sorry if it seemed that way. Any addition to Node-RED is always welcome. I certainly wasn't trying to put you off in any way. As usual, I probably should have kept my fingers off the keyboard :zipper_mouth_face:

I wish I could help you with your Dashboard efforts but I'm afraid that I really don't use it very much and I've certainly not done any development on it. Angular makes my head hurt!

1 Like

I hate it. Convoluted mess.

Ah, but it's from the chocolate factory - maybe the obtuse obesity is intentional.

Haha, well that was one of the main initial drivers behind uibuilder. That and the lack of flexibility of the Dashboard. Don't get me wrong, Dashboard has been a massive success for Node-RED and has really helped with its adoption. But, it isn't for everyone.

I also have concerns about the future for Dashboard because Angular v1 is due to be deprecated this year.

Anyway, I've always been clear that uibuilder deliberately took the opposite approach to Dashboard. The reasons being that (a) it was a LOT more flexible and could be used with any framework or none at all. (b) It was a lot more efficient under the skin. It didn't need to pass large blocks of code to the front-end for example but rather relied on standard web server http(s) mechanisms for that. (c) Dashboard already existed, the community didn't really need a competitor but rather something different.

And yet, it remains a key aim to reduce the boilerplate and complexity for beginners writing custom data-driven UI's. Lots more to do on that front.

Too late for me; I'm in way too deep. It's dashboards all the way down.

1 Like

Eek! I prefer code for my front-end :grin:

Well, I did pick those three flows because they are a little extreme... The first flow is an attempt at getting a dashboard graph to show historical data from InfluxDB. It works, but it's not very elegant. I'd hate to have to implement this on every graph by hand - I would have to turn it into a subflow or a dedicated node first. The second flow is the control for a 4-channel Modbus dimmer, which has physical button inputs for each channel. So the channel state and level can change independently of Node-RED, which makes the flow a little complex (multiple masters). But I specifically wanted this functionality, and spent a long time looking for dimmers which could be operated stand-alone; I could never look myself in the mirror if I built a system that wouldn't let me turn the lights on because the computer is down :slight_smile: It appears to be surprisingly common for IoT systems fall into that trap...

The third flow is a work-in-progress "status screen" which gives me an at-a-glance overview of important aspects of the system. The reason for not turning the many seemingly identical blocks into a subflow is that the types of messages they listen to, and the good/bad checks I perform (in the function nodes), can be wildly different; in many cases it's just checking if a numerical value is above or below certain limit(s), but for example the weather forecast function actually looks through the next two days of forecast data and warns if high wind or heavy rain is predicted. Most of the LEDs have four states; green for nominal, yellow for warn, red for alert, and grey to show that no data has been received on that topic for a set period (that's what the trigger nodes do), but some have fewer states. So it would be quite difficult to turn this into a generic subflow that would work for all cases. I know it's not pretty, but it's already proven to be a very useful addition, and will eventually become the default page shown on the wall.

Ah yes, even worse, so many "home" automation systems rely on the Internet AND the interest of some far-off supplier. That was always a key driver for me getting started with Node-RED.

I certainly wasn't criticising your flows :grin: Merely commenting that I don't like having so many nodes in my own flows. But then I started programming on IBM mainframes 40 years ago so I'm perhaps more than averagely comfortable with code :rofl:

I use Node-RED to handle the data and interfacing aspects of home automation. For the display/UI aspects I prefer to use standard web tooling as I always find myself hitting those nasty barriers that most frameworks introduce. But again, I've been doing web programming since the early days of the web (when I wrote my first content management system) - though not as a professional programmer since I've not been one of those in a very long time.

I worked as a web developer for 20 years, specialising in minimalistic adaptive user interfaces and bespoke content management systems. Over the years I've used many different technologies, from SSI/Perl, to ASP/VBscript, to .NET/C#, to Ruby on Rails. I've got experience in dozens of programming languages, database systems, mark-up languages, network protocols. Ruby will always have a special place in my heart - by far the most elegant language I've worked with, but I also count HTML, CSS, JavaScript and Python as core technologies I get along well with. HATEOAS, graceful degradation, adaptive layout, convention over configuration, fat model skinny controller, and REST are some key design philosophies I subscribe to. Unfortunately, the world has gone in the complete opposite direction and my skills are now largely obsolete; who cares about micro-APIs these days, or the semantics of REST, when you have huge client side "single page application" JS libraries which do all the thinking for you. Personally, I think it sucks, but what can you do? Adapt and survive.

Edit: Would you mind changing how you quoted me in the first quote? You make it sound as if I was saying the opposite of what I actually said... Especially important to me since this is one of my pet peeves with the way a lot of idIoT techonology is designed.

Edit2: For those who don't know what Ruby on Rails is, it is actually the server side framework used to power this very forum. A task for which it is eminently suited.

1 Like

Hi,
so are you planning to make this into a Pull Request for the core dashboard - or do you think you can manage it as a separate contrib-ui node ?

It does look very capable - but with that we need to be mindful of things like code-inject vulnerabilities if it is too open.
What other presets are there ? (and indeed what does Preset mean - when you then have custom two field below... ?)
Is the basic prompt (just about) the same as the existing dialog prompt ?
Should/could this be a (major) upgrade to the existing node ? or (if merged) does it duplicate functionality which we don't really want to confuse users with. If so then we would need to work out how to deprecate the existing, etc.

Thoughts please

Hello @dceejay, many thanks for taking a look at this - and for asking good questions :slight_smile: I approached this thinking I would write a "contrib" node, which I think would be better for a number of reasons:

  • My code may not be up to scratch for inclusion in dashboard core.
  • Getting code into core is a long process (as it should be), and I want to move faster.
  • Others may want to add functionality, which is easier if it's a separate project.
  • No need to deal with deprecation of duplicate functionality in the "Notification" node.

But I quickly ran into the limitation that a dashboard node cannot exist "outside" the tab/group structure, which would force me to use clunky work-arounds to get the functionality included in existing dashboards. I suspect it might also preclude the "send to all" functionality of the existing notification node, which is something I'd like to preserve. You encouraged me to look at ui.js and index.js, and I can see how addWidget calls the more fundamental add method, but this method is not exported to the scope the dashboard runs in and cannot be called directly by a "contrib" node. I think maybe this is where a change in the core code would be most effective, since it would enable others to create similar nodes. That all said, some things also make me think my node could be a good candidate for a core PR:

  • Looking at the code for the existing "Notification" node, it seems this was intended mainly as a wrapper for the mdToast class, which has a very different API to the mdDialog class. I'm afraid I have to insist that these are two quite different and distinct classes of UI elements, with different use cases and functionality - and arguably deserve their own nodes.
  • Because I particularly needed an authentication dialog, it seemed appropriate to add this as a separate "preset", so that it was easy to hook into the convertBack event for hashing the entered response - something no other dialog use case would require (I since added support for the existing "cred" typedInput as well, for good measure). But this requires access to the initialisation stage of AngularJS, which I don't think is available from a regular node.
  • Popping up a dialog and acting on the response seems like a fundamental piece of functionality in a UI framework, which although partly provided by the existing "Notification" node remains somewhat lacking in the dashboard. For example, there's currently no way to specify which widgets a dialog should contain (only as HTML), so you can't do date pickers or sliders. Also missing (and essential to me) is the ability to catch an incoming msg and only pass it on if "Ok" is clicked in the dialog.

WRT the difference between template and preset, it's pretty simple; a preset defines the behaviour of the dialog, a template its contents. So the "alert" preset only informs, with the sole action being "ok, I have read what you had to say", the "confirm" preset will ask "this is what I'm about to do, do you want me to proceed?" (ok/cancel), the "prompt" preset will ask for information, for example a date, or some numerical value, and you can only proceed once it has been supplied (or cancel the request). These three presets are already provided by the mdDialog object - I added the "passcode" preset for the reasons outlined above, and I've been thinking a lot about adding a "remote" preset for direct msg emission as well, though it's not clear to me yet whether this would be a good fit or even a useful addition. Potential use cases include things like:

  • A pop-up slider dialog, to interactively adjust the colour of a light, with the value being stored on "ok" and reverted on "cancel".
  • Custom touch-screen keypads, where each key emits a msg when pressed.
  • Adding "Edit" functionality to dashboard widgets, which open the editable settings in a pop-up dialog.

Admittedly you could do much of this with a custom "template" as well, but using mdDialog "presets" gives you some extra magic, and allows you to have server side code execute for some presets and not others - and intentionally follows the mdDialog API. End users can supply their own "templates", but not "presets".

WRT injection vulnerabilities, the node does not (currently) accept any input parameters which get directly rendered; the fields and values parameters will cause errors if they are not well formed objects, and are only used as recipes for the form fields. I'm not going to say it is invulnerable to injection (that's beyond my pay grade); for example I don't know what would happen if a function was supplied as a value - would AngularJS evaluate it? It is true that once I add mdHtmlContent, which the "Notification" node also allows, that may be a way to inject potentially harmful content, but I'm still reading up on how exactly the sanitation of this works.

At the end of all that, I just want to say I put this proposal out there to get help and feedback more than anything - perhaps I've overlooked something obvious, or taken the wrong approach in part or in whole. I would be the first to admit that I have much to learn, and I certainly do not want to try to force my requirements or solutions on anyone.

1 Like