Contenu du cours
Projet TodoList
Programmer une Todo list (liste de choses à faire) en Javascript est un excellent exercice d'apprentissage. Une Todo list couvre différents aspects et concepts que l'on retrouvera dans la majorité des projets web : Manipulation d'objets Javscript, du DOM HTML et du CSSOM, formulaire, changement d'états, animation, etc. Dans cette section, nous allons réaliser plusieurs versions d'une Todo list, en démarrant avec une version simple que nous ferons évoluer en ajoutant des fonctionnalités.
0/13
Introduction au Javascript
  • Dans votre projet VSCode, dupliquer le répertoire v8-inputs et nommer le v9-controls
  • Dans le nouveau répertoire v9-controls :
    • Remplacer le code du fichier index.html avec celui-ci
    • Renommer le fichier v8-inputs.js en v9-controls.js

Marquer une tâche comme validée

Pour pouvoir marquer une tâche comme validée ou non, nous aurons besoin de :

  • Une case à cocher
  • D’un événement sur cette case à cocher
  • D’une fonction déclenchée par cet événement
  • De mettre à jour le statut de l’objet Item concerné
  • Optionnellement d’ajouter / retirer une classe CSS sur le <li> pour pouvoir modifier le style de l’affichage
  • De donner un identifiant unique à chaque <li> pour pouvoir y les retrouver facilement dans le DOM

Modification de la fonction createLi

Dans notre version précédente, la fonction createLi était assez simple :

    createLi(item) {
        let li = document.createElement('li');
        li.textContent = '( ) ' + item;
        return li;
    }

Identifiant unique

Commençons par ajouter un identifiant unique à notre <li>. Une méthode possible est d’ajouter deux attributs à la balise HTML <li>:

  • L’identifiant de la liste à laquelle le <li> est rattaché
  • L’identifiant de l’Item dans la liste
  • On voudrait un résultat de la sorte :

On peut voir ici que chaque <li> possède un attribut data-ul-id et un attribut data-item-id, ces deux valeurs rendant chaque <li> unique.

On pourra, grâce à l’API DOM, sélectionner un <li> avec l’instruction :

 let li = document.querySelector('li[data-ul-id="1"][data-item-id="0"]');

Dans cet exemple, on sélectionnera le <li> de la liste id: 1 et l’Item id : 0

Identifiant des listes

Premièrement, on doit générer un identifiant pour nos listes.

Une technique est de comptabiliser le nombre d’objets ToDoDom qui sont créés. De fait, la première liste ToDoDom aurait l’identifiant 0, la 2e l’id: 1, la 3e l’id: 2, etc.

C’est le moment d’utiliser une propriété static. Pour rappel, les propriétés statiques existent au niveau de la classe et sont partagées par toutes les instances des objets de la classe :

class ToDoDom {

    static count = 0;

    constructor(todo) {
        this.id = ToDoDom.count++;

        ...
    }

    ...
}
  • On déclare un attribut statique count
  • À chaque appel de la méthode constructor, on augmente count de 1 ( ++ augmente de 1 une valeur entière)
  • On attribut le résultat à une nouvelle propriété this.id pour stocker le résultat
Identifiant des <li>

Modifions la fonction createLi :

    createLi(itemText) {
        let li = document.createElement('li');
        let itemId = this.todo.items.length - 1;
        li.dataset.ulId = this.id;
        li.dataset.itemId = itemId;
    }
  • L’instruction de l’API DOM dataset permet d’assigner des attributs “data” aux élément HTML.
  • li.dataset.ulId = this.id; aura pour résultat d’ajouter, par exemple, data-ul-id=”0″ à l’élément <li> si this.id est égal à 0
