DOM elementide redigeerimisfunktsiooni dubleerimise viga JavaScriptis
Oletame, et meil on mingi loend:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Saame loendi enda ja selle punktid eraldatud muutujatesse:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Teeme nii, et meie loendi punkte saaks redigeerida ilmuvate input'itega:
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);
});
}
Oletame nüüd, et tahame, et loendisse saaks lisada uusi punkte. Oletame, et selleks on meil loendi all vastav input:
<input id="adder">
Saame viite sellele input'ile muutujasse:
let adder = document.querySelector('#adder');
Teeme nii, et input'i fookuse kaotamisel lisataks loendisse uus punkt tekstiga, mis on võetud meie input'ist:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Oletame nüüd, et tahame, et ka uuesti lisatud punktid oleks samuti redigeeritavad. Iseseisvalt ei hakkas nende jaoks redigeerimine tööle, sest kui me klinksündmuse töötleja lisasime loendi punktidele, neid punkte veel ei olnud.
Vaatame võimalikke variante selle probleemi lahendamiseks.
Esimene lahendus
Kõige lihtsam lahendus on dubleerida
funktsiooni func kood,
seades selle ka uuesti loodud punktidele:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// siin me dubleerime koodi
});
ul.append(li);
});
Loomulikult näeme selles lahenduses kohe puudust - koodi dubleerimine ei ole õige.
Teine lahendus
Dubleerimise probleemi lahendamiseks
on loogiline viia funktsioon func
välja, tehes sellest 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);
}
Siin ootabki meid probleem.
Asi on selles, et meie funktsioon kasutas
muutujat li, mis saadi
välisest nähtavuspiirkonnast.
Kuid pärast funktsiooni väljaviimist see
muutuja pole enam nähtav!
Probleemi lahendamiseks edastame
meie li parameetrina:
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 siin tekitab meie lahendus veel ühe probleemi. Asi on selles, et ei saa lihtsalt nii parameetrit sündmuse töötlejasse edastada:
for (let li in lis) {
li.addEventListener('click', func(li)); // ei tööta!
}
Selle probleemi lahendamiseks lihtsalt kutsume meie funktsiooni välja anonüümse töötleja sees:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Ja samamoodi toimime uue loendi punkti loomisel:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Kolmas lahendus
On olemas elegantsem lahendus. Saab lihtsalt kasutada delegeerimist. Sel juhul probleem uute loendi punktidega lihtsalt ei teki:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // püüame just li klõpsu, mitte input'i klõpsu
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;
});
}
});
Sel juhul tsükkel loendi punktide kaupa meile üldse vajalik ei ole ja kood uue loendi punkti loomiseks taandub sellisele:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});