Contents
Persistir preferencias de usuario en localStorage desde JavaScript en WordPress — guía completa
Este artículo explica en detalle cómo guardar y recuperar las preferencias de usuario en el navegador usando localStorage, integrarlo correctamente en WordPress y, opcionalmente, sincronizar esas preferencias con el servidor (user meta) para usuarios autenticados. Incluye consideraciones de seguridad, privacidad, degradación elegante y ejemplos completos de código listos para usar.
Por qué usar localStorage
- Rápido: acceso sin round-trip al servidor, ideal para UI (modo oscuro, tamaño de fuente, diseños personalizados).
- Persistente: los datos sobreviven al cierre del navegador (a diferencia de sessionStorage).
- Independiente del servidor: útil para usuarios no autenticados.
Casos de uso típicos
- Modo oscuro / claro.
- Tamaño de fuente o tipografía preferida.
- Estado de paneles (colapsados/expandidos), orden de columnas, filtros por defecto.
- Preferencias de accesibilidad (contraste, espaciado).
Principios y buenas prácticas
- Namespace y versión: usa claves con prefijo del tema/plugin y versión, p. ej. wp_myplugin_prefs_v1. Facilita migraciones.
- Solo datos no sensibles: nunca almacenes contraseñas, tokens o datos personales sensibles en localStorage.
- Validación y saneamiento: valida y normaliza las preferencias al aplicarlas y antes de enviarlas al servidor.
- Degradación elegante: detecta ausencia de localStorage y usa cookies o defaults.
- Sincronización entre pestañas: usa el evento storage para sincronizar cambios en otras ventanas.
- Límites de tamaño: localStorage suele permitir varios MB por dominio mantén objetos ligeros (JSON pequeño).
Modelo de datos recomendado
Usa un objeto JSON con claves claras. Ejemplo:
- darkMode: true/false
- fontSize: smallmediumlarge
- layout: gridlist
Clave sugerida: wp_theme_prefs_v1
Ejemplo: integrar todo en WordPress
1) Registrar y encolar el script desde functions.php (o plugin). Exponer el nonce y la ruta REST para sincronizar (solo para usuarios autenticados).
// functions.php (o archivo principal del plugin) add_action(wp_enqueue_scripts, function() { wp_enqueue_script( user-prefs, get_template_directory_uri() . /js/user-prefs.js, array(), // dependencias si las hubiera filemtime( get_template_directory() . /js/user-prefs.js ), true ) wp_localize_script(user-prefs, UserPrefsData, array( nonce => wp_create_nonce(wp_rest), save_url => rest_url(user-prefs/v1/save), key => wp_theme_prefs_v1, user_id => get_current_user_id(), )) }) // Registrar endpoint REST para guardar preferencias (solo para usuarios autenticados) add_action(rest_api_init, function() { register_rest_route(user-prefs/v1, /save, array( methods => POST, callback => my_user_prefs_save, permission_callback => function() { return is_user_logged_in() }, )) }) function my_user_prefs_save(WP_REST_Request request) { user_id = get_current_user_id() if (!user_id) { return new WP_Error(no_user, Usuario no autenticado, array(status => 401)) } prefs = request->get_param(prefs) if (!is_array(prefs)) { return new WP_Error(invalid_data, Preferencias inválidas, array(status => 400)) } // Sanitizar valores según necesidades allowed_fonts = array(small,medium,large) if (isset(prefs[fontSize]) !in_array(prefs[fontSize], allowed_fonts, true)) { prefs[fontSize] = medium } prefs[darkMode] = !empty(prefs[darkMode]) ? true : false update_user_meta(user_id, user_preferences, prefs) return rest_ensure_response(array(success => true, prefs => prefs)) }
2) Script JS que maneja lectura, escritura, sincronización local y opcionalmente envía al servidor.
// js/user-prefs.js (function() { const KEY = (window.UserPrefsData UserPrefsData.key) wp_theme_prefs_v1 const SAVE_URL = (window.UserPrefsData UserPrefsData.save_url) null const NONCE = (window.UserPrefsData UserPrefsData.nonce) null // Comprueba disponibilidad de localStorage function storageAvailable() { try { const test = __storage_test__ localStorage.setItem(test, test) localStorage.removeItem(test) return true } catch (e) { return false } } // Fallback simple con cookies si no hay localStorage function setCookie(name, value, days = 365) { const expires = new Date(Date.now() days864e5).toUTCString() document.cookie = name = encodeURIComponent(value) expires= expires path=/ } function getCookie(name) { return document.cookie.split( ).reduce((r, v) => { const parts = v.split(=) return parts[0] === name ? decodeURIComponent(parts[1]) : r }, ) } function getPrefs() { if (storageAvailable()) { try { return JSON.parse(localStorage.getItem(KEY) {}) } catch (e) { return {} } } else { try { return JSON.parse(getCookie(KEY) {}) } catch (e) { return {} } } } function savePrefs(prefs) { const str = JSON.stringify(prefs) if (storageAvailable()) { localStorage.setItem(KEY, str) } else { setCookie(KEY, str) } // Opcional: sincronizar con servidor si es usuario autenticado y hay endpoint if (SAVE_URL NONCE) { syncToServer(prefs) } } function syncToServer(prefs) { fetch(SAVE_URL, { method: POST, headers: { Content-Type: application/json, X-WP-Nonce: NONCE }, credentials: same-origin, body: JSON.stringify({ prefs: prefs }) }).then(res => { if (!res.ok) { // manejar error (registro, retries, etc.) return res.json().then(err => console.warn(Error guardando prefs en servidor:, err)) } return res.json() }).catch(err => console.warn(Error fetch syncToServer:, err)) } // Aplicar preferencias al DOM: ejemplo para modo oscuro y tamaño de fuente function applyPrefs(prefs) { // Dark mode if (prefs.darkMode) { document.documentElement.classList.add(pref-dark-mode) } else { document.documentElement.classList.remove(pref-dark-mode) } // Font size document.documentElement.classList.remove(pref-font-small,pref-font-medium,pref-font-large) const fs = prefs.fontSize medium document.documentElement.classList.add(pref-font- fs) // Puedes aplicar más preferencias aquí (layout, ocultar elementos, etc.) } // Enlazar controles del UI (ejemplo: checkbox y select) function bindControls() { const darkToggle = document.querySelector([data-pref=darkMode]) const fontSelect = document.querySelector([data-pref=fontSize]) if (darkToggle) { darkToggle.addEventListener(change, function() { const prefs = getPrefs() prefs.darkMode = darkToggle.checked savePrefs(prefs) applyPrefs(prefs) }) } if (fontSelect) { fontSelect.addEventListener(change, function() { const prefs = getPrefs() prefs.fontSize = fontSelect.value savePrefs(prefs) applyPrefs(prefs) }) } } // Sincroniza entre pestañas window.addEventListener(storage, function(e) { if (e.key === KEY) { try { const newPrefs = JSON.parse(e.newValue {}) applyPrefs(newPrefs) } catch (err) { // ignore } } }) // Inicialización al cargar document.addEventListener(DOMContentLoaded, function() { const prefs = getPrefs() applyPrefs(prefs) bindControls() // Inicializar estados de controles si existen const darkToggle = document.querySelector([data-pref=darkMode]) const fontSelect = document.querySelector([data-pref=fontSize]) if (darkToggle) darkToggle.checked = !!prefs.darkMode if (fontSelect) fontSelect.value = prefs.fontSize medium }) })()
Ejemplo de estilo CSS mínimo para aplicar las clases
/ styles.css (o inline) / :root { --bg: #ffffff --text: #111 --font-size: 16px } .pref-dark-mode { --bg: #111 --text: #f5f5f5 } .pref-font-small { font-size: 14px } .pref-font-medium { font-size: 16px } .pref-font-large { font-size: 18px } body { background: var(--bg) color: var(--text) font-size: var(--font-size, 16px) }
Migración de versiones de preferencias
Si cambias la estructura de tu objeto de preferencias, añade un campo version y una rutina de migración que al leer las prefs detecte versiones antiguas y las actualice al nuevo esquema. Ejemplo:
- Si KEY es wp_theme_prefs_v1 y cambias a v2, lee v1, transforma y guarda en v2.
- Puedes mantener ambas claves temporalmente para compatibilidad.
Privacidad, GDPR y consideraciones legales
- localStorage puede almacenar identificadores que, combinados con otros datos, se consideran personales evalúa el impacto de privacidad.
- Informar en la política de privacidad si guardas preferencias que pueden identificar a un usuario. Para preferencias no personales (modo visual), normalmente no es necesario consentimiento explícito, pero verifica la legislación aplicable.
- Proporciona una forma para que el usuario borre sus preferencias (botón de reset) y, si sincronizas con el servidor, asegúrate de que también eliminas el user_meta en esa acción.
Problemas comunes y soluciones
- localStorage bloqueado en modo incógnito: algunos navegadores limitan o lanzan excepciones captura errores y usa cookies como fallback.
- Datos corruptos (JSON inválido): envolver parse en try/catch y restaurar valores por defecto si falla.
- Sincronización entre pestañas no funcionando: asegurarse de que el cambio se hace con localStorage.setItem (no sessionStorage) y que las pestañas están en el mismo origen.
Checklist de implementación
- Usar clave con namespace y versión.
- Validar y sanitizar valores antes de aplicar.
- Proveer fallback si localStorage no está disponible.
- Si sincronizas al servidor: usar REST API, nonce y permission_callback apropiado.
- Ofrecer botón para resetear preferencias.
- Documentar en la política de privacidad el tratamiento de dichas preferencias.
Resumen final
Persistir preferencias en localStorage es una estrategia eficiente para mejorar la experiencia de usuario en WordPress. Combínala con una integración prudente en el backend (REST API user_meta) cuando necesites persistencia entre dispositivos para usuarios autenticados. Sigue las buenas prácticas de namespacing, validación, migración de versiones y respeto a la privacidad para asegurar una implementación robusta y escalable.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |