JavaScript pour Pro

Jour 3

Les événements

Un événement - quelque chose qui arrive - est traduit en JavaScript par une action qui peut appeler une fonction. Ajouter un événement à une balise est possible.

La plupart des événements sont des actions de l'utilisateur (via la souris ou clavier), d'autres non (fin du chargement de la page, d'une lecture audio/video,  ...)

Principaux événements

De nombreux événements existent, notamment ceux spécifiques à certains appareils tactiles (tablette, smartphone)

Le principal événement est click.

Les balises pouvant être ciblées sont celles qui peuvent réagir à un événement du clavier : input, a (via la touche "Enter"). Via la touche de tabulation, on passe à la balise-cible suivante.

* Certains événements JS n'ont pas de sens sur un smartphone. Par exemple, mouseover et mousemove ne réagissent pas car le mouvement du doigt sur l'écran fait défiler la page, dblclick ne se déclenche pas ... tapoter deux fois déclenche en général un zoom.

hover n'est pas un événement JS. Il n'appartient pas à l'interface MouseEvent. Cet événement est la combinaison de deux événements :

mouseover et mouseout sont souvent préférables à mouseenter et mouseleave qui envoyent un message à tous les éléments de sa hiérarchie. (One mouseenter event is sent to each element of the hierarchy when entering them) Plus d'info

Événement tactile

Utilisation des événements

sans DOM

Une balise peut être liée à un (ou plusieurs) événement(s).

<p onclick="alert('Vous avez cliqué !')">Cliquez-moi !</p>

Dans le code JS de la fonction traitant l'événement, le mot-clé this représente la balise liée à cet événement.

<p onclick="alert(this.innerHTML)">Cliquez encore !</p>

Pratique à proscrire

Ne pas utiliser un lien pour provoquer une action autre qu'afficher une page (ou une section interne) ou télécharger un fichier.

Utilisé avec l'événement submit, return false; sert juste à bloquer l'action par défaut de l'événement. Tel que l'envoi des données, l'appel d'une page web, ...

<a href="#" onclick="alert('Vous avez cliqué !'); return false;">Cliquez-moi ! (v2)</a>

return false; car le dièse renvoie vers le haut de la page, ce qui est un effet non désiré.

Certains événements appliqués à une balise-parent peuvent se transmettre aux balises-enfants, c'est le cas des événements : mouseover, mouseout et mousemove.

DOM-0

Ici, l'événement n'est pas géré via un attribut onNomEvénement d'une balise. Mais, par la propriété .onNomEvénement d'un élément (récupéré par une méthode telle que document.getElementById()). Et, à cette propriété, on lui affecte une fonction anonyme.

Code HTML :

<p id="viaDOM_0">Cliquez 3 !</p>

Code JS :

  var cliquez3=document.getElementById("viaDOM_0");

  cliquez3.onclick=function(evt){
    alert("via DOM-0 et une fonction anonyme");
    /* L'objet evt (dont les propriétés sont adaptées à l'événement)
    n'est pas utilisé ici */
  }

Pourquoi faire simple, lorsqu'on peut faire compliqué !?
Re : Avec DOM-0, on peut avoir accès à l'objet Event  ...

le DOM-2 apporte un réel avantage  : lier différents événements à un même élément.

DOM-2

Le DOM Level 2, publié en 2000, a introduit l'objet Event et la méthode .addEventListener().

.addEventListener()

Code applicable :

var copyables = document.querySelectorAll(".copyable");
for (var item of copyables) {
  item.addEventListener('click', function(){
    navigator.clipboard.writeText(item.textContent); //  alert("Copié !");
  });
}

Code HTML :

<p id="viaDOM_2">Cliquez encore, encore et encore !</p>

Code JS :

/*
var cliquez4=document.getElementById("viaDOM_2");

cliquez4.addEventListener('click', function(){
  alert("via DOM-2");
});

Et, plus court ci-dessous */

document.getElementById("viaDOM_2").addEventListener('click', function(){
  alert("via DOM-2");
});

A priori, c'est encore plus "tordu" que DOM-0 !


Ce qui change par rapport à DOM-0, c'est que ce n'est plus une propriété qui est utilisé, mais la méthode .addEventListener() qui prend jusqu'à trois paramètres. Le premier, le nom de l'événement (sans "on"). Le second est souvent un appel de fonction anonyme ou, parfois, un appel à une fonction classique préalablement définie dans le code.

Le paramètre de cette fonction classique est passé par référence. Si plusieurs écouteurs sont créés dans une boucle for et si le paramètre est le compteur i, toutes les fonctions appelées par un des écouteurs auront le même paramètre dont la valeur sera i+1. La solution consiste à passer le paramètre par valeur, (par exemple, via une variable déclarée par le mot clé let)
Voir : addEventListeners_boucle.htm

Le troisième paramètre sera vu plus tard.

var cliquez4=document.getElementById("viaDOM_2");

/*
cliquez4.addEventListener('click', function(){
  alert("via DOM-2");
});

Ci-dessous, code équivalent */

var myFonction=function(){
  alert("via DOM-2");
}

cliquez4.addEventListener('click', myFonction);

De plus en plus "tordu" !


Avec le DOM-2, la création multiple d'événements (identiques ou non) pour un même élément est possible.

C'est fou ! Et, à quoi cela peut-il bien servir ?

Code HTML :

<button id="numero5">Clic n°5 !</button>

Code JS :

  var clic5=document.getElementById("numero5");

  clic5.addEventListener('click', function(){
    alert("clic n°5 AAAA");
  });

  clic5.addEventListener('mouseenter', function(){
    alert("clic n°5 BBBBBBBB");
  });

Le nom de l'événement est toujours entièrement en minuscules.
L'ordre des événements peut rendre impossible l'action définie pour un autre événement ...


La suppression des événements se fait avec la méthode .removeEventListener() en lui passant les mêmes paramètres [que la méthode .addEventListener()].

Code HTML :

<button id="clic6">Clic n°6 !</button>

Code JS :

  var clic6=document.getElementById("clic6");

  var myFonction6=function(){
    alert("clic n°6");
  }

  clic6.addEventListener('click', myFonction6);

  clic6.removeEventListener('click', myFonction6);

(le clic n'affiche pas de message d'alerte)


Avec la méthode .addEventListener() (et donc avec DOM-2), il est possible d'indiquer le sens du déclenchement (= troisième paramètre)

La capture (3ème paramètre = true) traite, pour le même événement, celui des balises supérieures puis celui des balises inférieures. Le bouillonnement (3ème paramètre = false), défini par défaut, traite, pour le même événement celui des balises inférieures puis celui des balises supérieures.

https://www.w3.org/TR/DOM-Level-3-Events/#event-flow

Moyen mnémotechnique : Lorsque cela bouillonne, cela remonte.

Event

L'objet Event est une mine d'informations sur l'événement déclenché : touches actuellement enfoncées, coordonnées du curseur, élément qui a déclenché l'événement, ...

Code HTML :

<button id="clic7">Clic n°7 !</button>

Code JS :

  var clic7=document.getElementById("clic7");

  clic7.addEventListener('click', function(e){
    alert(e.target.innerHTML);
  });

Ici, le paramètre de la fonction anonyme est e. C'est une convention. Mais, cela peut être n'importe autre nom : param1, evenement, ...

La propriété .target, appliquée à l'objet e (de la classe Event), pointe sur la balise liée à cet événement.

(affiche le texte du bouton)

Position de la souris

.clientX = propriété, appliquée à l'objet e, pour connaître la position horizontale de la souris (depuis le bord gauche de la fenêtre) et .clientY pour la position verticale (depuis le bord supérieur de la fenêtre).

Touche tapée

Les événements keydown et keyup retourneront un caractère majuscule, que la touche Maj soit pressée ou non.

L'événement keypress sert à capter les caractère uniquement (donc pas les touches CTRL, MAJ, ...). Dès lors, il tient compte des caractères majuscules et minuscules

La propriété keyCode, appliquée à l'objet e, retourne un code ASCII

  document.addEventListener('keypress', function(e) {
    press.innerHTML = e.keyCode;
  });

La méthode String.fromCharCode() retourne le caractère correspondant [au code ASCII passé en paramètre].

.preventDefault()

Le DOM-2 ajoute la méthode .preventDefault(), appliquée à l'objet (généralement nommé "e"; ici, nommé "toto") bloque l'action par défaut (comme return false;)

Champ qui n'accepte pas le coller :

document.getElementById('no-paste').addEventListener('keydown',function (toto){toto.preventDefault()});

Champ qui n'accepte pas le coller et informe l'utilisateur que le collage est interdit  :

document.getElementById('no-pasteWithMsg').addEventListener('keydown', function (x){
  if (x.keyCode == 86){ // 86 = code de la lettre "v"
    alert("Coller dans ce champ est interdit");
    x.preventDefault()
  }
});

Champ qui accepte le coller mais bloque le bouton du formulaire (sans informer que le collage est interdit) :

document.getElementById('no-pasteBtnGray').addEventListener('keydown', function (y){
  if (y.keyCode == 86){ // 86 = code de la lettre "v"
    document.getElementById('btnGray').disabled=true
  }
});

.relatedTarget

Parfois, un événement [de la souris] appliqué sur un parent se propage à ses enfants et provoque des comportements inattendus. Heureusement, des solutions existent. Elles utilisent la propriété .relatedTarget appliquée à l'objet e.

Plus d'info : developpez.com/tutoriels/javascript/dom/

Les tableaux dynamiques

Il est possible de créer un tableau (régulier) dynamiquement. Il convient alors de déterminer le nombre de colonnes; puis, d'ajouter le nombre de lignes voulues.

Exemple : Déterminer la part de chacun à une table de restaurant.

De plus, dans chaque cellule est créé un écouteur d'événement. En l'occurrence, un clic permute le 0 en 1 et le 1 en 0.

Les formulaires

La propriété utilisé d'un élément correspondant à la balise <input> est value. Elle correspond à la valeur du champ. Cette propriété s'applique aussi à la balise <textarea>

document.getElementById("texte1").value="Texte ajouté via JS";

Les trois propriétés - .disabled, .checked et .readonly - retourne un booléen.

Dans le cas de boutons radio, il faut utiliser une boucle for pour vérifier celui qui est coché : if (radios[i].checked==true) ...

Lorsqu'un bouton-radio est coché, sa propriété .checked vaut true.
=> on peut écrire : if (radios[i].checked) ...

document.querySelectorAll('input[type=radio]:checked') permet de retourner le bouton-radio coché.

Liste déroulante

Dans une liste déroulante, la propriété .options, appliquée à l'élément correspondant à la balise <select>, retourne un tableau de toutes les options.

Dans une liste déroulante, la propriété .selectedIndex, appliquée à l'élément correspondant à la balise <select>, retourne l'index de l'option sélectionnée.

la propriété .selectedIndex retourne l'index du premier élément sélectionné, si le <select multiple="multiple">.

tab.options[tab.selectedIndex].innerHTML

L'élément correspond à la balise <form> possède deux méthodes intéressantes. La première, .submit(), permet d'effectuer l'envoi d'un formulaire sans l'intervention de l'utilisateur. La deuxième, .reset()

NB : La méthode .submit() ne déclenche pas l'événement submit

La méthode .select(), en plus de donner le focus à l'élément, sélectionne le texte. Cette méthode ne fonctionne que sur des champs de texte ou bien un <textarea>

L'événement change ne fonctionne pas toujours comme son nom le suggère. Il attend que l'élément auquel il est attaché perde le focus avant de se déclencher.

<datalist>

La liste déroulante (<select>) est élément tellement important qu'une balise lui a été jointe : <datalist>

Gestion du CSS

La propriété .style d'un élément (de type 1) permet de modifier l'apparence d'une balise

element.style.width = '150px';

Ne pas oublier d'indiquer l'unité de la valeur (sans espace). Les valeurs sont de type String.

En JavaScript, les tirets sont interdits dans les noms des propriétés. La conversion des noms de propriété CSS en JS se fait supprimant le tiret et chaque lettre suivant ce tiret est remplacé par sa majuscule. Ainsi, par exemple, background-color (en CSS) devient backgroundColor (en JS)

Attention. La lecture du style via la propriété .style retourne la valeur de l'attribut style de la balise. Et non le style appliqué finalement à la balise, après avoir tenu compte de tous les héritages CSS.

Pour obtenir le style final, il faut utiliser la méthode getComputedStyle() de l'objet window.

getComputedStyle()

Les valeurs obtenues par le biais de [window.]getComputedStyle() sont en lecture seule. (Logique !)

L'unique paramètre obligatoire de getComputedStyle() est un nœud de type 1. Et, la valeur de retour .style est un objet CSSStyleDeclaration; raison pour laquelle, elle n'est pas récupérée directement. Est récupéré la valeur d'une des propriétés de cette valeur de retour, tel que la couleur du texte.

  var balise = document.getElementById('myBalise');
  var couleurTexte = getComputedStyle(balise).color;

Les .offset*

Certaines valeurs de positionnement ou de taille des éléments ne pourront pas être obtenues de façon simple avec getComputedStyle().

Les propriétés - offsetWidth, offsetHeight, offsetLeft, offsetTop, offsetParent - sont des propriétés, en lecture seule, d'un nœud (de type 1 = correspondant à une balise), et donc pas liées au CSS.

Les valeurs contenues dans ces propriétés (à part offsetParent) sont exprimées en pixels et sont donc de type Number.

Rappel : Un élément HTML en positionnement absolu se placera tout en haut à gauche de la page, par-dessus tous les autres éléments, s'il n'est pas déjà lui-même placé dans un élément en positionnement absolu.