/**
* @Datei events.js. Ein Ereignis-System (John Resig - Geheimnisse eines JS Ninja http://jsninja.com/)
* (Die ursprüngliche Buchversion war nicht vollständig brauchbar, daher wurden einige Dinge korrigiert und der Closure Compiler kompatibel gemacht)
* Dies sollte sehr ähnlich zu jQuery's Ereignisse arbeiten, aber es ist aus dem Buch-Version, die nicht so ist basiert
* robust wie die von Jquery, daher gibt es wahrscheinlich einige Unterschiede.
*
* @Datei events.js
* @Modul Ereignisse
* /
domData von './dom-data' importieren;
importiere * als Guid aus './guid.js';
import log from './log.js';
import window from 'global/window';
dokument aus 'global/document' importieren;
/**
* Bereinigung des Hörer-Caches und der Dispatcher
*
* @param {Element|Objekt} elem
* Element zum Aufräumen
*
* @param {string} type
* Art des zu bereinigenden Ereignisses
* /
function _cleanUpEvents(elem, type) {
if (!DomData.has(elem)) {
rückkehr;
}
const data = DomData.get(elem);
// Entfernen Sie die Ereignisse eines bestimmten Typs, wenn es keine mehr gibt
if (data.handlers[type].length === 0) {
data.handlers[type] löschen;
// data.handlers[type] = null;
// Das Setzen auf Null verursachte einen Fehler bei data.handlers
// Entfernen Sie den Meta-Handler aus dem Element
if (elem.removeEventListener) {
elem.removeEventListener(type, data.dispatcher, false);
} else if (elem.detachEvent) {
elem.detachEvent('on' + type, data.dispatcher);
}
}
// Entfernen Sie das Ereignisobjekt, wenn es keine Typen mehr gibt
if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
data.handlers löschen;
data.dispatcher löschen;
data.disabled löschen;
}
// Abschließend werden die Elementdaten entfernt, wenn keine Daten mehr vorhanden sind
if (Object.getOwnPropertyNames(data).length === 0) {
DomData.delete(elem);
}
}
/**
* Führt eine Schleife durch ein Array von Ereignistypen und ruft die gewünschte Methode für jeden Typ auf.
*
* @param {Funktion} fn
* Die Ereignismethode, die wir verwenden wollen.
*
* @param {Element|Objekt} elem
* Element oder Objekt, an das Zuhörer gebunden werden sollen
*
* @param {string} type
* Typ des Ereignisses, an das gebunden werden soll.
*
* @param {EventTarget~EventListener} callback
* Ereignis-Hörer.
* /
function _handleMultipleEvents(fn, elem, types, callback) {
types.forEach(function(type) {
// Aufruf der Ereignismethode für jeden der Typen
fn(elem, typ, rückruf);
});
}
/**
* Reparieren eines nativen Ereignisses, um Standard-Eigenschaftswerte zu haben
*
* @param {Object} event
* Zu behebendes Ereignisobjekt.
*
* @return {Object}
* Festes Ereignisobjekt.
* /
export function fixEvent(event) {
if (event.fixed_) {
ereignis zurückgeben;
}
function returnTrue() {
true zurückgeben;
}
function returnFalse() {
return false;
}
// Testen, ob eine Reparatur erforderlich ist
// Wird verwendet, um zu prüfen, ob !event.stopPropagation anstelle von isPropagationStopped
// Aber native Ereignisse geben für stopPropagation true zurück, haben aber keine
// andere erwartete Methoden wie isPropagationStopped. Scheint ein Problem zu sein
// mit dem Javascript-Ninja-Code. Wir überschreiben also jetzt einfach alle Ereignisse.
if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
const old = event || window.event;
ereignis = {};
// Klonen Sie das alte Objekt, damit wir die Werte ändern können event = {};
// IE8 mag es nicht, wenn man mit nativen Ereigniseigenschaften herumspielt
// Firefox liefert false für event.hasOwnProperty('type') und andere Requisiten
// was das Kopieren erschwert.
// TODO: Wahrscheinlich ist es am besten, eine Whitelist von Ereignisrequisiten zu erstellen
for (const key in old) {
// Safari 6.0.3 warnt Sie, wenn Sie versuchen, veraltete layerX/Y zu kopieren
// Chrome warnt Sie, wenn Sie versuchen, die veraltete keyboardEvent.keyLocation zu kopieren
// und webkitMovementX/Y
// Lighthouse beschwert sich, wenn Event.path kopiert wird
if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' &&
key !== 'webkitMovementX' && key !== 'webkitMovementY' &&
schlüssel !== 'Pfad') {
// Chrome 32+ warnt, wenn Sie versuchen, den veralteten returnValue zu kopieren, aber
// Wir wollen das auch dann, wenn preventDefault nicht unterstützt wird (IE8).
if (!(key === 'returnValue' && old.preventDefault)) {
event[key] = old[key];
}
}
}
// Das Ereignis ist bei diesem Element eingetreten
if (!event.target) {
event.target = event.srcElement || document;
}
// Handle, auf welches andere Element sich das Ereignis bezieht
if (!event.relatedTarget) {
event.relatedTarget = event.fromElement === event.target ?
event.toElement :
event.fromElement;
}
// Anhalten der Standard-Browser-Aktion
event.preventDefault = function() {
if (old.preventDefault) {
old.preventDefault();
}
event.returnValue = false;
old.returnValue = false;
event.defaultPrevented = true;
};
event.defaultPrevented = false;
// Stoppen Sie das Blubbern des Ereignisses
event.stopPropagation = function() {
if (old.stopPropagation) {
old.stopPropagation();
}
event.cancelBubble = true;
old.cancelBubble = true;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse;
// Stoppen des Ereignisses und Ausführen anderer Handler
event.stopImmediatePropagation = function() {
if (old.stopImmediatePropagation) {
old.stopImmediatePropagation();
}
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse;
// Behandlung der Mausposition
if (event.clientX !== null && event.clientX !== undefined) {
const doc = document.documentElement;
const body = document.body;
event.pageX = event.clientX +
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
(doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY +
(doc && doc.scrollTop || body && body.scrollTop || 0) -
(doc && doc.clientTop || body && body.clientTop || 0);
}
// Behandlung von Tastendrucken
event.which = event.charCode || event.keyCode;
// Schaltfläche für Mausklicks fixieren:
// 0 == links; 1 == Mitte; 2 == rechts
if (event.button !== null && event.button !== undefined) {
// Das Folgende ist deaktiviert, da es den videojs-standard nicht erfüllt
// und... igitt.
/* eslint-disable */
event.button = (event.button & 1 ? 0 :
(event.button & 4 ? 1 :
(event.button & 2 ? 2 : 0)));
/* eslint-aktivieren */
}
}
event.fixed_ = true;
// Rückgabe der reparierten Instanz
ereignis zurückgeben;
}
/**
* Ob passive Ereignislistener unterstützt werden
* /
let _supportsPassive;
const supportsPassive = function() {
if (typeof _supportsPassive !== 'boolean') {
_supportsPassive = false;
Versuchen {
const opts = Object.defineProperty({}, 'passiv', {
get() {
_supportsPassive = true;
}
});
window.addEventListener('test', null, opts);
window.removeEventListener('test', null, opts);
} catch (e) {
// außer Acht lassen
}
}
return _supportsPassive;
};
/**
* Berührungsereignisse, von denen Chrome erwartet, dass sie passiv sind
* /
const passiveEreignisse = [
touchstart',
touchmove
];
/**
* Hinzufügen eines Ereignis-Listeners zum Element
* Es speichert die Handler-Funktion in einem separaten Cache-Objekt
* und fügt dem Ereignis des Elements einen allgemeinen Handler hinzu,
* zusammen mit einer eindeutigen ID (guid) für das Element.
*
* @param {Element|Objekt} elem
* Element oder Objekt, an das Zuhörer gebunden werden sollen
*
* @param {string|string[]} type
* Typ des Ereignisses, an das gebunden werden soll.
*
* @param {EventTarget~EventListener} fn
* Ereignis-Hörer.
* /
export function on(elem, type, fn) {
if (Array.isArray(type)) {
return _handleMultipleEvents(on, elem, type, fn);
}
if (!DomData.has(elem)) {
DomData.set(elem, {});
}
const data = DomData.get(elem);
// Wir brauchen einen Ort, an dem wir alle unsere Handler-Daten speichern können
if (!data.handlers) {
data.handlers = {};
}
if (!data.handlers[type]) {
data.handlers[type] = [];
}
if (!fn.guid) {
fn.guid = Guid.newGUID();
}
data.handlers[type].push(fn);
if (!data.dispatcher) {
data.disabled = false;
data.dispatcher = function(event, hash) {
if (data.disabled) {
rückkehr;
}
event = fixEvent(event);
const handlers = data.handlers[event.type];
if (Handler) {
// Kopieren Sie die Handler, damit beim Hinzufügen/Entfernen von Handlern während des Prozesses nicht alles durcheinander gerät.
const handlersCopy = handlers.slice(0);
for (let m = 0, n = handlersCopy.length; m < n; m++) {
if (event.isImmediatePropagationStopped()) {
pause;
} else {
Versuchen {
handlersCopy[m].call(elem, event, hash);
} catch (e) {
log.error(e);
}
}
}
}
};
}
if (data.handlers[type].length === 1) {
if (elem.addEventListener) {
let options = false;
wenn (unterstütztPassiv() &&
passiveEvents.indexOf(type) > -1) {
optionen = {passiv: true};
}
elem.addEventListener(type, data.dispatcher, options);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, data.dispatcher);
}
}
}
/**
* Entfernt Ereignis-Listener von einem Element
*
* @param {Element|Objekt} elem
* Objekt, von dem Hörer entfernt werden sollen.
*
* @param {string|string[]} [Typ]
* Typ des zu entfernenden Hörers. Schließen Sie nicht ein, um alle Ereignisse aus dem Element zu entfernen.
*
* @param {EventTarget~EventListener} [fn]
* Spezifischer Hörer zum Entfernen. Hörer für ein Ereignis nicht entfernen
* typ.
* /
export function off(elem, type, fn) {
// Kein Cache-Objekt über getElData hinzufügen, wenn es nicht benötigt wird
if (!DomData.has(elem)) {
rückkehr;
}
const data = DomData.get(elem);
// Wenn keine Ereignisse vorhanden sind, gibt es nichts zu entbinden
if (!data.handlers) {
rückkehr;
}
if (Array.isArray(type)) {
return _handleMultipleEvents(off, elem, type, fn);
}
// Utility-Funktion
const removeType = function(el, t) {
data.handlers[t] = [];
_cleanUpEvents(el, t);
};
// Werden alle gebundenen Ereignisse entfernt?
if (type === undefined) {
for (const t in data.handlers) {
if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
removeType(elem, t);
}
}
rückkehr;
}
const handlers = data.handlers[type];
// Wenn keine Handler vorhanden sind, gibt es nichts zu entbinden
if (!handlers) {
rückkehr;
}
// Wenn kein Hörer angegeben wurde, werden alle Hörer für den Typ
if (!fn) {
removeType(elem, type);
rückkehr;
}
// Wir entfernen nur einen einzigen Handler
if (fn.guid) {
for (let n = 0; n < handlers.length; n++) {
if (handlers[n].guid === fn.guid) {
handlers.splice(n--, 1);
}
}
}
_cleanUpEvents(elem, type);
}
/**
* Auslösen eines Ereignisses für ein Element
*
* @param {Element|Objekt} elem
* Element zum Auslösen eines Ereignisses auf
*
* @param {EventTarget~Event|string} event
* Eine Zeichenkette (der Typ) oder ein Ereignisobjekt mit einem Typ-Attribut
*
* @param {Object} [Raute]
* datenhash, der mit dem Ereignis weitergegeben wird
*
* @return {boolean|undefined}
* Gibt das Gegenteil von `defaultPrevented` zurück, wenn default war
* verhindert. Andernfalls wird "undefiniert" zurückgegeben
* /
export function trigger(elem, event, hash) {
// Holt Elementdaten und einen Verweis auf das übergeordnete Element (für Bubbling).
// Es soll nicht für jedes Elternteil ein Datenobjekt in den Cache aufgenommen werden,
// also zuerst hasElData prüfen.
const elemData = DomData.has(elem) ? DomData.get(elem) : {};
const parent = elem.parentNode || elem.ownerDocument;
// type = event.type || event,
// handler;
// Wenn ein Ereignisname als String übergeben wurde, wird daraus ein Ereignis erzeugt
if (typeof event === 'string') {
event = {Typ: event, Ziel: elem};
} else if (!event.target) {
event.target = elem;
}
// Normalisiert die Ereigniseigenschaften.
event = fixEvent(event);
// Wenn das übergebene Element einen Dispatcher hat, werden die festgelegten Handler ausgeführt.
if (elemData.dispatcher) {
elemData.dispatcher.call(elem, event, hash);
}
// Es sei denn, das Ereignis wird explizit gestoppt oder es gibt keine Blasenbildung (z. B. bei Medienereignissen)
// ruft diese Funktion rekursiv auf, um das Ereignis im DOM nach oben zu blasen.
if (parent && !event.isPropagationStopped() && event.bubbles === true) {
trigger.call(null, parent, event, hash);
// Wenn es sich am oberen Rand des DOM befindet, löst es die Standardaktion aus, sofern es nicht deaktiviert ist.
} else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
if (!DomData.has(event.target)) {
DomData.set(event.target, {});
}
const targetData = DomData.get(event.target);
// Überprüft, ob das Ziel eine Standardaktion für dieses Ereignis hat.
if (event.target[event.type]) {
// Deaktiviert vorübergehend die Ereignisauslösung auf dem Ziel, da wir den Handler bereits ausgeführt haben.
targetData.disabled = true;
// Führt die Standardaktion aus.
if (typeof event.target[event.type] === 'function') {
event.target[event.type]();
}
// Aktiviert die Ereignisverteilung erneut.
targetData.disabled = false;
}
}
// Informieren Sie den Auslöser, wenn der Standard verhindert wurde, indem Sie false zurückgeben
return !event.defaultPrevented;
}
/**
* Lösen Sie einen Listener nur einmal für ein Ereignis aus.
*
* @param {Element|Objekt} elem
* Element oder Objekt, an das gebunden werden soll.
*
* @param {string|string[]} type
* Name/Typ der Veranstaltung
*
* @param {Event~EventListener} fn
* Ereignis-Listener-Funktion
* /
export function one(elem, type, fn) {
if (Array.isArray(type)) {
return _handleMultipleEvents(one, elem, type, fn);
}
const func = function() {
aus(elem, typ, func);
fn.apply(this, arguments);
};
// Kopieren Sie die Guid in die neue Funktion, damit sie mit der ID der ursprünglichen Funktion entfernt werden kann
func.guid = fn.guid = fn.guid || Guid.newGUID();
on(elem, typ, func);
}
/**
* Einen Hörer nur einmal auslösen und dann für alle ausschalten
* konfigurierte Ereignisse
*
* @param {Element|Objekt} elem
* Element oder Objekt, an das gebunden werden soll.
*
* @param {string|string[]} type
* Name/Typ der Veranstaltung
*
* @param {Event~EventListener} fn
* Ereignis-Listener-Funktion
* /
export function any(elem, type, fn) {
const func = function() {
aus(elem, typ, func);
fn.apply(this, arguments);
};
// Kopieren Sie die Guid in die neue Funktion, damit sie mit der ID der ursprünglichen Funktion entfernt werden kann
func.guid = fn.guid = fn.guid || Guid.newGUID();
// mehrere Einschaltungen, aber eine Ausschaltung für alles
on(elem, typ, func);
}