// Product Variants Management
// Handles dynamic variant input fields based on selected VariantCollection
class ProductVariantsManager {
constructor() {
this.variantCollectionSelect = document.getElementById('VariantCollectionId');
this.variantsJsonTextarea = document.getElementById('VariantsJson');
this.dynamicFieldsContainer = document.getElementById('dynamic-variant-fields');
this.advancedToggle = document.getElementById('toggle-advanced-variants');
this.advancedSection = document.getElementById('advanced-variant-section');
this.productStockInput = document.getElementById('StockQuantity');
this.productWeightUnitSelect = document.getElementById('WeightUnit');
if (!this.variantCollectionSelect || !this.variantsJsonTextarea || !this.dynamicFieldsContainer) {
console.error('ProductVariantsManager: Required elements not found');
return;
}
this.currentProperties = [];
this.init();
}
init() {
// Listen for variant collection selection changes
this.variantCollectionSelect.addEventListener('change', (e) => {
this.handleVariantCollectionChange(e.target.value);
});
// Toggle advanced JSON editing
if (this.advancedToggle) {
this.advancedToggle.addEventListener('click', (e) => {
e.preventDefault();
this.toggleAdvancedMode();
});
}
// Load existing variant data if in edit mode
if (this.variantCollectionSelect.value) {
this.handleVariantCollectionChange(this.variantCollectionSelect.value);
} else if (this.variantsJsonTextarea.value && this.variantsJsonTextarea.value.trim() !== '') {
// No collection selected but has JSON - show advanced mode
this.showAdvancedMode();
}
// Form submission handler
const form = this.variantCollectionSelect.closest('form');
if (form) {
form.addEventListener('submit', (e) => {
this.serializeVariantsToJson();
});
}
}
async handleVariantCollectionChange(collectionId) {
if (!collectionId || collectionId === '') {
this.clearDynamicFields();
this.currentProperties = [];
return;
}
try {
const response = await fetch(`/Admin/Products/GetVariantCollection?id=${collectionId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const collection = await response.json();
console.log('Loaded variant collection:', collection);
console.log('PropertiesJson raw:', collection.propertiesJson);
let propertyDefinitions = {};
try {
const parsed = JSON.parse(collection.propertiesJson || '{}');
console.log('Parsed properties:', parsed);
console.log('Parsed type:', typeof parsed, 'Is array:', Array.isArray(parsed));
if (typeof parsed === 'object' && !Array.isArray(parsed) && parsed !== null) {
propertyDefinitions = parsed;
} else if (Array.isArray(parsed)) {
const converted = {};
parsed.forEach(item => {
if (typeof item === 'string') {
converted[item] = null;
} else if (typeof item === 'object' && item.name) {
converted[item.name] = item.values || null;
}
});
propertyDefinitions = converted;
} else {
console.error('Unexpected PropertiesJson format:', parsed);
propertyDefinitions = {};
}
console.log('Property definitions:', propertyDefinitions);
} catch (err) {
console.error('Error parsing PropertiesJson:', err);
propertyDefinitions = {};
}
this.currentProperties = propertyDefinitions;
let existingVariants = [];
if (this.variantsJsonTextarea.value && this.variantsJsonTextarea.value.trim() !== '') {
try {
existingVariants = JSON.parse(this.variantsJsonTextarea.value);
if (!Array.isArray(existingVariants)) {
existingVariants = [];
}
} catch (err) {
console.warn('Could not parse existing variants JSON:', err);
existingVariants = [];
}
}
this.generateDynamicFields(propertyDefinitions, existingVariants);
} catch (error) {
console.error('Error loading variant collection:', error);
alert('Failed to load variant collection properties. Please try again.');
}
}
generateDynamicFields(propertyDefinitions, existingVariants = []) {
this.clearDynamicFields();
if (!propertyDefinitions || Object.keys(propertyDefinitions).length === 0) {
this.dynamicFieldsContainer.innerHTML = '
This variant collection has no properties defined.
';
return;
}
let variantsToDisplay = existingVariants;
let isAutoGenerated = false;
if (!variantsToDisplay || variantsToDisplay.length === 0) {
variantsToDisplay = this.generateCombinations(propertyDefinitions);
isAutoGenerated = true;
console.log('Auto-generated combinations:', variantsToDisplay);
} else {
console.log('Using existing variants:', variantsToDisplay);
}
if (variantsToDisplay.length === 0) {
this.dynamicFieldsContainer.innerHTML = ' No variant combinations to generate. Please configure property values in the variant collection.
';
return;
}
const variantRows = variantsToDisplay.map((combo, index) => this.createVariantRow(combo, index, propertyDefinitions)).join('');
const alertMessage = isAutoGenerated
? `Auto-Generated Variants: ${variantsToDisplay.length} variant combination(s) created. Fill in optional details for each variant.`
: `Existing Variants: ${variantsToDisplay.length} variant(s) loaded. You can modify or add new variants below.`;
this.dynamicFieldsContainer.innerHTML = `
${alertMessage}
${variantRows}
`;
document.getElementById('add-variant-row')?.addEventListener('click', () => {
this.addCustomVariantRow(propertyDefinitions);
});
this.attachVariantRowEventHandlers();
}
attachVariantRowEventHandlers() {
document.querySelectorAll('.remove-variant-row').forEach(btn => {
btn.addEventListener('click', (e) => {
const row = e.target.closest('.variant-row');
if (row) {
row.remove();
this.updateStockCalculation();
}
});
});
document.querySelectorAll('.toggle-variant-details').forEach(btn => {
btn.addEventListener('click', (e) => {
const rowIndex = btn.dataset.row;
const detailsSection = document.querySelector(`.variant-details-section[data-row="${rowIndex}"]`);
const icon = btn.querySelector('i');
if (detailsSection) {
if (detailsSection.style.display === 'none') {
detailsSection.style.display = 'block';
icon.classList.remove('fa-chevron-down');
icon.classList.add('fa-chevron-up');
} else {
detailsSection.style.display = 'none';
icon.classList.remove('fa-chevron-up');
icon.classList.add('fa-chevron-down');
}
}
});
});
document.querySelectorAll('.variant-stock').forEach(input => {
input.addEventListener('input', () => {
this.updateStockCalculation();
});
});
this.updateStockCalculation();
}
updateStockCalculation() {
if (!this.productStockInput) return;
const variantStockInputs = document.querySelectorAll('.variant-stock');
let totalVariantStock = 0;
let hasVariantStock = false;
variantStockInputs.forEach(input => {
const value = input.value.trim();
if (value && !isNaN(value)) {
totalVariantStock += parseInt(value);
hasVariantStock = true;
}
});
const stockLabel = this.productStockInput.closest('.mb-3')?.querySelector('label');
let warningIcon = document.getElementById('stock-variant-warning');
if (hasVariantStock) {
this.productStockInput.disabled = true;
this.productStockInput.value = totalVariantStock;
this.productStockInput.classList.add('bg-light');
if (!warningIcon && stockLabel) {
warningIcon = document.createElement('i');
warningIcon.id = 'stock-variant-warning';
warningIcon.className = 'fas fa-calculator text-info ms-2';
warningIcon.style.cursor = 'pointer';
warningIcon.setAttribute('data-bs-toggle', 'tooltip');
warningIcon.setAttribute('data-bs-placement', 'top');
warningIcon.setAttribute('title', `Calculated from variants: ${totalVariantStock} units total. Modify variant stock quantities to change total.`);
stockLabel.appendChild(warningIcon);
if (typeof bootstrap !== 'undefined') {
new bootstrap.Tooltip(warningIcon);
}
} else if (warningIcon) {
warningIcon.setAttribute('title', `Calculated from variants: ${totalVariantStock} units total. Modify variant stock quantities to change total.`);
if (typeof bootstrap !== 'undefined') {
const tooltip = bootstrap.Tooltip.getInstance(warningIcon);
if (tooltip) {
tooltip.dispose();
new bootstrap.Tooltip(warningIcon);
}
}
}
} else {
this.productStockInput.disabled = false;
this.productStockInput.classList.remove('bg-light');
if (warningIcon) {
const tooltip = bootstrap?.Tooltip?.getInstance(warningIcon);
if (tooltip) {
tooltip.dispose();
}
warningIcon.remove();
}
}
}
generateCombinations(propertyDefinitions) {
const properties = Object.keys(propertyDefinitions);
if (properties.length === 0) return [];
const propertyValues = properties.map(prop => {
const values = propertyDefinitions[prop];
return {
name: prop,
values: Array.isArray(values) && values.length > 0 ? values : [null]
};
});
const combinations = [];
const generate = (index, current) => {
if (index === propertyValues.length) {
combinations.push({...current});
return;
}
const prop = propertyValues[index];
for (const value of prop.values) {
current[prop.name] = value;
generate(index + 1, current);
}
};
generate(0, {});
return combinations;
}
createVariantRow(variantData, index, propertyDefinitions) {
const propertyFields = Object.entries(propertyDefinitions).map(([propName, propValues]) => {
const currentValue = variantData[propName];
let fieldHtml;
if (Array.isArray(propValues) && propValues.length > 0) {
const options = propValues.map(val =>
``
).join('');
fieldHtml = `
`;
} else {
fieldHtml = `
`;
}
return fieldHtml;
}).join('');
const variantLabel = Object.entries(variantData)
.filter(([k, v]) => v !== null && k !== 'Label' && k !== 'StockQty' && k !== 'Weight' && k !== 'WeightUnit')
.map(([k, v]) => v)
.join(' / ');
const existingLabel = variantData.Label || '';
const existingStockQty = variantData.StockQty || '';
const existingWeight = variantData.Weight || '';
const existingWeightUnit = variantData.WeightUnit !== undefined ? variantData.WeightUnit : (this.productWeightUnitSelect ? this.productWeightUnitSelect.value : '0');
const weightUnitOptions = `
`;
return `
`;
}
addCustomVariantRow(propertyDefinitions) {
const container = document.getElementById('variant-rows-container');
if (!container) return;
const existingRows = container.querySelectorAll('.variant-row').length;
const emptyVariant = {};
Object.keys(propertyDefinitions).forEach(prop => {
emptyVariant[prop] = null;
});
const newRowHtml = this.createVariantRow(emptyVariant, existingRows, propertyDefinitions);
container.insertAdjacentHTML('beforeend', newRowHtml);
this.attachVariantRowEventHandlers();
}
populateDynamicFields(variantData) {
if (!variantData || typeof variantData !== 'object') {
return;
}
Object.keys(variantData).forEach(key => {
const input = document.getElementById(`variant-${key}`);
if (input) {
input.value = variantData[key];
}
});
}
clearDynamicFields() {
this.dynamicFieldsContainer.innerHTML = '';
}
serializeVariantsToJson() {
if (!this.variantCollectionSelect.value) {
return;
}
const variantRows = document.querySelectorAll('.variant-row');
if (variantRows.length === 0) {
this.variantsJsonTextarea.value = '';
return;
}
const allVariants = [];
variantRows.forEach((row, rowIndex) => {
const variantData = {};
let hasValues = false;
const propertyInputs = row.querySelectorAll('.variant-property');
propertyInputs.forEach(input => {
const property = input.dataset.property;
const value = input.value.trim();
if (value) {
variantData[property] = value;
hasValues = true;
}
});
const labelInput = row.querySelector('.variant-label');
if (labelInput && labelInput.value.trim()) {
variantData['Label'] = labelInput.value.trim();
hasValues = true;
}
const stockInput = row.querySelector('.variant-stock');
if (stockInput && stockInput.value.trim()) {
variantData['StockQty'] = parseInt(stockInput.value.trim());
hasValues = true;
}
const weightInput = row.querySelector('.variant-weight');
if (weightInput && weightInput.value.trim()) {
variantData['Weight'] = parseFloat(weightInput.value.trim());
hasValues = true;
const weightUnitSelect = row.querySelector('.variant-weight-unit');
if (weightUnitSelect) {
variantData['WeightUnit'] = parseInt(weightUnitSelect.value);
}
}
if (hasValues) {
allVariants.push(variantData);
}
});
this.variantsJsonTextarea.value = allVariants.length > 0 ? JSON.stringify(allVariants) : '';
console.log('Serialized variants:', this.variantsJsonTextarea.value);
}
toggleAdvancedMode() {
if (this.advancedSection.style.display === 'none') {
this.showAdvancedMode();
} else {
this.hideAdvancedMode();
}
}
showAdvancedMode() {
if (this.advancedSection) {
this.advancedSection.style.display = 'block';
if (this.advancedToggle) {
this.advancedToggle.innerHTML = ' Hide Advanced JSON Editor';
}
}
}
hideAdvancedMode() {
if (this.advancedSection) {
this.advancedSection.style.display = 'none';
if (this.advancedToggle) {
this.advancedToggle.innerHTML = ' Show Advanced JSON Editor';
}
}
}
}
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', function() {
if (document.getElementById('VariantCollectionId')) {
window.productVariantsManager = new ProductVariantsManager();
console.log('ProductVariantsManager initialized');
}
});