DOM-elementtien muokkaustoiminnon kaksoiskappaleiden virhe JavaScriptissä
Oletetaan, että meillä on jokin lista:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Haetaan lista itse ja sen kohteet omiin muuttujiinsa:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Tehdään niin, että listan kohteita voidaan muokata esiin tulevalla input-kentällä:
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);
});
}
Oletetaan nyt, että haluamme, että listaan voi lisätä uusia kohteita. Oletetaan, että tätä varten meillä on listan alla vastaava input-kenttä:
<input id="adder">
Haetaan linkki tähän input-kenttään muuttujaan:
let adder = document.querySelector('#adder');
Tehdään niin, että kun input-kenttä menettää fokuksensa, listaan lisätään uusi kohde tekstillä, joka on otettu input-kentästämme:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Oletetaan nyt, että haluamme, että myös vasta lisätyt kohteet voidaan muokata. Itse itsessään muokkaus ei toimi niille, sillä kun kiinnitimme klikkausten käsittelijän listan kohteisiin, näitä kohteita ei ollut vielä olemassa.
Katsotaanpa mahdollisia vaihtoehtoja tämän ongelman ratkaisemiseksi.
Ensimmäinen ratkaisu
Yksinkertaisin ratkaisu - on kopioida
funktion func koodi,
liittäen sen
myös vasta luoduille kohteille:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// tässä kopioimme koodia
});
ul.append(li);
});
Tietenkin tässä ratkaisussa näemme heti haitan - koodin kopioiminen ei ole oikein.
Toinen ratkaisu
Ongelman ratkaisemiseksi kopioinnista
on loogista siirtää funktio func
ulos, tekemällä siitä 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);
}
Täällä ongelma odottaakin meitä.
Asia on niin, että funktiomme käytti
muuttujaa li, joka saatiin
ulkoisesta näkyvyysalueesta.
Mutta funktion siirtämisen jälkeen tämä
muuttuja ei ole enää näkyvissä!
Ongelman ratkaisemiseksi välitetään
meidän li parametrina:
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);
}
Ja tässä ratkaisumme synnyttää vielä toisen ongelman. Asia on niin, että ei voi vain niin yksinkertaisesti välittää parametria tapahtumankäsittelijään:
for (let li in lis) {
li.addEventListener('click', func(li)); // ei toimi!
}
Tämän ongelman ratkaisemiseksi kutsutaan yksinkertaisesti meidän funktiota anonyymin käsittelijan sisällä:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Ja vastaavasti toimitaan kun luodaan uusi listan kohde:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Kolmas ratkaisu
On olemassa tyylikkäämpi ratkaisu. Voidaan yksinkertaisesti hyödyntää delegoimista. Tässä tapauksessa ongelma uusien listakohteiden kansla ei vain synny:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // napataan nimenomaan klikkaus li:llä, ei input-kentässä
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;
});
}
});
Tässä tapauksessa silmukka listan kohteiden läpi ei yleensäkään tarvita, ja koodi uuden listan kohteen luomiseksi lyhenee tällaiseksi:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});