Chyba duplikování funkce pro editaci DOM prvků v JavaScriptu
Předpokládejme, že máme nějaký seznam:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Získáme samotný seznam a jeho položky do samostatných proměnných:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Zařídíme, aby položky našeho seznamu šlo editovat pomocí zobrazeného inputu:
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);
});
}
Předpokládejme nyní, že chceme, aby do seznamu šlo přidávat nové položky. Pro tento účel mějme pod seznamem odpovídající input:
<input id="adder">
Získáme odkaz na tento input do proměnné:
let adder = document.querySelector('#adder');
Zařídíme, aby po ztrátě focusu inputu byla do seznamu přidána nová položka s textem převzatým z našeho inputu:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Předpokládejme nyní, že chceme, aby i nově přidané položky bylo také možné editovat. Samy od sebe pro ně editování fungovat nebude, protože když jsme zavěšovali obslužnou funkci kliknutí na položky seznamu, tyto položky ještě neexistovaly.
Podívejme se na možné varianty řešení tohoto problému.
První řešení
Nejjednodušší řešení - zduplikovat
kód funkce func, připojit ji
i pro nově vytvořené položky:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// zde duplikujeme kód
});
ul.append(li);
});
Samozřejmě v tomto řešení hned vidíme nedostatek - duplikovat kód není správně.
Druhé řešení
Pro vyřešení problému duplikování
je logické vyjmout funkci func
ven, vytvořit z ní 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);
}
Zde na nás čeká problém.
Jde o to, že naše funkce používala
proměnnou li, získanou
z vnější oblasti viditelnosti.
Ale po vyjmutí funkce tato
proměnná nyní není vidět!
Pro vyřešení problému budeme předávat
naši li parametrem:
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);
}
A zde naše řešení vytváří ještě jeden problém. Jde o to, že nejde jen tak předat parametr obslužné funkci události:
for (let li in lis) {
li.addEventListener('click', func(li)); // nefunguje!
}
Pro vyřešení tohoto problému jednoduše zavoláme naši funkci uvnitř anonymní obslužné funkce:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
A obdobně postupujme při vytváření nové položky seznamu:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Třetí řešení
Existuje elegantnější řešení. Lze jednoduše využít delegování událostí. V tomto případě problém s novými položkami seznamu jednoduše nevznikne:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // zachytáváme právě 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;
});
}
});
V tomto případě cyklus po položkách seznamu vůbec nebude potřeba a kód pro vytvoření nové položky seznamu se zkrátí na toto:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});