Napaka podvajanja funkcije za urejanje DOM elementov v JavaScript
Recimo, da imamo nek seznam:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Pridobimo seznam in njegove elemente v ločene spremenljivke:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Naredimo tako, da lahko elemente našega seznama urejamo z vnosnim poljem, ki se prikaže:
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);
});
}
Recimo sedaj, da želimo v seznam dodajati nove elemente. Naj bo za to pod seznamom ustrezno vnosno polje:
<input id="adder">
Pridobimo referenco na to vnosno polje v spremenljivko:
let adder = document.querySelector('#adder');
Naredimo tako, da ob izgubi fokusa vnosnega polja v seznam doda nov element z besedilom, vzetim iz našega vnosnega polja:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Recimo sedaj, da želimo, da lahko tudi na novo dodane elemente urejamo. Samo po sebi zanje urejanje ne bo delovalo, ker ko smo dodajali obravnavalnik klikov na elemente seznama, teh elementov še ni bilo.
Poglejmo si možne variante rešitve te težave.
Prva rešitev
Najenostavnejša rešitev je podvajanje
kode funkcije func, jo povezati
tudi za na novo ustvarjene elemente:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// tukaj podvajamo kodo
});
ul.append(li);
});
Seveda v tej rešitvi takoj vidimo pomanjkljivost - podvajati kodo ni pravilno.
Druga rešitev
Za rešitev težave podvajanja
je logično postaviti funkcijo func
ven, in jo narediti kot 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);
}
Tu nas čaka težava.
Bistvo je v tem, da je naša funkcija uporabljala
spremenljivko li, pridobljeno
iz zunanjega obsega vidnosti.
Toda po premikanju funkcije ta
spremenljivka zdaj ni več vidna!
Za rešitev težave bomo posredovali
našo li kot parameter:
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);
}
In tu naša rešitev povzroči še eno težavo. Bistvo je v tem, da ne moremo preprosto posredovati parametra v obravnavalnik dogodka:
for (let li of lis) {
li.addEventListener('click', func(li)); // ne deluje!
}
Za rešitev te težave preprosto pokličimo našo funkcijo znotraj anonimnega obravnavalnika:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
In na enak način ravnamo pri ustvarjanju novega elementa seznama:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Tretja rešitev
Obstaja bolj elegantna rešitev. Lahko preprosto uporabimo delegiranje. V tem primeru se težava z novimi elementi seznama preprosto ne pojavi:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // ujamemo točno klik na li, ne na vnosno polje
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;
});
}
});
V tem primeru zanka po elementih seznama na sploh ne bo potrebna, in koda za ustvarjanje novega elementa seznama se skrajša na tole:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});