Arrays et traitement de masse
Dans le chapitre précédent il y a quelques trucs que l'on aurait dû faire mieux, mais on a pas les outils. On va changer ça avec ce chapitre.
For
for()
est un mot-clef qui permet de définir un bloc de code que l'on va répéter plusieurs fois.
Il se présente comme ceci:
- Initialisation de la boucle: une instruction qui s'exécute une fois, avant de rentrer dans la boucle.
- condition capturante : une condition qui est verifiée à chaque tour de boucle, on continue de faire des tours tant que la condition est vraie.
- Instruction à executer après chaque tour : généralement là pour faire progresser quelque chose qui nous permettera d'invalider la condition capturante.
Du coup, si j'écris ...
// pour une variable i valant 0
// tant que i est inférieure ou égal à 10
// en augmentant i de 1 à chaque tour...
for (let i = 0; i <= 10; i = i + 1) {
console.log(i); // on affiche i
}
C'est comme si j'écrivais ...
console.log(0);
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
console.log(6);
console.log(7);
console.log(8);
console.log(9);
console.log(10);
Ce qui est plutôt sympa parce que si maintenant j'ai besoin de faire ça de 0 à 150 plutôt que de 0 à 10, je peux juste changer le chiffre dans la condition du for()
.
for (let i = 0; i <= 150; i = i + 1) {
console.log(i);
}
plutôt que ça ...
console.log(0);
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
console.log(6);
console.log(7);
console.log(8);
console.log(9);
console.log(10);
console.log(11);
console.log(12);
console.log(13);
console.log(14);
console.log(15);
console.log(16);
console.log(17);
console.log(18);
console.log(19);
console.log(20);
console.log(21);
console.log(22);
console.log(23);
console.log(24);
console.log(25);
console.log(26);
console.log(27);
console.log(28);
console.log(29);
console.log(30);
console.log(31);
console.log(32);
console.log(33);
console.log(34);
console.log(35);
console.log(36);
console.log(37);
console.log(38);
console.log(39);
console.log(40);
console.log(41);
console.log(42);
console.log(43);
console.log(44);
console.log(45);
console.log(46);
console.log(47);
console.log(48);
console.log(49);
console.log(50);
console.log(51);
console.log(52);
console.log(53);
console.log(54);
console.log(55);
console.log(56);
console.log(57);
console.log(58);
console.log(59);
console.log(60);
console.log(61);
console.log(62);
console.log(63);
console.log(64);
console.log(65);
console.log(66);
console.log(67);
console.log(68);
console.log(69);
console.log(70);
console.log(71);
console.log(74);
console.log(75);
console.log(76);
console.log(77);
console.log(78);
console.log(79);
console.log(80);
console.log(81);
console.log(82);
console.log(84);
console.log(85);
console.log(86);
console.log(87);
console.log(88);
console.log(89);
console.log(90);
console.log(91);
console.log(92);
console.log(93);
console.log(94);
console.log(95);
console.log(96);
console.log(97);
console.log(98);
console.log(99);
console.log(100);
console.log(101);
console.log(102);
console.log(103);
console.log(104);
console.log(105);
console.log(106);
console.log(107);
console.log(108);
console.log(109);
console.log(110);
console.log(111);
console.log(112);
console.log(113);
console.log(114);
console.log(115);
console.log(116);
console.log(117);
console.log(118);
console.log(119);
console.log(120);
console.log(121);
console.log(122);
console.log(123);
console.log(124);
console.log(125);
console.log(126);
console.log(127);
console.log(128);
console.log(129);
console.log(130);
console.log(131);
console.log(132);
console.log(133);
console.log(134);
console.log(135);
console.log(136);
console.log(137);
console.log(138);
console.log(139);
console.log(140);
console.log(141);
console.log(142);
console.log(143);
console.log(144);
console.log(145);
console.log(146);
console.log(147);
console.log(148);
console.log(149);
console.log(150);
Outre le fait que c'est plus simple de changer une boucle, c'est d'autant plus important pour faire des manipulations répetitives parce que vous n'avez pas remarqués qu'il manquait console.log(83)
dans le bloc ci-dessus.
"Pourquoi i
?" "C'est quoi i
?"
i
?" "C'est quoi i
?"i
est le possible raccourcis de deux mot anglais:
index
que l'on pourrait traduire par "entrée" comme dans la phrase "abaca est la première entrée du dictionnaire".iteration
qui est le même mot en francais (à un accent près). Itérer est l'action de répeter le même procédé plusieurs fois. Une itération étant une répétetition.
Mais i
est seulement un nom, et en pratique cette variable contient le numéro de l'entrée à laquelle nous sommes dans la boucle. Ou en tout cas c'est quasiment toujours le cas. Rien nous force à ce que ce soi comme ça mais ne pas le faire reviens à intentionnellement rendre son code illisible, et personne devrait faire ça.
Si vraiment le nom i
vous perturbe vous pouvez utiliser autre chose comme :
for(let round = 0; round <= 10; round = round + 1){
// gna gna gna, le truc qu'on fait plein de fois
}
Mais sachez quand même que c'est le nom que presque tout le monde utilisera en dehors de vous. Donc il faudra au moins être capable de le lire.
Arrays
Pour rappel, un objet est une valeur représentant un ensemble de propriétés. Chaque propriété ayant un nom et une valeur.
Exemple:
{
name: "Maureen",
description:"Communication & Marketing",
status:"🌴",
img:{
src:"public/user/1856298.jpg",
alt:"Maureen's profile picture"
},
}
La propriété name
vaux "Maureen"
, la propriété status
vaux "🌴"
et la propriété img
est vaux un autre objet qui lui même a deux propriétés.
On dit d'un objet qu'il est une valeur composite, parce qu'il est composée de plusieurs valeurs.
Il existe un autre type de valeur composite qui se marrie très bien avec les boucles, ce sont les arrays ("tableaux" en francais).
Un array est une collection de plusieurs valeurs delimitées par des crochets []
.
Exemple:
[38, "test", false, 87.39];
Les valeurs d'un array sont identifiables par une adresse plutôt qu'un nom. La première adresse d'un array étant 0.
Pour cibler une valeur c'est aussi avec les crochet []
juste après le nom de l'array, dans lesquels on rentre l'adresse de la valeur que l'on veut.
const myArray = [38, "test", false, 87.39];
console.log(myArray[0]); // 38
console.log(myArray[1]); // "test"
console.log(myArray[2]); // false
console.log(myArray[3]); // 87.39
Il est possible de connaitre le nombre de valeurs que les arrays contiennent puisqu'ils ont une propriété .length
qui contient cette valeur.
(oui un array est aussi un objet, donc il a des propriétés, mais on ne va pas s'attarder là dessus)
C'est exactement pour ça qu'ils sont les amis des boucles, parce que je peux réécrire le bout de code ci-dessus en :
const myArray = [38, "test", false, 87.39];
// pour une variable i valant 0
// tant que i est inférieur au nombre d'éléments de l'array
// en augmentant i de 1 à chaque tour...
for (let i = 0; i < myArray.length; i = i + 1) {
console.log(myArray[i]);
}
- Au 1er tour
i
vaudra0
doncmyArray[i]
vaudramyArray[0]
donc38
- Au 2ème tour
i
vaudra1
doncmyArray[i]
vaudramyArray[1]
donc"test"
- Au 3ème tour
i
vaudra2
doncmyArray[i]
vaudramyArray[2]
doncfalse
- Au 4ème tour ... blah blah blah
En pratique c'est extrêmement utile puisque je peux reprendre mon exemple du chapitre précédent, dans lequel on affichait trois profils utilisateurs en fonction du bouton que l'on clique.
Sauf que ce coup-ci je peux mettre tous mes utilisateurs dans un array, et utiliser une boucle for()
pour créer autant de boutons que j'ai d'utilisateurs.
(pour faire plus simple pour le moment je vais juste garder les noms des utilisateurs, on leur rendra le reste de leurs profils dans quelques instants)
html
<nav></nav>
<main></main>
js
const users = ["Maureen", "Jean-Baptiste", "Tiphaine"];
const nav = document.querySelector("nav");
for (let i = 0; i < users.length; i = i + 1) {
nav.insertAdjacentHTML("beforeend", `<button>${users[i]}</button>`);
}
Et hop! Un bouton par utilisateur, sans copier-coller des trucs, et sans avoir à modifier quoique ce soit si on devait changer pour un array de 150 utilisateurs.
Ceci dit, on s'est pas mal éloignés du problème parce que nous n'avons pas réellement un array d'utilisateurs mais un array de strings. On peut s'en rapprocher en ayant un array d'objets, chaque objets ayant juste un nom.
const users = [
{ name: "Maureen" },
{ name: "Jean-Baptiste" },
{ name: "Tiphaine" },
];
const nav = document.querySelector("nav");
for (let i = 0; i < users.length; i = i + 1) {
//remarquez que ça change un petit peu vv ICI vv
nav.insertAdjacentHTML("beforeend", `<button>${users[i].name}</button>`);
}
- Au 1er tour
i
vaudra0
doncusers[i]
vaudrausers[0]
doncusers[0].name
vaudra"Maureen"
- Au 2ème tour
i
vaudra1
doncusers[i]
vaudrausers[1]
doncusers[1].name
vaudra"Jean-Baptiste"
- Au 3ème tour
i
vaudra2
doncusers[i]
vaudrausers[2]
doncusers[2].name
vaudra"Tiphaine"
Si on reprend l'astuce un peu moche de notre id qui permet de sélectionner l'élément fraichement ajouté, on peut accrocher une fonction à nos boutons comme ceci:
const users = [
{ name: "Maureen" },
{ name: "Jean-Baptiste" },
{ name: "Tiphaine" },
];
const nav = document.querySelector("nav");
const main = document.querySelector("main");
for (let i = 0; i < users.length; i = i + 1) {
//remarquez que ça change un petit peu vvvv ICI vvvv
nav.insertAdjacentHTML("beforeend", `<button id="fresh-user-btn">${users[i].name}</button>`);
const currentBtn = document.querySelector("#fresh-user-btn");
currentBtn.addEventListener("click",() => {
main.innerHTML = `<h2>${users[i].name}</h2>`
})
currentBtn.id = "";
}
Maintenant qu'on a vu la version avec un array d'objets simples, ça ne devrait pas être trop dur de comprendre la version totale:
const users = [
{
name: "Maureen",
description: "Communication & Marketing",
status: "🌴",
img: {
src: "https://picsum.photos/200/200",
alt: "Maureen's profile picture"
},
},
{
name: "Jean-baptiste",
description: "Entrepreneur",
status: "Tout est bon dans le poulet",
img: {
src: "https://picsum.photos/300/200",
alt: "Jean-baptiste's profile picture"
},
},
{
name: "Tiphaine",
description: "Manager",
status: "🍂 Sweater szn 🧡",
img: {
src: "https://picsum.photos/200/300",
alt: "Tiphaine's profile picture"
},
}
];
const nav = document.querySelector("nav");
const main = document.querySelector("main");
for (let i = 0; i < users.length; i = i + 1) {
nav.insertAdjacentHTML("beforeend", `<button id="fresh-user-btn">${users[i].name}</button>`);
const currentBtn = document.querySelector("#fresh-user-btn");
currentBtn.addEventListener("click",() => {
main.innerHTML = htmlOfUser(users[i]);
})
currentBtn.id = "";
}
function htmlOfUser(user){
const userHTML = `
<article>
<img src="${user.img.src}" alt="${user.img.alt}">
<hgroup>
<h2>${user.name}</h2>
<small>${user.status}</small>
</hgroup>
<p>${user.description}</p>
</article>`
return userHTML;
}
document.querySelectorAll()
document.querySelector()
, que vous connaissez, nous donne l'objet javascript du premier élément qui corrrespond au sélecteur CSS qu'on lui donne en paramètre.
document.querySelectorAll()
, nous donne un array qui contient tous les objets javascript de tous les éléments du document qui correspondent au selecteur CSS qu'on lui donne en paramètre.
Si on admet le CSS et l'HTML ci dessous...
css
.warning{
background-color: #db4d3f;
}
html
<button>Retirer les warnings</button>
<ul>
<li>1</li>
<li class="warning">2</li>
<li>3</li>
<li>4</li>
<li class="warning">5</li>
<li class="warning">6</li>
<li class="warning">7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li class="warning">11</li>
</ul>
... et qu'on log le résultat de document.querySelectorAll()
...
console.log(document.querySelectorAll(".warning"));
... on se retrouve avec une collection d'éléments, tous ayant la classe "warning".
La preuve étant qu'on peux s'afficher les 3ème comme ceci:
console.log(document.querySelectorAll(".warning")[2]);
Mais ça fait un peu trop long et pas très lisible parce que le [2]
qui est très important est loin à droite, donc ce sera mieux comme ceci:
const warnings = document.querySelectorAll(".warning");
console.log(warnings[2]);
Ce qui fait que maintenant vous devriez être en mesure de comprendre ceci:
const warnings = document.querySelectorAll(".warning");
document.querySelector("button").addEventListener(() => {
for(let i = 0; i < warnings.length; i = i + 1){
warnings[i].remove();
}
})
... qui va faire que tous les éléments ayant la classe "warning" vont disparaitres quand on va appuyer sur le bouton.
Problème
Reprennez l'exemple des todos du chapitre d'avant et ajoutez un bouton qui supprime toutes les todo qui sont cochées.
Solution
css
.done {
text-decoration: line-through;
color: grey;
}
html
<main>
<button id="add">Add</button>
<!-- le bouton purge est nouveau -->
<button id="purge">🧹</button>
<ul id="todos"></ul>
</main>
js
const todos = document.querySelector("#todos");
const addBtn = document.querySelector("button#add");
// début de ce qui est nouveau
const purgeBtn = document.querySelector("button#purge");
purgeBtn.addEventListener("click", () => {
const achievedTasks = document.querySelector(".done");
for(let i = 0; i < achievedTasks.length; i = i + 1){
achievedTasks.remove();
}
})
// fin de ce qui est nouveau
addBtn.addEventListener("click",() => {
todos.insertAdjacentHTML("beforeend",`
<li id="new-todo" class="todo">
<article>
<h2 contenteditable>Titre</h2>
<p contenteditable>Description</p>
<input type="checkbox" />
<button>❌</button>
</article>
</li>
`)
const newTodo = document.querySelector("#new-todo");
const checkbox = newTodo.querySelector("input");
const button = newTodo.querySelector("button");
checkbox.addEventListener("change", () => {
if(checkbox.checked){
newTodo.classList.add("done");
}else{
newTodo.classList.remove("done");
}
})
button.addEventListener("click", () => {
newTodo.remove();
});
newTodo.id = "";
})