DOM Elemanlarını Düzenleme Fonksiyonunun JavaScript'te Yinelenmesi Hatası
Diyelim ki bir listemiz var:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Listeyi ve liste öğelerini ayrı değişkenlere alalım:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Listemizin maddelerinin, görünen bir input ile düzenlenebilir olmasını sağlayalım:
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);
});
}
Diyelim ki şimdi listeye yeni öğeler ekleyebilmek istiyoruz. Bunun için listenin altında ilgili bir input olsun:
<input id="adder">
Bu inputa bir referansı bir değişkene alalım:
let adder = document.querySelector('#adder');
Inputun odak kaybında (blur), listeye, inputtan alınan metinle yeni bir öğe eklenmesini sağlayalım:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Diyelim ki şimdi, yeni eklenen öğelerin de düzenlenebilir olmasını istiyoruz. Kendi başlarına, onlar için düzenleme çalışmayacaktır, çünkü liste öğelerine tıklama işleyicisini eklediğimizde, bu öğeler henüz yoktu.
Bu sorunu çözmenin olası yollarına bir göz atalım.
Birinci Çözüm
En basit çözüm, func fonksiyonunun kodunu,
yeni oluşturulan öğeler için de bağlayarak kopyalamaktır:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// burada kodu yineliyoruz
});
ul.append(li);
});
Tabii ki, bu çözümde hemen bir eksiklik görüyoruz - kodu yinelemek doğru değildir.
İkinci Çözüm
Yineleme sorununu çözmek için, func fonksiyonunu
dışarı çıkarıp onu bir Function Declaration yapmak mantıklıdır:
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);
}
İşte tam da burada bir sorun bizi bekliyor.
Olay şu ki, fonksiyonumuz dış kapsamdan alınan
li değişkenini kullanıyordu.
Ancak fonksiyon dışarı çıkarıldıktan sonra
bu değişken artık görünür değil!
Sorunu çözmek için li değişkenimizi parametre olarak ileteceğiz:
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);
}
Ve burada çözümümüz başka bir soruna daha yol açıyor. Olay şu ki, bir olay işleyicisine parametre bu şekilde basitçe aktarılamaz:
for (let li of lis) {
li.addEventListener('click', func(li)); // çalışmaz!
}
Bu sorunu çözmek için fonksiyonumuzu anonim bir işleyici içinde çağıralım:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
Ve benzer şekilde yeni bir liste öğesi oluştururken de aynısını yapalım:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Üçüncü Çözüm
Daha zarif bir çözüm mevcuttur. Basitçe olay delegasyonu kullanılabilir. Bu durumda yeni liste öğeleri sorunu ortadan kalkar:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // inputa değil, li üzerindeki tıklamayı yakala
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;
});
}
});
Bu durumda liste öğeleri üzerinde döngü yapmamıza genel olarak gerek kalmayacak ve yeni liste öğesi oluşturma kodu şu hale gelecektir:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});