Памылка дублявання функцыі рэдагавання 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 = list.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 = 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);
});
Рашэнне трэцяе
Існуе больш вытанчанае рашэнне. Можна проста скарыстацца дэлегаваннем. У гэтым выпадку праблема з новымі пунктамі спісу проста не ўзнікне:
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);
});