7.7 KiB
7.7 KiB
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"(oinicializada, 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*-initializedmientras sigan en uso.
Ejemplos:
data-sig-select2-initialized="1"
data-sig-tooltip-initialized="1"
data-sig-dropzone-initialized="1"
data-sig-datepicker-initialized="1"
custom.jsya contiene banderas heredadas comoloaded,creadoyADsortable. 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-...]')oelement.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:
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
UpdatePanelreemplaza 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 tododocument. - Centralizar la conexión con
Sys.WebForms.PageRequestManager.endRequestoSys.Application.load; no registrar ese puente desde cada página. - El puente global debe registrarse una sola vez.
Ejemplo:
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,documento un contenedor permanente deben registrarse una sola vez mediante una bandera global. custom.jsya usawindow.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(),removeEventListenerni 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:
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:
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
UpdatePanelno 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
.aspxsi pertenecen globalmente aScripts/custom.js. - No definir implementaciones JavaScript ni cargar sus archivos desde
.ascxo su C#. Mantener el comportamiento en archivos deScripts/incluidos globalmente. Para llamadas puntuales desde el servidor, usar elRegistraScriptexistente (PageScriptRegistrar.RegistraScripten 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 placeholderJ; no crear un archivo.jsseparado 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.