Subflows and configuration: late and runtime parameters binding

The problem

To make the life better, and to make flows more robust, with a single centralized update, I make use of configuration/options constants.

But where to place configuration data?

Environement

  • pros: standard. Environment variables can be used as subflow parameters.
  • cons: complex installation. To update you must exit node-red and do some oprations (O.S. drpendant).

Not for all user, only valid for few and constant values (e.g. username), not for user options.

Global context

  • pros: easy to set and update inside node-red, e.g. using the node-contrib-config, very friendly.
  • cons: based on the current subflow implementation, parameters cannot come from the global context.

my solution

Until the subflow implementation does not change, I have found a simple workaround: a node-function that does a late binding (at runtime) of some/all parameters of a subflow, so we can implement an indirect parameter definition and use the node-contrib-config to manage the options.

features

late binding:

  1. if the subflow has a parameter whose value (string) start with:

    • global.
    • flow.
    • msg.

    this function calculates the actual value and assigns it to the parameter.

runtime update:

  1. if the incoming msg contains msg.<paramName>:tempvalue
    then tempvalue is temporarily assigned to parameter (optional).

  2. the dynamic value can be obtained inside the subflow using flow.get(<paramName>) (instead of env.get(<paramName>), still valid for static parameters).

use

In your subflow:

  1. Put a 'late blinding' function node between the msg input and your nodes.
  2. Update the function code, adding at end one line for any dynamic parameter:
       _upadateParam(<dynamicName1>); 
       _upadateParam(<dynamicName2>); 
  1. If you are updating an existing subflow, replace all env.get(<dynamicNameX> with flow.get(<dynamicNameX>.

Enjoy

[
    {
        "id": "faaa7cdb933cb272",
        "type": "subflow",
        "name": "late binding example",
        "info": "Subflows and configuration: late and runtime parameters binding.\n\n## The problem\nTo make the life better, and to make flows more robust, with a single centralized update, I make use of configuration/options constants.\n\nBut where to place configuration data?\n\n### Environement\n-    **pros:** standard. Environment  variables can be used as subflow parameters.\n-    **cons:** complex installation. To update you must exit node-red and do some opration (O.S. drpendant).\n\nNot for all user, only valid for few and constant values (e.g. username), not for user options.\n\n### Global context\n-    **pros:** easy to set and update inside node-red, e.g. using the **node-contrib-config**, very friendly.\n-    **cons:** based on the current subflow [implementation](https://nodered.org/docs/user-guide/environment-variables), parameters cannot come from the global context.\n\n## my solution\nUntil the subflow implementation does [not change](https://discourse.nodered.org/t/jsonata-in-subflow-interface/59813?u=msillano), I have found a simple workaround: a node-function that does a late binding (at runtime) of some/all parameters of a subflow, so we can implement an indirect parameter definition and use  the **node-contrib-config** to manage the options.\n\n## features\n**late binding:**\n 1. if the subflow has a parameter whose value (string) start with:\n      - `global.`\n      - `flow.`\n      - `msg.`\n  this function calculates the actual value and assigns it to the parameter.\n\n**runtime update:**\n 2. if the incoming `msg` contains `msg.<paramName>:tempvalue`\n    then `tempvalue` is temporarily assigned to parameter (optional).\n\n 3. the dynamic value can be obtained inside the subflow using `flow.get(<paramName>)` (instead of `env.get(<paramName>`), still valid for static parameters).\n \n## use\n  In your subflow:\n  1.  Put a 'late bimding' function node between the msg input and your nodes.\n  2.  Update the function code, adding at end one line for any dynamic parameter:\n````  \n       _upadateParam(<dynamicName1>); \n       _upadateParam(<dynamicName2>); \n````      \n   3. If you are updating an existing subflow, replace all  `env.get(<dynamicNameX>` with `flow.get(<dynamicNameX>`.\n\nEnjoy\n",
        "category": "",
        "in": [
            {
                "x": 60,
                "y": 60,
                "wires": [
                    {
                        "id": "323fcee7d2ae40cd"
                    }
                ]
            }
        ],
        "out": [],
        "env": [
            {
                "name": "mypameter",
                "type": "str",
                "value": ""
            }
        ],
        "meta": {},
        "color": "#DDAA99"
    },
    {
        "id": "323fcee7d2ae40cd",
        "type": "function",
        "z": "faaa7cdb933cb272",
        "name": "late binding",
        "func": "// This function updates a parameter value and save it in a flow variable with the same name.\n// The nodes in the subflow, instead of using env.get() must use flow.get().\n// Call  _upadateParam(<parameter_name>) for any parameter thath requires late or runtime binding.\n\nfunction _upadateParam(_pname){\n     var  _xvalue = env.get(_pname);\n     if (_xvalue){\n        _xvalue = _xvalue.toString().trim();\n        if (_xvalue.startsWith('global.'))\n           _xvalue = global.get(_xvalue.substring(7));\n        else if(_xvalue.startsWith('flow.'))\n           _xvalue = flow.get(\"$parent.\"+ _xvalue.substring(5));\n        else if(_xvalue.startsWith('msg.'))\n           _xvalue = msg[_xvalue.substring(4)];\n        else _xvalue = env.get(_pname);\n      }\n// optional: to set the parameter dynamically with a message property, same name\n      if(msg[_pname])\n           _xvalue = msg[_pname];\n           \n// save actual value in the (sub)flow context           \n      flow.set(_pname, _xvalue);\n      }\n      \n// here CUSTOMIZE:      \n_upadateParam(\"myparameter\") ;   // the parameter name\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 190,
        "y": 60,
        "wires": [
            [
                "a81b3db0f226a117"
            ]
        ]
    },
    {
        "id": "90e9b0da29850be2",
        "type": "comment",
        "z": "faaa7cdb933cb272",
        "name": "here more subflow nodes",
        "info": "",
        "x": 410,
        "y": 120,
        "wires": []
    },
    {
        "id": "a81b3db0f226a117",
        "type": "debug",
        "z": "faaa7cdb933cb272",
        "name": "only for test",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 390,
        "y": 60,
        "wires": []
    }
]