Can node property dialog use data from config node property?

Hi All,
Let me preface this by saying that I am not a programmer, but know enough to be dangerous. Also I am using a RPi4 running NR 3.0 and Node.js 16.17

OK here goes:
I am trying to modify an i2c port expander node that uses a hidden chip configuration node to define the port expander chip type. For the moment I am focusing on the browser side (HTML file).
When editing the property of the port node, the user can either select a previously defined chip configuration or create a new one. If the latter, the user selects the type of chip (from a list of 5 different port expanders), i2c bus and i2c address. The range of i2c address options is automatically changed based on the chip type selection. So far so good - this all works.
The user is then returned to the port node's property dialog where a port number needs to be selected (a separate port node is needed for each port on the chip). Ports can range from 0-7 for an 8 port expander, or 0-15 for a 16 port expander. What I would like to do is create a dropdown list for the port entry, where the number of options depends on the chip type selected in the chip configuration node, but have been unable to figure out how.

The node I am trying to modify is @pizzaprogram/mcp-pcf-aio

Welcome to the forum Joe.

This is a standard configuration for nodes. The configuration node type provides exactly the kind of drop-down of existing configs + an entry for defining a new config that you describe.

The docs for creating custom nodes contain the details of how this works and there are many examples you can draw on from existing nodes such as the serial node.

Configuration nodes : Node-RED (nodered.org)

Your main node stores the link to the selected configuration so that all relevant data can be accessed from the .js file for the main node.

Thanks @TotallyInformation . I have read the link you refer to (many times), but was trying to work on the HTML file before having to deal with the .js file. I feared that this may not be possible. I guess the chip configuration node needs to be created server-side before the editor can have access to its data?

Just so I fully understand does this mean the user needs to deploy the port node first before user can gain access to the configuration node's data in the property dialog, which would mean creating the port node with a dummy port entry, deploying and then opening the dialog again to select the port needed and redeploying?

OR

Does the .js file create the configuration node and make its data available to the port node editor the moment the user hits the button that closes the chip configuration node's editor to return to the port node's editor - in other words: almost like a "hidden deploy"?

Hmmm....now that I think about it, it is most likely the latter and I have answered my own question :slightly_smiling_face:

Your main node controls the creation and use of a configuration node. So you add the main node, open the node's config panel then the input field that wraps the config node can be seen and if no existing config nodes of that type exist, the flow author creates a new one. If one exists, it will be pre-selected, ...

Just add an mqtt node or a serial node or any node that contains a config node, you will see how it works.

Hi,
Thanks for trying my node.
I've red 3x your orig. post, still not understand what's your end goal is.

WHY needing a drop down list, if you can:

  1. simply type that number into that edit box OR
  2. you can feed that info from a connected CHANGE node too.

Did you check the Samples installed with the node?

@TotallyInformation: Understood and I actually use MQTT and have looked into its code. Also for serial port. Key difference is none of the main node's selections depend on the config node's definition. In my case the range of port numbers depends on the chip selected (e.g. MCP23008 has 8 ports, MCP23017 has 16 ports).

@PizzaProgram: I don't need a drop-down, I'd like to have a drop down for a couple of reasons

  1. Easier/more ergonomic node input: no need to switch from mouse to keyboard and back
  2. Selection is limited (only 8 or 16 options, depending on chip)
  3. Avoid typos: currently validation that entry is number, but for 8 port chip e.g. 15 can be entered

Given that a picture is worth a thousand words, here goes:


Main/Port (MCP23008 previously defined)


Chip config node defining MCP23017 on same i2c bus, different address


Main Port node, now showing MCP23017 chip in selection list


Main Port node, with MCP23017 selected. Bit (= Port) Number can only be entered as text. What I would like is for this to be a drop-down list showing ports 0-15 (same if PCF8575 were configured). If the MCP23008 configuration is selected, the drop-down list should limit selectable ports to 0-7 (same for PCF8574 and PCF8574A), or at the least limit entry to <8 for 8-port chips and <16 for 16-port chips.

I am starting to get the nagging feeling that what I want is impossible. If so, then I will revert to text entry, but want to verify if what I want is possible.

It is probably possible, but it's HTML scripting thing, I know very very little about.
I was happy to squees out 6 month of my time to learn JavaScript just to finish this node.

  • Input part is maybe still buggy.
  • Also interrupt needs testing.

So any help is greatly appreciated, If you may have spare time to test.
I think working on stability is 10x more important, than extras.

I think you might have missed something. Your main node records a REFERENCE to the chosen instance of the config node. That is made permanent when you redeploy. Then your .js runtime file needs to get that reference in the same way you take all of the other .html settings as in this snippet from one of my nodes:

/** 2) This is run when an actual instance of our node is committed to a flow
 * type {function(this:runtimeNode&senderNode, runtimeNodeConfig & senderNode):void}
 * @param {runtimeNodeConfig & cacheNode} config The Node-RED node instance config object
 * @this {runtimeNode & cacheNode}
 */
