Fejl ved duplikering af funktion til redigering af DOM-elementer i JavaScript
Lad os sige, at vi har en liste:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Lad os hente selve listen og dens punkter i separate variable:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Lad os gøre det således, at punkterne i vores liste kan redigeres med et input-felt, der popper op:
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);
});
}
Lad os nu sige, at vi ønsker at kunne tilføje nye punkter til listen. Lad os til dette have et tilsvarende input-felt under listen:
<input id="adder">
Lad os hente en reference til dette input-felt i en variabel:
let adder = document.querySelector('#adder');
Lad os gøre det således, at ved tab af fokus på input-feltet tilføjes et nyt punkt til listen med teksten, hentet fra vores input-felt:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Lad os nu sige, at vi ønsker, at de nyligt tilføjede punkter også kan redigeres. Det vil ikke fungere af sig selv for dem, fordi da vi tilføjede click-event-handleren på listepunkterne, eksisterede disse punkter endnu ikke.
Lad os se på de mulige løsninger på dette problem.
Første løsning
Den enkleste løsning er at duplikere
koden for funktionen func og tilknytte den
også for de nyligt oprettede punkter:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// her duplikerer vi koden
});
ul.append(li);
});
Selvfølgelig ser vi straks en ulempe ved denne løsning - det er ikke korrekt at duplikere kode.
Anden løsning
For at løse duplikeringsproblemet
er det logisk at flytte funktionen func
ud og gøre den til en 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);
}
Her venter problemet på os.
Problemet er, at vores funktion brugte
variablen li, som blev hentet
fra det ydre scope.
Men efter at have flyttet funktionen er denne
variabel nu ikke længere synlig!
For at løse problemet vil vi sende
vores li som en 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);
}
Og her skaber vores løsning endnu et problem. Problemet er, at man ikke bare kan sende en parameter til en event-handler:
for (let li of lis) {
li.addEventListener('click', func(li)); // virker ikke!
}
For at løse dette problem kan vi blot kalde vores funktion inde i en anonym event-handler:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Og vi gør på samme måde, når vi opretter et nyt listepunkt:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Tredje løsning
Der findes en mere elegant løsning. Man kan simpelthen bruge delegation. I dette tilfælde opstår problemet med nye punkter i listen slet ikke:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // fanger specifikt klik på li, ikke på 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;
});
}
});
I dette tilfælde har vi slet ikke brug for en løkke over listepunkterne, og koden for at oprette et nyt listepunkt bliver reduceret til dette:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});