Intro Programmation Web

Javascript - TodoList

Pierre Blarre - Florian Rodriguez

1. Qu'est ce une liste de todo? et pourquoi?

2. Une liste de todo

  • Toutes les opérations de base: CRUD (Create, Read, Update, Delete)
  • On a besoin:
    • stocker les todos
    • afficher les todos
    • ajouter un todo
    • modifier un todo
    • supprimer un todo

3. En «console»

console.log('********** Todo ***********');
// Stocker nos todos
let items =  [ "item1", "item2", "item3" ];
// Afficher les todos
console.log("Ma liste de todos:", items);
// Ajouter un todo
items.push("Mon nouvel item");
// Modifier un todo
items[1] = "item 2 est changé!"; 
// Supprimer un todo
items.splice(2,1);
console.log("Ma liste:", items);

4. Mise en place et v1

  • un fichier html (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>

  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
  • un fichier js (todos.js)
console.log('********** Todo ***********');

// Stocker nos todos
let items =  [ "item1", "item2", "item3" ];
// Afficher les todos
console.log("Ma liste:", items);
// Ajouter un todo
items.push("Mon nouvel item");
// Modifier un todo
items[1] = "item 2 est changé!"; 
// Supprimer un todo
items.splice(2,1); 
// J'affiche ma nouvelle liste
console.log("Ma liste:", items);

5. v2 - avec des fonctions (Exercice)

  • Refactoriser le code en fonctions
  • Ajouter des paramètres aux fonctions qui en ont besoin Nous aurons donc:
    • display()
    • add(item)
    • change(index,value)
    • delete(index)
  • Dans chacune des fonctions nous rappellerons la fonction display() pour afficher la liste mise à jour

5.1. v2 - avec des fonctions (Solution)

let items = ["item1", "item2", "item3"];

function display() {
    console.log('My items:', items);
}

function add(item) {
    items.push(item);
    display();
}

function change(index, value) {
    items[index] = value;
    display();
}

function remove(position) {
    items.splice(position, 1);
    display();
}

console.log('********** Todo ***********');
display();
console.log('********** Ajouter un todo ***********');
add("Mon nouvel item");
console.log('********** Modifier un todo ***********');
change(2, "item 3 est changé!");
console.log('********** Supprimer un todo ***********');
remove(1);

6. v3 - avec un objet todoList (Exercice)

  • Regrouper les fonctions et la liste dans un objet todoList
  • Les fonctions deviennent des méthodes de l'objet
  • Le tableau d'item devient une propriété (un attribut) de l'objet
  • On utilisera le mot-clé this pour accéder aux propriétés et méthodes de l'objet
  • Pour rappelle un objet en JS est une collection de paires clé/valeur:

    let monObjet = {
      cle1: valeur1,
      cle2: valeur2,
      methode1: function(param1) {
            // code
      }
    };
    
  • Nous utiliserons donc ensuite notre objet de cette façon:

    todoListe.add("Mon item");
    todoListe.change(0, "Nouvelle valeur");
    

6.1. v3 - avec un objet todoList (Solution)

let todoList = {
    items: [],
    display: function () {
        console.log('My items: ', this.items);
    },
    add: function (item) {
        this.items.push(item);
        this.display();
    },
    change: function (index, value) {
        this.items[index].text = value;
        this.display();
    },
    remove: function (index) {
        this.items.splice(index, 1);
        this.display();
    },
};

console.log('********** Todo ***********');
todoList.display();

console.log('********** Ajouter un todo ***********');
todoList.add("item1");
todoList.add("item2");
todoList.add("item3");

console.log('********** Modifier un todo ***********');
todoList.change(2, "item 3 est changé!");

console.log('********** Supprimer un todo ***********');
todoList.remove(1);

7. v4 - objets + booléens

  • Chaque item sera un objet avec deux propriétés:
    • text: le texte de l'item
    • completed: un booléen indiquant si le todo est complété ou non
  • La liste des todos sera un tableau d'objets item
  • Un item ressemblera donc à ça:

    let item = {
      text: "Mon todo",
      completed: false
    };
    
  • Nous aurons également la possibilité de marquer un todo comme complété en modifiant la propriété completed
  • Pour cela, nous ajouterons une méthode toggleCompleted(index) à notre objet todoList

7.1. v4 - objets + booléens

let todoList = {
    items: [],
    display: function () {
        console.log('My items: ', this.items);
    },
    add: function (item) {
        this.items.push({ text: item, completed: false });
        this.display();
    },
    change: function (index, value) {
        this.items[index].text = value;
        this.display();
    },
    toggleCompleted: function (index) {
        this.items[index].completed = ! this.items[index].completed;
        this.display();
    },
    remove: function (index) {
        this.items.splice(index, 1);
        this.display();
    },
};

console.log('********** Todo ***********');
todoList.display();

console.log('********** Ajouter un todo ***********');
todoList.add("item1");
todoList.add("item2");
todoList.add("item3");

console.log('********** Modifier un todo ***********');
todoList.change(2, "item 3 est changé!");

console.log('********** Supprimer un todo ***********');
todoList.remove(1);

console.log('********** Toggle Completed ***********');
todoList.toggleCompleted(0);

8. v5 - boucles display (Exercice)

  • Modifier la méthode display() pour qu'elle utilise une boucle pour afficher chaque todo de la liste
  • Pour chaque todo, afficher son texte et son état (complété ou non)
  • Utiliser une boucle for ou forEach pour parcourir la liste des todos
  • L'affichage devrait ressembler à ça:

    Ma liste de todos:
      ( ) Mon premier todo
      (x) Mon deuxième todo
      ( ) Mon troisième todo
    

8.1. v5 - boucles display (Solution)

let todoList = {
    items: [],
    display: function () {
        //console.log('My items: ', this.items);
        if (this.items.length === 0) {
            console.log('Your todo list is empty!');
        }
        else {
            console.log('My items:');
            for (let i = 0; i < this.items.length; i++) {
                if (this.items[i].completed) {
                    console.log('(x)', this.items[i].text);
                }
                else {
                    console.log('( )', this.items[i].text);
                }
            }
        }
    },
    add: function (aTodo) {
        this.items.push({ text: aTodo, completed: false });
        this.display();
    },
    change: function (index, value) {
        this.items[index].text = value;
        this.display();
    },
    toggleCompleted: function (index) {
        this.items[index].completed = ! this.items[index].completed;
        this.display();
    },
    remove: function (index) {
        this.items.splice(index, 1);
        this.display();
    },
};

console.log('********** Todo ***********');
todoList.display();

console.log('********** Ajouter un todo ***********');
todoList.add("item1");
todoList.add("item2");
todoList.add("item3");

console.log('********** Modifier un todo ***********');
todoList.change(2, "item 3 est changé!");

console.log('********** Supprimer un todo ***********');
todoList.remove(1);

console.log('********** Toggle Completed ***********');
todoList.toggleCompleted(0);

9. v6 - toggleAll (Exercice)

  • Ajouter une méthode toggleAll() à l'objet todoList
  • Vérifier sur le site todomvc.com le comportement attendu de cette méthode (Dans quel cas tous les todos doivent être marqués comme complétés ou non complétés)
  • Il faudra peut être compter le nombre de todos complétés avant de décider quoi faire (0, 1 ou tous)

9.1. v6 - toggleAll

let todoList = {
    items: [],
    display: function () {
        if (this.items.length === 0) {
            console.log('Your todo list is empty!');
        }
        else {
            console.log('My items:');
            for (let i = 0; i < this.items.length; i++) {
                if (this.items[i].completed) {
                    console.log('(x)', this.items[i].text);
                }
                else {
                    console.log('( )', this.items[i].text);
                }
            }
        }
    },
    add: function (aTodo) {
        this.items.push({ text: aTodo, completed: false });
        this.display();
    },
    change: function (index, value) {
        this.items[index].text = value;
        this.display();
    },
    toggleCompleted: function (index) {
        this.items[index].completed = !this.items[index].completed;
        this.display();
    },
    remove: function (index) {
        this.items.splice(index, 1);
        this.display();
    },
    toggleAll: function () {
      console.log('********** Toggle All ***********');
      /*
      // 1er methode
      // calculer le nombre d'item vrai (completed == true)
      let count = 0;
      for(let i=0;i<this.items.length;i++){
      if(this.items[i].completed) {
      count++;
      }
      }
      // si tout est vrai, tout devient faux
      if (count == this.items.length) {
      for(let i=0;i<this.items.length;i++){
      this.items[i].completed = false;
      }
      } else { // sinon, tout devient vrai
      for(let i=0;i<this.items.length;i++){
      this.items[i].completed = true;
      }
      }
      */

      // 2eme méthode
      // Si un seul est faux, tout devient vrai
      let oneCompleted = false;
      let i = 0;
      while(!oneCompleted && i<this.items.length){
        if (!this.items[i].completed){
          oneCompleted = true;
        }
        i++;
      }
      if (oneCompleted) {
        for(let i=0;i<this.items.length;i++){
          this.items[i].completed = true;
        }
      } else {
        for(let i=0;i<this.items.length;i++){
          this.items[i].completed = false;
        }
      }

      this.display();
    }

};

console.log('********** Todo ***********');
todoList.display();

console.log('********** Ajouter un todo ***********');
todoList.add("item1");
todoList.add("item2");
todoList.add("item3");

console.log('********** Modifier un todo ***********');
todoList.change(2, "item 3 est changé!");

console.log('********** Supprimer un todo ***********');
todoList.remove(1);

console.log('********** Toggle Completed ***********');
todoList.toggleCompleted(0);

todoList.toggleAll();
todoList.toggleAll();

10. v7 - HTML/DOM (Exercice)

  • Nous allons maintenant commencer à intéragir avec notre todoList via la page web
  • Nous allons créer 2 boutons dans le fichier index.html:
    • un bouton displayButton pour afficher notre todoListe (toujours en console)
    • un bouton toggleAllButton pour toggleAll
  • Nous allons ajouter des gestionnaires d'événements (event listeners) à ces boutons dans le fichier todos.js

10.1. v7 - HTML/DOM (Solution)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>
  <button id="displayButton">Afficher Tout</button>
  <button id="toggleAllButton">Toggle All</button>

  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
let todoList = {
    items: [],
    display: function () {
        if (this.items.length === 0) {
            console.log('Your todo list is empty!');
        }
        else {
            console.log('My items:');
            for (let i = 0; i < this.items.length; i++) {
                if (this.items[i].completed) {
                    console.log('(x)', this.items[i].text);
                }
                else {
                    console.log('( )', this.items[i].text);
                }
            }
        }
    },
    add: function (item) {
        this.items.push({ text: item, completed: false });
        this.display();
    },
    change: function (index, value) {
        this.items[index].text = value;
        this.display();
    },
    toggleCompleted: function (index) {
        this.items[index].completed = !this.items[index].completed;
        this.display();
    },
    remove: function (index) {
        this.items.splice(index, 1);
        this.display();
    },
    toggleAll: function () {
        console.log('********** Toggle All ***********');

        //Calculer le nombre de todo vrai (completed ==== true)
        let counter = 0;
        for (let i = 0; i < this.items.length; i++) {
            if (this.items[i].completed === true) {
                // counter = counter + 1;
                counter++;
            }
        }

        //console.log("Élements complétés: ", counter);
        //Si tout est vrai
        if (this.items.length === counter) {
            //il faut tout mettre à faux
            for (let i = 0; i < this.items.length; i++) {
                this.items[i].completed = false;
            }
        }
        else { //Sinon
            //il faut tout mettre à vrai
            for (let i = 0; i < this.items.length; i++) {
                this.items[i].completed = true;
            }

        }

        this.display();
    }
};


let displayButton = document.getElementById('displayButton');
let toggleAllButton = document.getElementById('toggleAllButton');

displayButton.addEventListener('click', function(){
  todoList.display();
});

toggleAllButton.addEventListener('click', function(){
  todoList.toggleAll();
});

todoList.add("item1");
todoList.add("item2");
todoList.add("item3");
todoList.toggleCompleted(0);

11. v8 - class Todo (Exercice)

  • Refactoriser l'objet todoList en une classe ToDo
  • La classe aura:
    • une propriété items (le tableau des todos)
    • des méthodes display(), add(item), change(index,value), delete(index), toggleCompleted(index), toggleAll()
  • Nous créerons une instance de la classe todoList pour gérer notre liste de todos

11.1. v8 - class Todo (Solution)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>
  <button id="displayTodosButton">Afficher Tout</button>
  <button id="toggleAllButton">Toggle All</button>
  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
class ToDo {

    constructor(title = 'My items', items = []) {
            this.title = title;
            this.items = items;
    }

    display() {

        if (this.items.length === 0) {
            console.log('Your todo list is empty!');
        }
        else {
            console.log('My items:');
            for (let i = 0; i < this.items.length; i++) {
                if (this.items[i].completed) {
                    console.log('(x)', this.items[i].text);
                }
                else {
                    console.log('( )', this.items[i].text);
                }
            }
        }
    }

    add(aTodo) {
        this.items.push({ text: aTodo, completed: false });
        this.display();
    }

    change(index, value) {
        this.items[index].text = value;
        this.display();
    }

    toggleCompleted(index) {
        this.items[index].completed = !this.items[index].completed;
        this.display();
    }

    remove(index) {
        this.items.splice(index, 1);
        this.display();
    }

    toggleAll() {
        console.log('********** Toggle All ***********');

        //Calculer le nombre de todo vrai (completed ==== true)
        let counter = 0;
        for (let i = 0; i < this.items.length; i++) {
            if (this.items[i].completed === true) {
                // counter = counter + 1;
                counter++;
            }
        }

        //console.log("Élements complétés: ", counter);
        //Si tout est vrai
        if (this.items.length === counter) {
            //il faut tout mettre à faux
            for (let i = 0; i < this.items.length; i++) {
                this.items[i].completed = false;
            }
        }
        else { //Sinon
            //il faut tout mettre à vrai
            for (let i = 0; i < this.items.length; i++) {
                this.items[i].completed = true;
            }

        }

        this.display();
    }
};

let todoList = new ToDo('My items', [{ text: 'item1', completed: false }, { text: 'item2', completed: false }, { text: 'item3', completed: false }]);

let displayButton = document.getElementById('displayButton');
let toggleAllButton = document.getElementById('toggleAllButton');

displayButton.addEventListener('click', function(){
  todoList.display();
});

toggleAllButton.addEventListener('click', function(){
  todoList.toggleAll();
});

todoList.toggleCompleted(0);

12. v9 - class Item (Exercice)

  • Créer une classe Item pour représenter chaque todo
  • La classe aura deux propriétés:
    • text (le texte du todo)
    • completed (le booléen indiquant si le todo est complété ou non)
  • Modifier la classe ToDo pour qu'elle utilise des instances de la classe Item dans son tableau items
  • Adapter les méthodes de la classe ToDo pour travailler avec des objets Item
  • Par exemple, la méthode add(itemText) créera une nouvelle instance de Item avec le texte fourni et l'ajoutera à la liste

12.1. v9 - class Item (Solution)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>
  <button id="displayButton">Afficher Tout</button>
  <button id="toggleAllButton">Toggle All</button>
  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
class Item {
  constructor(text, completed = false) {
    this.text = text;
    this.completed = completed;
  }

 display() {
   if (this.completed) {
      console.log('(x)', this.text);
    }
    else {
      console.log('( )', this.text);
    }
  }

  toggle() {
    this.completed = !this.completed;
  }
}

class ToDo {

  constructor(title = 'My items', items = []) {
    this.title = title;
    this.items = items;
  }

  display() {

    if (this.items.length === 0) {
      console.log('Your todo list is empty!');
    }
    else {
      console.log('My items:');
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].display();
        // if (this.items[i].completed) {
        //   console.log('(x)', this.items[i].text);
        // }
        // else {
        //   console.log('( )', this.items[i].text);
        // }
      }
    }
  }

  add(item) {
    this.items.push(new Item(item));
    this.display();
  }

  change(index, value) {
    this.items[index].text = value;
    this.display();
  }

  toggleCompleted(index) {
    this.items[index].toggle();
    //this.items[index].completed = !this.items[index].completed;
    this.display();
  }

  remove(index) {
    this.items.splice(index, 1);
    this.display();
  }

  toggleAll() {
    console.log('********** Toggle All ***********');

    //Calculer le nombre de todo vrai (completed ==== true)
    let counter = 0;
    for (let i = 0; i < this.items.length; i++) {
      if (this.items[i].completed === true) {
        // counter = counter + 1;
        counter++;
      }
    }

    //console.log("Élements complétés: ", counter);
    //Si tout est vrai
    if (this.items.length === counter) {
      //il faut tout mettre à faux
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].completed = false;
      }
    }
    else { //Sinon
      //il faut tout mettre à vrai
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].completed = true;
      }

    }

    this.display();
  }
};

