Fehler bei der Duplizierung der DOM-Element-Bearbeitungsfunktion in JavaScript
Nehmen wir an, wir haben eine bestimmte Liste:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Wir holen die Liste selbst und ihre Punkte in separate Variablen:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Sorgen wir dafür, dass die Punkte unserer Liste mit einem erscheinenden Input-Feld bearbeitet werden können:
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);
});
}
Nehmen wir nun an, wir möchten, dass der Liste neue Punkte hinzugefügt werden können. Dafür soll unter der Liste ein entsprechendes Input-Feld sein:
<input id="adder">
Wir holen eine Referenz auf dieses Input-Feld in eine Variable:
let adder = document.querySelector('#adder');
Sorgen wir dafür, dass beim Verlust des Fokus des Input-Felds in die Liste ein neuer Punkt mit dem Text hinzugefügt wird, der aus unserem Input-Feld entnommen wurde:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Nehmen wir nun an, wir möchten, dass auch die neu hinzugefügten Punkte bearbeitet werden können. Von selbst wird das Bearbeiten für sie nicht funktionieren, denn als wir den Event-Handler für den Klick auf die Listenelemente gesetzt haben, existierten diese Punkte noch nicht.
Schauen wir uns die möglichen Lösungsansätze für dieses Problem an.
Erste Lösung
Die einfachste Lösung ist, den
Code der Funktion func zu duplizieren
und sie auch für die neu erstellten Punkte zu binden:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// hier duplizieren wir den Code
});
ul.append(li);
});
Natürlich sehen wir in dieser Lösung sofort einen Nachteil - Code zu duplizieren ist nicht richtig.
Zweite Lösung
Um das Problem der Duplizierung zu lösen,
ist es logisch, die Funktion func
nach außen zu ziehen und sie zu einer Function Declaration zu machen:
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);
}
Hier lauert das Problem auf uns.
Die Sache ist die, dass unsere Funktion die
Variable li verwendet hat, die
vom äußeren Gültigkeitsbereich bezogen wurde.
Aber nachdem die Funktion herausgezogen wurde, ist diese
Variable nun nicht mehr sichtbar!
Um das Problem zu lösen, werden wir
unsere li als Parameter übergeben:
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);
}
Und hier erzeugt unsere Lösung noch ein weiteres Problem. Die Sache ist die, dass man nicht einfach so einen Parameter an einen Event-Handler übergeben kann:
for (let li in lis) {
li.addEventListener('click', func(li)); // funktioniert nicht!
}
Um dieses Problem zu lösen, rufen wir einfach unsere Funktion innerhalb eines anonymen Handlers auf:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Und genauso verfahren wir beim Erstellen eines neuen Listenelements:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Dritte Lösung
Es gibt eine elegantere Lösung. Man kann einfach Event-Delegation nutzen. In diesem Fall wird das Problem mit neuen Listenelementen einfach nicht auftreten:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // fangen genau den Klick auf li ab, nicht auf 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 diesem Fall wird die Schleife über die Listenelemente generell nicht nötig sein, und der Code für die Erstellung eines neuen Listenelements kürzt sich auf Folgendes:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});