Erreur de duplication de fonction d'édition d'éléments DOM en JavaScript
Supposons que nous ayons une certaine liste :
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Récupérons la liste elle-même et ses éléments dans des variables séparées :
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Faisons en sorte que les éléments de notre liste puissent être édités via un champ de saisie qui apparaît :
for (let li of lis) {
li.addEventListener('click', function func() {
let input = document.createElement('input');
input.value = list.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
});
}
Supposons maintenant que nous voulons pouvoir ajouter de nouveaux éléments à la liste. Pour cela, supposons que nous ayons un champ de saisie correspondant sous la liste :
<input id="adder">
Récupérons une référence à ce champ de saisie dans une variable :
let adder = document.querySelector('#adder');
Faisons en sorte qu'à la perte du focus du champ de saisie, un nouvel élément soit ajouté à la liste avec le texte pris de notre champ de saisie :
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Supposons maintenant que nous voulons que les éléments nouvellement ajoutés puissent également être édités. En soi, l'édition ne fonctionnera pas pour eux, car lorsque nous avons attaché le gestionnaire de clic aux éléments de la liste, ces éléments n'existaient pas encore.
Examinons les différentes options possibles pour résoudre ce problème.
Première solution
La solution la plus simple est de dupliquer
le code de la fonction func, en la liant
également pour les éléments nouvellement créés :
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// ici nous dupliquons le code
});
ul.append(li);
});
Bien sûr, dans cette solution, nous voyons immédiatement l'inconvénient - dupliquer le code n'est pas correct.
Deuxième solution
Pour résoudre le problème de duplication,
il est logique d'extraire la fonction func
vers l'extérieur, en en faisant une Function Declaration :
function func() {
let input = document.createElement('input');
input.value = list.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
}
C'est ici que nous rencontrons le problème.
Le fait est que notre fonction utilisait
la variable li, obtenue
depuis la portée externe.
Mais après l'extraction de la fonction, cette
variable n'est maintenant plus visible !
Pour résoudre le problème, nous allons passer
notre li en paramètre :
function func(li) {
let input = document.createElement('input');
input.value = list.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
}
Et ici, notre solution génère un autre problème. Le fait est que l'on ne peut pas simplement passer un paramètre au gestionnaire d'événement :
for (let li in lis) {
li.addEventListener('click', func(li)); // ne fonctionne pas !
}
Pour résoudre ce problème, nous allons simplement appeler notre fonction à l'intérieur d'un gestionnaire anonyme :
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Et nous procéderons de manière similaire lors de la création d'un nouvel élément de liste :
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Troisième solution
Il existe une solution plus élégante. On peut simplement utiliser la délégation d'événements. Dans ce cas, le problème avec les nouveaux éléments de liste ne se posera simplement pas :
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // nous capturons précisément le clic sur li, pas sur input
let li = event.target;
let input = document.createElement('input');
input.value = li.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
});
}
});
Dans ce cas, la boucle sur les éléments de la liste ne nous sera généralement plus nécessaire, et le code pour créer un nouvel élément de liste se réduira à ceci :
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});