# Reglas obligatorias para inicialización JavaScript idempotente Estas reglas complementan `AGENTS.md` y `AGENTS_WEBFORMS_PARTIAL_POSTBACKS.md`. ## Objetivo Los inicializadores deben poder ejecutarse después de la carga inicial y después de cualquier postback parcial sin: - inicializar dos veces el mismo control; - duplicar eventos; - destruir y reconstruir controles que siguen vigentes; - modificar elementos que ya fueron procesados; - producir errores porque un plugin ya existe. ## Banderas por elemento - Cada control inicializado debe conservar una bandera en el propio elemento HTML. - Los inicializadores deben seleccionar únicamente elementos que todavía no tengan su bandera. - Para código nuevo, usar atributos con namespace `data-sig--inicializado="1"` (o `inicializada`, según corresponda). - La bandera debe identificar el componente, no usar nombres genéricos que puedan colisionar. - La marca pertenece a cada elemento, no al tipo de UserControl: dos instancias iguales deben inicializarse independientemente. Una bandera global sólo protege el registro global y no sustituye las marcas por elemento. - Usar nombres en español para las marcas nuevas, por ejemplo `data-sig-confirmacion-inicializada="1"`; conservar las marcas heredadas `*-initialized` mientras sigan en uso. Ejemplos: ```html data-sig-select2-initialized="1" data-sig-tooltip-initialized="1" data-sig-dropzone-initialized="1" data-sig-datepicker-initialized="1" ``` - `custom.js` ya contiene banderas heredadas como `loaded`, `creado` y `ADsortable`. Deben respetarse mientras sus inicializadores sigan activos. - Al modificar completamente uno de esos inicializadores, puede migrarse a `data-sig-*`, pero deben actualizarse juntos todos sus selectores, lecturas, escrituras y rutinas de destrucción. - Para banderas nuevas visibles en el DOM, preferir `.attr('data-sig-...', '1')`, `.is('[data-sig-...]')` o `element.dataset`. - No mezclar `.data()` y `.attr()` para leer/escribir la misma bandera: jQuery mantiene un caché interno y ambos valores pueden quedar diferentes. ## Momento correcto para colocar la bandera - Comprobar la bandera antes de modificar el elemento o registrar eventos. - Colocar la bandera sólo después de que el plugin o control termine de inicializar correctamente. - Si la inicialización falla, el elemento debe permanecer sin bandera para permitir un reintento posterior. - No colocar la bandera antes de una operación que pueda lanzar una excepción. Ejemplo: ```javascript function initializeSelect2(root) { var $root = $(root || document); $root.find('.js-select2:not([data-sig-select2-initialized="1"])') .addBack('.js-select2:not([data-sig-select2-initialized="1"])') .each(function () { var $control = $(this); $control.select2({ width: '100%' }); $control.attr('data-sig-select2-initialized', '1'); }); } ``` ## Postbacks parciales y contenido nuevo - Cuando un `UpdatePanel` reemplaza su HTML, los elementos nuevos no contienen la bandera de la instancia anterior y serán inicializados normalmente. - Los elementos fuera del panel actualizado conservan su bandera y deben ignorarse. - Siempre que sea posible, pasar como `root` únicamente el panel actualizado en lugar de volver a recorrer todo `document`. - Centralizar la conexión con `Sys.WebForms.PageRequestManager.endRequest` o `Sys.Application.load`; no registrar ese puente desde cada página. - El puente global debe registrarse una sola vez. Ejemplo: ```javascript function initializeSigControls(root) { initializeSelect2(root); initializeTooltips(root); initializeDatePickers(root); } if (window.SIG_PARTIAL_INITIALIZERS_REGISTERED !== true) { window.SIG_PARTIAL_INITIALIZERS_REGISTERED = true; Sys.WebForms.PageRequestManager.getInstance().add_endRequest( function (sender, args) { var panels = args.get_panelsUpdated(); if (!panels || panels.length === 0) { initializeSigControls(document); return; } for (var index = 0; index < panels.length; index++) { initializeSigControls(panels[index]); } } ); } ``` ## Eventos delegados y eventos directos - Los eventos delegados sobre `body`, `document` o un contenedor permanente deben registrarse una sola vez mediante una bandera global. - `custom.js` ya usa `window.SIG_CUSTOM_READY_INITIALIZED`; conservar este patrón para el bloque global existente. - Está prohibido quitar y volver a agregar eventos para inicializar: no usar `.off().on()`, `.unbind().bind()`, `removeEventListener` ni equivalentes para ese propósito. Conservar los eventos existentes y omitir los elementos ya marcados; la destrucción explícita de un componente no es una estrategia de inicialización. - Para eventos directos inevitables, inicializar únicamente elementos sin bandera y usar eventos con namespace. Ejemplo de evento directo: ```javascript function initializeAmountInputs(root) { $(root || document) .find('.js-amount:not([data-sig-amount-initialized="1"])') .each(function () { var $input = $(this); $input.on('change.sigAmount', function () { // Manejar cambio. }); $input.attr('data-sig-amount-initialized', '1'); }); } ``` ## Destrucción y reinicialización explícita - No quitar una bandera sólo para forzar que el inicializador vuelva a ejecutarse. - Quitar la bandera únicamente cuando exista una necesidad real de destruir o reconfigurar el componente. - Antes de quitarla, llamar la API de destrucción del plugin y eliminar eventos con su namespace. - El orden obligatorio es: destruir plugin, retirar eventos propios, quitar bandera y finalmente reinicializar si corresponde. Ejemplo: ```javascript function destroySelect2(control) { var $control = $(control); if ($control.attr('data-sig-select2-initialized') !== '1') { return; } if ($control.hasClass('select2-hidden-accessible')) { $control.select2('destroy'); } $control.off('.sigSelect2'); $control.removeAttr('data-sig-select2-initialized'); } ``` ## Clonado y reemplazo de controles - No copiar accidentalmente banderas de inicialización al clonar controles. - Preferir clonar la plantilla antes de inicializarla. - Si se clona un elemento ya inicializado, retirar del clon las banderas `data-sig-*-initialized`, IDs generados por plugins y contenedores auxiliares antes de insertarlo. - Un control eliminado por un `UpdatePanel` no necesita limpieza manual de bandera, pero los recursos globales o listeners externos sí deben liberarse cuando el plugin lo requiera. ## Organización - Cada inicializador debe ser una función pequeña e idempotente que acepte un elemento raíz. - La función central sólo coordina inicializadores; no debe contener toda la lógica de cada plugin. - No duplicar inicializadores dentro de páginas `.aspx` si pertenecen globalmente a `Scripts/custom.js`. - No definir implementaciones JavaScript ni cargar sus archivos desde `.ascx` o su C#. Mantener el comportamiento en archivos de `Scripts/` incluidos globalmente. Para llamadas puntuales desde el servidor, usar el `RegistraScript` existente (`PageScriptRegistrar.RegistraScript` en UserControls), sin colas ni acciones serializadas alternativas. - El JavaScript específico de una página debe permanecer dentro de ese mismo `.aspx`, en un bloque `