# 💻 JAVASCRIPT PATTERNS ## 1. Inicialización del Componente ```javascript document.addEventListener('DOMContentLoaded', function() { console.log('✅ [ComponentName] Admin Panel cargado'); // 1. Cargar configuración guardada loadConfig(); // 2. Inicializar vista previa updatePreview(); // 3. Conectar event listeners initializeEventListeners(); }); ``` **Orden de ejecución:** 1. `loadConfig()`: Carga valores guardados desde localStorage/JSON 2. `updatePreview()`: Renderiza el preview inicial 3. `initializeEventListeners()`: Conecta los campos al preview --- ## 2. Event Listeners ### Patrón Básico ```javascript function initializeEventListeners() { // Lista de campos que deben actualizar el preview const fields = [ 'enabled', 'showOnMobile', 'bgColor', 'textColor', 'highlightText', 'messageText', 'showLink', 'linkText', 'linkUrl' ]; // Conectar event listeners automáticamente fields.forEach(fieldId => { const element = document.getElementById(fieldId); if (element) { // Checkboxes: usar 'change' if (element.type === 'checkbox') { element.addEventListener('change', updatePreview); } // Otros inputs: usar 'input' para tiempo real else { element.addEventListener('input', updatePreview); } } }); // Event listeners específicos initializeColorPickerListeners(); initializeTextareaCounters(); initializeResetButton(); } ``` ### Color Pickers con Display Hex ```javascript function initializeColorPickerListeners() { const colorFields = [ { input: 'bgColor', display: 'bgColorValue' }, { input: 'textColor', display: 'textColorValue' }, { input: 'highlightColor', display: 'highlightColorValue' } ]; colorFields.forEach(({ input, display }) => { const inputElement = document.getElementById(input); const displayElement = document.getElementById(display); if (inputElement && displayElement) { inputElement.addEventListener('input', function() { displayElement.textContent = this.value.toUpperCase(); updatePreview(); }); } }); } ``` ### Textareas con Contadores ```javascript function initializeTextareaCounters() { const textareas = [ { field: 'messageText', counter: 'messageTextCount', progress: 'messageTextProgress', max: 250 }, { field: 'description', counter: 'descriptionCount', progress: 'descriptionProgress', max: 500 } ]; textareas.forEach(({ field, counter, progress, max }) => { const textarea = document.getElementById(field); const counterElement = document.getElementById(counter); const progressElement = document.getElementById(progress); if (textarea && counterElement && progressElement) { textarea.addEventListener('input', function() { const length = this.value.length; const percentage = (length / max) * 100; // Actualizar contador counterElement.textContent = length; // Actualizar progress bar progressElement.style.width = percentage + '%'; progressElement.setAttribute('aria-valuenow', length); // Cambiar color según uso if (percentage > 90) { progressElement.style.backgroundColor = '#dc3545'; // Rojo } else if (percentage > 75) { progressElement.style.backgroundColor = '#ffc107'; // Amarillo } else { progressElement.style.backgroundColor = '#FF8600'; // Orange } updatePreview(); }); } }); } ``` ### Botón de Reset ```javascript function initializeResetButton() { const resetBtn = document.getElementById('resetDefaults'); if (resetBtn) { resetBtn.addEventListener('click', resetToDefaults); } } ``` --- ## 3. Función updatePreview() ```javascript /** * Actualiza la vista previa en tiempo real * REGLA: Solo modificar propiedades que el usuario puede cambiar */ function updatePreview() { const preview = document.getElementById('componentPreview'); if (!preview) return; // 1. Activar/desactivar componente const enabled = document.getElementById('enabled').checked; preview.style.display = enabled ? 'block' : 'none'; // 2. Colores const bgColor = document.getElementById('bgColor').value; const textColor = document.getElementById('textColor').value; preview.style.backgroundColor = bgColor; preview.style.color = textColor; // 3. Textos const highlightText = document.getElementById('highlightText').value; const messageText = document.getElementById('messageText').value; const highlightElement = document.getElementById('previewHighlight'); const messageElement = document.getElementById('previewMessage'); if (highlightElement) highlightElement.textContent = highlightText; if (messageElement) messageElement.textContent = messageText; // 4. Mostrar/ocultar elementos const showIcon = document.getElementById('showIcon').checked; const iconElement = document.getElementById('previewIcon'); if (iconElement) { iconElement.style.display = showIcon ? 'inline-block' : 'none'; } // 5. Cambiar clase del icono const iconClass = document.getElementById('iconClass').value; if (iconElement && iconClass) { iconElement.className = iconClass + ' notification-icon'; } // 6. Link const showLink = document.getElementById('showLink').checked; const linkElement = document.getElementById('previewLink'); if (linkElement) { linkElement.style.display = showLink ? 'inline-block' : 'none'; if (showLink) { const linkText = document.getElementById('linkText').value; const linkUrl = document.getElementById('linkUrl').value; const linkTarget = document.getElementById('linkTarget').value; linkElement.textContent = linkText; linkElement.href = linkUrl; linkElement.target = linkTarget; } } } ``` --- ## 4. Guardar Configuración ### localStorage (Temporal) ```javascript /** * Guarda la configuración en localStorage */ function saveConfig() { const config = { enabled: document.getElementById('enabled').checked, showOnMobile: document.getElementById('showOnMobile').checked, showOnDesktop: document.getElementById('showOnDesktop').checked, bgColor: document.getElementById('bgColor').value, textColor: document.getElementById('textColor').value, highlightColor: document.getElementById('highlightColor').value, highlightText: document.getElementById('highlightText').value, messageText: document.getElementById('messageText').value, showIcon: document.getElementById('showIcon').checked, iconClass: document.getElementById('iconClass').value, showLink: document.getElementById('showLink').checked, linkText: document.getElementById('linkText').value, linkUrl: document.getElementById('linkUrl').value, linkTarget: document.getElementById('linkTarget').value }; localStorage.setItem('componentConfig', JSON.stringify(config)); console.log('💾 Configuración guardada:', config); } ``` ### Archivo JSON (Persistente) ```javascript /** * Guarda la configuración en archivo JSON */ async function saveConfigToFile() { const config = { component: '[component-name]', // Nombre del componente en kebab-case version: '1.0', lastModified: new Date().toISOString(), config: { enabled: document.getElementById('enabled').checked, bgColor: document.getElementById('bgColor').value, textColor: document.getElementById('textColor').value, messageText: document.getElementById('messageText').value, // ... todos los campos del componente } }; try { const response = await fetch('./config.json', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(config, null, 2) }); if (response.ok) { console.log('💾 Configuración guardada exitosamente'); showNotification('Cambios guardados', 'success'); } else { throw new Error('Error al guardar'); } } catch (error) { console.error('❌ Error al guardar:', error); showNotification('Error al guardar cambios', 'error'); } } ``` --- ## 5. Cargar Configuración ### Desde localStorage ```javascript /** * Carga la configuración desde localStorage */ function loadConfig() { const saved = localStorage.getItem('componentConfig'); if (!saved) { console.log('ℹ️ No hay configuración guardada, usando valores por defecto'); return; } const config = JSON.parse(saved); // Aplicar valores guardados Object.keys(config).forEach(key => { const element = document.getElementById(key); if (element) { if (element.type === 'checkbox') { element.checked = config[key]; } else if (element.type === 'color') { element.value = config[key]; // Actualizar display del hex const displayElement = document.getElementById(key + 'Value'); if (displayElement) { displayElement.textContent = config[key].toUpperCase(); } } else { element.value = config[key]; } } }); console.log('📂 Configuración cargada:', config); updatePreview(); } ``` ### Desde archivo JSON ```javascript /** * Carga la configuración desde archivo JSON */ async function loadConfigFromFile() { try { const response = await fetch('./config.json'); if (!response.ok) { throw new Error('Config file not found'); } const data = await response.json(); const config = data.config; // Aplicar valores cargados (igual que con localStorage) Object.keys(config).forEach(key => { const element = document.getElementById(key); if (element) { if (element.type === 'checkbox') { element.checked = config[key]; } else { element.value = config[key]; } } }); console.log('📂 Configuración cargada desde archivo:', config); updatePreview(); } catch (error) { console.log('ℹ️ No se encontró config.json, usando valores por defecto'); resetToDefaults(); } } ``` --- ## 6. Reset a Valores por Defecto ```javascript /** * Restaura los valores por defecto */ function resetToDefaults() { if (!confirm('¿Estás seguro de restaurar los valores por defecto?')) { return; } // Valores por defecto const defaults = { enabled: true, showOnMobile: true, showOnDesktop: true, bgColor: '#0E2337', textColor: '#ffffff', highlightColor: '#FF8600', highlightText: 'Nuevo:', messageText: 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.', showIcon: true, iconClass: 'bi bi-megaphone-fill', showLink: true, linkText: 'Ver Catálogo →', linkUrl: '/catalogo', linkTarget: '_self', fontSize: 'normal' }; // Aplicar defaults Object.keys(defaults).forEach(key => { const element = document.getElementById(key); if (element) { if (element.type === 'checkbox') { element.checked = defaults[key]; } else { element.value = defaults[key]; } // Actualizar display de colores if (element.type === 'color') { const displayElement = document.getElementById(key + 'Value'); if (displayElement) { displayElement.textContent = defaults[key].toUpperCase(); } } } }); updatePreview(); saveConfig(); console.log('🔄 Valores por defecto restaurados'); } ``` --- ## 7. Validación de Formularios ```javascript /** * Valida los campos del formulario */ function validateForm() { let isValid = true; const errors = []; // 1. Validar campo requerido const messageText = document.getElementById('messageText').value.trim(); if (!messageText) { errors.push('El mensaje principal es requerido'); isValid = false; } // 2. Validar longitud if (messageText.length > 250) { errors.push('El mensaje no puede exceder 250 caracteres'); isValid = false; } // 3. Validar URL const linkUrl = document.getElementById('linkUrl').value.trim(); if (linkUrl && !isValidUrl(linkUrl)) { errors.push('La URL no es válida'); isValid = false; } // 4. Validar formato de clase CSS const iconClass = document.getElementById('iconClass').value.trim(); if (iconClass && !/^[\w\s-]+$/.test(iconClass)) { errors.push('La clase del icono contiene caracteres inválidos'); isValid = false; } if (!isValid) { alert('⚠️ Errores de validación:\n\n' + errors.join('\n')); } return isValid; } /** * Valida si una URL es válida */ function isValidUrl(string) { // Permitir rutas relativas if (string.startsWith('/')) { return true; } // Validar URLs absolutas try { new URL(string); return true; } catch (_) { return false; } } ``` --- ## 8. Sistema de Notificaciones ```javascript /** * Muestra notificación temporal */ function showNotification(message, type = 'success') { const notification = document.createElement('div'); notification.className = `alert alert-${type === 'success' ? 'success' : 'danger'} position-fixed top-0 start-50 translate-middle-x mt-3`; notification.style.zIndex = '9999'; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } // Uso showNotification('Cambios guardados', 'success'); showNotification('Error al guardar cambios', 'error'); ``` --- ## 9. Auto-save (Opcional) ```javascript /** * Auto-guarda cada vez que cambia un campo */ function initializeAutoSave() { const fields = document.querySelectorAll('input, select, textarea'); fields.forEach(field => { field.addEventListener('change', function() { saveConfig(); console.log('💾 Auto-guardado'); }); }); } // Llamar después de initializeEventListeners() ``` --- ## Volver al Índice [← Volver al README](README.md)