Aller au contenu

Client DOM - Vue.js

Le Document Object Model (DOM) est une interface de programmation pour les documents HTML et XML. Il fournit une représentation structurée du document et une manière d’accéder à cette structure. Cela permet notamment :

  • de parcourir cet arbre (cibler un élément, les enfants/parents, etc.)
  • de modifier la structure du document (ajouter, enlever, déplacer des éléments)
  • d’accéder au informations des nœuds (contenu, attribut, etc.)
  • d’ajouter des événements (interactions utilisateur)
  • de modifier les styles CSS d’un élément
  • etc.

Cela se fait grâce aux attributs et méthodes d’objets de différents éléments.

L’objet document#

L’objet global document est l’objet principal d’un document HTML. Il contient les méthodes de ciblage d’éléments :

  • document.getElementById(id) cible l'élément par son id
  • document.querySelector(selecteur) cible le premier élément qui correspond au sélecteur CSS
  • document.querySelectorAll(selecteur) cible tous les éléments qui correspondent au sélecteur CSS
// Cible l'élément avec l’id menu
let menu = document.getElementById('menu');

// Cible le premier paragraphe de la page
let texte = document.querySelector('p');

// Cible tous les liens de la div id=menu
let liens = document.querySelectorAll('#menu a');

Mais également une méthode de création d’objet DOM :

  • document.createElement('str') crée un nouveau noeud DOM (str est une chaîne de caractère représentant le type d'élément HTML souhaité)
// Crée une nouvelle <div> en mémoire (pas encore insérée dans la page)
let monElement = document.createElement('div');

Enfin, les éléments de base de la page peuvent être ciblés plus facilement :

  • document.documentElement est l'élément principal du document (html)
  • document.head est l'élément head
  • document.body est l'élément body

Liste complète des attributs et méthodes de document

Les éléments#

Voici quelques attributs et méthodes applicables aux éléments d’une page HTML, qu’ils soient créés en JS ou déjà présents dans la page :

Attributs#

  • monElement.id retourne (ou définit) l’identifiant d’un élément
  • monElement.classList est une interface pour ajouter/modifier/connaitre les classes sur un élément :
    • monElement.classList.add('maClasse')
    • monElement.classList.remove('maClasse')
    • monElement.classList.toggle('maClasse')
    • monElement.classList.contains('maClasse')
  • monElement.setAttribute(attr, value) définit la valeur de l’attribut spécifié
  • monElement.removeAttribute(attr) supprime l’attribut spécifié
  • monElement.getAttribute(attr) retourne la valeur de l’attribut spécifié

Parcours DOM#

  • monElement.children retourne tous les enfants d’un élément (un tableau d’éléments)
  • monElement.parentNode retourne l’élément parent d’un élément

Modification DOM#

  • monElement.innerText retourne (ou définit) le contenu textuel
  • monElement.innerHTML retourne (ou définit) le contenu HTML (attention à la sécurité)
  • monElement.value retourne la valeur d’un champ de formulaire (<textarea>, <input>, etc.)
// récupère le contenu texte
let texte = monElement.innerText;

// définit un nouveau contenu
monElement.innerText = 'Nouveau contenu';
monElement.innerHTML = '<p>Hello</p>';
  • monElement.appendChild(element) ajoute un nœud enfant à la fin
  • monElement.removeChild(element) supprime un nœud
  • monElement.insertBefore(elementAInserer, elementCible) insère le nœud a insérer juste avant l’élément cible, dans le nœud courant

Mise en forme#

  • monElement.style est l’objet pour modifier les styles CSS
// changer le couleur d’un élément
monElement.style.color = 'red';

Liste complète des attributs et méthodes des éléments

Exercice - Manipulations simples du DOM#

  • dans une page HTML (vous pouvez utiliser la page today.php)

    • créez un paragraphe contenant le texte "00:00:00"
  • au chargement, en JS

    • générez l’heure au format HH:MM:SS (avec new Date().toLocaleTimeString('fr'))
    • modifiez le texte du paragraphe avec l’heure
  • faites en sorte que l’heure se mette à jour automatiquement toutes les secondes (avec setTimeout())

  • affichez l’heure en rouge et en gras (en CSS) si les secondes sont multiples de 10 (15:52:00, 15:52:10, etc.)

Les évènements#

Les évènements permettent notamment d’interagir avec l’internaute. Il est possible d’effectuer des actions, comme :

  • récupérer le mouvement de la souris, ou le click
  • intercepter les touches du clavier
  • détecter la validation d’un formulaire
  • etc.

Pour cela, il nous faut «écouter» l’évènement de notre choix :

  • monElement.addEventListener(type, listener) ajoute un gestionnaire d’évènements de type type. La fonction listener sera exécutée dès que l’évènement se déclenchera (l’évènement peut être passé en paramètre).

Voici quelques types d’évènements :

  • click, mousedown, mouseup, mousemove, mouseover, mouseout, load, submit, scroll ou wheel, keydown, keyup, keypress, input, change

Affiche en console lors du click sur un élément

let monElement = document.getElementById("toto"); // cible #toto
monElement.addEventListener("click", function(){
    console.log("L'événement click a été déclenché");
});

Exercice - Manipulations simples du DOM (suite)#

  • créez un bouton HTML (balise <button>Switch timezone</button>)
  • ajoutez un événement, et lors du click :
    • récupérez une timezone aléatoire avec Intl.supportedValuesOf('timeZone')
      • vous aurez encore besoin de Math.random()
    • utilisez l’option timeZone pour pour générer l’heure dans cette timezone (new Date().toLocaleTimeString('fr', { timeZone: '...' })
    • affichez la timezone dans la page (dans un autre paragraphe par exemple)
  • vous pouvez également utiliser la même fonction au chargement et récupérer la timezone du client (Intl.DateTimeFormat().resolvedOptions().timeZone)

L’objet event#

L’objet event passé automatiquement à la fonction de callback a également ses propres attributs et méthodes

  • event.target est l’élément cible sur lequel l’évènement a eu lieu
  • event.type est le nom de l’évènement
  • event.preventDefault() empêche la propagation de l’évènement. Utile pour arrêter l’action par défaut du navigateur par exemple.

Récupère les coordonnées du click

let monElement = document.getElementById("toto"); // cible #toto
monElement.addEventListener("click", function(event){
  console.log(event.clientX, event.clientY);
});

Autres considérations#

Un évènement bien pratique est DOMContentLoaded sur l’objet window. En effet, cet évènement est déclenché au chargement complet du DOM. Il est ainsi possible d’exécuter du code JS après la création complète de l’arbre, et ainsi appeler ses fichiers JS depuis le <head> du document HTML.

Détection du chargement complet du DOM

window.addEventListener("DOMContentLoaded", function(){
  // DOM chargé
});

L’évènement load permet de détecter la fin complète du chargement de la page (après exécution des scripts potentiellement asynchrones)

Liste complète des évènements

Attention, dans le cas d’une fonction nommée, listener doit être une référence à la fonction, et pas un appel à cette fonction.

function onClick () {
  console.log('Click');
}

// OK
monElement.addEventListener('click', onClick)

// Pas OK: la fonction sera appelée avant même le click
monElement.addEventListener('click', onClick())

Pour passer des paramètres à cette fonction, il n’est donc pas possible de faire

// NOK
monElement.addEventListener('click', onClick(param))

Dans ce cas, deux solutions :

// fonction anonyme qui appelle notre fonction
monElement.addEventListener('click', function() { onClick(param) })

// utilisation de bind (changement de contexte)
monElement.addEventListener('click', onClick.bind(null, param))

Vue.js#

Vue.js est un framework qui permet d’accélerer le développement, tout en structurant le code client.

Voir le tutoriel détaillé dans la partie JavaScript/Outils

Exercice - Tweetbox Twitter X#

Le but de cet exercice est de créer le champ texte sur le principe de celui utilisé par Twitter. C’est à dire, un simple champ texte, limité à un nombre défini de caractères, un bouton de validation et un bouton d’ajout de photo qui limite encore plus le nombre de caractères.

Coté HTML (voir code plus bas) :

  • créez un champ texte pour écrire le message
  • affichez la limite de caractères max dans un paragraphe (10 pour vos tests)
  • créez un bouton de validation
  • chargez la librairie Vue

En JS :

  • créez une nouvelle instance de Vue
    • utilisez un élément parent pour binder l’application
    • stockez les données globales dans data (le texte, le nombre de caractères max)
    • utilisez max dans votre HTML
  • créez une propriété calculée nombreRestants qui renvoie le nombre de caractères restants et utilisez cette valeur pour l’affichage (à la place de max)
  • utilisez v-model pour mettre à jour la valeur du texte (le nombre de caractères restants devrait se mettre à jour automatiquement)
  • créez une seconde propriété calculée pour vérifier si la limite est atteinte (doit retourner true ou false). Utilisez cette propriété pour  :
    • afficher le texte en rouge (en ajoutant une classe)
    • désactiver le bouton (attribut disabled)

Ensuite :

  • en HTML, ajoutez une case à cocher pour simuler l’ajout de une photo (nous ne voulons pas créer la fonctionnalité) et un label associé avec le texte «Pas de photo»
  • coté JS, ajoutez dans data une propriété photo qui vaut false
  • utilisez v-model pour binder cette valeur
  • modifiez la propriété nombreRestants pour enlever ou pas des caractères en fonction de si la case est cochée ou non
  • le texte du label doit devenir «✓ Photo ajoutée»
  • testez les différents cas dans l’interface

Code HTML envisagé (sans code Vue)

<div id="app">
    <form action="">
        <textarea></textarea>
        <p>10</p>
        <button>Tweet</button>
        <input id="photo" type="checkbox">
        <label for="photo">Pas de photo</label>
    </form>
</div>

Instance Vue

Vue.createApp({
    data() {
        return {};
    },
    computed: {

    }
}).mount('#app');

Enfin, il faut pouvoir simuler l’action de tweeter et que ses tweets apparaissent sous la tweetbox (la partie serveur n’est pas gérée ici). Pour cela :

  • coté JS :
    • créez une liste (vide) de tweets dans data
    • ajoutez l’événement submit sur le formulaire, et :
      • empêchez la validation classique (utilisez @submit.prevent directement)
      • ajoutez les données du tweet actuel (message, image) dans la liste sous forme d’objet littéral
  • coté HTML (template) :
    • générez des blocs HTML basés sur cette liste (utilisation de v-for, v-if)
    • note: pour les images, utilisez un service de récupération d’image aléatoire comme https://picsum.photos avec un paramètre random, exemple: <img src="https://picsum.photos/200/200?random=2">
    • stylez en CSS

Exercice - Validation de formulaire#

En JavaScript, il est possible de vérifier et valider les champs de formulaire avant d'envoyer les informations au serveur. Cela permet de s'assurer que les données sont correctement formatées (ex: email bien formé, n° de téléphone à 10 chiffres, date ...).

Note: Nous n'aborderons pas ici le traitement des données du formulaire qui s'effectue sur le serveur. Une deuxième vérification des données est alors nécessaire pour valider les formulaires qui n'auraient pas été vérifié par JavaScript (JavaScript désactivé sur le navigateur, tentative de hack, etc.).

Prenons comme exemple ce formulaire HTML :

Formulaire HTML

<form action="#" method="get">
    <fieldset>
        <legend>Inscription</legend>
        <p><label>Nom<input type="text" name="nom"></label></p>
        <p><label>Prénom<input type="text" name="prenom"></label></p>
        <p><label>Tel<input type="text" maxlength="10" name="telephone"></label></p>
        <p><label>Email<input type="text" name="mail"></label></p>
        <p><button>Envoyer</button></p>
    </fieldset>
</form>

Le résultat est le suivant :

Inscription

  • premièrement, créez votre application Vue
  • créez 4 propriétés pour les 4 champs, et binder avec v-model

Vue.createApp({
    data() {
        return {
            nom: '',
            prenom: '',
            tel: '',
            mail: ''
        };
    }
}).mount('...');
<input type="text" name="nom" v-model="nom">
<input type="text" name="prenom" v-model="prenom">
<input type="text" maxlength="10" name="tel" v-model="tel">
<input type="text" name="mail" v-model="mail">

  • créez 4 propriétés calculées, pour chacun des champs, pour en vérifier la conformité
    • champ nom et prenom non vides, champ tel numérique à 10 chiffres, champ email un email
Vue.createApp({
    computed: {
        validNom() {
            return this.nom != '';
        },
        validTel() {
            return this.tel.length == 10 && !isNaN(this.tel);
        },
        validMail() {
            let regex = /^[a-z0-9]+([_|\.|-]{1}[a-z0-9]+)*@[a-z0-9]+([_|\.|-]­{1}[a-z0-9]+)*[\.]{1}[a-z]{2,6}$/;
            return regex.exec(this.mail);
        }
    }
});
  • ajoutez ensuite l’événement submit sur le formulaire, et exécutez une fonction
  • cette fonction doit empêcher la validation du formulaire si au moins un champ est mal rempli

<form action="#" method="get" @submit="validate">
Vue.createApp({
    methods: {
        validate(event) {
            if ( ... ) {
                event.preventDefault();
            }
        }
    }
});

Enfin, ajoutez une classe erreur sur chaque champ mal rempli. Pour cela :

  • créez une propriété errors, qui doit être un objet littéral avec 4 propriétés pour les 4 champs
Vue.createApp({
    data() {
        return {
            errors: {
                nom: false,
                prenom: false,
                tel: false,
                mail: false
            }
        };
    }
});
  • utilisez ces propriétés pour ajouter conditionnellement une classe
<input type="text" name="nom" v-model="nom" :class="{ error: errors.nom }">
  • pensez à changer à true dans la fonction validate
Vue.createApp({
    methods: {
        validate(event) {
            this.errors.nom = !this.validNom;

            ...
        }
    }
});
  • ajoutez également en CSS le changement de style
.error {
    background: red;
}