Thought I would share this with the community.
Its a generic dynamically generated series of dropdowns built from a configuration JSON file. The user can select a series of options as defined in a tree-like structured JSON and it sets a return value you use in your code.
Whats unique with this solution as compared to others on here, is that the code was 100% generated by AI. The new Claude.AI works fantastic.
Background
I am working on a Lidar project where we need to develop a Config node that allows the user to select a Lidar. Each Manufacturer has different options/models/data profiles etc. So we thought we could start with letting them select the Mfg and then dynamically present subsequent dropdowns based on their selection.
Then we thought why not make a flexible generic drop option HTML template we could use for anything.
So we came up with a JSON to describe the options and asked Claude.AI to generate the NodeRed HTML file to do what we wanted.
And voila. It created the HTML 100%. This is what it looks like and it works smoothly. As you make selections, it creates the next row with the next set of choices until it reaches the bottom of the JSON defined tree or 5 levels deep.
UI
This is truly remarkable to me. Again, Claude.AI generated the code 100%. We didn't need to modify anything.
HTML CODE
<script type="text/javascript">
RED.nodes.registerType('lidar', {
category: 'config',
color: '#FDD0A2',
defaults: {
nodeLabel: { value: "", required: false },
selection: { value: "", required: true }
},
label: function() {
return this.nodeLabel || "Lidar";
},
oneditprepare: function() {
const node = this;
const dropdownContainer = $('#option-dropdown-container');
const nodeLabelWidth = $('#node-config-input-nodeLabel').outerWidth();
$.getJSON('/resources/<your node>/LidarDrivers.json')
.done(function(data) {
const rootOptions = Object.keys(data.children).map(key => data.children[key].name);
createDropdown(dropdownContainer, data, 1, rootOptions);
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.error('Error fetching LidarDrivers.json:', errorThrown);
});
function createDropdown(container, data, level, options) {
const labelText = data.childrenLabel || '';
const descriptionText = data.childrenDescription || '';
const dropdownId = `node-config-input-option${level}`;
const labelElement = $('<label></label>')
.attr('for', dropdownId)
.html(`<i class="fa fa-tag"></i> ${labelText}`);
const dropdownElement = $('<select></select>')
.attr('id', dropdownId)
.css('width', nodeLabelWidth + 'px')
.prop('required', true)
.on('change', function() {
const selectedOption = $(this).val();
const selectedData = data.children.find(child => child.name === selectedOption);
if (selectedData && selectedData.children.length > 0) {
container.find(`.form-row:nth-child(n+${level + 1})`).remove();
createDropdown(container, selectedData, level + 1, Object.keys(selectedData.children).map(key => selectedData.children[key].name));
} else {
container.find(`.form-row:nth-child(n+${level + 1})`).remove();
node.selection = selectedData.value;
}
});
const descriptionElement = $('<div></div>')
.css({
'font-size': '12px',
'color': 'rgba(77, 64, 1, 0.692)'
})
.text(descriptionText);
dropdownElement.append($('<option></option>').val('').text(''));
options.forEach(option => {
dropdownElement.append($('<option></option>').val(option).text(option));
});
const formRow = $('<div></div>')
.addClass('form-row')
.css({
'display': 'flex',
'align-items': 'baseline'
})
.append($('<div></div>').css('width', '115px').append(labelElement))
.append($('<div></div>').css('flex-grow', '1').append(dropdownElement).append(descriptionElement));
container.append(formRow);
}
}
});
</script>
<script type="text/html" data-template-name="lidar">
<h2>Lidar Configuration</h2>
Configure your Lidar sensor settings.
<br>
<br>
<br>
<div class="form-row" style="display: flex; align-items: baseline;">
<label for="node-config-input-nodeLabel" style="width:115px;"><i class="fa fa-tag" aria-hidden="true"></i> Node Label:</label>
<div style="flex-grow: 1;">
<input type="text" id="node-config-input-nodeLabel" placeholder="Lidar" style="width:100%;">
<div style="font-size: 12px; color: rgba(77, 64, 1, 0.692);">Enter a descriptive label for this Lidar configuration node.</div>
</div>
</div>
<div id="option-dropdown-container"></div>
</script>
JSON
To make your own dynamic selection, create a JSON with your choices and put it in the resources directory.
Here's our Lidar selection JSON if you want to test it. Put it in:
/resources/LidarData.json
{
"name": "Lidar",
"value": "",
"childrenLabel": "Mfg",
"childrenDescription": "Select a Manufacturer",
"children": [
{
"name": "Ouster",
"value": "",
"childrenLabel": "Ver",
"childrenDescription": "Select version number",
"children": [
{
"name": "v1.0.0",
"value": "",
"childrenLabel": "Profile",
"childrenDescription": "Select a Profile",
"children": [
{
"name": "N16_R19_DUAL",
"value": "OUSTER_V1.0.0_N16_R19_DUAL.js",
"childrenLabel": "",
"childrenDescription": "",
"children": []
},
{
"name": "N16_R19",
"value": "OUSTER_V1.0.0_N16_R19.js",
"childrenLabel": "",
"childrenDescription": "",
"children": []
}
]
}
]
},
{
"name": "Velodyne",
"value": "",
"childrenLabel": "Model",
"childrenDescription": "Select model number",
"children": [
{
"name": "Model A",
"value": "",
"childrenLabel": "Ver",
"childrenDescription": "Select a version number.",
"children": [
{
"name": "v1.0.0",
"value": "VELODYNE_MODEL_A_V1.0.0.js",
"childrenLabel": "",
"childrenDescription": "",
"children": []
},
{
"name": "v1.0.1",
"value": "VELODYNE_MODEL_A_V1.0.1.js",
"childrenLabel": "",
"childrenDescription": "",
"children": []
}
]
},
{
"name": "Model B",
"value": "",
"childrenLabel": "Ver",
"childrenDescription": "Select a version number.",
"children": [
{
"name": "v1.0.0",
"value": "VELODYNE_MODEL_B_V1.0.0.js",
"childrenLabel": "",
"childrenDescription": "",
"children": []
},
{
"name": "v1.0.1",
"value": "VELODYNE_MODEL_B_V1.0.1.js",
"childrenLabel": "",
"childrenDescription": "",
"children": []
}
]
}
]
}
]
}
Claude.AI is a really capable NodeRed coder.
Have fun!