function nodeInstance(config) {
    // As a module-level named function, it will inherit `mod` and other module-level variables

    // If you need it - which you will here - or just use mod.RED if you prefer:
    const RED = mod.RED
    if (RED === null) return

    // Create the node instance - `this` can only be referenced AFTER here
    RED.nodes.createNode(this, config)

    /** Transfer config items from the Editor panel to the runtime */
    this.name = config.name
    this.cacheall = config.cacheall
    this.cacheKey = config.cacheKey || 'topic'
    this.newcache = config.newcache === undefined ? true : config.newcache
    this.num = config.num || 1 // zero is unlimited cache
    this.storeName = config.storeName || 'default'
    this.storeContext = config.storeContext || 'context'
    this.varName = config.varName || 'uib_cache'

One of the config variables (over-use of the word config I'm afraid) will now actually contain a REFERENCE to the actual instance of the config node's runtime and therefore you have access to all of the recorded settings in that node.

this.someconfignode = config.someconfignode || {} // comes from the Editor

const somethingimportant = this.someconfignode.myconfigsetting // Accesses the config node's data

Bear with me here, because I am at (over?) the level of my understanding:

When you say "redeploy", does that mean I need to deploy at least twice?

Looking at your code snippet (thank you BTW - I found the actual code on your GitHub repository; copious comments much appreciated) and comparing to what I have:

module.exports = function(RED) {

....

    function mcp_pcf_chipNode(n) {
        RED.nodes.createNode(this, n);
        var mainChipNode = this;

        this.chipType       = n.chipType;
        this.busNum         = parseInt(n.busNum, 10); // converts string to decimal (10)
        this.addr           = parseInt(n.addr  , 16); // converts from HEXA (16) to decimal (10)
        if (isNaN(this.addr)) {this.addr  = 0x20; }

....
    // REGISTERING the main chip : 
    RED.nodes.registerType("mcp_pcf_chip", mcp_pcf_chipNode);
....

//OUTPUT SECTION
    function mcp_pcf_outNode(_OutputConfig) {

        RED.nodes.createNode(this, _OutputConfig);

        var node 		  = this;
        node.bitNum       = _OutputConfig.bitNum;
        node.invert       = _OutputConfig.invert;
        node.lastState	  = -2;
        node.initOK       = false;
        // check Master-Chip setup
        let _parentChipNode = RED.nodes.getNode(_OutputConfig.chip); // this is not a visible node, but a hidden Chip-configuration one
        node.parentChip		= _parentChipNode;
        if (!_parentChipNode) {
            node.error("Master MCP23017 or PCF8574 Chip not set! Skipping node creation. Node.id=" + node.id);
            showState(node, -2, 2);
            return null;
        }
....    
    RED.nodes.registerType("MCP PCF Out", mcp_pcf_outNode);
}

the missing lines seem to be

If so, it is unclear to me where I would insert those lines: right after the function mcp_pcf_chipNode(n) - which registers the configuration node - or right after the function mcp_pcf_outNode(_OutputConfig) - which sets up the output Node and is the main node that uses the config node?

To reiterate, what I would like to do is use this.chipType, to predefine the bitNum select options in the editor.

Thanks again!

Hi @Joe-AB1DO

I am somewhat reading between the lines here so please ignore if I am misunderstanding your requirement.

Anyhow. If you add an mqtt-in node & then add an mqtt-config & set the protocol to v5, you will see different options in the mqtt-in node then if the config was set to v3 protocol. Try adding 2 config, 1 set to v5 & the other to v3, then switch between config - you will see the options in the form update dynamically.

Is that what you are trying to achieve?

Hi @Steve-Mcl

Yes, exactly! Setting up a new broker with different protocol, as you suggested, changed what MQTT options were available. In my case it would be a little simpler in that I would want to prefill the portNum select options, but the mechanism looks very much the same.

If you can point me in the right direction, I can take it from there.

Thanks much.

No, only once. A redeploy deploys all changes in the Editor at once.

Sorry, I later realised that might be confusing. That code comes from my re-worked way of writing nodes which deconstructs them in a way I find more logical. In doing so, you have to keep some other references including a reference to the RED object. In the "standard" way of constructing your node's runtime, you don't need to do that. The multi-layered nature of the node runtime can be very confusing, the way I do it breaks that down into more logical chunks in what I think is a more javascript-like way of working. It is probably only worth doing for more complex nodes (though I now do it for all my nodes since I have a template that makes it simple).

@Steve-Mcl , thanks to your response, I think I am getting close, but there's something I'm not quite getting right. In my case "node-input-broker" id is equivalent to "node-input-chip" id, which displays the chip config being used. Analogous to the MQTT-out node, where the highlighted line in the code snippet you provided resides, I added an oneditprepare option to my mcp_pcf_out node:

oneditprepare: function() {
			const node = this;
			const is16Port = function() {
				var chipConfNode = RED.nodes.node($('#node-input-chip').val());
				return chipConfNode && chipConfNode.chipType === "MCP23017";
			}

			const updatePortList = function() {
				var maxPorts = (is16Port)? 16:8;
				
				$('#node-input-bitNum').empty();
				for (var i = 0; i < maxPorts; i++) {
					var value = String(i);
					var text = String(i);
					$('#node-input-bitNum').append($("<option></option>").attr("value", value).text(text));
				}
			}

			$('#node-input-chip').on("change",function() {
			    updatePortList();
			});
		}

However, no matter what chipType I select in the config node, the port list in the mcp_pcf_out node is always 16 long, which to me would indicate that is16Port is always true?

The easiest way to debug what is happening is to use the debugger keyword

oneditprepare: function() {
    debugger 

Save and restart node-red.

Open node-red editor in your browser then open devtools (F12).

When you double click your node, the debugger should kick in and you can use break points and inspect code and also try stuff in the console (like trying out jQuery code to see if what you're using is correct)

Hi @Steve-Mcl ,
thanks for the suggestion on adding the debugger statement and how to invoke it. That kind of helped and I had to read up lots on how to use the debugger and breakpoints. However I've hit a wall: I cannot step into the is16Port = function(), so cannot see if if the var chipConfNode actually hass a value, despite having set breakpoints on every line in that function. Step-in is doing the same as step-over?

Also, when you say "try stuff in the console (like trying out jQuery code to see if what you're using is correct)", are you referring to the console tab in the devtools window that pops up when hitting F12 (I am using Firefox)? While this tab shows the code with breakpoints, there seems to be no way I can edit anything showing in that console tab, whether the code is running or not, so I must be missing something.

OK, figured out the console editing part. It turns out the jQuery statement $('#node-input-chip').val() is undefined.

Question I have, do I need to add an oneditsave() function to the config node, to save the chipType value, so that I can refer to it in the main node?

Getting very close: everything works as desired, except when the node is first used with no chips configured.

  1. On opening of the main node's properties dialog, the first chip is configured with chip configuration node. After configuration of the first chip, the chip shows up in the main property dialog, but the list of port numbers remains empty.
  2. Only when the main node properties dialog is closed and reopened does the port number dropdown list show the available ports (either 8 or 16 depending on what kind of chip is selected)
  3. From that point on chip configurations can be added and depending on which is selected, the port number list will show a list of 8 or 16 ports, all without closing the main property dialog.
  4. The configuration nodes all show up in Node-Red editor's config nodes panel on the right, the moment they are created, so also when the first chip is configured

In the node's main property dialog, a configuration node's settings are obtained in the oneditprepare function using:

var chipConfNode = RED.nodes.node($('#node-input-chip').val());

which returns an object with all the settings of the relevant configuration. The actual Chip Type is then obtained using chipConfNode.chipType;. In the debugger this shows the Chip Type as selected, except on first instance as described under 1. above, where the property is undefined, even after the first chip configuration node is created, but the main property dialog not closed and reopened.

I suspect this has to do with the fact that I am referring to the config node object in the oneditprepare option of that main property dialog. When the dialog is first opened, the oneditprepare function has already run, but the config node property is undefined. With the property dialog still open and after creating the first chip configuration the chipConfNode object is still undefined. Only after closing and reopening the main property dialog does oneditprepare run again with the chipConfNode object defined.

If this is indeed true, then I am wondering how to ensure the chipConfNode object is defined immediately after creation of the first config node instance?

My apologies in advance for the long post!

Boiling it down to its essence:

  • I have a main node that uses a hidden configuration node. In the main node's properties dialog, one or more instances of the configuration node can be created, as would be expected. As each one is created, I see it appear in the browser configuration nodes side panel to the right of the flow editor panel.

  • The main node has an oneditprepare function that refers to a selected instance of the configuration node object using RED.nodes.node($('#node-input-chip').val()), where node-input-chip refers to the id of the html input tag that holds the list of created instances of the config node. One of the object's properties (.chipType) is used to prefill the options of a select list.

  • All this works as designed, but only after at least one instance of the configuration node has been created and the main node properties dialog closed by clicking Done. When reopening the properties dialog, the select list options are prefilled as expected. From that point on, additional instances of the config node can be created and the select list options prefill as expected. Switching between instances of the config node is also responsive.

  • Debugging shows that before any instance of the config node has been created, the RED.nodes.node($('#node-input-chip').val()) object is undefined. It is only defined after creation of at least one instance of the config node, clicking Done to close the main node's properties dialog and reopening it, as described above. It's almost as if after creating an instance of the config node, I need to rerun the main node's oneditprepare function.

  • Although I could live with this situation, it seems a little clumsy. I have seen with the MQTT node, that when creating various brokers, of which at least one uses protocol 5, the main node immediately responds to the selected broker (show/hide protocol 5 options), even when no broker configuration has been previously defined, i.e. no need to close and reopen the main dialog window as in my case. I have gone over and over the MQTT node's code, but for the life of me cannot find how this is done.

Although @Steve-Mcl has pointed me in the right direction to get this far, I just need one more nudge.
I am getting so close I can smell it!

Thanks,
Joe