/**
* @archivo eventos.js. Un sistema de eventos (John Resig - Secretos de un JS Ninja http://jsninja.com/)
* (La versión del libro original no se podía usar por completo, por lo que se arreglaron algunas cosas y se hizo compatible con Closure Compiler)
* Esto debería funcionar de manera muy similar a los eventos de jQuery, sin embargo, se basa en la versión del libro, que no es tan
* robusto como el de jquery, por lo que probablemente haya algunas diferencias.
*
* @archivo eventos.js
* @module eventos
* /
importar DomData desde './dom-data';
importar * como Guid desde './guid.js';
importar registro desde './log.js';
importar ventana desde 'global/window';
importar documento desde 'global/document';
/ **
* Limpiar el caché del oyente y los despachadores
*
* @param {Elemento|Objeto} elemento
* Elemento para limpiar
*
* @param {cadena} tipo
* Tipo de evento a limpiar
* /
función _cleanUpEvents (elemento, tipo) {
si (!DomData.has(elem)) {
devolver;
}
datos constantes = DomData.get(elem);
// Elimina los eventos de un tipo en particular si no queda ninguno
if (data.handlers[tipo].longitud === 0) {
eliminar data.handlers[tipo];
// data.handlers[tipo] = null;
// Establecer en nulo estaba causando un error con data.handlers
// Eliminar el metamanejador del elemento
si (elem.removeEventListener) {
elem.removeEventListener(type, data.dispatcher, false);
} más si (elem.detachEvent) {
elem.detachEvent('on' + tipo, data.dispatcher);
}
}
// Eliminar el objeto de eventos si no quedan tipos
if (Object.getOwnPropertyNames(data.handlers).length < = 0) {
eliminar manejadores de datos;
eliminar data.dispatcher;
eliminar datos.deshabilitado;
}
// Finalmente, elimine los datos del elemento si no quedan datos
if (Objeto.getOwnPropertyNames(datos).longitud === 0) {
DomData.delete(elemento);
}
}
/ **
* Recorre una serie de tipos de eventos y llama al método solicitado para cada tipo.
*
* @param {Función} fn
* El método de evento que queremos usar.
*
* @param {Elemento|Objeto} elemento
* Elemento u objeto para vincular a los oyentes
*
* @param {cadena} tipo
* Tipo de evento al que vincularse.
*
* @param {EventTarget~EventListener} devolución de llamada
* Oyente de eventos.
* /
function _handleMultipleEvents(fn, elem, tipos, devolución de llamada) {
tipos.forEach(función(tipo) {
// Llamar al método de evento para cada uno de los tipos
fn(elemento, tipo, devolución de llamada);
});
}
/ **
* Arreglar un evento nativo para tener valores de propiedad estándar
*
* evento @param {Objeto}
* Objeto de evento para arreglar.
*
* @return {Objeto}
* Objeto de evento fijo.
* /
función de exportación fixEvent (evento) {
si (evento.fijo_) {
evento de retorno;
}
función devuelveVerdadero() {
devolver verdadero;
}
función devolverFalso() {
falso retorno;
}
// Probar si es necesario arreglar
// Se usa para verificar si !event.stopPropagation en lugar de isPropagationStopped
// Pero los eventos nativos devuelven verdadero para stopPropagation, pero no tienen
// otros métodos esperados como isPropagationStopped. parece ser un problema
// con el código Javascript Ninja. Así que estamos anulando todos los eventos ahora.
if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
const viejo = evento || ventana.evento;
evento = {};
// Clona el objeto antiguo para que podamos modificar los valores event = {};
// A IE8 no le gusta cuando te metes con las propiedades de los eventos nativos
// Firefox devuelve false para event.hasOwnProperty('type') y otras propiedades
// lo que dificulta la copia.
// HACER: Probablemente sea mejor crear una lista blanca de accesorios para eventos.
for (clave const en antiguo) {
// Safari 6.0.3 le advierte si intenta copiar la capa obsoleta X/Y
// Chrome le advierte si intenta copiar el obsoleto keyboardEvent.keyLocation
// y webkitMovementX/Y
// Lighthouse se queja si se copia Event.path
if (clave !== 'capaX' && tecla !== 'capaY' && clave !== 'ubicaciónclave' &&
tecla !== 'webkitMovimientoX' && tecla !== 'webkitMovementY' &&
tecla !== 'ruta') {
// Chrome 32+ advierte si intenta copiar returnValue obsoleto, pero
// todavía queremos hacerlo si preventDefault no es compatible (IE8).
if (!(clave === 'returnValue' && old.preventDefault)) {
evento[clave] = antiguo[clave];
}
}
}
// El evento ocurrió en este elemento
if (!evento.objetivo) {
evento.objetivo = evento.srcElement || documento;
}
// Manejar con qué otro elemento está relacionado el evento
if (!evento.objetivorelacionado) {
event.relatedTarget = event.fromElement === event.target ?
evento.toElement :
evento.fromElement;
}
// Detener la acción predeterminada del navegador
event.preventDefault = function() {
if (antiguo.prevenir por defecto) {
old.preventDefault();
}
evento.returnValue = falso;
antiguo.returnValue = falso;
evento.defaultPrevented = verdadero;
};
evento.defaultPrevented = falso;
// Evita que el evento burbujee
event.stopPropagation = function() {
if (antiguo.detenerPropagación) {
old.stopPropagation();
}
event.cancelBubble = verdadero;
old.cancelBubble = verdadero;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse;
// Evita que el evento burbujee y ejecute otros controladores
event.stopImmediatePropagation = function() {
if (antiguo. detener la propagación inmediata) {
old.stopImmediatePropagation();
}
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse;
// Manejar la posición del mouse
si (evento.clienteX! == nulo && evento.clientX !== indefinido) {
const doc = documento.documentElement;
const cuerpo = documento.cuerpo;
evento.paginaX = evento.clienteX +
(doc && doc.scrollLeft || cuerpo && cuerpo.scrollLeft || 0) -
(doc && doc.clienteIzquierdo || cuerpo && cuerpo.clienteIzquierdo || 0);
evento.paginaY = evento.clienteY +
(doc && doc.scrollTop || cuerpo && cuerpo.scrollTop || 0) -
(doc && doc.clienteTop || cuerpo && cuerpo.clientTop || 0);
}
// Manejar pulsaciones de teclas
evento.que = evento.charCode || evento.keyCode;
// Botón de reparación para clics del mouse:
// 0 == izquierda; 1 == medio; 2 == correcto
if (evento.boton !== nulo && event.button !== indefinido) {
// Lo siguiente está deshabilitado porque no pasa videojs-standard
// y... ay.
/* eslint-desactivar */
evento.boton = (evento.boton & 1? 0 :
(evento.botón & 4? 1:
(evento.botón & 2? 2: 0)));
/* habilitar eslint */
}
}
evento.fijo_ = verdadero;
// Devuelve la instancia arreglada
evento de retorno;
}
/ **
* Si se admiten detectores de eventos pasivos
* /
let _supportsPassive;
const admite Pasivo = función () {
if (typeof _supportsPassive !== 'booleano') {
_supportsPassive = falso;
intentar {
const opts = Object.defineProperty({}, 'pasivo', {
conseguir() {
_supportsPassive = verdadero;
}
});
ventana.addEventListener('prueba', nulo, opciones);
ventana.removeEventListener('prueba', nulo, opciones);
} catch (e) {
// ignorar
}
}
volver _supportsPassive;
};
/ **
* Eventos táctiles que Chrome espera que sean pasivos
* /
const eventos pasivos = [
'inicio táctil',
'tocar mover'
];
/ **
* Agregar un detector de eventos al elemento
* Almacena la función del controlador en un objeto de caché separado
* y agrega un controlador genérico al evento del elemento,
* junto con una identificación única (guid) para el elemento.
*
* @param {Elemento|Objeto} elemento
* Elemento u objeto para vincular a los oyentes
*
* @param {cadena|cadena[]} tipo
* Tipo de evento al que vincularse.
*
* @param {EventTarget~EventListener} fn
* Oyente de eventos.
* /
función de exportación en (elemento, tipo, fn) {
if (Array.isArray(tipo)) {
return _handleMultipleEvents(on, elem, type, fn);
}
si (!DomData.has(elem)) {
DomData.set(elemento, {});
}
datos constantes = DomData.get(elem);
// Necesitamos un lugar para almacenar todos nuestros datos de controlador
si (! manejadores de datos) {
manejadores de datos = {};
}
if (!data.handlers[tipo]) {
manejadores de datos[tipo] = [];
}
si (!fn.guid) {
fn.guid = Guid.nuevoGUID();
}
manejadores de datos[tipo].push(fn);
if (! datos.despachador) {
datos.deshabilitado = falso;
data.dispatcher = función (evento, hash) {
si (datos.deshabilitado) {
devolver;
}
evento = fixEvent(evento);
manejadores const = data.handlers[evento.tipo];
si (manejadores) {
// Copie los controladores, de modo que si se agregan/eliminan controladores durante el proceso, no se deshace de todo.
const handlersCopy = handlers.slice(0);
for (sea m = 0, n = handlersCopy.length; m < norte; m++) {
if (event.isImmediatePropagationStopped()) {
romper;
} else {
intentar {
handlersCopy[m].call(elem, event, hash);
} catch (e) {
log.error(e);
}
}
}
}
};
}
if (data.handlers[tipo].longitud === 1) {
si (elem.addEventListener) {
dejar opciones = falso;
if (soporta Pasivo() &&
eventos pasivos.indexOf(tipo) > -1) {
opciones = {pasivo: verdadero};
}
elem.addEventListener(tipo, data.dispatcher, opciones);
} más si (elem.attachEvent) {
elem.attachEvent('on' + tipo, data.dispatcher);
}
}
}
/ **
* Elimina detectores de eventos de un elemento
*
* @param {Elemento|Objeto} elemento
* Objeto del que eliminar oyentes.
*
* @param {cadena|cadena[]} [tipo]
* Tipo de oyente a eliminar. No incluyas para eliminar todos los eventos del elemento.
*
* @param {EventTarget~EventListener} [fn]
* Oyente específico para eliminar. No incluir para eliminar oyentes de un evento
* tipo.
* /
función de exportación desactivada (elemento, tipo, fn) {
// No quiero agregar un objeto de caché a través de getElData si no es necesario
si (!DomData.has(elem)) {
devolver;
}
datos constantes = DomData.get(elem);
// Si no existen eventos, nada que desvincular
si (! manejadores de datos) {
devolver;
}
if (Array.isArray(tipo)) {
return _handleMultipleEvents(off, elem, type, fn);
}
// Función de utilidad
const removeType = function(el, t) {
manejadores de datos[t] = [];
_limpiarEventos(el, t);
};
// ¿Estamos eliminando todos los eventos vinculados?
si (tipo === indefinido) {
for (const t en data.handlers) {
if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
eliminarTipo(elemento, t);
}
}
devolver;
}
manejadores const = data.handlers[tipo];
// Si no existen controladores, nada que desvincular
si (! manejadores) {
devolver;
}
// Si no se proporcionó ningún oyente, elimine todos los oyentes para el tipo
si (!fn) {
removeType(elemento, tipo);
devolver;
}
// Solo estamos eliminando un único controlador
si (fn.guid) {
para (sea n = 0; n < manejadores.longitud; n++) {
if (controladores[n].guid === fn.guid) {
manejadores.empalme(n--, 1);
}
}
}
_cleanUpEvents(elemento, tipo);
}
/ **
* Activar un evento para un elemento
*
* @param {Elemento|Objeto} elemento
* Elemento para desencadenar un evento en
*
* @param {EventTarget~Event|cadena} evento
* Una cadena (el tipo) o un objeto de evento con un atributo de tipo
*
* @param {Objeto} [hash]
* hash de datos para pasar junto con el evento
*
* @return {booleano|indefinido}
* Devuelve lo contrario de `defaultPrevented` si el valor predeterminado era
* prevenido. De lo contrario, devuelve `indefinido`
* /
disparador de función de exportación (elemento, evento, hash) {
// Obtiene datos del elemento y una referencia al padre (para burbujear).
// No quiero agregar un objeto de datos al caché para cada padre,
// así que revisa primero hasElData.
const elemData = DomData.has(elem) ? DomData.get(elemento): {};
const padre = elem.parentNode || elem.propietarioDocumento;
// tipo = evento.tipo || evento,
// manipulador;
// Si el nombre de un evento se pasó como una cadena, crea un evento a partir de él
if (tipo de evento === 'cadena') {
evento = {tipo: evento, objetivo: elem};
} más si (!evento.objetivo) {
evento.objetivo = elemento;
}
// Normaliza las propiedades del evento.
evento = fixEvent(evento);
// Si el elemento pasado tiene un despachador, ejecuta los manejadores establecidos.
si (elemData.despachador) {
elemData.dispatcher.call(elem, evento, hash);
}
// A menos que se detenga explícitamente o que el evento no aparezca (p. ej., eventos multimedia)
// llama recursivamente a esta función para hacer subir el evento en el DOM.
si (padre && !event.isPropagationStopped() && evento.burbujas === verdadero) {
trigger.call(null, padre, evento, hash);
// Si está en la parte superior del DOM, activa la acción predeterminada a menos que esté deshabilitada.
} más si (!padre && !event.predeterminadoEvitado && evento.objetivo && evento.objetivo[evento.tipo]) {
if (!DomData.has(evento.objetivo)) {
DomData.set(evento.objetivo, {});
}
const targetData = DomData.get(event.target);
// Comprueba si el objetivo tiene una acción predeterminada para este evento.
if (evento.objetivo[evento.tipo]) {
// Deshabilita temporalmente el envío de eventos en el destino ya que ya hemos ejecutado el controlador.
targetData.deshabilitado = verdadero;
// Ejecuta la acción predeterminada.
if (tipo de evento.objetivo[evento.tipo] === 'función') {
evento.objetivo[evento.tipo]();
}
// Vuelve a habilitar el envío de eventos.
targetData.deshabilitado = falso;
}
}
// Informar al activador si se evitó el valor predeterminado devolviendo falso
volver !evento.defaultPrevented;
}
/ **
* Activar un oyente solo una vez para un evento.
*
* @param {Elemento|Objeto} elemento
* Elemento u objeto a enlazar.
*
* @param {cadena|cadena[]} tipo
* Nombre/tipo de evento
*
* @param {Evento~EventoListener} fn
* Función de escucha de eventos
* /
función de exportación uno (elemento, tipo, fn) {
if (Array.isArray(tipo)) {
return _handleMultipleEvents(uno, elemento, tipo, fn);
}
función const = función () {
off(elemento, tipo, función);
fn.apply(esto, argumentos);
};
// copia el guid a la nueva función para que pueda eliminarse usando el ID de la función original
func.guid = fn.guid = fn.guid || Guid.nuevoGUID();
on(elemento, tipo, función);
}
/ **
* Activar un oyente solo una vez y luego apagarlo para todos
* eventos configurados
*
* @param {Elemento|Objeto} elemento
* Elemento u objeto a enlazar.
*
* @param {cadena|cadena[]} tipo
* Nombre/tipo de evento
*
* @param {Evento~EventoListener} fn
* Función de escucha de eventos
* /
función de exportación cualquiera (elemento, tipo, fn) {
función const = función () {
off(elemento, tipo, función);
fn.apply(esto, argumentos);
};
// copia el guid a la nueva función para que pueda eliminarse usando el ID de la función original
func.guid = fn.guid = fn.guid || Guid.nuevoGUID();
// varios complementos, pero uno solo para todo
on(elemento, tipo, función);
}