168 lines
7.7 KiB
Markdown
168 lines
7.7 KiB
Markdown
# 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-<componente>-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 `<script>` del placeholder `J`; no crear un archivo `.js` separado ni trasladarlo a un bundle global, salvo petición explícita del usuario. Debe seguir las mismas reglas de banderas y postbacks parciales.
|
|
- No modificar archivos minificados ni dependencias de terceros para implementar estas banderas.
|