Aller au contenu

Client HTML - Vue.js

Coté client, JavaScript va nous permettre de modifier le rendu de la page HTML. Pour cela, l’API DOM doit être utilisée. Cette API fournit une représentation structurée du document et une manière d’accéder à cette structure. Cela permet notamment :

  • de parcourir l’arbre des éléments
  • de modifier la structure de cet arbre (ajouter, enlever, déplacer des éléments)
  • d’accéder aux informations des nœuds (contenu, attribut, etc.)
  • de réagir à des événements (interactions utilisateur)
  • etc.

L’objet global document est l’objet principal d’un document HTML.

DOM vs. Vue.js#

Pour simplifier et accélérer le développement, des librairies comme Vue.js peuvent être utilisées. Son principal objectif est de mettre à jour le contenu d’une page HTML (au fil de la vie de l’application), en s’assurant que les développeurs ne modifient pas eux-mêmes le DOM, le tout, en structurant le code client.

Pour cela, des conventions sont mises en place.

Vue.js#

Pour commencer à utiliser Vue, il suffit de charger la librairie. Ici on utilisera Vue 3, avec le style «Options API».

<script src="https://cdn.jsdelivr.net/npm/vue"></script>

Et de déclarer une nouvelle instance de Vue avec vos données dans data (coté JS), et ces mêmes données coté template/rendu (donc HTML)

<div id="app">
  {{ message }}
</div>
Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
    };
  },
}).mount('#app');

Notez que data est une fonction qui retourne un objet littéral contenant nos données.

On parle alors de binding. Les données coté JS sont liées au rendu HTML. Dans Vue, le binding classique se fait avec la notation {{ }}. Pour binder la valeur à un attribut, il faut utiliser la directive v-bind:

<div v-bind:title="message"></div>

Ou simplement la version raccourcie avec deux-points

<div :title="message"></div>

Pour éviter de mettre trop de logique dans le template (HTML), on peut créer des propriétés calculées, à définir dans l’option computed (et non plus data). Par exemple, pour afficher le message en sens inverse

Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
    };
  },
  computed: {
    reversedMessage () {
      // `this` pointe sur l'instance Vue
      return this.message.split('').reverse().join('');
    },
  },
}).mount('#app');
<div id="app">
  {{ reversedMessage }}
</div>

Notez que reversedMessage est une fonction, mais qui est utilisée telle une propriété

Pour les formulaires, il est souvent nécessaire de binder dans les 2 sens. Du code vers l’interface, mais également de l’interface vers le code (case à cocher, champ texte, etc.). Pour cela, on utilisera la directive v-model.

<input type="text" v-model="message">
<input type="checkbox" v-model="cochee">
Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
      cochee: true,
    };
  },
}).mount('#app');

Ainsi, il n’est pas forcément nécessaire de créer d’évènements. Au chargement de la page, le champ texte contient le texte "Hello Vue !" et la checkbox est cochée. Si l’utilisateur change le texte depuis l’interface, la variable message est mise à jour dans le code. Si il décoche la checkbox, idem, la variable cochee est mise à jour. Et les éventuelles propriétés calculées sont recalculées en temps-réel.

Vue propose également d’autres directives intéressantes qui permettent d’ajouter de la logique dans vos templates :

  • v-if/v-else pour de l’affichage conditionnel
  • v-for pour générer des éléments HTML à la volée

Les évènements#

La directive v-on: permet d’ajouter des évènements qui appelleront des méthodes stockées dans methods

Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
    };
  },
  methods: {
    hello () {
      alert('Hello !');
    },
  },
}).mount('#app');
<button v-on:click="hello">Say Hello</button>

Ou simplement la version raccourcie avec @

<button @click="hello">Say Hello</button>

Pour passer des variables, vous utiliserez

<button @click="hello('world', $event)">Say Hello</button>

Il existe également des modificateurs pour gérer quelques actions classiques :

<!-- call event.preventDefault() -->
<form @submit.prevent="submit"></form>

<!-- call submit() when the keyCode is 13 (enter) -->
<input @keyup.13="submit">

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;
}