JAVASCRIPT // DOM & EVENTSD::02 BAŞLANGIÇ+
18m READCOMPLETION: 88%ID::JS-201

JAVASCRIPT DOM MANİPÜLASYONU VE EVENT SİSTEMİ

DOM seçimi, event delegation ve IntersectionObserver

DOM (Document Object Model) ile tarayıcı sayfalarını dinamik hale getirirsiniz. Bu derste DOM manipülasyonu, event sistemi, form yönetimi ve modern browser API'lerini derinlemesine inceliyoruz.

DOM Seçimi ve Manipülasyon

// JAVASCRIPT //
// Element seçimi
const baslik        = document.querySelector('h1');
const tumButonlar   = document.querySelectorAll('button');
const idliEleman    = document.getElementById('ana-icerik');
const sinifliEleman = document.getElementsByClassName('kart'); // HTMLCollection
 
// Element oluşturma ve ekleme
function kartOlustur(veri) {
  const kart = document.createElement('div');
  kart.className = 'kart bg-white shadow rounded p-4';
  kart.dataset.id = veri.id; // data-id="123"
 
  // innerHTML yerine DOM API — XSS güvenliği
  const baslik = document.createElement('h2');
  baslik.textContent = veri.baslik; // textContent — HTML parse etmez
 
  const aciklama = document.createElement('p');
  aciklama.textContent = veri.aciklama;
 
  const buton = document.createElement('button');
  buton.textContent = 'Detay';
  buton.setAttribute('aria-label', `${veri.baslik} detayını gör`);
 
  kart.append(baslik, aciklama, buton);
  return kart;
}
 
// Element ekleme yöntemleri
const konteyner = document.getElementById('kartlar');
konteyner.append(kartOlustur(veri));          // Sona ekle
konteyner.prepend(kartOlustur(veri));         // Başa ekle
konteyner.insertAdjacentElement('afterbegin', // Diğer seçenekler
  kartOlustur(veri));
 
// Element silme
const silinecek = document.querySelector('.eski');
silinecek?.remove(); // Optional chaining — element yoksa hata vermez
 
// CSS manipülasyon
const kutu = document.querySelector('.kutu');
kutu.classList.add('aktif');
kutu.classList.remove('pasif');
kutu.classList.toggle('gizli');     // Varsa kaldır, yoksa ekle
kutu.classList.replace('eski', 'yeni');
kutu.style.transform = 'translateX(100px)'; // Inline stil (kaçınılmalı)

Event Sistemi

// JAVASCRIPT //
// addEventListener — tercih edilen yöntem
const buton = document.getElementById('gonder-btn');
 
function tiklamaIsleci(event) {
  event.preventDefault();      // Varsayılan davranışı engelle
  event.stopPropagation();     // Bubble'ı durdur
  console.log('Tıklandı!', event.target, event.currentTarget);
}
 
buton.addEventListener('click', tiklamaIsleci);
buton.removeEventListener('click', tiklamaIsleci); // Aynı referans!
 
// Event delegation — toplu listener (performans)
const liste = document.getElementById('urun-listesi');
liste.addEventListener('click', event => {
  const buton = event.target.closest('[data-aksiyon]');
  if (!buton) return;
 
  const aksiyon = buton.dataset.aksiyon;
  const urunId  = buton.closest('[data-urun-id]')?.dataset.urunId;
 
  switch (aksiyon) {
    case 'sil':      urunSil(urunId);    break;
    case 'duzenle':  urunDuzenle(urunId); break;
    case 'sepete':   sepeteEkle(urunId); break;
  }
});
 
// Keyboard event
document.addEventListener('keydown', event => {
  if (event.key === 'Escape')         modaliKapat();
  if (event.ctrlKey && event.key === 's') { event.preventDefault(); kaydet(); }
  if (event.key === 'ArrowDown')      sonraki();
});
 
// Custom event
const ozelEtkinlik = new CustomEvent('urun:guncellendi', {
  detail:  { urunId: 42, yeniAd: 'Laptop' },
  bubbles: true,
});
document.dispatchEvent(ozelEtkinlik);
 
document.addEventListener('urun:guncellendi', event => {
  console.log(event.detail.urunId); // 42
});

Form Yönetimi

// JAVASCRIPT //
// Form validasyon ve gönderme
const form = document.getElementById('kayit-formu');
 
form.addEventListener('submit', async event => {
  event.preventDefault();
 
  // FormData API
  const veriler = new FormData(form);
  const json = Object.fromEntries(veriler.entries()); // {email: "...", sifre: "..."}
 
  // Validasyon
  const hatalar = formValidasyonu(json);
  if (Object.keys(hatalar).length > 0) {
    hatalariGoster(hatalar);
    return;
  }
 
  // Gönderme durumu
  const gonderBtn = form.querySelector('[type="submit"]');
  gonderBtn.disabled = true;
  gonderBtn.textContent = 'Kaydediliyor...';
 
  try {
    const cevap = await fetch('/api/kayit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(json),
    });
 
    if (!cevap.ok) throw new Error('Sunucu hatası');
 
    form.reset();
    basariMesajiGoster('Kayıt başarılı!');
    window.location.href = '/dashboard';
 
  } catch (hata) {
    hataGoster(hata.message);
  } finally {
    gonderBtn.disabled = false;
    gonderBtn.textContent = 'Kaydet';
  }
});
 
