- Replace hardcoded position strings with POSITIONS.* constants - Prefix unused variables with _ to resolve no-unused-vars warnings - Fix no-prototype-builtins with Object.prototype.hasOwnProperty.call() - Extract menuUtils.js (543 lines) into 6 focused modules under menu/ - menuUtils.js now 35 lines, delegates via prototype mixin pattern - Add 158 unit tests for ConfigManager, MeasurementContainer, ValidationUtils Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
284 lines
9.8 KiB
JavaScript
284 lines
9.8 KiB
JavaScript
/**
|
|
* 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;
|