Identifiants des Item
  • La fonction createLi est appelée dans la fonction add
  • Cela signifie qu’elle est appelée quand on vient juste d’ajouter un nouvel élément à notre objet ToDo (propriété this.todo de l’objet de la classe ToDoDom)
  • On peut donc utiliser comme valeur pour l’identifiant de notre élément, la longueur du tableau de this.todo.items – 1
    • Ainsi, si notre liste contient 1 élément, la longeur du tableau sera 1, et son identifiant sera donc 0
    • Si le tableau contient maintenant 2 éléments, la longueur du tableau sera 2, son identifiant sera 1
    • etc.
  • On ajoute ensuite cet identifiant au <li> avec li.dataset.itemId = itemId; ce qui ajoutera data-item-id=”0″ au <li> lorsque itemId est égal à 0

Modifiez votre code et vérifiez que les attributs sont bien ajoutés correctement au DOM avant de passer à la suite.

Ajout d’une checkbox

Dans la méthode createLi, ajoutons un élément input de type checkbox, avec un écouteur d’événement du clic de la souris :

        let checkbox = document.createElement('input');
        checkbox.type = "checkbox";
        checkbox.addEventListener('click', () => this.toggle(itemId));
        li.appendChild(checkbox);

On peut voir qu’à l’événement click, on appelle une fonction toggle de la classe ToDoDom.
Implémentons cette méthode :

    toggle(id) {
        this.todo.toggleCompleted(id);
        let li = document.querySelector('li[data-ul-id="' + this.id + '"][data-item-id="' + id + '"]');
        let checkbox = li.getElementsByTagName('input')[0];
        checkbox.checked = this.todo.items[id].done;
        if (this.todo.items[id].done) {
            li.classList.add('done');
        }
        else {
            li.classList.remove('done');
        }
    }
  • Premièrement, on change le statut de l’objet Item en appelant la méthode toggleCompleted de la classe ToDo
    • Ce changement se passe au niveau des objets ToDo et Item, indépendamment de l’interface
  • Puis on sélectionne le <li> correspondant dans le DOM
  • On récupère ensuite l’élément checkbox de l’élement <li> avec getElementsByTagName : let checkbox = li.getElementsByTagName(‘input’)[0];
  • On modifie l’état de la checkbox avec le statut de l’Item. Si item.done vaut true, alors la checkbox sera cochée, sinon décochée
  • Et enfin, dans le if…else, on ajoute ou retire une classe CSS “done”, qui nous permettra par la suite de styliser le <li> en CSS

Enfin, on doit rajouter le texte de l’Item au <li>. On va créer un élément <span> :

        let span = document.createElement('span');
        span.textContent = itemText;
        li.appendChild(span);

Du CSS étant déjà présent dans le code du fichier index.html, si tout est correctement implémenté vous devriez pourvoir observer le résultat !

On peut observer dans la console du DevTools que les console.log de nos classes ToDo et Item sont toujours appelés.

Notre interface graphique HTML/CSS est bien découplée du “moteur” de l’application, yayy !

À ce stade

Supprimer une tâche

La suppression d’une tâche est similaire :

  • On crée un bouton que l’on ajoute au <li>
  • Le bouton doit déclencher la suppression de l’Item dans l’objet ToDo
  • Puis mettre à jour l’interface en supprimant le <li>
        let removeButton = document.createElement('button');
        removeButton.textContent = 'X';
        removeButton.addEventListener('click', () => {
            this.todo.remove(itemId);
            li.remove();
        });
        li.appendChild(removeButton);

Un point sur les fonctions remove présentent dans ce code :

  • L’instruction this.todo.remove(itemId); appelle la méthode remove de la classe ToDo
  • L’instruction li.remove(); appelle une fonction de l’API DOM qui retire un élément du DOM

Vous devriez maintenant voir apparaitre le bouton de suppression sur les <li> et la suppression d’une tâche au clic de celui-ci :

Magnifique ! Notre application TodoList est fonctionnelle !

Il reste un dernier point important à régler… on perd nos listes à chaque fois que la page est rafraichit … C’est problématique !

Dans notre dernière version, on va ajouter une sauvegarde locale pour éviter ce désagrément.