let todoList = new ToDo('My items', [new Item('item1'), new Item('item2',true), new Item('item3')]);

let displayButton = document.getElementById('displayButton');
let toggleAllButton = document.getElementById('toggleAllButton');

displayButton.addEventListener('click', function(){
  todoList.display();
});

toggleAllButton.addEventListener('click', function(){
  todoList.toggleAll();
});

13. v10 - Handlers (Exercice)

  • Ajouter des champs de saisie (input) dans le fichier index.html pour permettre à l'utilisateur d'ajouter (addTodoTextInput), modifier (changeTodoTextInput, changeTodoPositionInput) et supprimer (deleteTodoPositionInput) des items
  • Ajouter des boutons pour chaque action avec un attribut onclick qui appelle une fonction JavaScript correspondante dans le fichier todos.js
  • Dans le fichier todos.js, créer les fonctions add(), change() et delete() qui récupèrent les valeurs des champs de saisie et appellent les méthodes appropriées de l'objet todoList
  • Dans un deuxième temps nous regrouperons ces fonctions dans une class Handlers

13.1. v10 - Handlers (Solution)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>

  <div>
    <button onclick="handlers.display()">Afficher Tout</button>
    <button onclick="handlers.toggleAll()">Toggle All</button>
  </div>

  <!-- control de .addTodo -->
  <div>
    <button onclick="handlers.add()">Ajouter</button>
    <input id="addTodoTextInput" type="text" />
  </div>

  <!-- control de .changeTodo -->
  <div>
    <button onclick="handlers.change()">Changer</button>
    <input id="changeTodoTextInput" type="text" />
    <input id="changeTodoPositionInput" type="number" />
  </div>

  <!-- control de .deleteTodo -->
  <div>
    <button onclick="handlers.remove()">Supprimer</button>
    <input id="deleteTodoPositionInput" type="number" />
  </div>

  <!-- control de .toggleCompleted -->
  <div>
    <button onclick="handlers.toggleCompleted()">Toggle completed</button>
    <input id="toggleCompletedPositionInput" type="number" />
  </div>

  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
