Feil med duplisering av funksjon for redigering av DOM-elementer i JavaScript
La oss si at vi har en liste:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
La oss hente selve listen og dens punkter i separate variabler:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
La oss gjøre det slik at punktene i vår liste kan redigeres med et input-felt som vises:
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);
});
}
La oss nå si at vi ønsker å kunne legge til nye punkter i listen. La oss ha et tilhørende input-felt under listen for dette:
<input id="adder">
La oss hente en referanse til dette input-feltet i en variabel:
let adder = document.querySelector('#adder');
La oss gjøre det slik at når input-feltet mister fokus legges et nytt punkt til i listen med teksten hentet fra vårt input-felt:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
La oss nå si at vi ønsker at også nylig tilføyde punkter skal kunne redigeres. Av seg selv vil ikke redigering fungere for dem, for når vi la til klikk-håndtereren på listepunktene, fantes ikke disse punktene ennå.
La oss se på mulige løsninger på dette problemet.
Første løsning
Den enkleste løsningen er å duplisere
koden til funksjonen func, og knytte den
også for nylig opprettede punkter:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// her dupliserer vi koden
});
ul.append(li);
});
Selvfølgelig ser vi umiddelbart en ulempe med denne løsningen - det er ikke riktig å duplisere kode.
Andre løsning
For å løse dupliseringsproblemet
er det logisk å flytte funksjonen func
ut, og gjø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 møter vi problemet.
Saken er at vår funksjon brukte
variabelen li, hentet
fra det ytre scope.
Men etter at funksjonen er flyttet ut, er denne
variabelen ikke lenger synlig!
For å løse problemet skal vi sende
vår 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 skaper vår løsning enda et problem. Saken er at man ikke bare kan sende en parameter til en event-håndterer:
for (let li of lis) {
li.addEventListener('click', func(li)); // virker ikke!
}
For å løse dette problemet kan vi enkelt kalle vår funksjon inni en anonym håndterer:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Og på tilsvarende måte gjør vi ved opprettelse av et nytt 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
Det finnes en mer elegant løsning. Man kan ganske enkelt bruke delegering. I dette tilfellet vil ikke problemet med nye punkter i listen oppstå i det hele tatt:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // fanger nettopp klikk 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 tilfellet vil vi ikke trenge en loop i det hele tatt, og koden for opprettelse av et nytt listepunkt blir redusert til dette:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});