140 lines
5.8 KiB
JavaScript
140 lines
5.8 KiB
JavaScript
/**
|
|
* ViewSwitcher — Gestor Universal y Extensible de Modos de Visualización
|
|
*
|
|
* Permite alternar instantáneamente entre vista de Tabla (grilla densa)
|
|
* y vista de Tarjetas (mosaico responsive), preservando filtros, búsquedas
|
|
* y persistiendo la preferencia del usuario en localStorage.
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
class ViewSwitcherManager {
|
|
constructor() {
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
this.setupViewSwitchers();
|
|
});
|
|
}
|
|
|
|
setupViewSwitchers() {
|
|
const switchers = document.querySelectorAll('.view-switcher-group');
|
|
if (!switchers.length) return;
|
|
|
|
switchers.forEach(group => {
|
|
const context = group.getAttribute('data-context') || 'default';
|
|
const defaultView = group.getAttribute('data-default-view') || 'cards';
|
|
|
|
// Determinar vista inicial:
|
|
// 1. URL search param (?display=...)
|
|
// 2. localStorage
|
|
// 3. defaultView del contenedor
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const urlView = urlParams.get('display');
|
|
const storedView = localStorage.getItem(`view_preference_${context}`);
|
|
const activeView = urlView || storedView || defaultView;
|
|
|
|
this.applyView(context, activeView, false);
|
|
|
|
// Asignar listeners a los botones del selector
|
|
const buttons = group.querySelectorAll('.view-switcher-btn');
|
|
buttons.forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const targetView = btn.getAttribute('data-view');
|
|
if (targetView) {
|
|
this.applyView(context, targetView, true);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Aplica la vista seleccionada en el contexto dado.
|
|
* @param {string} context - Identificador del módulo (ej: 'classrooms', 'reservations')
|
|
* @param {string} viewName - Nombre de la vista ('table', 'cards', etc.)
|
|
* @param {boolean} persist - Si debe persistirse en localStorage y actualizar URL
|
|
*/
|
|
applyView(context, viewName, persist = true) {
|
|
// 1. Actualizar botones activos en el grupo selector
|
|
const group = document.querySelector(`.view-switcher-group[data-context="${context}"]`);
|
|
if (group) {
|
|
const buttons = group.querySelectorAll('.view-switcher-btn');
|
|
buttons.forEach(btn => {
|
|
const btnView = btn.getAttribute('data-view');
|
|
if (btnView === viewName) {
|
|
btn.classList.remove('btn-outline-secondary');
|
|
btn.classList.add('btn-primary', 'active');
|
|
btn.setAttribute('aria-pressed', 'true');
|
|
} else {
|
|
btn.classList.remove('btn-primary', 'active');
|
|
btn.classList.add('btn-outline-secondary');
|
|
btn.setAttribute('aria-pressed', 'false');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 2. Conmutar visibilidad de los contenedores de vista
|
|
const containers = document.querySelectorAll(`[data-view-context="${context}"]`);
|
|
let matched = false;
|
|
|
|
containers.forEach(container => {
|
|
const containerView = container.getAttribute('data-view-container');
|
|
if (containerView === viewName) {
|
|
container.classList.remove('d-none');
|
|
container.removeAttribute('aria-hidden');
|
|
matched = true;
|
|
} else {
|
|
container.classList.add('d-none');
|
|
container.setAttribute('aria-hidden', 'true');
|
|
}
|
|
});
|
|
|
|
// Fallback: si la vista solicitada no tiene contenedor, restaurar a la primera visible
|
|
if (!matched && containers.length > 0) {
|
|
containers[0].classList.remove('d-none');
|
|
viewName = containers[0].getAttribute('data-view-container') || 'cards';
|
|
}
|
|
|
|
// 3. Persistir preferencia y actualizar formularios y URL
|
|
if (persist) {
|
|
try {
|
|
localStorage.setItem(`view_preference_${context}`, viewName);
|
|
} catch (e) {
|
|
console.warn('localStorage no disponible para guardar preferencia de vista:', e);
|
|
}
|
|
|
|
// Actualizar parámetro display en la URL sin recargar
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('display', viewName);
|
|
window.history.replaceState({}, '', url);
|
|
}
|
|
|
|
// 4. Sincronizar campo hidden 'display' en formularios de búsqueda/filtro
|
|
const forms = document.querySelectorAll('form');
|
|
forms.forEach(form => {
|
|
let hiddenInput = form.querySelector('input[name="display"]');
|
|
if (!hiddenInput) {
|
|
hiddenInput = document.createElement('input');
|
|
hiddenInput.type = 'hidden';
|
|
hiddenInput.name = 'display';
|
|
form.appendChild(hiddenInput);
|
|
}
|
|
hiddenInput.value = viewName;
|
|
});
|
|
|
|
// 5. Emitir evento personalizado para componentes dinámicos
|
|
window.dispatchEvent(new CustomEvent('viewModeChanged', {
|
|
detail: { context, view: viewName }
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Instanciar singleton
|
|
window.ViewSwitcher = new ViewSwitcherManager();
|
|
})();
|