class Item {
    constructor(text, completed = false) {
        this.text = text;
        this.completed = completed;
    }
    display() {
        console.log('Item: ', this.text, 'Completed: ', this.completed);
    }

    toggle() {
        this.completed = !this.completed;
    }
}

class ToDo {

    constructor(title = 'My items', items = []) {
            this.title = title;
            this.items = items;
    }

    display() {

        if (this.items.length === 0) {
            console.log('Your todo list is empty!');
        }
        else {
            console.log('My items:');
            for (let i = 0; i < this.items.length; i++) {
                if (this.items[i].completed) {
                    console.log('(x)', this.items[i].text);
                }
                else {
                    console.log('( )', this.items[i].text);
                }
            }
        }
    }

    add(item) {
        this.items.push({ text: item, completed: false });
        this.display();
    }

    change(index, value) {
        this.items[index].text = value;
        this.display();
    }

    toggleCompleted(index) {
        this.items[index].completed = !this.items[index].completed;
        this.display();
    }

    remove(index) {
        this.items.splice(index, 1);
        this.display();
    }

    toggleAll() {
        console.log('********** Toggle All ***********');

        //Calculer le nombre de todo vrai (completed ==== true)
        let counter = 0;
        for (let i = 0; i < this.items.length; i++) {
            if (this.items[i].completed === true) {
                // counter = counter + 1;
                counter++;
            }
        }

        //console.log("Élements complétés: ", counter);
        //Si tout est vrai
        if (this.items.length === counter) {
            //il faut tout mettre à faux
            for (let i = 0; i < this.items.length; i++) {
                this.items[i].completed = false;
            }
        }
        else { //Sinon
            //il faut tout mettre à vrai
            for (let i = 0; i < this.items.length; i++) {
                this.items[i].completed = true;
            }

        }

        this.display();
    }
};

