Event Handling
Giriş — Web Sayfalarına Hayat Vermek
Şimdiye kadar HTML elementlerini seçmeyi ve değiştirmeyi öğrendik. Ama tüm bunları elle, konsoldan yapıyorduk. Gerçek bir web uygulamasında, değişiklikleri kullanıcının eylemi tetikler: butona tıklamak, form doldurmak, sayfayı kaydırmak, klavyeye basmak, fareyi hareket ettirmek. İşte event handling (olay yönetimi), JavaScript'i kullanıcı eylemleriyle buluşturan mekanizmadır.
Her gün kullandığınız web sitelerini düşünün — Instagram'da bir fotoğrafı beğenmek, YouTube'da videoyu duraklatmak, Google Maps'te haritayı sürüklemek. Bunların hepsi event'lerle çalışır. Bir event gerçekleşir, JavaScript onu "dinler" ve tepki verir.
Analoji: Bir apartmandaki kapı zili sistemini düşünün. Her dairenin bir zili (event) ve zilini duyan bir kişi (event listener) vardır. Zile basan misafir (kullanıcı eylemi), zilin çalmasına neden olur. Zilini duyan kişi kapıyı açar (handler fonksiyonu çalışır). Zili sökerseniz, kimse gelmez. Üç ayrı kişiyi dinlemeye koyarsanız, üçü de gelir. Event sistemi tam olarak böyle çalışır.
Event Nedir?
Event (olay), tarayıcıda gerçekleşen herhangi bir şeydir. Kullanıcı kaynaklı olabilir (tıklama, yazma) veya sistem kaynaklı olabilir (sayfa yüklendi, resim yüklendi, ağ bağlantısı koptu).
Yaygın Event Tipleri
| Kategori | Event | Ne Zaman Tetiklenir |
|---|---|---|
| Fare | click | Element tıklandığında |
dblclick | Çift tıklandığında | |
mouseenter | Fare elementin üzerine girdiğinde | |
mouseleave | Fare elementten ayrıldığında | |
mousemove | Fare hareket ettiğinde | |
contextmenu | Sağ tıklandığında | |
| Klavye | keydown | Tuşa basıldığında |
keyup | Tuş bırakıldığında | |
| Form | submit | Form gönderildiğinde |
input | Input değeri değiştiğinde (her tuşta) | |
change | Input değeri değişip focus kaybolduğunda | |
focus | Element focus aldığında | |
blur | Element focus kaybettiğinde | |
| Belge | DOMContentLoaded | DOM hazır olduğunda |
load | Tüm kaynaklar yüklendiğinde | |
scroll | Sayfa kaydırıldığında | |
resize | Pencere boyutu değiştiğinde | |
| Dokunma | touchstart | Parmak dokunduğunda |
touchend | Parmak kalktığında | |
touchmove | Parmak kaydırdığında |
addEventListener — Event Dinleyici
addEventListener, bir elemente event dinleyici ekleyen modern ve önerilen yöntemdir.
const buton = document.querySelector("#tikla-btn");
// Temel sözdizimi
buton.addEventListener("click", function() {
console.log("Butona tıklandı!");
});
// Arrow function ile
buton.addEventListener("click", () => {
console.log("Arrow function ile dinleme");
});
// Ayrı fonksiyon referansı ile (önerilen)
function butonTiklandi() {
console.log("Buton tıklandı!");
}
buton.addEventListener("click", butonTiklandi);
// Aynı elemente birden fazla listener eklenebilir
buton.addEventListener("click", () => console.log("Birinci listener"));
buton.addEventListener("click", () => console.log("İkinci listener"));
buton.addEventListener("click", () => console.log("Üçüncü listener"));
// Üçü de çalışır, sırasıyla!Listener'ı Kaldırma (removeEventListener)
function handler() {
console.log("Tıklandı!");
}
// Ekle
buton.addEventListener("click", handler);
// Kaldır — aynı fonksiyon REFERANSINI geçmeniz gerekir!
buton.removeEventListener("click", handler);
// ❌ Bu ÇALIŞMAZ — farklı fonksiyon referansı
buton.addEventListener("click", () => console.log("Merhaba"));
buton.removeEventListener("click", () => console.log("Merhaba")); // Kaldırmaz!
// İki arrow function birbirine eşit değildir — farklı objelerdir
// ✅ Bir kez çalışıp kendi kendini kaldıran listener
buton.addEventListener("click", function tekSeferlik() {
console.log("Sadece bir kez çalışır");
buton.removeEventListener("click", tekSeferlik);
});
// ✅ Daha kolay yöntem: { once: true }
buton.addEventListener("click", () => {
console.log("Sadece bir kez çalışır — otomatik kaldırılır");
}, { once: true });Eski Yöntemler (Kullanmayın)
// ❌ Inline HTML event — bakımı zor, güvenlik riski
// <button onclick="alert('Tıklandı!')">Tıkla</button>
// ❌ DOM property — sadece tek listener atanabilir
buton.onclick = function() { console.log("Birinci"); };
buton.onclick = function() { console.log("İkinci"); };
// Sadece "İkinci" çalışır — birinci override edildi!
// ✅ addEventListener — birden fazla listener, temiz kaldırma
buton.addEventListener("click", () => console.log("Birinci"));
buton.addEventListener("click", () => console.log("İkinci"));
// İkisi de çalışır!Event Object — Olay Bilgileri
Her event tetiklendiğinde, tarayıcı otomatik olarak bir event nesnesi oluşturur ve handler fonksiyonuna parametre olarak geçer. Bu nesne, olay hakkında her türlü bilgiyi içerir.
const buton = document.querySelector("#btn");
buton.addEventListener("click", function(event) {
// event (veya kısaca e) — olay hakkında bilgi
console.log(event.type); // "click"
console.log(event.target); // Tıklanan element
console.log(event.currentTarget); // Listener'ın bağlı olduğu element
console.log(event.timeStamp); // Olayın zamanı (ms)
// Fare pozisyonu
console.log(event.clientX, event.clientY); // Viewport'a göre
console.log(event.pageX, event.pageY); // Sayfaya göre (scroll dahil)
console.log(event.screenX, event.screenY); // Ekrana göre
// Modifier tuşları
console.log(event.shiftKey); // Shift basılı mı?
console.log(event.ctrlKey); // Ctrl basılı mı?
console.log(event.altKey); // Alt basılı mı?
console.log(event.metaKey); // Cmd (Mac) / Win tuşu basılı mı?
});Klavye Event'leri
document.addEventListener("keydown", (e) => {
console.log(e.key); // "Enter", "a", "ArrowUp", "Escape"
console.log(e.code); // "Enter", "KeyA", "ArrowUp", "Escape"
console.log(e.keyCode); // 13 (⚠️ deprecated — kullanmayın)
// key vs code farkı:
// key → basılan tuşun DEĞERI (dile bağlı: "a" veya "ş")
// code → fiziksel tuşun KODU (her zaman aynı: "KeyA")
// Kısayol tuşları
if (e.ctrlKey && e.key === "s") {
e.preventDefault(); // Tarayıcının kaydetme dialogunu engelle
console.log("Ctrl+S — kaydet!");
}
if (e.key === "Escape") {
modalKapat();
}
});
// Input'ta yazma takibi
const arama = document.querySelector("#arama-input");
arama.addEventListener("input", (e) => {
console.log(e.target.value); // Input'un anlık değeri
console.log(e.data); // Girilen karakter
console.log(e.inputType); // "insertText", "deleteContentBackward"...
});Form Event'leri
const form = document.querySelector("#kayit-formu");
form.addEventListener("submit", (e) => {
e.preventDefault(); // Sayfanın yenilenmesini engelle!
// Form verilerini oku
const formData = new FormData(form);
const veri = {
isim: formData.get("isim"),
email: formData.get("email"),
mesaj: formData.get("mesaj")
};
console.log("Form verisi:", veri);
// API'ye gönder...
});
// Input event — her değişiklikte (canlı arama için ideal)
const aramaInput = document.querySelector("#arama");
aramaInput.addEventListener("input", (e) => {
const sorgu = e.target.value.trim().toLowerCase();
urunleriFiltrele(sorgu);
});
// Change event — değer değişip focus kaybedildiğinde
const selectBox = document.querySelector("#sehir-select");
selectBox.addEventListener("change", (e) => {
console.log("Seçilen şehir:", e.target.value);
});
// Focus ve Blur — input'a odaklanma/ayrılma
const emailInput = document.querySelector("#email");
emailInput.addEventListener("focus", () => {
emailInput.parentElement.classList.add("aktif");
});
emailInput.addEventListener("blur", () => {
emailInput.parentElement.classList.remove("aktif");
emailDogrula(emailInput.value);
});preventDefault ve stopPropagation
// preventDefault — tarayıcının varsayılan davranışını engeller
const link = document.querySelector("a.ozel-link");
link.addEventListener("click", (e) => {
e.preventDefault(); // Sayfaya yönlendirmeyi engelle
console.log("Link tıklandı ama sayfa değişmedi");
// Kendi navigasyon mantığını yaz...
});
// Form submit'te sayfa yenilemeyi engelleme
form.addEventListener("submit", (e) => {
e.preventDefault(); // Sayfa yenilenmez
// AJAX ile form gönder...
});
// Sağ tıklamayı engelleme
document.addEventListener("contextmenu", (e) => {
e.preventDefault();
// Özel sağ tık menüsü göster...
ozelMenuGoster(e.clientX, e.clientY);
});
// stopPropagation — event'in üst elementlere yayılmasını durdurur
// (aşağıdaki bubbling bölümünde detaylı anlatılacak)Event Bubbling ve Capturing
Event sistemi, sadece tıklanan elementte değil, DOM ağacı boyunca çalışır. Bu mekanizmayı anlamak, kompleks uygulamalarda event yönetimi için kritiktir.
Bubbling — Olayın Yukarı Yayılması
Bir elemente tıkladığınızda, event o elementten başlayıp DOM ağacında yukarı doğru tüm atalara yayılır. Tıpkı sudaki bir hava kabarcığının (bubble) dipten yüzeye yükselmesi gibi.
<!-- HTML yapısı -->
<div id="buyukbaba">
Büyükbaba
<div id="baba">
Baba
<button id="cocuk">Çocuk Buton</button>
</div>
</div>document.querySelector("#buyukbaba").addEventListener("click", () => {
console.log("3️⃣ Büyükbaba tıklandı");
});
document.querySelector("#baba").addEventListener("click", () => {
console.log("2️⃣ Baba tıklandı");
});
document.querySelector("#cocuk").addEventListener("click", () => {
console.log("1️⃣ Çocuk tıklandı");
});
// "Çocuk Buton"a tıklandığında konsol çıktısı:
// 1️⃣ Çocuk tıklandı
// 2️⃣ Baba tıklandı
// 3️⃣ Büyükbaba tıklandı
// Sadece butona tıkladık ama üç listener da çalıştı!
// Event, cocuk → baba → buyukbaba sırasıyla YUKARI yayıldı.Capturing — Olayın Aşağı İnmesi
Bubbling'in tersi olan capturing fazında, event en üstten başlayıp hedef elemente doğru aşağı iner. Capturing listener'ları { capture: true } ile eklenir.
// Capturing + Bubbling — tam event akışı
document.querySelector("#buyukbaba").addEventListener("click", () => {
console.log("📥 Büyükbaba — CAPTURING");
}, { capture: true }); // veya true
document.querySelector("#buyukbaba").addEventListener("click", () => {
console.log("📤 Büyükbaba — BUBBLING");
});
document.querySelector("#cocuk").addEventListener("click", () => {
console.log("🎯 Çocuk — HEDEF");
});
// Çocuk butonuna tıklandığında:
// 📥 Büyükbaba — CAPTURING (1. Aşağı iniyor)
// 🎯 Çocuk — HEDEF (2. Hedefe ulaştı)
// 📤 Büyükbaba — BUBBLING (3. Yukarı çıkıyor)Tam event akışı:
Capturing fazı Bubbling fazı
(yukarıdan aşağı) (aşağıdan yukarı)
document ──→ ──→ document
html ──→ ──→ html
body ──→ ──→ body
div ──→ ──→ div
button ─── HEDEF ────────→ buttonstopPropagation — Yayılmayı Durdurma
document.querySelector("#baba").addEventListener("click", () => {
console.log("Baba tıklandı");
});
document.querySelector("#cocuk").addEventListener("click", (e) => {
e.stopPropagation(); // Yayılma burada durur!
console.log("Çocuk tıklandı — baba bunu bilmeyecek");
});
// Çocuğa tıklanınca:
// "Çocuk tıklandı — baba bunu bilmeyecek" (sadece bu)
// Baba'nın listener'ı çalışMAZ — propagation durduruldu⚠️ Dikkat: stopPropagation()'ı dikkatli kullanın. Event yayılımını durdurmak, üst elementlerdeki event delegation gibi mekanizmaları bozabilir. Sadece gerçekten gerekli olduğunda kullanın.
event.target vs event.currentTarget
Bu ayrım, özellikle bubbling'de çok önemlidir:
document.querySelector("#baba").addEventListener("click", (e) => {
console.log("target:", e.target); // Tıklanan asıl element
console.log("currentTarget:", e.currentTarget); // Listener'ın bağlı olduğu element
});
// Çocuk butona tıklanınca:
// target: <button id="cocuk"> — gerçekte tıklanan
// currentTarget: <div id="baba"> — listener'ın sahibi
// Baba div'e tıklanınca:
// target: <div id="baba"> — gerçekte tıklanan
// currentTarget: <div id="baba"> — listener'ın sahibi (aynı)Event Delegation — Akıllı Olay Yönetimi
Event delegation, bubbling mekanizmasını kullanarak tek bir üst elementte dinleyip, tüm alt elementlerin event'lerini yakalama tekniğidir. Bu, dinamik içerikte (sonradan eklenen elementler) ve performansta büyük avantaj sağlar.
Problem: Her Elemente Ayrı Listener
// ❌ Her liste öğesine ayrı listener — kötü pratik
const listeOgeleri = document.querySelectorAll("#gorev-listesi li");
listeOgeleri.forEach(li => {
li.addEventListener("click", () => {
li.classList.toggle("tamamlandi");
});
});
// Problemler:
// 1. 1000 öğe = 1000 listener = bellek israfı
// 2. Sonradan eklenen öğeler listener'a sahip OLMAZ
// 3. Toplu kaldırma zorÇözüm: Event Delegation
// ✅ Tek listener — parent'ta dinle, target ile ayırt et
const liste = document.querySelector("#gorev-listesi");
liste.addEventListener("click", (e) => {
// Tıklanan element bir <li> mi?
const li = e.target.closest("li");
if (!li) return; // li değilse hiçbir şey yapma
if (!liste.contains(li)) return; // Liste dışındaki bir li'ye tıklanmışsa
li.classList.toggle("tamamlandi");
});
// Artık sonradan eklenen öğeler de otomatik çalışır!
const yeniGorev = document.createElement("li");
yeniGorev.textContent = "Yeni görev";
liste.appendChild(yeniGorev);
// Tıklanınca çalışır — ayrı listener eklemeye gerek yok!closest() — En Yakın Atayı Bul
closest(), event delegation'ın en iyi arkadaşıdır. Bir elementten başlayarak DOM ağacında yukarı doğru ilerleyip, verilen CSS selector'ına uyan ilk atayı bulur.
// HTML:
// <ul id="urun-listesi">
// <li class="urun" data-id="1">
// <span class="urun-isim">Laptop</span>
// <span class="urun-fiyat">54999 ₺</span>
// <button class="sil-btn">🗑️</button>
// </li>
// </ul>
const urunListesi = document.querySelector("#urun-listesi");
urunListesi.addEventListener("click", (e) => {
// Silme butonuna mı tıklandı?
const silBtn = e.target.closest(".sil-btn");
if (silBtn) {
const urunLi = silBtn.closest(".urun");
const urunId = urunLi.dataset.id;
urunSil(urunId);
urunLi.remove();
return;
}
// Ürün satırına mı tıklandı?
const urunLi = e.target.closest(".urun");
if (urunLi) {
urunDetayGoster(urunLi.dataset.id);
}
});Delegation ile Dinamik Tablo
// Tablo satırlarında düzenleme ve silme butonları
const tablo = document.querySelector("#kullanici-tablosu tbody");
tablo.addEventListener("click", (e) => {
const btn = e.target.closest("button");
if (!btn) return;
const satir = btn.closest("tr");
const kullaniciId = satir.dataset.id;
if (btn.classList.contains("duzenle-btn")) {
duzenlemeModuAc(satir, kullaniciId);
} else if (btn.classList.contains("sil-btn")) {
if (confirm("Silmek istediğinize emin misiniz?")) {
satir.remove();
kullaniciSil(kullaniciId);
}
}
});
// Yeni satır eklendiğinde listener eklemeye gerek yok
function kullaniciEkle(kullanici) {
tablo.insertAdjacentHTML("beforeend", `
<tr data-id="${kullanici.id}">
<td>${kullanici.isim}</td>
<td>${kullanici.email}</td>
<td>
<button class="duzenle-btn">✏️</button>
<button class="sil-btn">🗑️</button>
</td>
</tr>
`);
// Event delegation sayesinde butonlar otomatik çalışır!
}💡 İpucu: Event delegation'ı üç durumda tercih edin: (1) çok sayıda benzer element varsa, (2) elementler dinamik olarak eklenip çıkarılıyorsa, (3) bellek kullanımını optimize etmek istiyorsanız. Temel kural: "listener'ı mümkün olduğunca üst seviyede tutun."
Throttle ve Debounce — Performans Kontrolleri
Bazı event'ler çok sık tetiklenir: scroll, resize, mousemove, input. Her tetiklendiğinde ağır bir işlem yaparsanız (API çağrısı, DOM güncelleme, hesaplama), sayfa performansı ciddi şekilde düşer. Throttle ve debounce, bu sorunu çözen iki klasik tekniktir.
Debounce — "Son Hareketten Sonra Bekle"
Debounce, event tetiklenmeyi durdurana kadar bekler, sonra bir kez çalıştırır. Kullanıcı yazmayı bitirdiğinde arama yapmak için idealdir.
Analoji: Asansör kapısı gibi düşünün. Birisi gelip butona basıyor, kapı kapanmaya başlıyor — ama o sırada bir kişi daha geliyor ve butona basıyor, kapı tekrar açılıp bekliyor. Bu sürekli devam eder. Kapı ancak kimse gelmeyince (belirli bir süre geçince) kapanır. Debounce de böyledir — her tetiklemede zamanlayıcı sıfırlanır, ancak tetikleme durunca fonksiyon çalışır.
function debounce(fn, bekleme) {
let zamanlayici;
return function(...args) {
clearTimeout(zamanlayici); // Önceki zamanlayıcıyı iptal et
zamanlayici = setTimeout(() => {
fn.apply(this, args); // Bekleme süresi dolunca çalıştır
}, bekleme);
};
}
// Kullanım: Arama input'u — kullanıcı yazmayı bitirince ara
const aramaInput = document.querySelector("#arama");
const aramaYap = debounce((sorgu) => {
console.log("API'de aranıyor:", sorgu);
// fetch(`/api/arama?q=${sorgu}`)...
}, 300); // 300ms bekle
aramaInput.addEventListener("input", (e) => {
aramaYap(e.target.value);
});
// Kullanıcı "javascript" yazarken:
// j → (zamanlayıcı başladı)
// ja → (zamanlayıcı sıfırlandı)
// jav → (zamanlayıcı sıfırlandı)
// java → (zamanlayıcı sıfırlandı)
// javas → (zamanlayıcı sıfırlandı)
// ...
// javascript → (zamanlayıcı sıfırlandı)
// (300ms bekleme...)
// API çağrısı: "javascript" — TEK bir çağrı!Throttle — "Belirli Aralıklarla Çalıştır"
Throttle, bir fonksiyonu belirli bir zaman aralığında en fazla bir kez çalıştırır. Sürekli tetiklenen ama periyodik güncelleme gerektiren durumlar için idealdir.
Analoji: Bir musluktan su damlatmak gibi düşünün. Musluğu ne kadar açarsanız açın, damlatıcı her 2 saniyede bir damla bırakır — ne daha fazla, ne daha az. Throttle de budur: event ne kadar sık tetiklenirse tetiklensin, fonksiyon belirli aralıklarla çalışır.
function throttle(fn, aralik) {
let sonCalisma = 0;
return function(...args) {
const simdi = Date.now();
if (simdi - sonCalisma >= aralik) {
sonCalisma = simdi;
fn.apply(this, args);
}
};
}
// Kullanım: Scroll pozisyonunu takip — her 100ms'de bir güncelle
const scrollTakip = throttle(() => {
const scrollY = window.scrollY;
const sayfaYuzdesi = Math.round(
(scrollY / (document.body.scrollHeight - window.innerHeight)) * 100
);
document.querySelector("#ilerleme-bar").style.width = `${sayfaYuzdesi}%`;
}, 100);
window.addEventListener("scroll", scrollTakip);
// Pencere boyutu değişikliği — her 200ms'de bir layout hesapla
const boyutDegisti = throttle(() => {
console.log(`Pencere: ${window.innerWidth}x${window.innerHeight}`);
layoutYenidenHesapla();
}, 200);
window.addEventListener("resize", boyutDegisti);Debounce vs Throttle — Ne Zaman Hangisi?
| Senaryo | Doğru Seçim | Neden |
|---|---|---|
| Arama input'u | Debounce | Yazmayı bitirince ara — gereksiz API çağrısı yapma |
| Otomatik kaydetme | Debounce | Düzenleme bitince kaydet |
| Scroll pozisyon takibi | Throttle | Düzenli güncelleme gerekli — ama her scroll event'inde değil |
| Pencere resize | Throttle | Düzenli aralıklarla layout hesapla |
| Buton tıklama (çift tıklama önleme) | Throttle | İlk tıklamayı al, kısa süre tekrar tıklamayı engelle |
| Form validasyonu (input) | Debounce | Kullanıcı yazarken değil, durduktan sonra doğrula |
| Oyun fizik hesaplama | Throttle | Sabit FPS'te hesaplama yap |
| Sonsuz scroll | Throttle | Belirli aralıklarla scroll pozisyonunu kontrol et |
// Gerçek dünya: Pencere resize + debounce (SON boyut yeterli)
window.addEventListener("resize", debounce(() => {
gridYenidenOlustur(); // Sadece resize durduğunda çalışır
}, 250));
// Gerçek dünya: Mouse move + throttle (ARA DEĞERLER önemli)
canvas.addEventListener("mousemove", throttle((e) => {
cizgiCiz(e.clientX, e.clientY); // Her 16ms'de bir çiz (60fps)
}, 16));Özel Event'ler (Custom Events)
Kendi event'lerinizi oluşturup tetikleyebilirsiniz. Bu, bileşenler arası iletişim için güçlü bir mekanizmadır.
// Custom event oluşturma
const sepetGuncellendi = new CustomEvent("sepet:guncellendi", {
detail: {
urunSayisi: 5,
toplamFiyat: 349.99,
sonEklenen: "Kulaklık"
},
bubbles: true // Üst elementlere yayılsın
});
// Dinleme
document.addEventListener("sepet:guncellendi", (e) => {
console.log(`Sepette ${e.detail.urunSayisi} ürün`);
console.log(`Toplam: ${e.detail.toplamFiyat} ₺`);
sepetBadgeGuncelle(e.detail.urunSayisi);
});
// Tetikleme
document.dispatchEvent(sepetGuncellendi);
// Pratik kullanım: Sepete ürün eklendiğinde
function sepeteEkle(urun) {
sepet.push(urun);
// Custom event ile diğer bileşenleri haberdar et
document.dispatchEvent(new CustomEvent("sepet:guncellendi", {
detail: {
urunSayisi: sepet.length,
toplamFiyat: sepet.reduce((t, u) => t + u.fiyat, 0),
sonEklenen: urun.isim
},
bubbles: true
}));
}Gerçek Dünya Örneği: Etkileşimli Form Validasyonu
Bu örnek, event handling'in birçok yönünü bir arada kullanır:
class FormDogrulayici {
constructor(formSelector) {
this.form = document.querySelector(formSelector);
this.hatalar = new Map();
this.kurallar = {
isim: (deger) => {
if (!deger.trim()) return "İsim alanı zorunludur";
if (deger.trim().length < 2) return "İsim en az 2 karakter olmalı";
return null;
},
email: (deger) => {
if (!deger.trim()) return "E-posta alanı zorunludur";
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(deger)) return "Geçerli bir e-posta girin";
return null;
},
sifre: (deger) => {
if (!deger) return "Şifre alanı zorunludur";
if (deger.length < 8) return "Şifre en az 8 karakter olmalı";
if (!/[A-Z]/.test(deger)) return "En az bir büyük harf gerekli";
if (!/[0-9]/.test(deger)) return "En az bir rakam gerekli";
return null;
}
};
this.dinleyicileriKur();
}
dinleyicileriKur() {
// Input event — debounce ile canlı doğrulama
this.form.addEventListener("input", debounce((e) => {
const input = e.target.closest("input, textarea");
if (input && this.kurallar[input.name]) {
this.alanDogrula(input);
}
}, 300));
// Blur event — focus kaybedince anında doğrula
this.form.addEventListener("blur", (e) => {
const input = e.target.closest("input, textarea");
if (input && this.kurallar[input.name]) {
this.alanDogrula(input);
}
}, true); // Capturing — blur bubble etmez, capturing ile yakala
// Submit event — event delegation ile
this.form.addEventListener("submit", (e) => {
e.preventDefault();
if (this.tumunuDogrula()) {
console.log("Form geçerli — gönderilebilir!");
this.form.dispatchEvent(new CustomEvent("form:gecerli", {
detail: Object.fromEntries(new FormData(this.form))
}));
}
});
}
alanDogrula(input) {
const kural = this.kurallar[input.name];
const hata = kural(input.value);
if (hata) {
this.hatalar.set(input.name, hata);
input.classList.remove("gecerli");
input.classList.add("hatali");
this.hataMesajiGoster(input, hata);
} else {
this.hatalar.delete(input.name);
input.classList.remove("hatali");
input.classList.add("gecerli");
this.hataMesajiGizle(input);
}
}
hataMesajiGoster(input, mesaj) {
let hataSpan = input.parentElement.querySelector(".hata-mesaji");
if (!hataSpan) {
hataSpan = document.createElement("span");
hataSpan.className = "hata-mesaji";
input.parentElement.appendChild(hataSpan);
}
hataSpan.textContent = mesaj;
}
hataMesajiGizle(input) {
const hataSpan = input.parentElement.querySelector(".hata-mesaji");
if (hataSpan) hataSpan.remove();
}
tumunuDogrula() {
const inputlar = this.form.querySelectorAll("input[name], textarea[name]");
inputlar.forEach(input => {
if (this.kurallar[input.name]) {
this.alanDogrula(input);
}
});
return this.hatalar.size === 0;
}
}
// Debounce yardımcı fonksiyonu
function debounce(fn, bekleme) {
let zamanlayici;
return function(...args) {
clearTimeout(zamanlayici);
zamanlayici = setTimeout(() => fn.apply(this, args), bekleme);
};
}
// Kullanım
const dogrulayici = new FormDogrulayici("#kayit-formu");
// Custom event ile form gönderimi
document.querySelector("#kayit-formu").addEventListener("form:gecerli", (e) => {
console.log("Sunucuya gönderiliyor:", e.detail);
});Bu örnekte kullanılan event kavramları:
addEventListener — click, input, blur, submit dinleme
Event delegation — form üzerinde tek listener, input'ları
closest()ile ayırt etmepreventDefault — form submit'te sayfa yenilemesini engelleme
Capturing — blur event'i capturing fazında yakalama (blur bubble etmez)
Debounce — canlı input validasyonunda gereksiz kontrolü engelleme
Custom Event — form doğrulandığında diğer bileşenleri haberdar etme
Özet
🔹 `addEventListener` modern event dinleme yöntemidir — birden fazla listener destekler,
{ once: true }ile tek seferlik,{ capture: true }ile capturing fazında dinleme yapılabilir.🔹 Event object (
e) olay hakkında her bilgiyi taşır:e.target(tıklanan),e.currentTarget(listener'ın sahibi),e.key(basılan tuş),e.preventDefault()(varsayılan davranışı engelle).🔹 Event'ler DOM ağacında capturing (aşağı) → target → bubbling (yukarı) sırasıyla yayılır.
stopPropagation()yayılmayı durdurur.🔹 Event delegation: Üst elementte tek listener ile alt elementlerin event'lerini yakalama.
e.target.closest()ile hedef ayırt edilir. Dinamik içerik ve performans için zorunlu teknik.🔹 Debounce — event durduğunda çalıştırır (arama, validasyon). Throttle — belirli aralıklarla çalıştırır (scroll, resize). İkisi de sık tetiklenen event'lerin performans maliyetini azaltır.
🔹 Custom Event'ler (
new CustomEvent) ile bileşenler arası iletişim sağlanır.detailproperty'si ile veri taşınır,dispatchEvent()ile tetiklenir.
AI Asistan
Sorularını yanıtlamaya hazır