Compare commits

..

2 Commits

Author SHA1 Message Date
EmmanuelTiamzon d1e3f215aa update 2026-04-07 12:08:01 +02:00
EmmanuelTiamzon cc752a0fc9 commencement du tp2, ex0 fini 2026-04-07 12:07:22 +02:00
23 changed files with 1055 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# TP javascript : DOM
Un peu [d'aide](./aide.md).
#### Ex0
On stocke dans un objet une liste de favoris :
```js
let favs = [
{
nom:"Google",
url:"http://www.google.fr"
},
{
nom:"Le Monde",
url:"http://www.google.fr"
},
{
nom:"L'Equipe",
url:"http://www.lequipe.fr"
}
];
```
Compléter le fichier `favoris.js` de manière à créer dans la page html la liste de liens
correspondant
![lien}](./img/liens.png?style=centerme)
Il vous faut créer dynamiquement les noeuds nécessaires avec l'api dom de javascript.
#### Ex1
Il s'agit de réaliser une version "simple" du jeu (whac-a-mole)[https://en.wikipedia.org/wiki/Whac-A-Mole].
Le principe du jeu est de frapper à l'aide d'un marteau sur le plus grand nombre
de taupes parmi celles qui sortent pour un temps très limité et aléatoirement
des trous situés sur un panneau de contrôle.
<div align="center">
<img src="./img/mole.png">
</div>
#### Ex2
Vous devez compléter les [sources](./src/ex1) d'un jeu qui consiste à essayer
d'éteindre toutes les lumières d'une grille en utilisant la règle suivante.
Quand on allume/éteint une lumière, on allume/éteint aussi ses voisines.
<div align="center">
<img src="./img/lights.png">
</div>
Chaque lumière, via l'interface dataset, possède un numéro (de 0 à size^2-1).
+132
View File
@@ -0,0 +1,132 @@
#### Selection d'éléments
```js
// le premier
document.querySelector(".box")
// ou tous
document.querySelectorAll(".box")
// avec l'id
document.getElementById("toto")
// selection d'un élément dans un autre
let container = document.querySeletor('.container')
container.querySelector('.box')
```
#### Traverser le dom
```js
var box = document.querySelector(".box")
box.nextElementSibling
box.previousElementSibling
box.parentElement
box.childNodes
```
#### Création/insertion d'éléments
```js
let p = document.createElement('p')
p.textContent="blabla"
document
.getElementById("myDiv")
.appendChild(p)
div.replaceChildren(p)
```
#### Gestionnaire évènementiels
```js
document.querySelector(".button").addEventListener("click", (e) => { /* ... */ })
document.querySelector(".button").addEventListener("mouseenter", (e) => { /* ... */ })
document.addEventListener("keyup", (e) => { /* ... */ })
```
#### window/document prêt
```js
document.addEventListener("DOMContentLoaded",() = > { .....})
// le dom a été construit (on n'attend pas le chargement du css, images, etc.
windon.addEventListener('load',()=>{...})
// le dom est prêt et toutes les ressources ont été chargées.
```
#### dataset
```html
<div id="user" data-id="1234567890" data-user="carinaanand" data-date-of-birth>
Carina Anand
</div>
```
```javascript
const el = document.querySelector("#user");
// el.id === 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'carinaanand'
// el.dataset.dateOfBirth === ''
```
#### Local storage
```js
localStorage.setItem('monChat', 'Tom')
let cat = localStorage.getItem('myCat')
localStorage.clear()
```
#### Limiter les appels successifs à une fonction
```js
function debounce(f,wait)
{
let timeout
return function(...args){
clearTimeout(timeout)
timeout=setTimeout(()=>f(...args),wait)
}
}
```
#### Gestion du css depuis le DOM
```js
// Select the first .box and change its text color to #000
document.querySelector(".box").style.color = "#000";
// Set color to #000 and background to red
var box = document.querySelector(".box")
box.style.color = "#000"
box.style.backgroundColor = "red"
// Set all styles at once (and override any existing styles)
box.style.cssText = "color: #000; background-color: red"
// Hide and show an element by changing "display" to block and none
document.querySelector(".box").style.display = "none"
document.querySelector(".box").style.display = "block"
// Add, remove, and the toggle the "focus" class
var box = document.querySelector(".box")
box.classList.add("focus")
box.classList.remove("focus")
box.classList.toggle("focus")
box.classList.add("focus", "highlighted")
box.classList.remove("focus", "highlighted")
box.classList.add("focus", "highlighted")
box.classList.remove("focus", "highlighted")
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<title>Favoris</title>
<script src="./js/favoris.js"></script>
</head>
<body>
<main>
<h1>Favoris</h1>
</main>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
let favoris = [
{
nom:"Google" ,
url:"https://www.google.fr",
img:"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/200px-Google_2015_logo.svg.png"
},
{
nom:"Le Monde",
url:"https://www.lemonde.fr",
img:"https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Lemonde_fr_2005_logo.svg/200px-Lemonde_fr_2005_logo.svg.png?uselang=fr"
},
{
nom:"L'Equipe",
url:"https://www.lequipe.fr",
img:"https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/L%27%C3%89quipe_wordmark.svg/200px-L%27%C3%89quipe_wordmark.svg.png"
}
]
window.addEventListener("load",()=>{
let laListe = document.createElement("ul");
favoris.forEach((favori) => {
let li = document.createElement("li");
let lien = document.createElement("a");
let image = document.createElement("img");
lien.href = favori.url;
lien.textContent = favori.nom;
image.src = favori.img;
lien.append(image);
li.append(lien);
laListe.append(li);
});
document.body.append(laListe);
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

+37
View File
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Whack-a-Mole Game</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<div class="game">
<h1>Whack-a-Mole!</h1>
<div class="score-board">
<span>Score: </span><span id="score">0</span>
</div>
<div class="holes">
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole up"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
</div>
<button id="startButton">START</button>
</div>
</main>
<script src="script.js"></script>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
const holes = document.querySelectorAll('.hole');
const moles = document.querySelectorAll('.mole');
const scoreBoard = document.getElementById('score');
const startButton = document.getElementById('startButton');
let lastHole;
let timeUp = false;
let score = 0;
let duration = 30
function randomTime(min, max) {
return Math.round(Math.random() * (max - min) + min);
}
function randomHole(holes) {
const idx = Math.floor(Math.random() * holes.length);
const hole = holes[idx];
if (hole === lastHole) {
return randomHole(holes);
}
lastHole = hole;
return hole;
}
function peep() {
const time = randomTime(1000, 1500);
const hole = randomHole(holes);
// TODO
setTimeout(() => {
// TODO
//
}}, time);
}
function startGame() {
scoreBoard.textContent = 0;
score = 0;
timeUp = false;
// TODO
}
function bonk(e) {
// TODO
}
moles.forEach(mole => mole.addEventListener('click', bonk));
startButton.addEventListener('click', startGame);
+68
View File
@@ -0,0 +1,68 @@
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
/* font-family: Arial, sans-serif;*/
}
.game {
text-align: center;
}
.holes {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
width: 400px;
margin: 20px auto;
}
.hole {
width: 100px;
height: 100px;
background : radial-gradient(rgb(150,250,150), #00ee00);
/* background-color: red;/*#8b4513;*/
/* background-image : url('img/mole2.png');*/
position: relative;
border-radius: 50%;
margin: 10px;
overflow: hidden;
}
.mole img{
width: 10px;
height: 10px;
/* background-color: #555;*/
position: absolute;
bottom: -100px;
left: 0px;
/*border-radius: 50%;*/
transition: bottom 0.3s;
}
.mole.up img{
/* background-image : url('img/mole2.png');
background-size:contain;
background-repeat: no-repeat;*/
width:90px;
height:90px;
bottom: 0px;
}
.mole.whacked img {
content: url('img/mole-whacked.png');
/* background-image : url('img/mole-whacked.png');
background-size:contain;
background-repeat: no-repeat;*/
width:90px;
height:90px;
bottom: -20px;
}
.score-board {
font-size: 1.5em;
margin: 20px;
}
+71
View File
@@ -0,0 +1,71 @@
// variables de vue
const button = document.querySelector("input[type=submit]");
const select = document.querySelector("select");
const board = document.querySelector(".board");
const message = document.querySelector("#message");
let lights = null
// state <-> tableau de booléen pour
// stocker l'état des lumières
// neighbors <-> tableau des voisins de chaque lumière
let state = [];
let neighbors = [];
let newBoard = ( (size ) => {
let frag = document.createDocumentFragment()
// TODO
// la fonction crée dans le dom la grille des lumières
board.replaceChildren(frag)
});
let calcNeighbours = ( (size) =>{
let res = [];
for(let i = 0; i < size; i ++){
for(let j = 0; j < size; j++){
let v = [];
v.push(i*size + j);
if ( (j - 1) >= 0) v.push(i*size + j - 1);
if ( (j + 1) < size) v.push(i*size + j + 1);
if ( (i - 1) >= 0) v.push(size*(i-1) + j );
if ( (i + 1) < size) v.push(size*(i+1) + j);
res.push(v)
}
}
return res
});
function play(){
let size = select.value;
newBoard(size);
neighbors = calcNeighbours(size);
lights = document.querySelectorAll(".board span");
state = Array.from({length: size*size}, () => Math.random() < 0.5 ? 1:0);
state.forEach((el,i)=>{
if (el) lights[i].classList.toggle("off");
});
message.textContent = "";
}
// abonnements
document
.querySelector(".board")
.addEventListener("click", ev => {
// TODO
// permet de choisir d'éteindre/allumer une lumière et
// ses voisines
// Il faut en outre detecter la fin de partie
});
button.addEventListener("click",play);
play();
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<!-- Centered viewport -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body>
<main>
<h4>Lights</h4>
<p>Le but est d'éteindre l'ensemble des lumières.</p>
<p id="message"></p>
<article>
<div class="board">
<!--div class="row">
<span data-num="0"></span>
<span data-num="1"></span>
<span data-num="2"></span>
</div>
<div class="row">
<span data-num="3"></span>
<span data-num="4"></span>
<span data-num="5"></span>
</div>
<div class="row">
<span data-num="6"></span>
<span data-num="7"></span>
<span data-num="8"></span-->
</div>
</div>
</article>
<div role="group">
<select aria-label="Select size" required>
<option selected value="3">3x3</option>
<option value="2">2x2</option>
<option value="6">6x6</option>
<option value="7">7x7</option>
<option value="8">8x8</option>
<option value="10">10x10</option>
</select>
<input type="submit" value="New Game">
</div>
</main>
<script src="app.js"></script>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
/*.grid {
display:grid;
grid-template-columns : 100px 100px 100px;
grid-template-rows: auto;
grid-gap: 10px;
}*/
.board > div {
display : flex;
}
.board span {
display: inline-block;
vertical-align:baseline;
width: 100px;
height: 100px;
/*padding : 1em;*/
margin : 0.1em;
cursor: pointer;
border: 1px solid #444;
border-radius: 50px;
background: radial-gradient(rgb(150,250,150), #00ee00);
}
.board span.off {
background: #009900;
}
.board span.off:hover {
background: #3AB903;
}
.board span:hover {
background: #3AB903;
background: radial-gradient(rgb(66, 255, 66), #065006);
}
/*
.wrapper {
display : flex;
justify-content: space-around;
}
.wrapper .grid {
flex-basis : auto;
}
*/
+172
View File
@@ -0,0 +1,172 @@
#### Ex3 : modele MVC
Le but est d'écrire une todolist en javascript, en respectant le pattern MVC. Il est important de mener à bout cet exercice, car il nous servira
de fil rouge notamment en ajoutant une api rest et ajax pour la communication avec le service.
<div align="center">
<img src="./img/todo.png">
</div>
Le contrôleur a accès à la vue et au modèle.
##### Le modèle
Cette "classe" (rien à compléter) utilise
l'objet [localStorage](https://developer.mozilla.org/fr/docs/Web/API/Window/localStorage)
pour sauvegarder la todolist.
Chaque todo est un objet json de la forme
```js
{ id : 1 , text : "apprendre le js", done : false }
```
La liste des méthodes publiques de cette classe
```js
/** @brief return the todolist
* @param filter "all" or "active" or "done"
* @return array of todos
*/
getTodos(filter)
/** @brief add (and save) a new todo with todoText
* @param todoText : text of the new todo
*/
add(todoText)
/** @brief delete a todo
* @param id id of the todo
*/
delete(id)
/** @brief update a todo
* @param id id of the todo
*/
edit(id,updatedText)
/** @brief toggle a todo (done<->active)
* @param id id of the todo
* @param updatedText text of the todo
*/
toggle(id)
```
##### La vue
Cette "classe" permet au contrôleur de gérer la vue.
Liste des méthodes publiques
```js
/** @brief change the active tab (all,active or done)
* @param filter the active tab (all, active or done)
*/
setFilterTabs(filter)
/** @brief update the todo list with todos
* @param todos array of todo
*/
renderTodoList(todos)
```
Le contrôleur peut s'abonner (notification de la vue au contrôleur)
aux événements suivants :
add/delete/edit/toggle todo : avec `bind{Add|Delete|Edit|Toggle}Todo`
Ces méthodes d'abonnement prennent en paramètre une fonction du contrôleur qui est appelé par la vue pour lui notifier
une requête.
```js
/** @brief subscribe to add event
* @param handler function(text)
*/
bindAddTodo(handler)
/** @brief suscribe to delete event
* @param handler function(id)
*/
bindDeleteTodo(handler)
/** @brief suscribe to edit event
* @param handler function(id,text)
*/
bindEditTodo(handler)
/** @brief suscribe to toggle event
* @param handler function(id)
*/
bindToggleTodo(handler)
```
##### Le contrôleur
C'est lui qui gére le routage. Il y a trois urls possibles `index.html/#/{all|active|done}` (listener sur l'évenement
dom `hashchange`.
Liste des méthodes
```js
/** @brief get the todolist from the model
* and use the view to render
*/
filterTodoList()
/** @brief binding function called by the view
* to add a new todo
* @param text text of the new todo
*/
addTodo(text)
/** @brief binding function called by the view
* to delete a todo
* @param id id of the todo
*/
deleteTodo(id)
/** @brief binding function to toggle the state
* of a todo
* @param id id of the todo
*/
toggleTodo(id)
/** @brief binding function to change the text
* of a todo
* @param id id of the todo
* @param changedText new text for the todo
*/
editTodo(id,changedText)
```
Par exemple, lorsque l'utilisateur ajoute une todo :
1. la vue est sensible à l'événement de soumission du formulaire,
2. la fonction reflexe récupére le texte saisie,
3. elle appelle la fonction avec laquelle le contrôleur s'est abonné, en lui passant le texte,
4. la fonction du contrôleur `addTodo` récupére le texte,
5. le contrôleur demande au modèle la création d'une nouvelle todo,
6. le contrôleur demande un rafraichissement de la vue.
#### Travail à faire
- Commpléter la méthode privée dans la vue `#createNewTodoElement` qui permet de créer un
nouvel élément dom représentant une todo. Ce qui est attendu est de la forme
```html
<li class="todo" data-id="1">
<label>
<input type="checkbox">
<span>apprendre un peu de javascript</span>
</label>
<i class="fas fa-trash"></i>
</li>
```
- Compléter la méthode `bindDeleteTodo` de la vue.
- Compléter la méthode `bindToggleTodo` de la vue.
- Compléter la méthode `bindEditTodo` de la vue.
#### Ex4
On veut maintenant utiliser l'oberserver Pattern. Quand le **modèle change**, il notifie automatiquement tous les observateurs.
> Le Modèle est un obervable.
> La Vue est un oberver.
- Ajouter dans le modèle un tableau d'obervateurs (callbacks), et une méthode notify.
- La vue devient un observeur.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+63
View File
@@ -0,0 +1,63 @@
:focus-visible {outline:none;}
.is-active {
color : #8EB901;
}
#add_form > div {
display : flex;
justify-content:space-between;
}
.todo {
display : flex;
align-items: center;
}
.todo checkbox,.todo i {
flex-grow : 0;
flex-shrink : 0;
flex-basis : auto;
}
.todo span {
flex-grow : 1;
flex-shrink : 1;
flex-basis : auto;
}
.todolist {
padding-left: 0;
}
.todolist li {
list-style: none;
padding: var(--pico-form-element-spacing-vertical) 0;
display: flex;
justify-content: space-between;
align-items: center;
}
#todos_filter {
display:flex;
justify-content : center;
}
#todos_filter a {
margin:1em;
}
.todolist i {
cursor: pointer;
}
.todolist li:not(:last-child) {
border-bottom: 1.5px solid var(--pico-form-element-border-color);
}
.todolist > li > label:has(input:checked) {
text-decoration: line-through;
}
footer {
text-align: center;
}
+53
View File
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Todo</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<header>
<h1>To Do List</h1>
</header>
<main>
<article>
<header>
<form id='add_form'>
<fieldset role="group">
<input id="input_todo" type="text" placeholder="Buy milk and eggs...">
<button><i class="fas fa-plus"></i></button>
</fieldset>
<div>
<span>
<a id="active" href="#/active"><i class="far fa-circle"></i></a>
<a id="done" href="#/done"><i class="fas fa-circle"></i></a>
<a id="all" href="#/all"><i class="fas fa-adjust"></i></a>
</span>
<small><span id="count">0</span>/50 characters</small>
</div>
</form>
</header>
<!-- Render todos -->
<ul id="todos_list" class="todolist"></ul>
</article>
</main>
</body>
<script src="js/model.js"></script>
<script src="js/view.js"></script>
<script src="js/controller.js"></script>
</html>
+64
View File
@@ -0,0 +1,64 @@
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.filter = "all";
/** Abonnements à la vue **/
this.view.bindAddTodo(this.addTodo.bind(this));
this.view.bindDeleteTodo(this.deleteTodo.bind(this));
this.view.bindToggleTodo(this.toggleTodo.bind(this));
this.view.bindEditTodo(this.editTodo.bind(this));
/** filtrages par url **/
this.routes = ['all','active','done'];
/** Routage **/
window.addEventListener("load",this.routeChanged.bind(this));
window.addEventListener("hashchange",this.routeChanged.bind(this));
}
routeChanged(){
let route = window.location.hash.replace(/^#\//,'');
this.filter = this.routes.find( r => r === route) || 'all';
this.filterTodoList();
}
filterTodoList () {
let todos = this.model.getTodos(this.filter)
this.view.renderTodoList(todos)
this.view.setFilterTabs(this.filter)
}
addTodo(text) {
this.model.add(text)
this.filterTodoList()
}
deleteTodo(id) {
this.model.delete(parseInt(id))
this.filterTodoList()
}
toggleTodo(id) {
this.model.toggle(parseInt(id))
this.filterTodoList()
}
editTodo(id, text) {
this.model.edit(parseInt(id),text)
this.filterTodoList()
}
}
const model = new Model()
const view = new View()
const app = new Controller(model, view)
+48
View File
@@ -0,0 +1,48 @@
class Model {
constructor() {
this.todos = JSON.parse(localStorage.getItem('todos')) || []
}
#commit(todos) {
localStorage.setItem('todos', JSON.stringify(todos))
}
getTodos(filter){
if (filter === "active")
return this.todos.filter(todo => !todo.done)
if (filter === "done")
return this.todos.filter(todo => todo.done)
return this.todos
}
add(todoText) {
const todo = {
id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
text: todoText,
done : false,
}
this.todos.push(todo)
this.#commit(this.todos)
}
edit(id, updatedText) {
this.todos = this.todos.map(todo =>
todo.id === id ? { id: todo.id, text: updatedText, done: todo.done} : todo
)
this.#commit(this.todos)
}
delete(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
this.#commit(this.todos)
}
toggle(id) {
this.todos = this.todos.map(todo =>
todo.id === id ? { id: todo.id, text: todo.text, done: !todo.done } : todo
)
this.#commit(this.todos)
}
}
+83
View File
@@ -0,0 +1,83 @@
class View {
charLimit = 70;
constructor() {
this.form = document.querySelector("#add_form")
this.input = document.querySelector("#input_todo")
this.list = document.querySelector("#todos_list")
this.tabs = document.querySelectorAll("#add_form span a")
this.loader = document.querySelector("#loader")
this.count = document.querySelector("#count")
this.input.addEventListener("input", (e) => {
if (e.target.value.length >= this.charLimit) {
e.target.value = e.target.value.substring(0,this.charLimit);
}
count.textContent = e.target.value.length;
});
count.nextSibling.textContent = `/${this.charLimit} characters`
}
#getTodo() {
return this.input.value
}
#resetInput() {
this.input.value = ''
count.textContent = 0
}
#createNewTodoElement(todo){
let li = document.createElement("li")
// TODO
return li
}
setFilterTabs(filter){
this.tabs.forEach( tab => {
if (filter === tab.id)
tab.classList.add("is-active")
else
tab.classList.remove("is-active")
})
}
renderTodoList(todos) {
let list = new DocumentFragment()
for (let todo of todos){
list.appendChild(this.#createNewTodoElement(todo))
}
this.list.replaceChildren(list)
}
/** Abonnements événements **/
bindAddTodo(handler) {
this.form.addEventListener("submit", (e=>{
e.preventDefault()
let text = this.#getTodo()
handler(text)
this.#resetInput()
}))
}
bindDeleteTodo(handler) {
// TODO
}
bindEditTodo(handler) {
// TODO
}
bindToggleTodo(handler) {
// TODO
}
}