class Handlers {
    constructor(todoList = new ToDo()) {
        this.todoList = todoList;
    }
    display() {
        todoList.display();
    }

    toggleAll() {
        todoList.toggleAll();
    }

    add() {
        let addTodoTextInput = document.getElementById('addTodoTextInput');
        todoList.add(addTodoTextInput.value);
        addTodoTextInput.value = '';
    }

    change() {
        let changeTodoPositionInput = document.getElementById('changeTodoPositionInput');
        let changeTodoTextInput = document.getElementById('changeTodoTextInput');
        todoList.change(changeTodoPositionInput.valueAsNumber, changeTodoTextInput.value);
        changeTodoPositionInput.value = '';
        changeTodoTextInput.value = '';
    }

    remove() {
        let deleteTodoPositionInput = document.getElementById('deleteTodoPositionInput');
        todoList.remove(deleteTodoPositionInput.valueAsNumber);
        deleteTodoPositionInput.value = '';
    }

    toggleCompleted() {
        let toggleCompletedPositionInput = document.getElementById('toggleCompletedPositionInput');
        todoList.toggleCompleted(toggleCompletedPositionInput.valueAsNumber);
        toggleCompletedPositionInput.value = '';
    }
}


let todoList = new ToDo('My items', [new Item('item1'), new Item('item2'), new Item('item3')]);

