Migrating configuration to a configuration node (or, programmatically instantiating a config node)

I'm considering moving some configuration fields from an existing node to a (new) config node, but would like to avoid a breaking change if possible. Is there a way to instantiate a config node programmatically? If there is, I figured I could just hide the original input fields from the edit node dialog, check if a config node instance matching any "legacy" config exists, and instantiate it if it doesn't. (I would also remove the legacy config, so the check wouldn't have to run again.)

I'm guessing it's is not possible, as the only thing I found on the topic was someone who talked about it being a breaking change. Figured I should ask, though, things might have changed in the ~ year since.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.

Hi @sverre,

I had overlooked your question, due to the large amount of posts that are created nowadays on Discourse. But it was a very good question!

Last night I was also wondering how I needed to do it, so I found your post. I have asked Paul to reopen this discussion, so I can share my solution. Perhaps you don't need it anymore, but then others might benefit from it ...

The original node

In your original node you have a property with a default value:

<script type="text/javascript">
    RED.nodes.registerType('my-node',{
        defaults: {
            originalProperty: {value: "defaultValue"}
        },    
        ...
    });
</script>

That property is displayed on the node's config screen:

<script type="text/x-red" data-template-name="my-node">
   ...
   <div class="form-row">
        <label for="node-input-originalProperty">Original property</span></label>
        <input type="number" id="node-input-originalProperty">
   </div>
   ...
</script>

And the entered property value becomes available on the server side after a deploy:

 module.exports = function(RED) {
    var settings = RED.settings;

    function MyNode(config) {
        RED.nodes.createNode(this, config);
        this.originalProperty = config.originalProperty;
        ...
    }
    
    RED.nodes.registerType("my-node", MyNode);
}

So far so good. But as soon as lots of these nodes are added to a flow, people need to start copying the value of originalProperty to all those nodes to keep them in sync. Which is rather annoying...

The updated node

So you want to introduce a config node, so users can enter their value in that config node (and re-use it in multiple nodes). But you need to migrate the original value into the config node, otherwise people will loose their original property values (because the original input field won't be visible anymore).

  1. Create a config node (i.e. html and js file) which contains now the originalProperty:

    <script type="text/javascript">
        RED.nodes.registerType('my-config-node',{
            category: "config",
            defaults: {
                originalProperty: {value: "defaultValue"}
            },    
            ...
        });
    </script>
    

    And the original property is now displayed on the config node' config screen:

    <script type="text/x-red" data-template-name="my-node">
       ...
       <div class="form-row">
            <label for="node-input-config-originalProperty">Original property</span></label>
           <input type="number" id="node-input-config-originalProperty">
       </div>
       ...
    </script>
    
  2. Remove the original html input element "node-input-originalProperty" because the original property shouldn't be visualized anymore on the node's config screen. Instead we will add a new input field, that will be used as a placeholder where Node-RED can show a dropdown with all available config nodes:

    <script type="text/x-red" data-template-name="my-node">
       ...
       <div class="form-row">
            <label for="node-input-myConfig">Configuration</span></label>
            <input type="number" id="node-input-myConfig">
       </div>
       ...
    </script>
    
  3. Since the original property values need to be migrated, make sure that those values are still being remembered by Node-Red by keeping the originalProperty under the cover:

    <script type="text/javascript">
        RED.nodes.registerType('my-node',{
            defaults: {
                originalProperty: {value: null},
                myConfig: {value:"", type: "my-config-node"},
            },    
            ...
        });
    </script>
    

    Note that we use a default value of null for the original value, to be able afterwards to determine whether we have an original value (that needs to be migrated).

  4. As soon as the config screen of our original node is opened, a new config node needs to be instantiated (when there is no config node yet and there are original values to be migrated):

    oneditprepare: function() {
       var node = this;
    
       // Migration of old nodes when no config node is available, and the original property is available            
       if(!node.myConfig && node.originalProperty) {
          var configNodeId = RED.nodes.id();
          var configNodeName = "some config name";
          
          // Create a new config node
          node.myConfig = {
             id: configNodeId,
             _def: RED.nodes.getType("my-config-node"),
             type: "my-config-node",
             hasUsers: false, 
             users: [],
             name: configNodeName,
             label: function() { return this.name || "some name"}
          }
          
          // Copy the value of the original property to the config node
          node.myConfig.originalProperty = node.originalProperty;
    
          node.originalProperty = null;
    
          // Make sure Node-RED knows about the new config node instance (so you can deploy it)
          RED.nodes.add(node.myConfig);
          
          // Add the new config node in the dropdown and make sure it selected.
          // Otherwise it would only be displayed in the list, the next time you open this config screen...
          $("#node-input-myConfig").append($('<option>', {
             value: configNodeId,
             text: configNodeName,
             selected: "selected"
           }));
       }
    
  5. Update the server side (js file) to use the originalProperty from the config node. Or if there is no config node yet, use the originalProperty which is still available in the node (which will be the case if the user has not opened the node's config screen yet):

    module.exports = function(RED) {
       var settings = RED.settings;
    
       function MyNode(config) {
           RED.nodes.createNode(this, config);
    
           // Retrieve the config node, where the timings are configured
           var myConfigNode = RED.nodes.getNode(config.myConfig);
        
           if (myConfigNode) {
              // Use the original property value from the config node
              node.originalProperty = myConfigNode.originalProperty;
           }
           else {
              // Use the original property value from the original node
              node.originalProperty = config.originalProperty;           
           }
           ...
       }
    
       RED.nodes.registerType("my-node", MyNode);
    }
    

This way a new config node is automatically created when you open the node's config screen, where the original property value is displayed. And when a new node is dragged onto the flow, no new config node is created automatically: indeed the user perhaps want to re-use an existing config node...

Good luck!
Bart

P.S. Perhaps my code doesn't cover all edge cases, because I wrote it last night and so it hasn't been fully tested yet. If I find any issue, I will report it here...