PageSourceSearch

https://app.clavebursatil.com/js/app.offcanvas.js?v=1791225064

js clavebursatil.com collected 2026-10-05 23:19:11 UTC 12,548 bytes, 323 lines download raw bytes

1/* ============================================================================
2   OFFCANVAS — abrir y cerrar el panel lateral derecho
3   ============================================================================
4   El comportamiento del componente `<x-offcanvas>`
5   (`resources/views/components/offcanvas.blade.php`). Los estilos van aparte,
6   en la sección 51 de `public/css/app.theme.css`.
7
8   POR QUÉ ES UN ARCHIVO PROPIO Y NO UNA FUNCIÓN EN `app.custom.js`
9   ---------------------------------------------------------------
10   Porque `app.custom.js` y `app.theme.js` salen los dos de `partials/footer`,
11   que incluyen también las 13 landings públicas. Un offcanvas es del panel, y
12   no hay ninguna razón para que una landing de broker baje este código. Éste se
13   enlaza sólo desde `layouts/app` y `layouts/master`.
14
15   CÓMO SE USA, desde el HTML
16   --------------------------
17   Abrir:   <button data-offcanvas-open="idDelPanel">
18   Cerrar:  <button data-offcanvas-close>   (adentro del panel)
19   También cierran el fondo, la × del encabezado y la tecla Escape.
20
21   El botón que abre puede pedir dos cosas más:
22
23       data-offcanvas-title="Editar etiqueta"   cambia el encabezado
24       data-offcanvas-load="/tags/7/form"       trae ESE html y lo pone adentro
25
26   Con `load`, el contenido que vino con la página (`[data-offcanvas-slot]`) se esconde y en su
27   lugar va lo que devuelva la URL (`[data-offcanvas-remote]`); sin `load` se ve el slot. ESTE
28   ARCHIVO NO SABE QUÉ ESTÁ CARGANDO: recibe una dirección y pone el resultado.
29
30   EVENTOS, para lo que el formulario necesite y el panel no tiene por qué saber:
31
32       offcanvas:opened   al abrir            detail.container = el slot
33       offcanvas:loaded   tras el AJAX        detail.container = el contenedor remoto
34
35   En `/tags` y `/categories` es lo que engancha el `stringToSlug` del campo slug, que es del
36   formulario y no del panel.
37
38   FORMULARIOS QUE SE GUARDAN SIN SALIR DEL PANEL (2026-09-25)
39   ------------------------------------------------------------
40       <form data-offcanvas-ajax ...>
41
42   Un formulario con ese atributo, adentro de un panel, se envía por AJAX y LO QUE DEVUELVA EL
43   SERVIDOR REEMPLAZA EL CONTENIDO DEL PANEL: el mismo formulario con los errores (422) o lo que
44   haya que mostrar después de guardar (200). Nació para "Registrar pago", que vive en el panel
45   de detalle de una suscripción y ese panel se abre desde varias pantallas: con un envío normal,
46   un error de validación recargaría la página y cerraría el panel. Viaja con `FormData`, así que
47   los archivos adjuntos también.
48
49   PESTAÑAS QUE TRAEN SU CONTENIDO AL ABRIRSE (2026-09-30)
50   --------------------------------------------------------
51       <div class="tab-pane" id="x" data-cargar-al-abrir="/orders/30457/history"></div>
52
53   Una pestaña de Bootstrap con ese atributo nace vacía y pide su html la PRIMERA vez que se
54   abre. Nació para el "Historial" del detalle de un pedido, que sale de la tienda y no de
55   nuestra base: pedirlo junto con el detalle haría esperar a WooCommerce para dibujar la venta.
56   Vale adentro y afuera de un panel (la página de un pedido usa el mismo detalle).
57
58   LÍMITE CONOCIDO: lo que llega por `load` NO puede tener Livewire adentro. Un componente
59   inyectado con `innerHTML` no se inicializa y queda muerto.
60
61   TODO DELEGADO EN `document`, a propósito: los botones que abren pueden estar
62   en cualquier parte de la pantalla, y los de adentro del panel los puede haber
63   redibujado Livewire después de cargar la página. Con listeners atados uno por
64   uno al arrancar, ésos quedarían muertos y el botón no haría nada.
65   ========================================================================= */
66(function () {
67    'use strict';
68
69    function panelDe(id) {
70        return document.getElementById(id);
71    }
72
73    function fondoDe(id) {
74        return document.querySelector('[data-offcanvas-backdrop="' + id + '"]');
75    }
76
77    function avisar(panel, evento, contenedor) {
78        panel.dispatchEvent(new CustomEvent(evento, {
79            bubbles: true,
80            detail: { container: contenedor }
81        }));
82    }
83
84    /**
85     * Trae el html de `url` y lo deja en el contenedor remoto del panel, escondiendo el
86     * contenido que vino con la página. El estado intermedio se dice: sin el "Cargando…", un
87     * servidor lento se ve igual que un panel roto.
88     */
89    function cargar(panel, url) {
90        var slot = panel.querySelector('[data-offcanvas-slot]');
91        var remoto = panel.querySelector('[data-offcanvas-remote]');
92
93        if (!remoto) return;
94
95        if (slot) slot.style.display = 'none';
96        remoto.style.display = 'block';
97        remoto.innerHTML = '<p class="text-muted">Cargando…</p>';
98
99        var xhr = new XMLHttpRequest();
100        xhr.open('GET', url, true);
101        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
102        xhr.setRequestHeader('Accept', 'text/html');
103
104        xhr.onload = function () {
105            if (xhr.status >= 200 && xhr.status < 300) {
106                remoto.innerHTML = xhr.responseText;
107                avisar(panel, 'offcanvas:loaded', remoto);
108            } else {
109                remoto.innerHTML = '<p class="text-danger">No se pudo cargar el formulario.</p>';
110            }
111        };
112
113        xhr.onerror = function () {
114            remoto.innerHTML = '<p class="text-danger">Error de conexión.</p>';
115        };
116
117        xhr.send();
118    }
119
120    /** Vuelve al contenido que vino con la página y descarta el remoto, si lo hubo. */
121    function mostrarSlot(panel) {
122        var slot = panel.querySelector('[data-offcanvas-slot]');
123        var remoto = panel.querySelector('[data-offcanvas-remote]');
124
125        if (slot) slot.style.display = '';
126        if (remoto) {
127            remoto.style.display = 'none';
128            remoto.innerHTML = '';
129        }
130    }
131
132    function abrir(id) {
133        var panel = panelDe(id);
134        var fondo = fondoDe(id);
135
136        if (!panel) return;
137
138        panel.classList.add('is-open');
139        panel.setAttribute('aria-hidden', 'false');
140
141        if (fondo) {
142            fondo.classList.add('is-open');
143            fondo.setAttribute('aria-hidden', 'false');
144        }
145
146        // El panel scrollea por dentro; sin esto, la rueda mueve la página de atrás.
147        document.body.style.overflow = 'hidden';
148    }
149
150    function cerrar(panel) {
151        if (!panel) return;
152
153        var fondo = fondoDe(panel.id);
154
155        panel.classList.remove('is-open');
156        panel.setAttribute('aria-hidden', 'true');
157
158        if (fondo) {
159            fondo.classList.remove('is-open');
160            fondo.setAttribute('aria-hidden', 'true');
161        }
162
163        document.body.style.overflow = '';
164    }
165
166    function cerrarTodos() {
167        Array.prototype.forEach.call(
168            document.querySelectorAll('.cb-offcanvas.is-open'),
169            cerrar
170        );
171    }
172
173    document.addEventListener('click', function (e) {
174        var abridor = e.target.closest('[data-offcanvas-open]');
175        if (abridor) {
176            e.preventDefault();
177
178            var id = abridor.getAttribute('data-offcanvas-open');
179            var panel = panelDe(id);
180            if (!panel) return;
181
182            var titulo = abridor.getAttribute('data-offcanvas-title');
183            if (titulo) {
184                var tituloEl = panel.querySelector('.cb-offcanvas-title');
185                if (tituloEl) tituloEl.textContent = titulo;
186            }
187
188            var url = abridor.getAttribute('data-offcanvas-load');
189            if (url) {
190                cargar(panel, url);
191            } else {
192                mostrarSlot(panel);
193            }
194
195            abrir(id);
196
197            if (!url) avisar(panel, 'offcanvas:opened', panel.querySelector('[data-offcanvas-slot]'));
198            return;
199        }
200
201        var cerrador = e.target.closest('[data-offcanvas-close]');
202        if (cerrador) {
203            e.preventDefault();
204            cerrar(cerrador.closest('.cb-offcanvas'));
205            return;
206        }
207
208        var fondo = e.target.closest('.cb-offcanvas-backdrop');
209        if (fondo) {
210            cerrar(panelDe(fondo.getAttribute('data-offcanvas-backdrop')));
211        }
212    });
213
214    document.addEventListener('submit', function (e) {
215        var form = e.target.closest('form[data-offcanvas-ajax]');
216        if (!form) return;
217
218        var remoto = form.closest('[data-offcanvas-remote]');
219        if (!remoto) return;
220
221        e.preventDefault();
222
223        // Un doble clic no puede mandar el pago dos veces.
224        var boton = form.querySelector('[type="submit"]');
225        if (boton) {
226            if (boton.disabled) return;
227            boton.disabled = true;
228        }
229
230        var xhr = new XMLHttpRequest();
231        xhr.open((form.getAttribute('method') || 'POST').toUpperCase(), form.action, true);
232        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
233        xhr.setRequestHeader('Accept', 'text/html');
234
235        xhr.onload = function () {
236            if (xhr.status === 200 || xhr.status === 422) {
237                remoto.innerHTML = xhr.responseText;
238                remoto.scrollTop = 0;
239                var panel = remoto.closest('.cb-offcanvas');
240                if (panel) avisar(panel, 'offcanvas:loaded', remoto);
241            } else {
242                if (boton) boton.disabled = false;
243                alert('No se pudo guardar (error ' + xhr.status + '). No se registró nada; probá de nuevo.');
244            }
245        };
246
247        xhr.onerror = function () {
248            if (boton) boton.disabled = false;
249            alert('Error de conexión. No se registró nada; probá de nuevo.');
250        };
251
252        xhr.send(new FormData(form));
253    });
254
255    document.addEventListener('keydown', function (e) {
256        if (e.key === 'Escape') cerrarTodos();
257    });
258
259    // LA PESTAÑA QUE TRAE SU CONTENIDO AL ABRIRSE. Mostrarla es cosa de Bootstrap; acá sólo se
260    // mira si su panel pide contenido y todavía no lo tiene. Con el clic y no con
261    // `shown.bs.tab`: ese evento es de jQuery y este archivo no depende de él.
262    document.addEventListener('click', function (e) {
263        var pestania = e.target.closest('[data-toggle="tab"]');
264        if (!pestania) return;
265
266        var destino = pestania.getAttribute('href') || '';
267        if (destino.charAt(0) !== '#') return;
268
269        var panel = document.getElementById(destino.slice(1));
270        if (!panel) return;
271
272        var url = panel.getAttribute('data-cargar-al-abrir');
273        if (!url || panel.getAttribute('data-cargado')) return;
274
275        // Se marca antes de pedir: dos clics seguidos no piden dos veces. Si falla se desmarca,
276        // para que volver a la pestaña lo reintente.
277        panel.setAttribute('data-cargado', '1');
278        panel.innerHTML = '<p class="text-muted">Cargando…</p>';
279
280        var fallo = function (mensaje) {
281            panel.removeAttribute('data-cargado');
282            panel.innerHTML = '<p class="text-danger">' + mensaje + '</p>';
283        };
284
285        var xhr = new XMLHttpRequest();
286        xhr.open('GET', url, true);
287        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
288        xhr.setRequestHeader('Accept', 'text/html');
289
290        xhr.onload = function () {
291            if (xhr.status >= 200 && xhr.status < 300) {
292                panel.innerHTML = xhr.responseText;
293            } else {
294                fallo('No se pudo cargar.');
295            }
296        };
297
298        xhr.onerror = function () {
299            fallo('Error de conexión.');
300        };
301
302        xhr.send();
303    });
304
305    // El panel puede venir ABIERTO desde el servidor: es lo que pasa cuando el
306    // formulario falló y hay que mostrar el error adentro. En ese caso la clase
307    // `is-open` ya está puesta en el HTML, pero el fondo y el bloqueo del scroll
308    // no, así que se completan acá.
309    document.addEventListener('DOMContentLoaded', function () {
310        Array.prototype.forEach.call(
311            document.querySelectorAll('.cb-offcanvas.is-open'),
312            function (panel) {
313                abrir(panel.id);
314
315                // Y puede venir pidiendo que se rearme su contenido remoto: es lo que pasa
316                // cuando falló el formulario que se había traído por AJAX. La página se recargó
317                // y ese html ya no está, así que se vuelve a pedir.
318                var url = panel.getAttribute('data-offcanvas-autoload');
319                if (url) cargar(panel, url);
320            }
321        );
322    });
323})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.