let handlers = new Handlers(todoList);

/* 
let displayButton = document.getElementById('displayButton');
let toggleAllButton = document.getElementById('toggleAllButton');

displayButton.addEventListener('click', function(){
  todoList.display();
});

toggleAllButton.addEventListener('click', function(){
  todoList.toggleAll();
});

 */

14. v11 - View (Exercice)

  • Nous allons maintenant afficher la liste des todos dans la page web au lieu de la console
  • Nous allons créer une class View qui aura une méthode display()
  • La méthode display() mettra à jour le contenu d'un élément HTML (par exemple un ul) pour afficher la liste des todos
  • Chaque todo sera affiché comme un élément de liste (li) avec son texte et une indication de son état (complété ou non)
  • Nous utiliserons la méthode innerHTML pour mettre à jour le contenu de l'élément HTML

14.1. v11 - View (Solution)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>

  <div>
    <button onclick="handlers.display()">Afficher Tout</button>
    <button onclick="handlers.toggleAll()">Toggle All</button>
  </div>

  <!-- control de .addTodo -->
  <div>
    <button onclick="handlers.add()">Ajouter</button>
    <input id="addTodoTextInput" type="text" />
  </div>

  <!-- control de .changeTodo -->
  <div>
    <button onclick="handlers.change()">Changer</button>
    <input id="changeTodoTextInput" type="text" />
    <input id="changeTodoPositionInput" type="number" />
  </div>

  <!-- control de .deleteTodo -->
  <div>
    <button onclick="handlers.remove()">Supprimer</button>
    <input id="deleteTodoPositionInput" type="number" />
  </div>

  <!-- control de .toggleCompleted -->
  <div>
    <button onclick="handlers.toggleCompleted()">Toggle completed</button>
    <input id="toggleCompletedPositionInput" type="number" />
  </div>

  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
class Item {
  constructor(text, completed = false) {
    this.text = text;
    this.completed = completed;
  }
  display() {
    if (this.completed) {
      console.log("(x)", this.text);
    }
    else {
      console.log("( )", this.text);
    }
  }
  toggle() {
    this.completed = !this.completed;
  }
}

class ToDo {
  constructor(title = "My items", items = []) {
    this.title = title;
    this.items = items;
  }

  display() {
    //console.log('My items: ', this.items);
    if (this.items.length === 0) {
      console.log('Your todo list is empty!');
    }
    else {
      console.log('My items:');
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].display();
      }
    }
  }
  add(todo) {
    this.items.push(new Item(todo));
  }
  change(position, newValue) {
    this.items[position].text = newValue;
  }
  toggleCompleted(position) {
    this.items[position].toggle();
    //this.items[position].completed = ! this.items[position].completed;
  }
  remove(position) {
    this.items.splice(position, 1);
  }
  toggleAll() {
    // Si un seul est faux, tout devient vrai
    let oneCompleted = false;
    let i = 0;
    while (!oneCompleted && i < this.items.length) {
      if (!this.items[i].completed) {
        oneCompleted = true;
      }
      i++;
    }
    if (oneCompleted) {
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].completed = true;
      }
    } else {
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].completed = false;
      }
    }
  }
}

class Handlers {
  constructor(todoList = new ToDo(),view = new View()){
    this.todoList = todoList;
    this.view = view;
  }
  display() {
    this.view.display();
    //this.view.displayConsole();
  }
  add() {
    let addTodoTextInput = document.getElementById('addTodoTextInput');
    this.todoList.add(addTodoTextInput.value);
    addTodoTextInput.value = '';
    this.display();
  }

