Σφάλμα διπλασιασμού της λειτουργίας επεξεργασίας στοιχείων DOM στην JavaScript
Ας υποθέσουμε ότι έχουμε μια κάποια λίστα:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Ας πάρουμε την ίδια τη λίστα και τα στοιχεία της σε ξεχωριστές μεταβλητές:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Ας κάνουμε τα στοιχεία της λίστας μας επεξεργάσιμα μέσω ενός input που εμφανίζεται:
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);
});
}
Ας υποθέσουμε τώρα ότι θέλουμε να μπορούμε να προσθέτουμε νέα στοιχεία στη λίστα. Ας υπάρχει ένα αντίστοιχο input κάτω από τη λίστα για αυτό το σκοπό:
<input id="adder">
Ας πάρουμε μια αναφορά σε αυτό το input σε μια μεταβλητή:
let adder = document.querySelector('#adder');
Ας κάνουμε έτσι ώστε όταν χάσει το focus το input, να προστίθεται ένα νέο στοιχείο στη λίστα με το κείμενο που πήραμε από το input μας:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Ας υποθέσουμε τώρα ότι θέλουμε και τα νέα στοιχεία που προστίθενται να μπορούν επίσης να επεξεργαστούν. Από μόνο του, η επεξεργασία για αυτά δεν θα λειτουργήσει, γιατί όταν είχαμε προσθέσει τον χειριστή κλικ στα στοιχεία της λίστας, αυτά τα στοιχεία δεν υπήρχαν ακόμα.
Ας δούμε μερικές πιθανές λύσεις σε αυτό το πρόβλημα.
Πρώτη Λύση
Η απλούστερη λύση - είναι να αντιγράψουμε
τον κώδικα της συνάρτησης func, προσθέτοντάς την
και για τα νέα στοιχεία που δημιουργούνται:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// εδώ αντιγράφουμε τον κώδικα
});
ul.append(li);
});
Φυσικά, σε αυτή τη λύση βλέπουμε αμέσως το μειονέκτημα - το να αντιγράφουμε κώδικα δεν είναι σωστό.
Δεύτερη Λύση
Για να λύσουμε το πρόβλημα του αντιγραφής κώδικα,
είναι λογικό να βγάλουμε τη συνάρτηση func
έξω, κάνοντάς την Function Declaration:
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);
}
Εδώ μας περιμένει το πρόβλημα.
Το θέμα είναι ότι η συνάρτησή μας χρησιμοποιούσε
τη μεταβλητή li, που είχε ληφθεί
από την εξωτερική εμβέλεια ορισμού.
Αλλά μετά την αφαίρεση της συνάρτησης, αυτή
η μεταβλητή πλέον δεν είναι ορατή!
Για να λύσουμε το πρόβλημα, θα περάσουμε
τη μεταβλητή li μας ως παράμετρο:
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);
}
Και εδώ η λύση μας δημιουργεί ένα ακόμη πρόβλημα. Το θέμα είναι ότι δεν μπορούμε απλά να περάσουμε μια παράμετρο σε έναν χειριστή συμβάντος:
for (let li in lis) {
li.addEventListener('click', func(li)); // δεν λειτουργεί!
}
Για να λύσουμε αυτό το πρόβλημα, απλά καλούμε τη συνάρτησή μας μέσα σε έναν ανώνυμο χειριστή:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Και με παρόμοιο τρόπο ενεργούμε κατά τη δημιουργία νέου στοιχείου λίστας:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Τρίτη Λύση
Υπάρχει μια πιο κομψή λύση. Μπορούμε απλά να χρησιμοποιήσουμε την ανάθεση συμβάντων (delegation). Σε αυτή την περίπτωση, το πρόβλημα με τα νέα στοιχεία της λίστας απλά δεν θα προκύψει:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // πιάνουμε συγκεκριμένα κλικ στο li, όχι στο 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;
});
}
});
Σε αυτή την περίπτωση, ο βρόχος για τα στοιχεία της λίστας δεν θα μας χρειαζόταν καθόλου, και ο κώδικας για τη δημιουργία νέου στοιχείου λίστας θα μειωνόταν ως εξής:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});