function formValidasyonu(veriler) {
  const hatalar = {};
 
  if (!veriler.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(veriler.email)) {
    hatalar.email = 'Geçerli bir e-posta girin';
  }
 
  if (!veriler.sifre || veriler.sifre.length < 8) {
    hatalar.sifre = 'Şifre en az 8 karakter olmalı';
  }
 
  return hatalar;
}
 
function hatalariGoster(hatalar) {
  // Önceki hataları temizle
  document.querySelectorAll('.hata-mesaji').forEach(el => el.remove());
 
  Object.entries(hatalar).forEach(([alan, mesaj]) => {
    const input = document.querySelector(`[name="${alan}"]`);
    if (!input) return;
 
    input.classList.add('border-red-500');
 
    const hataMesaji = document.createElement('p');
    hataMesaji.className = 'hata-mesaji text-red-500 text-sm mt-1';
    hataMesaji.textContent = mesaj;
 
    input.parentNode.insertBefore(hataMesaji, input.nextSibling);
  });
}

Intersection Observer — Lazy Loading

// JAVASCRIPT //
// Görünürlük takibi — sayfa içi analitik, lazy load
const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.classList.add('gorunde');
        observer.unobserve(entry.target); // Bir kez tetikle
      }
    });
  },
  {
    threshold: 0.1, // %10 görünürse tetikle
    rootMargin: '0px 0px -100px 0px', // Alt sınırı 100px içeri çek
  }
);
 
document.querySelectorAll('.animasyonlu').forEach(el => observer.observe(el));
 
// Sonsuz scroll
const sonYuklenenRef = document.getElementById('son-eleman');
const scrollObserver = new IntersectionObserver(async ([entry]) => {
  if (!entry.isIntersecting || yukleniyorRef.current) return;
 
  yukleniyorRef.current = true;
  const yeniOgeler = await daha_fazla_getir();
  listeye_ekle(yeniOgeler);
  yukleniyorRef.current = false;
});
 
scrollObserver.observe(sonYuklenenRef);

MutationObserver — DOM Değişikliği Takibi

// JAVASCRIPT //
const targetNode = document.getElementById('dinamik-icerik');
 
const observer = new MutationObserver(mutations => {
  for (const mutation of mutations) {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          console.log('Yeni element eklendi:', node);
          // Yeni elemana event listener ekle
          if (node.matches('[data-tooltip]')) {
            tooltipBagla(node);
          }
        }
      });
    }
  }
});
 
observer.observe(targetNode, {
  childList:  true, // Alt elemanlar
  subtree:    true, // Tüm alt ağaç
  attributes: false,
});
 
observer.disconnect(); // Takibi durdur

ResizeObserver ve requestAnimationFrame

// JAVASCRIPT //
// Element boyutu değişim takibi
const resizeObserver = new ResizeObserver(entries => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;
    console.log(`${entry.target.id}: ${width}x${height}`);
 
    if (width < 600) {
      entry.target.classList.add('mobil');
    } else {
      entry.target.classList.remove('mobil');
    }
  }
});
 
resizeObserver.observe(document.querySelector('.esnek-kutu'));
 
// requestAnimationFrame — akıcı animasyon
function animasyonBaslat(element, hedefX, hedefY) {
  let baslangic = null;
  const sure = 500; // ms
 
  function adim(timestamp) {
    if (!baslangic) baslangic = timestamp;
    const ilerleme = Math.min((timestamp - baslangic) / sure, 1);
 
    // Easing — ease-out
    const kolaylestirilmis = 1 - Math.pow(1 - ilerleme, 3);
 
    element.style.transform = `translate(${hedefX * kolaylestirilmis}px, ${hedefY * kolaylestirilmis}px)`;
 
    if (ilerleme < 1) {
      requestAnimationFrame(adim);
    }
  }
 
  requestAnimationFrame(adim);
}

Clipboard ve Navigator API

// JAVASCRIPT //
// Modern Clipboard API
async function kopyala(metin) {
  try {
    await navigator.clipboard.writeText(metin);
    bildirimGoster('Kopyalandı!');
  } catch {
    // Fallback — eski yöntem
    const gecici = document.createElement('textarea');
    gecici.value = metin;
    document.body.appendChild(gecici);
    gecici.select();
    document.execCommand('copy');
    gecici.remove();
  }
}
 
// Geolocation
navigator.geolocation.getCurrentPosition(
  konum => {
    console.log(konum.coords.latitude, konum.coords.longitude);
    haritaGoster(konum.coords);
  },
  hata => console.warn('Konum alınamadı:', hata.message),
  { enableHighAccuracy: true, timeout: 10000 }
);
 
// Visibility API — sayfa görünürlüğü
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    videoRef?.pause();
  } else {
    videoRef?.play();
  }
});

Sonuç

DOM API'si ile gerçek zamanlı arayüz güncellemeleri, event delegation ile performanslı olay yönetimi, Observer API'leri ile akıllı izleme ve modern browser API'leri ile kullanıcı deneyimi zenginleştirme; modern JavaScript geliştiricisinin temel becerileridir. Bir sonraki derste JavaScript tasarım desenleri ve mimarisi konularını ele alacağız.