Custom node with dynamic properties

I want to create a node with a very similar functionality as the switch node (as in user can dynamically add rules)
Though I can't find any resources how the switch node actually accomplishes its dynamic properties.
Currently I generate a few text inputs in a form-row whenever the user presses a button.

That works fine but when I click done and open the node properties again, everything is gone.
How do I make that persistent and also don't loose whatever the user typed into my generated inputs?

I looked at the switch node source but can't really see what it does differently. Its quite a huge node which doesn't make it easy to read to begin with.

It doesn't seem to populate the property UI from context as far as I can see so I thought node-red might handle the persistence for me if the UI is setup in a specific way that I'm missing?

Hi,

Since you don't have shared your code, I will try to explain in short how the switch node manages its rules on the config screen:

  • When the node is registered in Node-RED, the 'rules' property is declared. This is an array of rules, which gets one rule by default:
    RED.nodes.registerType('switch', {
         ...
         defaults: {
             ...
             rules: {value:[{t:"eq", v:"", vt:"str"}]},
             ...
         },
    
  • When the config screen is opened afterwards, all the rules in the array will be displayed on the screen:
    oneditprepare: function() {
         ...
         for (var i=0;i<this.rules.length;i++) {
                 var rule = this.rules[i];
                 $("#node-input-rule-container").editableList('addItem',{r:rule,i:i});
             }
    },
    
  • When the config screen is closed (with the 'done' button), the rules on the config screen need to be copied to the rules array:
    oneditsave: function() {
         var rules = $("#node-input-rule-container").editableList('items');
         var node = this;
         node.rules = [];
         rules.each(function(i) {
                 ...
                 node.rules.push(r);
         });
    },
    

This way the 'rules' field always contains the correct rule set. So when the rules are not displayed, this means you don't show the rules array items on your screen OR you don't store the screen items in your rules array...

It is not easy to create such a node for the first time...

Good luck with it !
Bart

1 Like

Hmm, perhaps a short intro about the rule list widget in html side of the config screen:

  • The visual widget is an editable list container, which needs to be declared like this:
     <div class="form-row node-input-rule-container-row">
         <ol id="node-input-rule-container"></ol>
     </div>
    
  • In oneditsave (see code snippet in my first post) the var rules refers to that editable list, so you can loop through the editable list with rules.each (and copy the rules to node.rules i.e. to the rule array).
  • In oneditprepare it works a similar way, but copying in the other direction.
1 Like

Thank you, I managed to make it work.
It wasnt obvious to me that you have to repopulate the list manually.

It also tripped me up that my array for saving the user values has to be present in the defaults object in order for nod-red to persistently store it and recognize changes after saving.

Just found this thread and thanks for the help, it does exactly what I'm looking for. For future people finding this thread, the location of the switch node source got changed to a different path. Here's the new link for the html and for the javascript file. These aren't permalinks and refer to the master branch, but they're currently located in the node-red repository with the following path: packages/node_modules/@node-red/nodes/core/logic/

2 Likes

How would i make added li-s stay after i click done.

  const addToList = () => {
              let selectedItems = $("#node-input-options option:selected");
              selectedItems.each(function (i, el) {
                // console.log(el, i);
                let text = $(el).text();
                let val = $(el).val();
                var li = $("<li>").text(text).val(val).attr("title", val);
                list.append(li);
                li.on("dblclick", function () {
                  li.remove();
                });
             
              });
            };

@jake123 please do not cross-post. You already have your thread on this topic and people are actively trying to help.

1 Like