Nous pouvons non seulement affecter des gestionnaires, mais également générer des événements à partir de JavaScript.
Les événements personnalisés peuvent être utilisés pour créer des âcomposants graphiquesâ. Par exemple, un élément racine de notre propre menu basé sur JS peut déclencher des événements indiquant ce qui se passe avec le menu: open (menu ouvert), select (un élément est sélectionné) et ainsi de suite. Un autre code peut écouter les événements et observer ce qui se passe avec le menu.
Nous pouvons générer non seulement des événements complètement nouveaux, que nous inventons pour nos propres besoins, mais aussi des événements intégrés, tels que click, mousedown, etc. Cela peut être utile pour les tests automatisés.
Constructeur dâévénements
Les classes dâévénements intégrées forment une hiérarchie, similaire aux classes dâéléments DOM. La racine est la classe intégrée Event.
Nous pouvons créer des objets Event comme ceci:
let event = new Event(type[, options]);
Arguments:
-
type â type dâévénement, soit une chaîne comme
"click"ou la nôtre comme"my-event". -
options â lâobjet avec deux propriétés facultatives:
bubbles: true/falseâ sitrue, alors lâévénement bouillonne.cancelable: true/falseâ sitrue, alors âlâaction par défautâ peut être empêchée. Plus tard, nous verrons ce que cela signifie pour les événements personnalisés.
Par défaut, les deux sont âfalseâ:
{bubbles: false, cancelable: false}.
dispatchEvent
Après la création dâun objet événement, nous devons le âlancerâ sur un élément en utilisant lâappel elem.dispatchEvent(event).
Ensuite, les gestionnaires réagissent comme sâil sâagissait dâun événement de navigateur normal. Si lâévénement a été créé avec le marqueur bubbles, alors il bouillonne.
Dans lâexemple ci-dessous lâévénement click est initié avec JavaScript. Le gestionnaire fonctionne de la même manière que si le bouton était cliqué:
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
Il existe un moyen de distinguer un événement utilisateur âréelâ dâun événement généré par script.
La propriété event.isTrusted est true pour les événements qui proviennent dâactions réelles de lâutilisateur et false pour les événements générés par un script.
Exemple de bouillonnement
Nous pouvons créer un événement qui bouillonne avec le nom "hello" et lâattraper sur document.
Tout ce dont nous avons besoin de faire est de définir bulles en tant que true:
<h1 id="elem">Hello from the script!</h1>
<script>
// attraper sur document...
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello de H1
});
// ...distribué sur elem!
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
// le gestionnaire sur le document activera et affichera le message.
</script>
Remarques:
- Nous devrions utiliser
addEventListenerpour nos événements personnalisés, caron<event>nâexiste que pour les événements intégrés,document.onhellone fonctionne pas. - Il est essentiel de définir
bubbles: true, sinon lâévénement ne bouillonnera pas.
Le mécanisme de bouillonnement est le même pour les événements intégrés (click) et personnalisés (hello). Il existe également des étapes de capture et de bouillonnement.
MouseEvent, KeyboardEvent et autres
Voici une courte liste de classes pour les événements UI de la spécification:
UIEventFocusEventMouseEventWheelEventKeyboardEvent- â¦
Nous devrions les utiliser au lieu de new Event si nous voulons créer de tels événements. Par exemple, new MouseEvent("click").
Le bon constructeur permet de spécifier des propriétés standard pour ce type dâévénement.
Comme clientX/clientY pour un événement de souris:
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
Remarque: le constructeur générique Event ne le permet pas.
Essayons:
let event = new Event("click", {
bubbles: true, // bouillonne uniquement et annulable
cancelable: true, // travail dans le constructeur de l'événement
clientX: 100,
clientY: 100
});
alert(event.clientX); // indéfini, la propriété inconnue est ignorée!
Techniquement, nous pouvons contourner cela en attribuant directement event.clientX=100 après la création. Câest donc une question de commodité et de respect des règles. Les événements générés par le navigateur ont toujours le bon type.
La liste complète des propriétés des différents événements UI se trouve dans la spécification, par exemple, MouseEvent.
Ãvénements personnalisés
Pour nos propres types dâévénements comme "hello", nous devrions utiliser new CustomEvent. Techniquement, CustomEvent est identique à Event, à une exception près.
Dans le deuxième argument (objet), nous pouvons ajouter une propriété supplémentaire detail pour toute information personnalisée que nous voulons transmettre avec lâévénement.
Par exemple:
<h1 id="elem">Hello for John!</h1>
<script>
// des détails supplémentaires sont fournis avec l'événement au gestionnaire
elem.addEventListener("hello", function(event) {
alert(event.detail.name);
});
elem.dispatchEvent(new CustomEvent("hello", {
detail: { name: "John" }
}));
</script>
La propriété detail peut avoir nâimporte quelle donnée. Techniquement, nous pourrions vivre sans, car nous pouvons attribuer nâimporte quelle propriété à un objet new Event normal après sa création. Mais CustomEvent fournit le champ spécial detail pour éviter les conflits avec dâautres propriétés dâévénement.
De plus, la classe event décrit âquel genre dâévénementâ il sâagit, et si lâévénement est personnalisé, alors nous devrions utiliser CustomEvent juste pour être clair sur ce que câest.
event.preventDefault()
De nombreux événements de navigateur ont une âaction par défautâ, telle que la navigation vers un lien, le démarrage dâune sélection, etc.
Pour les nouveaux événements personnalisés, il nây a certainement pas dâactions de navigateur par défaut, mais un code qui distribue un tel événement peut avoir ses propres plans pour ce quâil faut faire après le déclenchement de lâévénement.
En appelant event.preventDefault(), un gestionnaire dâévénements peut envoyer un signal indiquant que ces actions doivent être annulées.
Dans ce cas, lâappel à elem.dispatchEvent(event) retourne false. Et le code qui lâa envoyé sait quâil ne devrait pas continuer.
Voyons un exemple pratique â un lapin qui se cache (peut être un menu de clôture ou autre chose).
Ci-dessous, vous pouvez voir une fonction #rabbit et hide() qui la distribue lâévénement "hide", pour faire savoir à toutes les parties intéressées que le lapin va se cacher.
Nâimporte quel gestionnaire peut écouter cet événement avec rabbit.addEventListener('hide',...) et, si nécessaire, annuler lâaction en utilisant event.preventDefault(). Alors le lapin ne disparaîtra pas:
<pre id="rabbit">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<button onclick="hide()">Hide()</button>
<script>
function hide() {
let event = new CustomEvent("hide", {
cancelable: true // sans ce marqueur, preventDefault ne fonctionne pas
});
if (!rabbit.dispatchEvent(event)) {
alert('The action was prevented by a handler');
} else {
rabbit.hidden = true;
}
}
rabbit.addEventListener('hide', function(event) {
if (confirm("Call preventDefault?")) {
event.preventDefault();
}
});
</script>
Remarque: lâévénement doit avoir le marqueur cancelable: true, sinon lâappel event.preventDefault() est ignoré.
Les événements imbriqués sont synchrones
Les événements sont généralement traités dans une file dâattente. Câest-à -dire : si le navigateur traite onclick et quâun nouvel événement se produit, par exemple la souris a bougé, sa gestion est mise en file dâattente, les gestionnaires mousemove correspondants seront appelés après la fin du traitement onclick.
Lâexception notable est lorsquâun événement est déclenché à partir dâun autre, par exemple en utilisant dispatchEvent. Ces événements sont traités immédiatement: les nouveaux gestionnaires dâévénements sont appelés, puis la gestion des événements en cours est reprise.
Par exemple, dans le code ci-dessous, lâévénement menu-open est déclenché pendant onclick.
Il est traité immédiatement, sans attendre la fin du gestionnaire onclick :
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// se déclenche entre 1 et 2
document.addEventListener('menu-open', () => alert('nested'));
</script>
Lâordre de sortie est: 1 â imbriqué â 2.
Veuillez noter que lâévénement imbriqué menu-open est intercepté sur le document. La propagation et la gestion de lâévénement imbriqué sont terminées avant que le traitement ne revienne au code externe (onclick).
Il ne sâagit pas seulement de dispatchEvent, il y a dâautres cas. Si un gestionnaire dâévénements appelle des méthodes qui déclenchent dâautres événements â ils sont également traités de manière synchrone, de manière imbriquée.
Disons que nous nâaimons pas ça. Nous voudrions que onclick soit entièrement traité en premier, indépendamment de menu-open ou de tout autre événement imbriqué.
Alors, nous pouvons soit mettre le dispatchEvent (ou un autre appel déclencheur dâévénement) à la fin de onclick ou, peut-être mieux, lâenvelopper dans setTimeout:
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
document.addEventListener('menu-open', () => alert('nested'));
</script>
Désormais, dispatchEvent sâexécute de manière asynchrone une fois lâexécution du code en cours terminée, y compris mouse.onclick, les gestionnaires dâévénements sont donc totalement séparés.
Lâordre de sortie devient: 1 â 2 â imbriqué.
Résumé
Pour générer un événement à partir du code, nous devons dâabord créer un objet événement.
Le constructeur générique Event(name, options) accepte un nom dâévénement arbitraire et lâobjet options avec deux propriétés:
bubbles: truesi lâévénement doit bouillonner.cancelable: truesievent.preventDefault()doit fonctionner.
Dâautres constructeurs dâévénements natifs tels que MouseEvent, KeyboardEvent et ainsi de suite acceptent des propriétés spécifiques à ce type dâévénement. Par exemple, clientX pour les événements de souris.
Pour les événements personnalisés, nous devons utiliser le constructeur CustomEvent. Il a une option supplémentaire nommée detail, nous devons lui attribuer les données spécifiques à lâévénement. Ensuite, tous les gestionnaires peuvent y accéder en tant que event.detail.
Malgré la possibilité technique de générer des événements de navigateur comme click ou keydown, nous devons les utiliser avec le plus grand soin.
Nous ne devrions pas générer dâévénements de navigateur car câest une manière pirate dâexécuter des gestionnaires. Câest une mauvaise architecture la plupart du temps.
Des événements natifs peuvent être générés:
- En tant que bidouille pour faire fonctionner les bibliothèques tierces de la manière nécessaire, si elles ne fournissent pas dâautres moyens dâinteraction.
- Pour les tests automatisés, pour âcliquer sur le boutonâ dans le script et voir si lâinterface réagit correctement.
Les événements personnalisés avec nos propres noms sont souvent générés à des fins architecturales, pour signaler ce qui se passe dans nos menus, curseurs, carrousels, etc.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)