Node Editor: fields that depend one to another

Hi,
I’m developing my first custom node.
At the end the node will need to call a Web API, but now I have problems with the configuration of the node.
Simplifying a lot: it will have a set of dropdowns for possible values to be chosen. This value are result of a API call to a metadata function.
I’m able to initiate the first metadata call (using a configuration node), and when it’s retrieved it enables and populate the first dropdown with 1st level decision. Then once a value is selected, it enable and fills the second dropdowns. Until this point, it seems ok.
First problem I have is that if I change the selected value of the 1st dropdown, nothing happens in the second dropdown. It looks like once it’s populated and created, I cannot change the content of the dropdown.

Do you know if this is even possible?
I can share some code later if it helps.

Thanks

Hi and Welcome,
Yeah please share some code to help you

Here's my example.
The configuration of my node consists of a 3 level hierarchy:

  • Server: which is the connection to the server hosting the application on which I want to call the API. The configuration of the server consists of address and username/psw. This is a configuration node.
  • then Plant: the server application provide the possibility to deploy several plant hosted on the same server.
  • then Application: a Server will contain a list of deployed application per plant (I have a metadata url endpoint to retrieve the list of application on the server, given a specified plant.)
  • then Command: each application host a list of command. I can get the list of deployed command using a metadata call again.

What I want to achieve is that once the Server parameter is configured, the list of application will be retrieved, and once an application is selected the list of command will be retrieved.
I'm able to do so, but only at the first "selection": If I change the selected Server, the list of application will not change. It looks like the dropdown for application can be drawn only once.
Same goes for Application and Commands.

Here's my node js/html, there might be still some trying I made in the code, but the problem persists:
server.html

    RED.nodes.registerType('server',{
        category: 'config',
        defaults: {
		    name: {value:"localhost",required:true},
            serverName: {value:"localhost",required:true},
            username: {value:"username",required:true},
            password: {value:"password",required:true}
        },
        label: function() {
            return this.name+" ("+this.username+")";
        }
    });
</script>

<script type="text/html" data-template-name="server">
	<div class="form-row">
        <label for="node-config-input-name"><i class="fa fa-bookmark"></i> Name</label>
        <input type="text" id="node-config-input-name" />
    </div>
	<div class="form-row">
        <label for="node-config-input-serverName"><i class="fa fa-bookmark"></i> Base URL</label>
        <input type="text" id="node-config-input-serverName" />
    </div>
    <div class="form-row">
        <label for="node-config-input-username"><i class="fa fa-bookmark"></i> Username</label>
        <input type="text" id="node-config-input-username" />
    </div>
    <div class="form-row">
        <label for="node-config-input-password"><i class="fa fa-bookmark"></i> Password</label>
        <input type="text" id="node-config-input-password" />
    </div>
</script>

server.js

module.exports = function(RED) {
    function ServerNode(n) {
        RED.nodes.createNode(this,n);
        this.serverName = n.serverName;
        this.username = n.username;
        this.password = n.password;		
    }
	
    RED.nodes.registerType("server",ServerNode);
}

command.html

