Chyba duplikovania funkcie pre editáciu DOM elementov v JavaScripte
Majme nejaký zoznam:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Získajme samotný zoznam a jeho položky do samostatných premenných:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Urobme tak, aby položky nášho zoznamu bolo možné editovať zobrazeným inputom:
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);
});
}
Povedzme teraz, že chceme, aby do zoznamu bolo možné pridávať nové položky. Nech preto pod zoznamom máme príslušný input:
<input id="adder">
Získajme odkaz na tento input do premennej:
let adder = document.querySelector('#adder');
Urobme tak, aby po strate focusu inputu do zoznamu pribudla nová položka s textom, prevzatým z nášho inputu:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Povedzme teraz, že chceme, aby aj novo pridané položky bolo tiež možné editovať. Samo od seba pre ne editovanie nebude fungovať, pretože keď sme zavesili obsluhu kliknutia na položky zoznamu, tieto položky ešte neexistovali.
Pozrime sa na možné varianty riešenia tohto problému.
Riešenie prvé
Najjednoduchšie riešenie - je zduplikovať
kód funkcie func, pripojiac ju
aj pre novo vytvorené položky:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// tu duplikujeme kód
});
ul.append(li);
});
Samozrejme, v tomto riešení hneď vidíme nedostatok - duplikovať kód nie je správne.
Riešenie druhé
Na vyriešenie problému duplikovania
je logické vyňať funkciu func
von, čím z nej urobíme 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 nás ale čaká problém.
Vec sa má tak, že naša funkcia používala
premennú li, získanú
z vonkajšej oblasti viditeľnosti.
Ale po vyňatí funkcie táto
premenná teraz nie je viditeľná!
Na vyriešenie problému budeme odovzdávať
našu li parametrom:
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);
}
A tu naše riešenie vytvára ešte jeden problém. Vec sa má tak, že nie je možné jednoducho odovzdať parameter do obsluhy udalosti:
for (let li of lis) {
li.addEventListener('click', func(li)); // nefunguje!
}
Na vyriešenie tohto problému jednoducho zavolajme našu funkciu vnútri anonymnej obsluhy:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
A analogicky postupujme pri vytváraní novej položky zoznamu:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Riešenie tretie
Existuje elegantnejšie riešenie. Môžeme jednoducho využiť delegovanie udalostí. V tomto prípade problém s novými položkami zoznamu jednoducho nevznikne:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // zachytávame konkrétne klik na li, nie 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;
});
}
});
V tomto prípade cyklus cez položky zoznamu nám vôbec nebude potrebný a kód pre vytvorenie novej položky zoznamu sa zredukuje na takýto:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});