Грешка при дублиране на функция за редактиране на DOM елементи в JavaScript
Да предположим, че имаме някакъв списък:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Нека получим самия списък и неговите елементи в отделни променливи:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Нека направим така, че елементите на нашия списък да могат да се редактират чрез появяващо се поле за въвеждане:
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);
});
}
Да предположим сега, че искаме да можем да добавяме нови елементи към списъка. Нека за това под списъка имаме съответно поле за въвеждане:
<input id="adder">
Нека получим препратка към това поле за въвеждане в променлива:
let adder = document.querySelector('#adder');
Нека направим така, че при загуба на фокус от полето за въвеждане в списъка да се добави нов елемент с текст, взет от нашето поле за въвеждане:
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 = li.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 = li.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 of 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);
});
Трето решение
Съществува по-елегантно решение. Може просто да се възползваме от делегиране на събития. В този случай проблемът с новите елементи на списъка просто няма да възникне:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // улавяме точно клик върху li, не върху поле за въвеждане
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);
});