<script type="text/javascript">
    RED.nodes.registerType('command',{
        category: 'function', 
        color: '#a6bbcf',
        defaults: {
            name: {value:""},
            server: {value:"",required:true,type:"server"}, 
            plant: {value:""},
            application: {value: ""},
            command: {value: ""}
        },
        inputs: 1,
        outputs: 1,
        icon: "file.svg",
        label: function() {
            return this.name||"Command";
        },
        oneditprepare: function() { 

            var serverField = $('#node-input-server');
            var plantField = $('#node-input-plant');
            var applicationField = $('#node-input-application');
            var configMetadata; 
  
            plantField.prop("disabled", true);
            applicationField.prop("disabled", true);

            serverField.change(function() {
                var serverVal = RED.nodes.node($('#node-input-server').val()); 
                if (serverVal === undefined)
                {
                    configMetadata = null;
                    setPlant(false);
                }
                else
                {
                    getConfigMetadata(serverVal).then(function (res){
                        configMetadata = res;
                        setPlant(true);
                    });                 
                }
            });

            plantField.change(function() {
                var plantVal = $('#node-input-plant').val();
                if (plantVal === undefined || plantVal == "" || plantVal === null)
                {                    
                    setApplication(false);
                }
                else
                {
                    setApplication(true);
                }
            });


            var setPlant = function(enabled) {
                Plant = $('#node-input-plant');
                if (!enabled) {
                    Plant.prop('disabled',true); 
                }
                else {
                    Plant.prop('disabled',false); 
                    const plants = Object.keys(configMetadata.plants); 
                    Plant.typedInput({ type: 'plant', types: [{ value: 'plant', options: plants }] }); 
                }
            }

            var setApplication = function(enabled) {
                Application = $('#node-input-application');
                if (!enabled)
                {
                    Application.prop('disabled',true);
                }
                else 
                {
                    Application.prop('disabled',false);

                    opcPlant = $('#node-input-plant').val(); 
                    const plant = configMetadata.plants[opcPlant]; 
                    const apps = Object.keys(plant.applicationServiceUrls);
                    Application.typedInput({ types: [{ options: apps }] }); 
                } 
            }

            var setCommand = function(enabled) {
                Command = $('#node-input-command');
            }

            var getConfigMetadata = async function(server) {
                var baseUrl = "https://"+server.serverName+"/config";
 
                const options = {method: 'GET', headers: {Accept: 'application/xml, text/plain, */*'}};

                try {
                    const response = await fetch(baseUrl, options);
                    const data = await response.json();
                    console.log(data);
                    return data;
                } catch (error) {
                    console.error(error);
                }
            };
        }
    });
</script>

<script type="text/html" data-template-name="command">
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div> 
    <div class="form-row">
        <label for="node-input-server"><i class="fa fa-tag"></i>Server</label>
        <input type="text" id="node-input-server">
    </div>
    <div class="form-row">
        <label for="node-input-plant"><i class="fa fa-tag"></i> Plant</label>
        <input type="text" id="node-input-plant">
    </div>  
    <div class="form-row">
        <label for="node-inputapplication"><i class="fa fa-tag"></i> Application</label>
        <input type="text" id="node-input-application">
    </div>  
    <div class="form-row">
        <label for="node-input-command"><i class="fa fa-tag"></i> Command</label>
        <input type="text" id="node-input-command">
    </div> 
</script>

command.js

module.exports = function(RED) {
    function CommandNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        node.on('input', function(msg) {
            msg.payload = msg.payload.toLowerCase();
            node.send(msg);
        });

    }
    RED.nodes.registerType("command",CommandNode);
}

Because you are using typedInput wrong:

This is for initialize the input. Basically this is the definition of the input.

Application.typedInput({ types: [{ options: apps }] });

And this to change types (after initialization).

Application.typedInput("types", [{ options: apps }]);

Hi there, happy new year!
I tried to refactor a little bit, but it's still not working.
I removed all the "disabled" stuff, I will eventually handle it later.
Now when I load the panel for the first time, with this code plantField.typedInput({ types: [{ options: [] }] }); this is shown (the dropdown is empty)

image

then if I configure the Server connection, the dropdown remains empty, even if this piece of code is triggered

image

as you can see, the plants variable is a list containing one item, and I'm using your snippet to change the value in the dropdown, but it's not working.
For reference, this is how I retrieve the opcenterPlant variable opcenterPlant = $('#node-input-opcenterPlant');

Do you know if I'm doing something wrong?

plants should have been:

[{ value: 'Ras', label: 'Ras' }]

The label is not mandatory.

Hi,
I tried using the map function, now i have this

const plants = Object.keys(configMetadata.plants);   
const transformedPlantList = plants.map(item => ({
    value: item,
    label: item
}));
opcenterPlant.typedInput("types", [{ options: transformedPlantList }] ); 

the objects now is correct

image

but the dropdown still remains empty after this...

is any of this documented somewhere? I still can't make it work.

Thanks

I believe it's because the type id is not defined:

opcenterPlant.typedInput("types", [{ value: "plants", options: transformedPlantList }] );

https://nodered.org/docs/api/ui/typedInput/#types-typedefinition

Hi,
sorry for the delay in my response.
That solved the issue. Unfortunately I found this type of implementation really difficult to understand, so I gave up my personal project :sweat_smile: but thank for the support!