  change() {
        let changeTodoPositionInput = document.getElementById('changeTodoPositionInput');
        let changeTodoTextInput = document.getElementById('changeTodoTextInput');
        this.todoList.change(changeTodoPositionInput.valueAsNumber, changeTodoTextInput.value);
        changeTodoPositionInput.value = '';
        changeTodoTextInput.value = '';
        this.display();
    }

    remove() {
        let deleteTodoPositionInput = document.getElementById('deleteTodoPositionInput');
        this.todoList.remove(deleteTodoPositionInput.valueAsNumber);
        deleteTodoPositionInput.value = '';
        this.display();
    }

    toggleCompleted() {
        let toggleCompletedPositionInput = document.getElementById('toggleCompletedPositionInput');
        this.todoList.toggleCompleted(toggleCompletedPositionInput.valueAsNumber);
        toggleCompletedPositionInput.value = '';
        this.display();
    }
}

class View {
  constructor(todoList = new ToDo()){
    this.todoList = todoList;
  }
  displayConsole() {
    this.todoList.display();
  }
  display(){
    console.log('*** View Display ****');
    let todoElt = document.querySelector('ul');
    // On commence par vider ma liste
    todoElt.innerHTML = '';
    // On boucle sur l'attribut items de notre todoList
    // Pour chaque item -> 
    //      -> on créer un element li
    //      -> on ajoute le texte de l'attribut
    //      -> on insère l'élement li dans notre élement ul
    for (let i= 0; i < this.todoList.items.length;i++){
      let li = document.createElement('li');
      let item = this.todoList.items[i];
      li.textContent = item.text;
      if (item.completed) {
        li.style.textDecoration = 'line-through';
      }
      todoElt.appendChild(li);
    }

  }
}

//let todoList = new ToDo("Ma todo",[{text:'item1',completed:false},{text:'item2',completed:true}]);
let todoList = new ToDo();
let view = new View(todoList);
let handlers = new Handlers(todoList,view);

let displayButton = document.getElementById('displayButton');
let toggleAllButton = document.getElementById('toggleAllButton');

displayButton.addEventListener('click', function () {
  todoList.display();
});

toggleAllButton.addEventListener('click', function () {
  todoList.toggleAll();
})

15. v12 - View update (checkbox & delete) (Exercice)

  • Modifier la méthode display() de la class View pour qu'elle affiche chaque todo avec:
    • une case à cocher (checkbox) qui reflète l'état completed du todo
    • un bouton delete pour supprimer le todo

15.1. v12 - View update (checkbox & delete) (Solution)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TodoList</title>
</head>
<body>
  <h1 style="color:red">Ma TodoList</h1>

  <div>
    <button onclick="handlers.display()">Afficher Tout</button>
    <button onclick="handlers.toggleAll()">Toggle All</button>
  </div>

  <!-- control de .addTodo -->
  <div>
    <button onclick="handlers.add()">Ajouter</button>
    <input id="addTodoTextInput" type="text" />
  </div>

  <!-- control de .changeTodo -->
  <div>
    <button onclick="handlers.change()">Changer</button>
    <input id="changeTodoTextInput" type="text" />
    <input id="changeTodoPositionInput" type="number" />
  </div>

  <!-- control de .deleteTodo -->
  <div>
    <button onclick="handlers.remove()">Supprimer</button>
    <input id="deleteTodoPositionInput" type="number" />
  </div>

  <!-- control de .toggleCompleted -->
  <div>
    <button onclick="handlers.toggleCompleted()">Toggle completed</button>
    <input id="toggleCompletedPositionInput" type="number" />
  </div>

  <main>
    <ul>
      <li>
        <input type="checkbox" id="todo1">
        <label for="todo1">Faire la vaisselle</label>
      </li>
    </ul>
  </main>

  <script type="text/javascript" src="todos.js"></script>

  </body>
</html>
class Item {
  constructor(text, completed = false) {
    this.text = text;
    this.completed = completed;
  }

  display() {
    if (this.completed) {
      console.log('(x)', this.text);
    }
    else {
      console.log('( )', this.text);
    }
  }

  toggle() {
    this.completed = !this.completed;
  }
}

class ToDo {

  constructor(title = 'My items', items = []) {
    this.title = title;
    this.items = items;
  }

  display() {

    if (this.items.length === 0) {
      console.log('Your todo list is empty!');
    }
    else {
      console.log('My items:');
      for (let i = 0; i < this.items.length; i++) {
        if (this.items[i].completed) {
          console.log('(x)', this.items[i].text);
        }
        else {
          console.log('( )', this.items[i].text);
        }
      }
    }
  }

  add(item) {
    this.items.push(new Item(item));
    this.display();
  }

  change(index, value) {
    this.items[index].text = value;
    this.display();
  }

  toggleCompleted(index) {
    this.items[index].completed = !this.items[index].completed;
    this.display();
  }

  remove(index) {
    this.items.splice(index, 1);
    this.display();
  }

