Fout bij het dupliceren van de DOM-elementbewerkingsfunctie in JavaScript
Stel we hebben een bepaalde lijst:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Laten we de lijst zelf en zijn items in aparte variabelen zetten:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Laten we ervoor zorgen dat de items van onze lijst kunnen worden bewerkt met een verschijnende input:
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);
});
}
Stel dat we nu nieuwe items aan de lijst willen kunnen toevoegen. Stel dat we daarvoor onder de lijst een bijbehorende input hebben:
<input id="adder">
Laten we een referentie naar deze input in een variabele zetten:
let adder = document.querySelector('#adder');
Laten we ervoor zorgen dat bij het verlies van focus van de input er een nieuw item aan de lijst wordt toegevoegd met de tekst die uit onze input is gehaald:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Stel dat we nu willen dat ook de nieuw toegevoegde items kunnen worden bewerkt. Op zichzelf zal het bewerken voor hen niet werken, omdat toen we de click handler op de lijstitems plaatsten, deze items er nog niet waren.
Laten we kijken naar mogelijke oplossingen voor dit probleem.
Eerste oplossing
De eenvoudigste oplossing is om de
code van functie func te dupliceren,
door deze ook voor de nieuw gemaakte items te binden:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// hier dupliceren we de code
});
ul.append(li);
});
Natuurlijk zien we in deze oplossing meteen een nadeel - het is niet correct om code te dupliceren.
Tweede oplossing
Om het duplicatieprobleem op te lossen
is het logisch om functie func
eruit te halen, en er een Function Declaration van te maken:
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);
}
Hier loert het probleem op ons.
Het zit zo, dat onze functie de
variabele li gebruikte,
verkregen uit de externe scope.
Maar na het verplaatsen van de functie is deze
variabele nu niet meer zichtbaar!
Om het probleem op te lossen zullen we
onze li als parameter doorgeven:
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);
}
En hier creëert onze oplossing nog een ander probleem. Het zit zo, dat je niet zomaar een parameter kunt doorgeven aan een event handler:
for (let li of lis) {
li.addEventListener('click', func(li)); // werkt niet!
}
Om dit probleem op te lossen roepen we eenvoudigweg onze functie aan binnen een anonieme handler:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
En op een vergelijkbare manier handelen we bij het maken van een nieuw lijstitem:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Derde oplossing
Er bestaat een meer elegante oplossing. We kunnen simpelweg gebruikmaken van delegatie. In dat geval doet het probleem met nieuwe items in de lijst zich simpelweg niet voor:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // vangen specifiek klik op li op, niet op 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;
});
}
});
In dit geval is de lus door de lijstitems helemaal niet meer nodig, en de code voor het maken van een nieuw lijstitem wordt teruggebracht tot dit:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});