# URL State Management Implementation ## Overview This feature allows users to: 1. **Bookmark** their progress through the navigation flow 2. **Refresh** the page without losing their selections 3. **Share** URLs with specific products or navigation states 4. **Deep link** directly to products ## URL Parameter Structure ### Query Parameters - `b` - Bitwise value representing all selections (integer) - `q` - Current question ID (string) - `p` - Product code for direct product view (string) ### Examples ``` # At start https://yoursite.com/ # After selecting Door > Storm Door > Aluminum https://yoursite.com/?b=16401&q=q-dimensions # Viewing a specific product https://yoursite.com/?p=BGIST&b=16401 # Bit value 16401 decodes to: # - Door (bit 0) # - Aluminum (bit 4) # - Storm Door subtype (bit 14) ``` ## Implementation Code ### 1. Add to Global Variables (top of script.js) ```javascript // Add these to existing global variables let accumulatedBitValue = 0; let bitwiseData = {}; // Bit definitions (must match generate_bitwise_helper.py) const BIT_DEFINITIONS = { // Base Types 'base_door': 0, // 1 'base_window': 1, // 2 // Flags 'is_accessory': 2, // 4 'specific_item': 3, // 8 // Materials 'material_aluminum': 4, // 16 'material_vinyl': 5, // 32 // Colors 'color_black': 6, // 64 'color_white': 7, // 128 'color_bronze': 8, // 256 'color_tan': 9, // 512 'color_mill': 10, // 1024 'color_sandstone': 11, // 2048 // Subtypes (from bitwise_legend.json) 'subtype_patio_door': 12, // 4096 'subtype_primary_window': 13, // 8192 'subtype_storm_door': 14, // 16384 'subtype_storm_window': 15 // 32768 }; ``` ### 2. Update init() Function ```javascript function init() { // Load all data files (including bitwise) 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; }); // Initialize from URL or start fresh initFromURL(); }) .catch(error => { console.error('Error loading data:', error); showError('Failed to load application data.'); }); } ``` ### 3. URL Initialization ```javascript function initFromURL() { const params = new URLSearchParams(window.location.search); // Priority 1: Direct product view const productCode = params.get('p'); if (productCode) { accumulatedBitValue = parseInt(params.get('b') || '0', 10); showProductByCode(productCode); return; } // Priority 2: Restore navigation state const bitValue = params.get('b'); const questionKey = params.get('q'); if (bitValue && questionKey) { accumulatedBitValue = parseInt(bitValue, 10); restoreStateFromBitValue(accumulatedBitValue, questionKey); return; } // Priority 3: Start fresh loadContent('start'); } function restoreStateFromBitValue(bitValue, questionKey) { // Decode bit value back to user selections // Base type if (bitValue & 1) { userAnswers['start'] = 'Door'; } else if (bitValue & 2) { userAnswers['start'] = 'Window'; } // Materials if (bitValue & 16) { userAnswers['q-material.material'] = 'Aluminum'; } else if (bitValue & 32) { userAnswers['q-material.material'] = 'Vinyl'; } // Colors const colorMap = { 64: 'Black', 128: 'White', 256: 'Bronze', 512: 'Tan', 1024: 'Mill', 2048: 'Sandstone' }; for (const [bit, color] of Object.entries(colorMap)) { if (bitValue & parseInt(bit)) { userAnswers['q-color.color'] = color; break; // Only store first color found } } // Subtypes const subtypeMap = { 4096: 'Patio Door', 8192: 'Primary Window', 16384: 'Storm Door', 32768: 'Storm Window' }; for (const [bit, subtype] of Object.entries(subtypeMap)) { if (bitValue & parseInt(bit)) { userAnswers['q-subtype'] = subtype; break; // Only store first subtype found } } // Navigate to the saved question history = ['start']; if (questionKey !== 'start') { history.push(questionKey); } loadContent(questionKey); } ``` ### 4. Update URL on Each Selection ```javascript function updateURL(questionKey = null) { const params = new URLSearchParams(); if (accumulatedBitValue > 0) { params.set('b', accumulatedBitValue.toString()); } if (questionKey) { params.set('q', questionKey); } else if (history.length > 0) { params.set('q', history[history.length - 1]); } const newURL = window.location.pathname + (params.toString() ? '?' + params.toString() : ''); window.history.replaceState( { bitValue: accumulatedBitValue, questionKey: questionKey }, '', newURL ); } function updateBitValue(answerObject) { if (!answerObject.filter) return; const filter = answerObject.filter; // Base type if (filter.baseType === 'Door') { accumulatedBitValue |= (1 << BIT_DEFINITIONS['base_door']); } else if (filter.baseType === 'Window') { accumulatedBitValue |= (1 << BIT_DEFINITIONS['base_window']); } // Materials if (filter.material === 'Aluminum') { accumulatedBitValue |= (1 << BIT_DEFINITIONS['material_aluminum']); } else if (filter.material === 'Vinyl') { accumulatedBitValue |= (1 << BIT_DEFINITIONS['material_vinyl']); } // Colors const colorKey = filter.color ? 'color_' + filter.color.toLowerCase() : null; if (colorKey && BIT_DEFINITIONS[colorKey]) { accumulatedBitValue |= (1 << BIT_DEFINITIONS[colorKey]); } // Subtypes const subtypeKey = filter.subType ? 'subtype_' + filter.subType.toLowerCase().replace(/ /g, '_') : null; if (subtypeKey && BIT_DEFINITIONS[subtypeKey]) { accumulatedBitValue |= (1 << BIT_DEFINITIONS[subtypeKey]); } } ``` ### 5. Update handleAnswer Function ```javascript 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); // Update URL with new state updateURL(nextKey); loadContent(nextKey); } ``` ### 6. Product View with URL ```javascript function showProductByCode(productCode) { const product = productData.find(p => p.productCode === productCode); if (!product) { showError(`Product ${productCode} not found`); return; } showProductDetail(product); } function showProductDetail(product) { // Update URL with product code const params = new URLSearchParams(); params.set('p', product.productCode); if (accumulatedBitValue > 0) { params.set('b', accumulatedBitValue.toString()); } window.history.pushState( { productCode: product.productCode }, '', '?' + params.toString() ); // Render product detail const compatibleAccessories = accessoryData.filter(acc => product.compatibleAccessories && product.compatibleAccessories.includes(acc.id) ); const contentDiv = document.getElementById('content'); let html = `
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 ? `