# Required Code Changes for Conditional Navigation ## Overview To support conditional material/color questions and dynamic navigation, the following changes are needed in your application code. --- ## 1. JavaScript Changes (`js/script.js`) ### Current State - Loads `questions.json` statically - Has hardcoded conditional for `q-material-conditional` - No product data loading ### Required Changes #### A. Load Multiple Data Files ```javascript // At the top of script.js - modify loadQuestionData() to load both files let questionData = {}; let productData = []; let accessoryData = []; let bitwiseData = {}; let accumulatedBitValue = 0; function init() { // Load all data files Promise.all([ fetch('data/navigation.json').then(r => r.json()), fetch('data/products.json').then(r => r.json()), fetch('data/accessories.json').then(r => r.json()), fetch('data/product_bitwise.json').then(r => r.json()) ]) .then(([questions, products, accessories, bitwise]) => { questionData = questions; productData = products; accessoryData = accessories; // Index bitwise data by product code bitwiseData = {}; bitwise.forEach(item => { bitwiseData[item.PROD_CODE] = item.BIT_VALUE; }); // Check if there's state in URL initFromURL(); // If no URL state, start normally if (!window.location.search) { loadContent('start'); } }) .catch(error => { console.error('Error loading data:', error); // Show error message }); } ``` #### B. Add Dynamic Next Resolution Function ```javascript // Add this new function to handle conditional navigation function resolveNextQuestion(currentKey, selectedAnswer, answerObject) { // Get the current accumulated filters const currentFilters = buildFilterFromAnswers(); // Add the new filter from this answer if (answerObject.filter) { Object.assign(currentFilters, answerObject.filter); } // Check if next question in chain should be shown const nextKey = answerObject.next; const nextQuestion = questionData[nextKey]; // If next question has conditional flag, evaluate it if (nextQuestion && nextQuestion.conditional) { return evaluateConditional(nextQuestion, currentFilters); } return nextKey; } // Evaluate if a conditional question should be shown function evaluateConditional(question, filters) { if (question.conditional.type === 'material') { // Check if products matching current filters have multiple materials const matchingProducts = filterProducts(filters); const materials = getAvailableMaterials(matchingProducts); if (materials.length <= 1) { // Skip this question, auto-apply material if exists if (materials.length === 1) { userAnswers[question.id + '.material'] = materials[0]; } // Return the fallback next return question.conditional.fallbackNext || question.next; } } if (question.conditional.type === 'color') { // Similar logic for colors const matchingProducts = filterProducts(filters); const colors = getAvailableColors(matchingProducts); if (colors.length <= 1) { if (colors.length === 1) { userAnswers[question.id + '.color'] = colors[0]; } return question.conditional.fallbackNext || question.next; } } // Show the question return question.id; } // Build current filter object from user answers function buildFilterFromAnswers() { const filters = {}; // Extract filter info from answers for (const [key, value] of Object.entries(userAnswers)) { if (key === 'start') { filters.baseType = value; } else if (key.includes('type')) { filters.subType = value; } else if (key.includes('material')) { filters.material = value; } else if (key.includes('color')) { filters.color = value; } } return filters; } // Filter products based on criteria function filterProducts(filters) { return productData.filter(product => { if (filters.baseType && product.baseType !== filters.baseType) { return false; } if (filters.subType) { const subType = product.subType.door || product.subType.window; if (subType !== filters.subType) { return false; } } if (filters.material && !product.materials.includes(filters.material)) { return false; } if (filters.color && !product.colors.includes(filters.color)) { return false; } return true; }); } // Get available materials from product set function getAvailableMaterials(products) { const materials = new Set(); products.forEach(p => { p.materials.forEach(m => materials.add(m)); }); return Array.from(materials); } // Get available colors from product set function getAvailableColors(products) { const colors = new Set(); products.forEach(p => { p.colors.forEach(c => colors.add(c)); }); return Array.from(colors); } ``` #### C. Update handleAnswer Function ```javascript // Replace the existing handleAnswer function function handleAnswer(currentKey, answerValue, answerIndex) { const currentQuestion = questionData[currentKey]; const answerObject = currentQuestion.answers[answerIndex]; // Store the answer userAnswers[currentKey] = answerValue; // Update bit value based on selection updateBitValue(answerObject); // Resolve the next question (handles conditionals) const nextKey = resolveNextQuestion(currentKey, answerValue, answerObject); history.push(nextKey); loadContent(nextKey); } ``` #### D. Update renderQuestion to Pass Answer Index ```javascript // In renderQuestion function, change the button onclick // FROM: // onclick="handleAnswer('${currentKey}', '${answer.caption}', '${answer.next}')" // TO: data.answers.forEach((answer, index) => { html += ` `; }); ``` #### D2. Add URL State Management ```javascript // Track accumulated bit value from user selections let accumulatedBitValue = 0; // Load bitwise helper data let bitwiseData = {}; function loadBitwiseData() { return fetch('data/product_bitwise.json') .then(r => r.json()) .then(data => { bitwiseData = {}; data.forEach(item => { bitwiseData[item.PROD_CODE] = item.BIT_VALUE; }); return bitwiseData; }); } // Initialize from URL on page load function initFromURL() { const params = new URLSearchParams(window.location.search); // Check for bit value in URL const bitValue = params.get('b'); if (bitValue) { accumulatedBitValue = parseInt(bitValue, 10); // Restore state based on bit value restoreStateFromBitValue(accumulatedBitValue); } // Check for product code in URL const productCode = params.get('p'); if (productCode) { showProductByCode(productCode); } } // Update URL with current state function updateURL() { const params = new URLSearchParams(); if (accumulatedBitValue > 0) { params.set('b', accumulatedBitValue); } // Get current question if (history.length > 0) { const currentKey = history[history.length - 1]; params.set('q', currentKey); } // Update URL without reloading page const newURL = window.location.pathname + '?' + params.toString(); window.history.replaceState({ bitValue: accumulatedBitValue }, '', newURL); } // Update bit value based on user selection function updateBitValue(answerObject) { if (answerObject.filter) { // Add bits based on filter if (answerObject.filter.baseType === 'Door') { accumulatedBitValue |= 1; // Bit 0 } else if (answerObject.filter.baseType === 'Window') { accumulatedBitValue |= 2; // Bit 1 } if (answerObject.filter.material === 'Aluminum') { accumulatedBitValue |= 16; // Bit 4 } else if (answerObject.filter.material === 'Vinyl') { accumulatedBitValue |= 32; // Bit 5 } // Add color bits const colorBits = { 'Black': 64, 'White': 128, 'Bronze': 256, 'Tan': 512, 'Mill': 1024, 'Sandstone': 2048 }; if (answerObject.filter.color && colorBits[answerObject.filter.color]) { accumulatedBitValue |= colorBits[answerObject.filter.color]; } // Add subtype bits (based on bitwise_legend.json) const subtypeBits = { 'Patio Door': 4096, 'Primary Window': 8192, 'Storm Door': 16384, 'Storm Window': 32768 }; if (answerObject.filter.subType && subtypeBits[answerObject.filter.subType]) { accumulatedBitValue |= subtypeBits[answerObject.filter.subType]; } } updateURL(); } // Restore state from bit value function restoreStateFromBitValue(bitValue) { // Decode bit value back to user selections const selections = {}; if (bitValue & 1) selections.baseType = 'Door'; if (bitValue & 2) selections.baseType = 'Window'; if (bitValue & 16) selections.material = 'Aluminum'; if (bitValue & 32) selections.material = 'Vinyl'; const colors = ['Black', 'White', 'Bronze', 'Tan', 'Mill', 'Sandstone']; const colorBits = [64, 128, 256, 512, 1024, 2048]; colors.forEach((color, idx) => { if (bitValue & colorBits[idx]) { selections.color = color; } }); // Store in userAnswers if (selections.baseType) userAnswers['start'] = selections.baseType; if (selections.material) userAnswers['q-material.material'] = selections.material; if (selections.color) userAnswers['q-color.color'] = selections.color; // Navigate to appropriate question from URL param const params = new URLSearchParams(window.location.search); const questionKey = params.get('q') || 'start'; loadContent(questionKey); } // Share current state function shareCurrentState() { const url = window.location.href; // Copy to clipboard if (navigator.clipboard) { navigator.clipboard.writeText(url).then(() => { alert('Link copied to clipboard! Share this link to return to this exact state.'); }); } else { // Fallback: show URL prompt('Copy this URL to share:', url); } } // Add share button to breadcrumb function updateBreadcrumbWithShare() { const breadcrumbDiv = document.getElementById('breadcrumb'); const shareButton = ` `; breadcrumbDiv.innerHTML += shareButton; } ``` #### E. Add Product Results Page ```javascript // Add new function to show filtered products function showProductResults(filters) { const matchingProducts = filterProducts(filters); const contentDiv = document.getElementById('content'); if (matchingProducts.length === 0) { contentDiv.innerHTML = `
Code: ${product.productCode}
Materials: ${product.materials.join(', ')}
Colors: ${product.colors.join(', ')}
${compatibleAccessories.length > 0 ? `Available Options:
Product Code: ${product.productCode}
Category: ${product.category}
Base Type: ${product.baseType || 'N/A'}
Materials: ${product.materials.join(', ') || 'N/A'}
Colors: ${product.colors.join(', ') || 'N/A'}
${compatibleAccessories.length > 0 ? `