  toggleAll() {
    console.log('********** Toggle All ***********');

    //Calculer le nombre de todo vrai (completed ==== true)
    let counter = 0;
    for (let i = 0; i < this.items.length; i++) {
      if (this.items[i].completed === true) {
        // counter = counter + 1;
        counter++;
      }
    }

    //console.log("Élements complétés: ", counter);
    //Si tout est vrai
    if (this.items.length === counter) {
      //il faut tout mettre à faux
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].completed = false;
      }
    }
    else { //Sinon
      //il faut tout mettre à vrai
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].completed = true;
      }

    }

    this.display();
  }
};

class Handlers {
  constructor(todoList = new ToDo(), view = new View()) {
    this.todoList = todoList;
    this.view = this.view;
  }

  display() {
    todoList.display(); // en console
    view.display(); // en graphique
  }
  toggleAll() {
    todoList.toggleAll();
  }
  add() {
    let addTodoTextInput = document.getElementById('addTodoTextInput');
    todoList.add(addTodoTextInput.value);
    addTodoTextInput.value = '';
    this.display();
  }
  change() {
    let changeTodoPositionInput = document.getElementById('changeTodoPositionInput');
    let changeTodoTextInput = document.getElementById('changeTodoTextInput');
    todoList.change(changeTodoPositionInput.valueAsNumber, changeTodoTextInput.value);
    changeTodoPositionInput.value = '';
    changeTodoTextInput.value = '';
    this.display();
  }
  remove() {
    let deleteTodoPositionInput = document.getElementById('deleteTodoPositionInput');
    todoList.remove(deleteTodoPositionInput.valueAsNumber);
    deleteTodoPositionInput.value = '';
    this.display();
  }

  toggleCompleted() {
    let toggleCompletedPositionInput = document.getElementById('toggleCompletedPositionInput');
    todoList.toggleCompleted(toggleCompletedPositionInput.valueAsNumber);
    toggleCompletedPositionInput.value = '';
    this.display();
  }
}

class View {
  constructor(todoList = new ToDo(), selector = 'ul') {
    this.todoList = todoList;
    this.todoElt = document.querySelector('ul');
  }
  displayConsole() {
    todoList.display();
  }
  display() {
    this.todoElt.innerHTML = '';
    for (let i = 0; i < todoList.items.length; i++) {
      let li = document.createElement('li');
      let item = todoList.items[i];

      li.appendChild(this.createCheckbox(item.completed));

      let text = document.createTextNode(item.text);
      li.appendChild(text);
      if (item.completed) {
        li.style.textDecoration = 'line-through';
      }

      li.appendChild(this.createDeleteButton());

      this.todoElt.appendChild(li);
    }
  }
  createCheckbox(completed) {
    let checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.checked = completed;
    return checkbox;
  }

  createDeleteButton() {
    let deleteButton = document.createElement('button');
    deleteButton.textContent = 'DELETE';
    return deleteButton;
  }
}

let todoList = new ToDo('My items', [new Item('item1'), new Item('item2', true), new Item('item3')]);

let view = new View(todoList);

let handlers = new Handlers(todoList, view);

view.display();
// let displayButton = document.getElementById('displayButton');
// let toggleAllButton = document.getElementById('toggleAllButton');

// displayButton.addEventListener('click', function(){
//   todoList.display();
// });

// toggleAllButton.addEventListener('click', function(){
//   todoList.toggleAll();
// });

