Eroare de duplicare a funcției de editare a elementelor DOM în JavaScript
Să presupunem că avem o listă:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Să obținem lista în sine și elementele sale în variabile separate:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Să facem astfel încât elementele listei noastre să poată fi editate printr-un câmp de introducere care apare:
for (let li of lis) {
li.addEventListener('click', function func() {
let input = document.createElement('input');
input.value = li.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
});
}
Să presupunem acum că vrem să putem adăuga elemente noi în listă. Să presupunem că pentru aceasta avem un câmp de introducere corespunzător sub listă:
<input id="adder">
Să obținem o referință la acest câmp de introducere într-o variabilă:
let adder = document.querySelector('#adder');
Să facem astfel încât la pierderea focusului câmpului de introducere în listă să fie adăugat un nou element cu textul preluat din câmpul nostru de introducere:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Să presupunem acum că vrem ca și elementele nou adăugate să poată fi de asemenea editate. În sine, editarea pentru ele nu va funcționa automat, deoarece atunci când am atașat handler-ul de click pe elementele listei, aceste elemente nu existau încă.
Să ne uităm la posibilele variante de rezolvare a acestei probleme.
Soluția prima
Cea mai simplă soluție - este să duplicăm
codul funcției func, atașând-o
și pentru elementele nou create:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// aici duplicăm codul
});
ul.append(li);
});
Desigur, în această soluție vedem imediat un dezavantaj - duplicarea codului nu este corectă.
Soluția a doua
Pentru a rezolva problema duplicării
este logic să scoatem funcția func
în afară, făcând-o o Declarație de Funcție (Function Declaration):
function func() {
let input = document.createElement('input');
input.value = li.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
}
Aici ne așteaptă problema.
Faptul este că funcția noastră folosea
variabila li, obținută
din domeniul de vizibilitate extern.
Dar după scoaterea funcției, această
variabilă nu mai este vizibilă!
Pentru a rezolva problema, vom transmite
li-ul nostru ca parametru:
function func(li) {
let input = document.createElement('input');
input.value = li.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
}
Și aici soluția noastră generează încă o problemă. Faptul este că nu se poate transmite pur și simplu un parametru într-un handler de eveniment:
for (let li of lis) {
li.addEventListener('click', func(li)); // nu funcționează!
}
Pentru a rezolva această problemă, pur și simplu apelăm funcția noastră în interiorul unui handler anonim:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Și în mod similar procedăm la crearea unui nou element de listă:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Soluția a treia
Există o soluție mai elegantă. Se poate pur și simplu folosi delegarea (delegation). În acest caz problema cu elementele noi ale listei pur și simplu nu va apărea:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // prindem tocmai click-ul pe li, nu pe 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;
});
}
});
În acest caz bucla pentru elementele listei nici nu va mai fi necesară, iar codul pentru crearea unui nou element de listă se va reduce la asta:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});