Greška dupliciranja funkcije za uređivanje DOM elemenata u JavaScriptu
Pretpostavimo da imamo neki spisak:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Uzmimo sam spisak i njegove stavke u posebne promenljive:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Učinimo da se stavke našeg spiska mogu uređivati pomoću pojavljujućeg input polja:
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);
});
}
Pretpostavimo sada da želimo da u spisak moguće je dodavati nove stavke. Neka ispod spiska postoji odgovarajuće input polje za to:
<input id="adder">
Uzmimo referencu na ovo input polje u promenljivu:
let adder = document.querySelector('#adder');
Učinimo da se gubitkom fokusa input polja u spisak doda nova stavka sa tekstom, preuzetim iz našeg input polja:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Pretpostavimo sada da želimo da se i novododate stavke takođe mogu uređivati. Samo po sebi za njih uređivanje neće raditi, jer kada smo postavljali osluškivač klika na stavke spiska, tih stavki još nije bilo.
Pogledajmo moguće varijante rešavanja ovog problema.
Prvo rešenje
Najjednostavnije rešenje je dupliciranje
koda funkcije func, povezujući je
i za novokreirane stavke:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// ovde dupliramo kod
});
ul.append(li);
});
Naravno, u ovom rešenju odmah vidimo nedostatak - duplicirati kod nije ispravno.
Drugo rešenje
Za rešavanje problema dupliciranja
logično je izneti funkciju func
napolje, napravivši je kao 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);
}
Ovde nas upravo čeka problem.
Stvar je u tome što je naša funkcija koristila
promenljivu li, dobijenu
iz spoljašnje oblasti vidljivosti.
Ali nakon iznošenja funkcije ova
promenljiva sada nije vidljiva!
Za rešavanje problema prosledićemo
našu li kao parametar:
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 ovde naše rešenje stvara još jedan problem. Stvar je u tome što ne možemo tek tako proslediti parametar u osluškivač događaja:
for (let li of lis) {
li.addEventListener('click', func(li)); // ne radi!
}
Za rešavanje ovog problema jednostavno ćemo pozvati našu funkciju unutar anonimnog osluškivača:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
I na isti način ćemo postupiti prilikom kreiranja nove stavke spiska:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Treće rešenje
Postoji i elegantnije rešenje. Možemo jednostavno iskoristiti delegiranje. U ovom slučaju problem sa novim stavkama spiska jednostavno neće nastati:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // hvata upravo klik na li, ne na 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;
});
}
});
U ovom slučaju petlja po stavkama spiska nam uopšte neće trebati, a kod za kreiranje nove stavke spiska smanjiće se na ovo:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});