16. v13 - listener (délégation d'événement) (Exercice)

  • Ajouter des gestionnaires d'événements pour les cases à cocher et les boutons delete
  • Supprimer les inputs et boutons inutiles du fichier index.html

16.1. v13 - listener (délégation d'événement) (Solution)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TodoList</title>
  </head>
  <body>
    <h1 style="color: red">Ma TodoList</h1>
    <div>
      <button id="displayButton" onclick="handlers.display()">
        Afficher Tout
      </button>
      <button id="toggleAllButton" onclick="handlers.toggleAll()">
        Toggle All
      </button>
    </div>

    <!-- control de .addTodo -->
    <div>
      <button id="addButton" onclick="handlers.add()">Ajouter</button>
      <input id="addTodoTextInput" type="text" />
    </div>

    <!-- control de .changeTodo -->
    <div>
      <button onclick="handlers.change()">Changer</button>
      <input id="changeTodoTextInput" type="text" />
      <input id="changeTodoPositionInput" type="number" />
    </div>

    <!-- control de .deleteTodo -->
    <!-- <div>
      <button onclick="handlers.remove()">Supprimer</button>
      <input id="deleteTodoPositionInput" type="number" />
    </div> -->

    <!-- control de .toggleCompleted -->
    <!-- <div>
      <button onclick="handlers.toggleCompleted()">Toggle completed</button>
      <input id="toggleCompletedPositionInput" type="number" />
    </div> -->

    <main>
      <ul>
        <li>
          <input type="checkbox" id="todo1" />
          <label for="todo1">Faire la vaisselle</label>
        </li>
      </ul>
    </main>

    <script type="text/javascript" src="todos.js"></script>
  </body>
</html>
class Item {
    constructor(text, completed = false) {
      this.text = text;
      this.completed = completed;
    }

    display() {
      if (this.completed) {
        console.log('(x)', this.text);
      }
      else {
        console.log('( )', this.text);
      }
    }

    toggle() {
      this.completed = !this.completed;
    }
  }

  class ToDo {

    constructor(title = 'My items', items = []) {
      this.title = title;
      this.items = items;
    }

    display() {

      if (this.items.length === 0) {
        console.log('Your todo list is empty!');
      }
      else {
        console.log('My items:');
        for (let i = 0; i < this.items.length; i++) {
          if (this.items[i].completed) {
            console.log('(x)', this.items[i].text);
          }
          else {
            console.log('( )', this.items[i].text);
          }
        }
      }
    }

    add(item) {
      this.items.push(new Item(item));
      this.display();
    }

    change(index, value) {
      this.items[index].text = value;
      this.display();
    }

    toggleCompleted(index) {
      this.items[index].completed = !this.items[index].completed;
      this.display();
    }

    remove(index) {
      this.items.splice(index, 1);
      this.display();
    }

    toggleAll() {
      console.log('********** Toggle All ***********');

      //Calculer le nombre de todo vrai (completed ==== true)
      let counter = 0;
      for (let i = 0; i < this.items.length; i++) {
        if (this.items[i].completed === true) {
          // counter = counter + 1;
          counter++;
        }
      }

      //console.log("Élements complétés: ", counter);
      //Si tout est vrai
      if (this.items.length === counter) {
        //il faut tout mettre à faux
        for (let i = 0; i < this.items.length; i++) {
          this.items[i].completed = false;
        }
      }
      else { //Sinon
        //il faut tout mettre à vrai
        for (let i = 0; i < this.items.length; i++) {
          this.items[i].completed = true;
        }

      }

      this.display();
    }
  };

  class Handlers {
    constructor(todoList = new ToDo(), view = new View()) {
      this.todoList = todoList;
      this.view = this.view;
    }

    display() {
      todoList.display(); // en console
      view.display(); // en graphique
    }
    toggleAll() {
      todoList.toggleAll();
    }
    add() {
      let addTodoTextInput = document.getElementById('addTodoTextInput');
      todoList.add(addTodoTextInput.value);
      addTodoTextInput.value = '';
      this.display();
    }
    change() {
      let changeTodoPositionInput = document.getElementById('changeTodoPositionInput');
      let changeTodoTextInput = document.getElementById('changeTodoTextInput');
      todoList.change(changeTodoPositionInput.valueAsNumber, changeTodoTextInput.value);
      changeTodoPositionInput.value = '';
      changeTodoTextInput.value = '';
      this.display();
    }
    remove(position) {
     // let deleteTodoPositionInput = document.getElementById('deleteTodoPositionInput');
      todoList.remove(position);
     // deleteTodoPositionInput.value = '';
      this.display();
    }

    toggleCompleted(position) {
     // let toggleCompletedPositionInput = document.getElementById('toggleCompletedPositionInput');
      todoList.toggleCompleted(position);
      //toggleCompletedPositionInput.value = '';
      this.display();
    }
  }

  class View {
    constructor(todoList = new ToDo(), selector = 'ul') {
      this.todoList = todoList;
      this.todoElt = document.querySelector('ul');
    }
    displayConsole() {
      todoList.display();
    }
    display() {
      this.todoElt.innerHTML = '';
      for (let i = 0; i < todoList.items.length; i++) {
        let li = document.createElement('li');
        li.id = i;

        let item = todoList.items[i];

        li.appendChild(this.createCheckbox(item.completed));

        let text = document.createTextNode(item.text);
        li.appendChild(text);
        if (item.completed) {
          li.style.textDecoration = 'line-through';
        }

        li.appendChild(this.createDeleteButton());

        this.todoElt.appendChild(li);
      }
    }
    createCheckbox(completed) {
      let checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.checked = completed;
      checkbox.className = 'checkbox';
      return checkbox;
    }

    createDeleteButton() {
      let deleteButton = document.createElement('button');
      deleteButton.textContent = 'DELETE';
      deleteButton.className = 'deleteButton';
      return deleteButton;
    }

    setUpEventListeners() {
      this.todoElt.addEventListener('click',function(event) {
        //console.log(event.target);
        let elementClicked = event.target;
        if (elementClicked.className === 'deleteButton'){
          handlers.remove(parseInt(elementClicked.parentNode.id));
        }
        if (elementClicked.className === 'checkbox'){
          handlers.toggleCompleted(parseInt(elementClicked.parentNode.id));
        }
      });
    }
  }

  let todoList = new ToDo('My items', [new Item('item1'), new Item('item2', true), new Item('item3')]);

  let view = new View(todoList);

  let handlers = new Handlers(todoList, view);

  view.display();
  view.setUpEventListeners();

  // let displayButton = document.getElementById('displayButton');
  // let toggleAllButton = document.getElementById('toggleAllButton');

  // displayButton.addEventListener('click', function(){
  //   todoList.display();
  // });

  // toggleAllButton.addEventListener('click', function(){
  //   todoList.toggleAll();
  // });

17. v14 - clean et forEach (Exercice)

  • Nettoyer le code en supprimant les parties redondantes ou inutiles
  • Utiliser la méthode forEach pour parcourir la liste des todos dans la méthode display() de la class View