/** * Dropdown population methods for MenuUtils. * Handles populating and cascading dropdown menus for assets, suppliers, models, units, etc. */ const dropdownPopulation = { populateSmoothingMethods(configUrls, elements, node) { this.fetchData(configUrls.cloud.config, configUrls.local.config) .then((configData) => { const smoothingMethods = configData.smoothing?.smoothMethod?.rules?.values?.map( (o) => o.value ) || []; this.populateDropdown( elements.smoothMethod, smoothingMethods, node, "smooth_method" ); }) .catch((err) => { console.error("Error loading smoothing methods", err); }); }, populateInterpolationMethods(configUrls, elements, node) { this.fetchData(configUrls.cloud.config, configUrls.local.config) .then((configData) => { const interpolationMethods = configData?.interpolation?.type?.rules?.values.map((m) => m.value) || []; this.populateDropdown( elements.interpolationMethodInput, interpolationMethods, node, "interpolationMethod" ); // Find the selected method and use it to spawn 1 more field to fill in tension //const selectedMethod = interpolationMethods.find(m => m === node.interpolationMethod); this.initTensionToggles(elements, node); }) .catch((err) => { console.error("Error loading interpolation methods", err); }); }, populateLogLevelOptions(logLevelSelect, configData, node) { // debug log level //console.log("Displaying configData => ", configData) ; const logLevels = configData?.general?.logging?.logLevel?.rules?.values?.map( (l) => l.value ) || []; //console.log("Displaying logLevels => ", logLevels); // Reuse your existing generic populateDropdown helper this.populateDropdown(logLevelSelect, logLevels, node.logLevel); }, //cascade dropdowns for asset type, supplier, subType, model, unit fetchAndPopulateDropdowns(configUrls, elements, node) { this.fetchData(configUrls.cloud.config, configUrls.local.config) .then((configData) => { const assetType = configData.asset?.type?.default; const localSuppliersUrl = this.constructUrl(configUrls.local.taggcodeAPI,`${assetType}s`,"suppliers.json"); const cloudSuppliersUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/vendor/get_vendors.php"); return this.fetchData(cloudSuppliersUrl, localSuppliersUrl) .then((supplierData) => { const suppliers = supplierData.map((supplier) => supplier.name); // Populate suppliers dropdown and set up its change handler return this.populateDropdown( elements.supplier, suppliers, node, "supplier", function (selectedSupplier) { if (selectedSupplier) { this.populateSubTypes(configUrls, elements, node, selectedSupplier); } } ); }) .then(() => { // If we have a saved supplier, trigger subTypes population if (node.supplier) { this.populateSubTypes(configUrls, elements, node, node.supplier); } }); }) .catch((error) => { console.error("Error in initial dropdown population:", error); }); }, populateSubTypes(configUrls, elements, node, selectedSupplier) { this.fetchData(configUrls.cloud.config, configUrls.local.config) .then((configData) => { const assetType = configData.asset?.type?.default; const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI, `${assetType}s`, selectedSupplier ); const localSubTypesUrl = this.constructUrl(supplierFolder, "subtypes.json"); const cloudSubTypesUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_subtypesFromVendor.php?vendor_name=" + selectedSupplier); return this.fetchData(cloudSubTypesUrl, localSubTypesUrl) .then((subTypeData) => { const subTypes = subTypeData.map((subType) => subType.name); return this.populateDropdown( elements.subType, subTypes, node, "subType", function (selectedSubType) { if (selectedSubType) { // When subType changes, update both models and units this.populateModels( configUrls, elements, node, selectedSupplier, selectedSubType ); this.populateUnitsForSubType( configUrls, elements, node, selectedSubType ); } } ); }) .then(() => { // If we have a saved subType, trigger both models and units population if (node.subType) { this.populateModels( configUrls, elements, node, selectedSupplier, node.subType ); this.populateUnitsForSubType(configUrls, elements, node, node.subType); } //console.log("In fetch part of subtypes "); // Store all data from selected model /* node["modelMetadata"] = modelData.find( (model) => model.name === node.model ); console.log("Model Metadata: ", node["modelMetadata"]); */ }); }) .catch((error) => { console.error("Error populating subtypes:", error); }); }, populateUnitsForSubType(configUrls, elements, node, selectedSubType) { // Fetch the units data this.fetchData(configUrls.cloud.units, configUrls.local.units) .then((unitsData) => { // Find the category that matches the subType name const categoryData = unitsData.units.find( (category) => category.category.toLowerCase() === selectedSubType.toLowerCase() ); if (categoryData) { // Extract just the unit values and descriptions const units = categoryData.values.map((unit) => ({ value: unit.value, description: unit.description, })); // Create the options array with descriptions as labels const options = units.map((unit) => ({ value: unit.value, label: `${unit.value} - ${unit.description}`, })); // Populate the units dropdown this.populateDropdown( elements.unit, options.map((opt) => opt.value), node, "unit" ); // If there's no currently selected unit but we have options, select the first one if (!node.unit && options.length > 0) { node.unit = options[0].value; elements.unit.value = options[0].value; } } else { // If no matching category is found, provide a default % option const defaultUnits = [{ value: "%", description: "Percentage" }]; this.populateDropdown( elements.unit, defaultUnits.map((unit) => unit.value), node, "unit" ); console.warn( `No matching unit category found for subType: ${selectedSubType}` ); } }) .catch((error) => { console.error("Error fetching units:", error); }); }, populateModels( configUrls, elements, node, selectedSupplier, selectedSubType ) { this.fetchData(configUrls.cloud.config, configUrls.local.config) .then((configData) => { const assetType = configData.asset?.type?.default; // save assetType to fetch later node.assetType = assetType; const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI,`${assetType}s`,selectedSupplier); const subTypeFolder = this.constructUrl(supplierFolder, selectedSubType); const localModelsUrl = this.constructUrl(subTypeFolder, "models.json"); const cloudModelsUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_product_models.php?vendor_name=" + selectedSupplier + "&product_subtype_name=" + selectedSubType); return this.fetchData(cloudModelsUrl, localModelsUrl).then((modelData) => { const models = modelData.map((model) => model.name); // use this to populate the dropdown // If a model is already selected, store its metadata immediately if (node.model) { node["modelMetadata"] = modelData.find((model) => model.name === node.model); } this.populateDropdown(elements.model, models, node, "model", (selectedModel) => { // Store only the metadata for the selected model node["modelMetadata"] = modelData.find((model) => model.name === selectedModel); }); /* console.log('hello here I am:'); console.log(node["modelMetadata"]); */ }); }) .catch((error) => { console.error("Error populating models:", error); }); }, async populateDropdown( htmlElement, options, node, property, callback ) { this.generateHtml(htmlElement, options, node[property]); htmlElement.addEventListener("change", async (e) => { const newValue = e.target.value; console.log(`Dropdown changed: ${property} = ${newValue}`); node[property] = newValue; RED.nodes.dirty(true); if (callback) await callback(newValue); // Ensure async callback completion }); }, }; module.exports = dropdownPopulation;