CM3: Exécuter du code dans une page Web (côté client)
Du code sur le navigateur ?
HTML permet de définir la structure de la page Web, et CSS son affichage.
Cependant, pour générer du contenu dynamique et gérer les interactions avec l'utilisateur, il est nécessaire de pouvoir exécuter du code arbitraire sur le navigateur.
Pour cela, le navigateur est capable d'interpréter nativement deux langages :
- le JavaScript ;
- le WebAssembly (une sorte d'assembleur pour navigateur).
Pour utiliser d'autres langages, il faut alors les convertir soit en JavaScript, soit en WebAssembly (e.g. avec emscriptem).
⚠ Contrairement à JavaScript, le WebAssembly n'a pas un accès direct au DOM.
Afin de vous éviter d'avoir à apprendre un nouveau langage, nous utiliserons Brython qui permet d'exécuter du code Python sur le navigateur. L'un des avantages de Brython est qu'il permet d'utiliser les API natives du navigateur.
Pour les VCOD, nous verrons par la suite TypeScript en Programmation Web (S4) et Programmation Web pour la Visualisation (S5). Vous apprendrez donc un nouveau langage, mais serez déjà familiarisés avec les API utilisées.
Exécuter du code dans la page Web
Dans l'en-tête de la page Web, permet d'exécuter du code sur la page :
💡 indique le type du script (ici un module JavaScript).
💡 permet d'indiquer quand exécuter le code inclus :
- par défaut : exécuter le script avant d'interpréter le reste de la page (à éviter).
- : continuer à interpréter la page Web pendant que le script se télécharge.
- : commencer à télécharger le script, mais attendre que la page ai finie de se charger avant de l'exécuter.
⚠ Il recommandé d'attendre que la page ai finie de se charger avant de la manipuler. En effet, si le code HTML n'a pas fini d'être interprété lors de l'exécution du script, certains éléments ne seront pas encore chargés dans le DOM.
La console de la page Web
Les erreurs survenant durant l'exécution du code sont affichées dans la console de la page Web. [TODO: ouvrir la console de la page Web]
La console gère plusieurs niveau d'affichages dont vous pouvez filtrer l'affichage :
- : par défaut.
- : affiche aussi la pile d'appel.
💡 Vous pouvez formatter l'affichage dans la console en préfixant le texte par %c, e.g.
💡 Vous pouvez manipuler la console via :
- : effacer la console.
- / : ouvre/ferme un groupe indenté qui peut être replié (*collapsed*) ou déplié.
💡 console offre d'autres fonctions d'affichages :
- : affiche une erreur si est faux.
- : afficher les propriétés d'un objet.
- : afficher le(s) object(s) sous la forme d'un tableau.
- : afficher la pile d'appel.
💡 Cette console permet aussi d'entrer du code JavaScript à exécuter grâce à une interface REPL (Read–eval–print loop).
Spécificités de Brython
L'API du navigateur est accessible à partir du module
En JavaScript, représente la "fenêtre" de la page Web actuelle. Il contient l'ensemble des variables/classes/fonctions globales de la page Web. En particulier, représente l'arbre DOM de la page Web.
💡 En JavaScript, est implicite, i.e. on peut écrire au lieu de . En Brython, il faudra obligatoirement expliciter .
⚠ Afin que les objets Python s'affichent correctement, il est important d'utiliser la version Brython de en l'important via :
⚠ Brython a aussi du mal à distinguer les classes des fonctions JavaScripts. Pour instancier une nouvelle classe il faudra ainsi écrire
Manipulation du DOM
Sélecteurs
Pour interagir avec le DOM, il convient de sélectionner les éléments HTML sur lesquels on souhaite interagir.
Pour cela on utilise qui retourne le (ou les) descendants de correspondant au sélecteur :
💡 retourne le premier ancêtre correspondant au sélecteur .
💡 retourne un booléen indiquant si l'élément correspond au sélecteur .
💡 Dans un sélecteur, permet de désigner l'élément à partir du lequel on appelle ces fonctions.
💡 Vous pouvez utiliser ces fonctions sur , mais aussi sur :
- : la racine ().
💡 Il existe aussi les sélecteurs XPath, plus puissants, mais plus complexes.
Créer un élément HTML
Vous pouvez aussi créer un élément HTML via :
- : duplique l'élément .
- : créé un élément de nom .
Manipuler les attributs et classes
En premier lieu il est possible de modifier les attributs et les classes d'un élément.
| Opération | Classes | Attributs |
|---|---|---|
| Lister | ||
| Obtenir | ||
| Ajouter | ||
| Supprimer | ||
| Basculer | ||
| Contient ? |
💡 permet d'obtenir le nom de la balise HTML.
Manipuler les attributs data-*
Les attributs sont un peu particuliers. En effet, ils peuvent être manipulés via , mais aussi via qui se manipule comme un dictionnaire associatif. Chaque clé de correspondant à un attribut de l'élément HTML.
Ils sont utilisés afin que les développeurs puissent définir des attributs personnalisés, sans entrer en conflit avec les attributs définis par HTML, e.g. .
Manipuler le style
La modification de l'affichage d'un élément se fait usuellement via la modification de ses classes. Il est cependant tout à fait possible de manipuler directement ses propriétés CSS. Dans ce cas de figure, on préfère généralement se limiter à la manipulation des propriétés personnalisées.
Les styles peuvent être accédés de deux manières distinctes :
- : les styles **déclarés** sur ;
- : les styles **calculés** appliqués sur (en lecture seule).
Il est alors possible d'accéder ou de modifier les propriétés CSS via :
| Opération | |
|---|---|
| Obtenir | |
| Définir | |
| Supprimer |
⚠ Pour obtenir des positions d'éléments aisément comparables, utilisez qui retourne la position relativement à la zone d'affichage (i.e. la fenêtre).
Manipuler le contenu
Il existe beaucoup de manière d'obtenir un (ou des) élément(s) à partir d'un autre. Nous verrons les plus génériques :
- : le père d'un élément.
- : la liste des éléments fils de l'élément.
- : idem, mais inclus aussi les contenus autres, e.g. textes.
Il existe beaucoup de manière de modifier le contenu d'un élément. Nous verrons les plus génériques :
- permet d'accéder et de définir le contenu **textuel** d'un élément.
- : ajouter les noeud (éléments ou textes).
- : retirer l'élément de son père.
- : remplace l'élément par dans son père.
- : remplace le contenu de l'élément par .
⚠ Un élément ne peut être qu'à un seul endroit à la fois. L'insérer dans un élément le retirera donc de son précédent père.
Événements
-> [zone d'affichage, comparer des positions sinon infernal] -> .scroll()
Principe
-> boucle d'events & promesses async (différent en Python) (corrigé avec SBrython)
-> trigger
Types d'évents
TODO...
Delegated events
Debounce/Throttle/setAnimationFrameRequest/hoist.
- remove from DOM ?
Signaux
Formulaires et manipulation d'URL
- validation (cf CM? avec regex & zod)
- History/location.
Autres éléments d'intéractions
-> attrs -> dialog/popover/details-summary -> CSS aussi... / caroussel / etc.
Autres choses possibles
- manipuler le shadowRoot (cf PW?)
- calculs webworker/serviceworker/worklet cf PWV
- stocker côté client (...) cf PWV
- dessiner canvas et animations (...) cf ?
- iframe cf CM4?
- fetch/download CM4?
- générateur cf PWV (infini ou très gros)
- components real API (cf PW)
-> profile/profileEnd() => profiler (perfs) -> CM5 ?
⚠ Pour des raisons de sécurité, ce code arbitraire n'a pas accès aux ressources locales (i.e. sur votre poste de travail), ou aux autres page Web. Imaginez en effet qu'à la simple visite d'une page Web, votre navigateur se mette à exécuter un code supprimant tous vos fichiers !
old
- cf README.md
-> order of deps => dev log.
@starting-style for animations
:(user-)valid :(user-)invalid :has()
document. body/head. rootElement.
activeElement (qui a le focus). documentElement
Événements
Bubble vs Capture
Dans les navigateurs, les interactions sont gérées via des événements, qui se composent de 2 phases :capture
bubble
bubble: true), le navigateur va transmettre l'événement à l'élément père de l'élément courant, puis recommence de manière récursive, l'élément père devant l'élément courant.
Écouter un événement
// [JS] Javascript
function handler(ev) {
// ev.currentTarget : $ELEM
// ev.target : source of the event.
// ev.type : $EVENT_TYPE
// ev.detail : $EVENT_DATA (si CustomEvent, cf plus bas)
// ev.preventDefault() : annuler l'action par défaut du navigateur
// e.g. lorsqu'on clique sur un lien, empêcher d'aller vers la page.
// ev.stopImmediatePropagation() : ne pas appeler les autres
// handleurs pour cet événement.
}
$ELEM.addEventListener($EVENT_TYPE, handler);
// ou
$ELEM.addEventListener($EVENT_TYPE, ev => ... );
# [🐍] Python
def handler(ev):
# ev.currentTarget : $ELEM
# ev.target : source of the event.
# ev.type : $EVENT_TYPE
# ev.detail : $EVENT_DATA (si CustomEvent, cf plus bas)
# ev.preventDefault() : annuler l'action par défaut du navigateur
# e.g. lorsqu'on clique sur un lien, empêcher d'aller vers la page.
# ev.stopImmediatePropagation() : ne pas appeler les autres
# handleurs pour cet événement.
$ELEM.addEventListener($EVENT_TYPE, handler)
📖 Plus d'informations dans la documentation.
Écouteur délégué
Il arrive qu'on souhaite écouter des événements sur les descendants d'un élément, qui peuvent être ajoutés, déplacés, supprimés, etc. Le problème est que cela nécessiterait de créer un écouteur pour chaque éléments et de les supprimer/ajouter à chaque modifications du DOM.
Heureusement, il est possible d'utiliser un écouteur délégué, i.e. d'écouter l'événement sur le descendant (nécessite que l'événement soit bubble, i.e. se propage dans le DOM).
// [JS] Javascript
function handler(ev) {
if( ev.target.matches($CSS_SELECTOR) ) {
// ...
}
// or
let target;
if( target = ev.target.closest($CSS_SELECTOR) ) {
// ...
}
}
$ELEM.addEventListener($EVENT_TYPE, handler);
# [🐍] Python
def handler(ev):
if ev.target.matches($CSS_SELECTOR):
# ...
# or
if target := ev.target.closest($CSS_SELECTOR):
# ...
$ELEM.addEventListener($EVENT_TYPE, handler)
Créer un événement
// [JS] Javascript
$ELEM.dispatchEvent( new Event($EVENT_TYPE) );
// ou
$ELEM.dispatchEvent( new CustomEvent($EVENT_TYPE, {detail: $EVENT_DATA}) );
# [🐍] Python
$ELEM.dispatchEvent( Event.new($EVENT_TYPE) )
# ou
$ELEM.dispatchEvent( CustomEvent.new($EVENT_TYPE, {"detail": $EVENT_DATA}) )
💡 Vous pouvez aussi ajouter, au 2ème argument, l'option bubble: true pour faire en sorte que l'événement soit bubble, i.e. se propage dans le DOM (par default bubble: false).