/**
* @file src/js/event-target.js
*/
import * as Events from './utils/events.js' ;
import window from 'global/window' ;
/**
* `EventTarget` est une classe qui peut avoir la même API que le DOM `EventTarget`. Il
* ajoute des fonctions abrégées qui enveloppent les fonctions longues. Par exemple :
* la fonction `on` est une enveloppe autour de `addEventListener`.
*
* @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
* @classe EventTarget
*/
const EventTarget = function() {} ;
/**
* Un événement DOM personnalisé.
*
* @typedef {Objet} EventTarget~Event
* @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
*/
/**
* Tous les récepteurs d'événements doivent respecter le format suivant.
*
* @callback EventTarget~EventListener
* @this {EventTarget}
*
* @param {EventTarget~Event} event
* l'événement qui a déclenché cette fonction
*
* @param {Objet} [hash]
* hachage des données envoyées pendant l'événement
*/
/**
* Un objet contenant des noms d'événements comme clés et des booléens comme valeurs.
*
* > NOTE : Si un nom d'événement est défini à une valeur vraie ici {@link EventTarget#trigger}
* aura des fonctionnalités supplémentaires. Voir cette fonction pour plus d'informations.
*
* @property EventTarget.prototype.allowedEvents_
* @private
*/
EventTarget.prototype.allowedEvents_ = {} ;
/**
* Ajoute un "écouteur d'événement" à une instance d'une "cible d'événement". Un "auditeur d'événements" est un élément
* qui sera appelée lorsqu'un événement portant un certain nom sera déclenché.
*
* @param {string|string[]} type
* Un nom d'événement ou un tableau de noms d'événements.
*
* @param {EventTarget~EventListener} fn
* La fonction à appeler avec `EventTarget`s
*/
EventTarget.prototype.on = function(type, fn) {
// Supprimer l'alias addEventListener avant d'appeler Events.on
// pour ne pas entrer dans une boucle de type infini
const ael = this.addEventListener ;
this.addEventListener = () => {} ;
Events.on(this, type, fn) ;
this.addEventListener = ael ;
};
/**
* Un alias de {@link EventTarget#on}. Permet à `EventTarget` d'imiter
* l'API DOM standard.
*
* @fonction
* @see {@link EventTarget#on}
*/
EventTarget.prototype.addEventListener = EventTarget.prototype.on ;
/**
* Supprime un `écoute d'événement` pour un événement spécifique d'une instance de `EventTarget`.
* Ainsi, le `event listener` ne sera plus appelé lorsque la fonction
* l'événement nommé se produit.
*
* @param {string|string[]} type
* Un nom d'événement ou un tableau de noms d'événements.
*
* @param {EventTarget~EventListener} fn
* La fonction à supprimer.
*/
EventTarget.prototype.off = function(type, fn) {
Events.off(this, type, fn) ;
};
/**
* Un alias de {@link EventTarget#off}. Permet à `EventTarget` d'imiter
* l'API DOM standard.
*
* @fonction
* @see {@link EventTarget#off}
*/
EventTarget.prototype.removeEventListener = EventTarget.prototype.off ;
/**
* Cette fonction ajoutera un `event listener` qui ne sera déclenché qu'une seule fois. Après la
* le premier déclencheur, il sera supprimé. Cela revient à ajouter un "écouteur d'événements" (`event listener`)
* avec {@link EventTarget#on} qui appelle {@link EventTarget#off} sur lui-même.
*
* @param {string|string[]} type
* Un nom d'événement ou un tableau de noms d'événements.
*
* @param {EventTarget~EventListener} fn
* La fonction à appeler une fois pour chaque nom d'événement.
*/
EventTarget.prototype.one = function(type, fn) {
// Suppression de l'alias addEventListener Events.on
// pour ne pas entrer dans une boucle de type infini
const ael = this.addEventListener ;
this.addEventListener = () => {} ;
Events.one(this, type, fn) ;
this.addEventListener = ael ;
};
EventTarget.prototype.any = function(type, fn) {
// Suppression de l'alias addEventListener Events.on
// pour ne pas entrer dans une boucle de type infini
const ael = this.addEventListener ;
this.addEventListener = () => {} ;
Events.any(this, type, fn) ;
this.addEventListener = ael ;
};
/**
* Cette fonction provoque un événement. Cela aura pour conséquence que tous les `écoutants d'événements`
* qui attendent cet événement pour être appelés. S'il n'y a pas d'"auditeurs d'événements", il n'y a pas d'"auditeurs d'événements"
* pour un événement, il ne se passera rien.
*
* Si le nom de l'événement déclenché se trouve dans `EventTarget.allowedEvents_`.
* Le déclencheur appellera également la fonction `on` + `uppercaseEventName`.
*
* Exemple:
* 'click' est dans `EventTarget.allowedEvents_`, donc le trigger va essayer d'appeler
* `onClick` s'il existe.
*
* @param {string|EventTarget~Event|Object} event
* Le nom de l'événement, un `Event`, ou un objet dont la clé est de type
* un nom d'événement.
*/
EventTarget.prototype.trigger = function(event) {
const type = event.type || event ;
// dépréciation
// Dans une prochaine version, nous devrions définir la cible par défaut comme étant `this`
// similaire à la façon dont nous avons fixé par défaut la cible à `elem` en
// `Events.trigger`. Pour l'instant, la "cible" par défaut est
// `document` à cause de l'appel `Event.fixEvent`.
if (typeof event === 'string') {
événement = {type} ;
}
event = Events.fixEvent(event) ;
if (this.allowedEvents_[type] && this['on' + type]) {
this['on' + type](event) ;
}
Events.trigger(this, event) ;
};
/**
* Un alias de {@link EventTarget#trigger}. Permet à `EventTarget` d'imiter
* l'API DOM standard.
*
* @fonction
* @see {@link EventTarget#trigger}
*/
EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger ;
let EVENT_MAP ;
EventTarget.prototype.queueTrigger = function(event) {
// ne mettre en place EVENT_MAP que si elle sera utilisée
if (!EVENT_MAP) {
EVENT_MAP = nouvelle Map() ;
}
const type = event.type || event ;
let map = EVENT_MAP.get(this) ;
if (!map) {
map = nouvelle Map() ;
EVENT_MAP.set(this, map) ;
}
const oldTimeout = map.get(type) ;
map.delete(type) ;
window.clearTimeout(oldTimeout) ;
const timeout = window.setTimeout(() => {
map.delete(type) ;
// si nous avons supprimé tous les délais d'attente pour la cible actuelle, supprimer sa carte
if (map.size === 0) {
map = null ;
EVENT_MAP.delete(this) ;
}
this.trigger(event) ;
}, 0) ;
map.set(type, timeout) ;
};
export default EventTarget ;