Errore di duplicazione della funzione di modifica degli elementi DOM in JavaScript
Supponiamo di avere un elenco:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Otteniamo l'elenco stesso e i suoi elementi in variabili separate:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Facciamo in modo che gli elementi del nostro elenco possano essere modificati con un input che appare:
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);
});
}
Supponiamo ora di voler aggiungere nuovi elementi all'elenco. Per farlo, sotto l'elenco avremo un input corrispondente:
<input id="adder">
Otteniamo un riferimento a questo input in una variabile:
let adder = document.querySelector('#adder');
Facciamo in modo che quando l'input perde lo stato attivo, un nuovo elemento venga aggiunto all'elenco con il testo preso dal nostro input:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Supponiamo ora di volere che anche gli elementi appena aggiunti possano essere modificati. Di per sé, la modifica non funzionerà per loro, perché quando abbiamo assegnato il gestore del click agli elementi dell'elenco, questi elementi non esistevano ancora.
Diamo un'occhiata alle possibili soluzioni a questo problema.
Prima soluzione
La soluzione più semplice è duplicare
il codice della funzione func, associandola
anche per gli elementi appena creati:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// qui duplichiamo il codice
});
ul.append(li);
});
Naturalmente, in questa soluzione vediamo immediatamente uno svantaggio: duplicare il codice non è corretto.
Seconda soluzione
Per risolvere il problema della duplicazione,
è logico estrarre la funzione func
fuori, rendendola una 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);
}
Qui ci aspetta il problema.
Il fatto è che la nostra funzione utilizzava
la variabile li, ottenuta
dall'ambito esterno.
Ma dopo aver estratto la funzione, questa
variabile ora non è visibile!
Per risolvere il problema, passiamo
la nostra li come parametro:
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);
}
E qui la nostra soluzione genera un altro problema. Il fatto è che non si può semplicemente passare un parametro a un gestore di eventi:
for (let li of lis) {
li.addEventListener('click', func(li)); // non funziona!
}
Per risolvere questo problema, chiamiamo semplicemente la nostra funzione all'interno di un gestore anonimo:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
E facciamo lo stesso quando creiamo un nuovo elemento dell'elenco:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Terza soluzione
Esiste una soluzione più elegante. Si può semplicemente usare il delegazione degli eventi. In questo caso il problema con i nuovi elementi dell'elenco semplicemente non si presenterà:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // intercettiamo il click proprio su li, non sull'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 questo caso il ciclo sugli elementi dell'elenco non ci servirà affatto, e il codice per creare un nuovo elemento dell'elenco si ridurrà a questo:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});