Takip et

Form Doğrulama Evrimi: customError’dan Sade Nesnelere Geçiş

Web formları, kullanıcı etkileşiminin ve veri toplamanın omurgasını oluştururken, doğru ve güvenilir veri girişi her uygulamanın başarısı için kritik öneme sahiptir. Kullanıcılarımızın formları sorunsuz doldurabilmesi ve yanlış veri girişlerinden kaynaklanan hayal kırıklıklarını yaşamaması için etkili form doğrulama mekanizmalarına sahip olmak zorunludur. Peki, bu süreçte eski ve kısıtlayıcı customError yaklaşımlarından, modern ve esnek sade JavaScript nesnelerine geçişin ardında yatan evrim nedir?

Form doğrulama, hem kullanıcı deneyimi hem de veri bütünlüğü açısından vazgeçilmez bir süreçtir. Yanlış girilen bir e-posta adresi, eksik bırakılan bir zorunlu alan veya hatalı formatta bir telefon numarası, sadece kullanıcıyı değil, aynı zamanda uygulamanın arka uç sistemlerini de olumsuz etkileyebilir. Bu nedenle, formlarımızın doğru bilgiyi almasını sağlamak, geliştiriciler olarak en öncelikli görevlerimizden biridir.

Geçmişte, tarayıcıların sunduğu yerleşik doğrulama mekanizmaları ve JavaScript’in setCustomValidity gibi özellikleri sıklıkla kullanılıyordu. Örneğin, bir input alanının geçerliliğini kontrol etmek ve özel bir hata mesajı göstermek için aşağıdaki gibi bir yapı tercih edilebilirdi:


const emailInput = document.getElementById('email');

emailInput.addEventListener('input', () => {
  if (emailInput.validity.typeMismatch) {
    emailInput.setCustomValidity('Lütfen geçerli bir e-posta adresi giriniz.');
  } else if (emailInput.validity.valueMissing) {
    emailInput.setCustomValidity('E-posta alanı boş bırakılamaz.');
  } else {
    emailInput.setCustomValidity(''); // Hata yoksa mesajı temizle
  }
  emailInput.reportValidity(); // Hata mesajını göster
});
    

Bu yaklaşım, basit senaryolar için yeterli olsa da, karmaşık formlar ve modern uygulamaların gereksinimleri karşısında yetersiz kalmaktaydı. İlk olarak, setCustomValidity tarayıcının kendi hata gösterim mekanizmalarını tetikler ve bu mekanizmaların görünümü, stil kontrolü ve konumlandırması üzerinde sınırlı bir hakimiyetimiz olurdu. Her tarayıcı kendi varsayılan stilini uyguladığı için, farklı tarayıcılarda tutarsız bir kullanıcı deneyimi ortaya çıkabiliyordu. Ayrıca, aynı anda birden fazla alanı doğrularken, tüm hataları tek bir çatı altında yönetmek ve kullanıcıya toplu bir geri bildirim sunmak oldukça zordu.

Dahası, bu yöntem genellikle alan bazında tek bir hata mesajını göstermeye odaklanır. Örneğin, bir alan hem boş olamaz hem de belirli bir formatta olmalıdır. setCustomValidity ile bu iki hatayı aynı anda yönetmek ve kullanıcıya her iki sorunu da bildirmek, ek mantık ve geçici çözümler gerektirebilirdi. Bu durum, özellikle formun genel geçerliliği üzerinde tam bir kontrol sağlamak isteyen geliştiriciler için önemli bir kısıtlamaydı. Modern web uygulamalarında ise kullanıcıların anında ve kapsamlı geri bildirim alması beklenir; bu da eski yöntemlerin yeteneklerini aşan bir gereksinimdir.

Son olarak, customError yaklaşımı, form doğrulama mantığını UI'dan (Kullanıcı Arayüzü) ayırmayı zorlaştırır. Doğrulama kuralları genellikle doğrudan DOM manipülasyonuyla iç içe geçer, bu da test edilebilirliği azaltır ve kodun yeniden kullanılabilirliğini sınırlar. Framework tabanlı geliştirme çağında, bu tür bağımlılıklar kod tabanını karmaşıklaştırır ve bakımı güçleştirir. Tüm bu sınırlamalar, geliştiricileri daha esnek, kontrol edilebilir ve ölçeklenebilir bir hata yönetimi çözümüne itmiştir.

Sade Nesnelerle Hata Yönetimine Giriş: Modern Yaklaşımın Temelleri Nelerdir?

customError gibi tarayıcıya bağımlı ve kısıtlayıcı yaklaşımlardan uzaklaşarak, modern form doğrulamada "sade JavaScript nesneleri" kullanmak, geliştiricilere yepyeni bir esneklik ve kontrol düzeyi sunar. Peki, bu sade nesneler tam olarak nedir ve neden bu kadar popüler hale gelmiştir? Temel olarak, bir sade nesne (plain object), formdaki her bir alan için hata mesajlarını veya hata durumlarını tutan basit bir anahtar-değer çiftleri koleksiyonudur. Örneğin, bir e-posta alanındaki hata durumu errors.email şeklinde, bir şifre alanındaki hata ise errors.password şeklinde temsil edilebilir.

Bu yaklaşımın en büyük avantajlarından biri, hata durumunu uygulamanızın diğer parçalarından tamamen bağımsız bir şekilde yönetebilmenizdir. Doğrulama mantığı, doğrudan DOM'a müdahale etmek yerine, bu nesne üzerindeki verileri manipüle eder. Bu sayede, doğrulama kurallarınızı kullanıcı arayüzünüzden tamamen ayırabilir, böylece kodunuzu daha okunabilir, daha test edilebilir ve daha yeniden kullanılabilir hale getirebilirsiniz. Bir başka önemli fayda, birden fazla hatayı aynı anda ele alma yeteneğidir. setCustomValidity genellikle tek bir hata mesajına odaklanırken, sade nesnelerle bir alan için birden fazla hata mesajını (örneğin, "Boş bırakılamaz" ve "Minimum 6 karakter olmalı") kolayca tutabilir ve UI'da hepsini gösterebilirsiniz.

Ayrıca, sade nesnelerle hata yönetimi, herhangi bir JavaScript kütüphanesi veya çerçevesiyle (React, Vue, Angular vb.) sorunsuz bir şekilde entegre edilebilir. Çünkü bu nesneler, framework'lerin veri akışı ve durum yönetimi paradigmalarına doğal olarak uyum sağlar. Geliştiriciler, hata nesnesini uygulamanın durumu (state) içinde tutarak, UI'ın bu duruma göre otomatik olarak güncellenmesini sağlayabilirler. Bu, özellikle karmaşık formlar ve dinamik olarak değişen alanlar içeren uygulamalar için büyük bir kolaylık sağlar.

İşte basit bir sade nesne tabanlı hata yönetiminin temel yapısı:


let formErrors = {}; // Başlangıçta boş bir hata nesnesi

function validateInput(fieldName, value) {
  let errors = []; // Bu alan için hataları topla

  if (fieldName === 'username') {
    if (!value) {
      errors.push('Kullanıcı adı boş bırakılamaz.');
    }
    if (value && value.length < 3) {
      errors.push('Kullanıcı adı en az 3 karakter olmalı.');
    }
  }

  if (fieldName === 'email') {
    if (!value) {
      errors.push('E-posta boş bırakılamaz.');
    } else if (!/\S+@\S+\.\S+/.test(value)) {
      errors.push('Lütfen geçerli bir e-posta adresi giriniz.');
    }
  }

  // Hatalar varsa nesneye ekle, yoksa ilgili alanı kaldır
  if (errors.length > 0) {
    formErrors = { ...formErrors, [fieldName]: errors };
  } else {
    // Hata yoksa o alanı formErrors nesnesinden kaldır
    const { [fieldName]: removed, ...rest } = formErrors;
    formErrors = rest;
  }
}

// Örnek kullanım:
validateInput('username', '');
validateInput('email', 'invalid-email');
validateInput('password', '12'); // Diyelim ki password için de bir validasyon var
console.log(formErrors);
/* Çıktı:
{
  username: ["Kullanıcı adı boş bırakılamaz.", "Kullanıcı adı en az 3 karakter olmalı."],
  email: ["Lütfen geçerli bir e-posta adresi giriniz."]
}
*/
validateInput('username', 'user123'); // Geçerli bir kullanıcı adı girildi
console.log(formErrors);
/* Çıktı:
{
  email: ["Lütfen geçerli bir e-posta adresi giriniz."]
}
*/
    

Görüldüğü gibi, formErrors nesnesi, her bir alan için bir dizi hata mesajı tutarak, hata durumunu merkezi ve erişilebilir bir şekilde yönetmemizi sağlıyor. Bu yapı, hem formun anlık geçerliliğini kontrol etmeyi hem de kullanıcılara doğru ve kapsamlı geri bildirimler sunmayı oldukça kolaylaştırır. Ayrıca, bu yaklaşım sayesinde hata mesajlarının stilini ve yerleşimini tamamen kontrol edebiliriz, bu da marka tutarlılığı ve kullanıcı deneyimi açısından çok önemlidir.

Vaka Analizi: E-ticaret Sepeti Formunda customError'dan Plain Objects'a Geçiş Nasıl Yapılır?

Bir e-ticaret uygulamasında, kullanıcıların satın alma işlemini tamamlamaları için doldurmaları gereken karmaşık bir ödeme formu olduğunu varsayalım. Bu form; teslimat adresi, fatura adresi, kredi kartı bilgileri, iletişim bilgileri gibi birçok alanı içeriyor. Eski yaklaşımda, her bir alan için ayrı ayrı setCustomValidity kullanmak, hem kod karmaşasına yol açacak hem de kullanıcı deneyimini olumsuz etkileyecekti. Örneğin, kullanıcı formu göndermeye çalıştığında, tarayıcı sadece ilk hatalı alanı işaret edecek ve diğer hataları gizli tutacaktı. Bu da kullanıcının formu tekrar tekrar denemesine neden olacaktı.

Vaka Analizi: Ödeme Formunda Gelişim

Problem: Eski sistemde ödeme formunda hatalar tek tek gösteriliyor, kullanıcı tüm hataları aynı anda göremiyordu. Ayrıca, hata mesajlarının görünümü tarayıcıya göre değişiyordu.

Hedef: Tüm form hatalarını tek bir yerden yönetmek, kullanıcıya anında ve toplu geri bildirim sağlamak, hata mesajlarının stilini tamamen kontrol etmek.

Eski Yaklaşım (setCustomValidity ile kısaca):

Diyelim ki bir "Adres Satırı 1" alanı için basit bir doğrulama yapıyorduk:




    


const addressInput = document.getElementById('address1');
const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
if (!addressInput.value) {
addressInput.setCustomValidity('Adres alanı boş bırakılamaz.');
} else {
addressInput.setCustomValidity('');
}
if (!form.checkValidity()) {
e.preventDefault(); // Formun gönderilmesini engelle
}
});

Bu senaryoda, kullanıcı "Adres Satırı 1" alanını boş bırakırsa, tarayıcı tarafından belirlenen bir pop-up mesajı görürdü. Ancak "Şehir" ve "Posta Kodu" gibi diğer zorunlu alanlar da boşsa, bu hatalar ancak "Adres Satırı 1" düzeltildikten sonra ortaya çıkacaktı.

Yeni Yaklaşım (Sade Nesnelerle):

Şimdi aynı form için sade nesnelerle hata yönetimini uygulayalım. Tüm hataları içeren merkezi bir errors nesnesi tutacağız:



const checkoutForm = document.getElementById('checkoutForm');
let formErrors = {}; // Tüm hataları tutacak merkezi nesne

function validateField(fieldName, value) {
let fieldErrors = [];

switch (fieldName) {
case 'address1':
if (!value.trim()) {
fieldErrors.push('Adres Satırı 1 boş bırakılamaz.');
}
break;
case 'city':
if (!value.trim()) {
fieldErrors.push('Şehir boş bırakılamaz.');
}
break;
case 'zipCode':
if (!value.trim()) {
fieldErrors.push('Posta Kodu boş bırakılamaz.');
} else if (!/^\d{5}$/.test(value)) {
fieldErrors.push('Lütfen 5 haneli geçerli bir posta kodu giriniz.');
}
break;
// Diğer alanlar için validasyon kuralları...
}
return fieldErrors;
}

function displayErrors() {
// Önceki tüm hata mesajlarını temizle
document.querySelectorAll('.error-message').forEach(el => el.textContent = '');

// Yeni hataları göster
for (const fieldName in formErrors) {
const errorMessages = formErrors[fieldName];
const errorDiv = document.getElementById(error-${fieldName});
if (errorDiv && errorMessages.length > 0) {
errorDiv.textContent = errorMessages.join(' '); // Birden fazla hatayı tek bir satırda göster
errorDiv.style.color = 'red'; // Hata mesajlarını kırmızı yap
}
}
}

checkoutForm.addEventListener('input', (e) => {
const { name, value } = e.target;
const errors = validateField(name, value);

if (errors.length > 0) {
formErrors = { ...formErrors, [name]: errors };
} else {
// Hata yoksa, ilgili alanı hata nesnesinden kaldır
const { [name]: removed, ...rest } = formErrors;
formErrors = rest;
}
displayErrors(); // Her inputta hataları güncelle
});

checkoutForm.addEventListener('submit', (e) => {
e.preventDefault(); // Formun varsayılan gönderimini engelle

// Tüm alanları doğrulayalım
const formData = new FormData(checkoutForm);
formErrors = {}; // Her submit'te hataları sıfırla

for (let [name, value] of formData.entries()) {
const errors = validateField(name, value);
if (errors.length > 0) {
formErrors = { ...formErrors, [name]: errors };
}
}

displayErrors(); // Tüm hataları göster

if (Object.keys(formErrors).length === 0) {
alert('Form başarıyla gönderildi!');
// Gerçek bir uygulamada burada sunucuya veri gönderilir.
} else {
alert('Lütfen tüm hataları düzeltin.');
}
});

Bu yeni yaklaşımda, kullanıcı formu göndermeye çalıştığında veya bir alanı doldurduğunda, formErrors nesnesi güncellenir ve displayErrors fonksiyonu tüm hataları ilgili alanların altında aynı anda gösterir. Kullanıcı, tüm hataları tek bir bakışta görebilir ve onları gidermek için harekete geçebilir. Bu, kullanıcı deneyimini önemli ölçüde iyileştirir ve formun daha hızlı tamamlanmasını sağlar. Ayrıca, hata mesajlarının stilini CSS ile tamamen kontrol edebiliriz, bu da marka tutarlılığı için büyük bir avantajdır.

Adım Adım Uygulama: JavaScript ile Dinamik Form Doğrulama Nasıl Geliştirilir?

Modern web geliştirmenin vazgeçilmez bir parçası olan dinamik form doğrulama, kullanıcıların formları daha verimli ve hatasız doldurmalarına olanak tanır. Sade nesnelerle bu süreci nasıl adım adım inşa edebileceğimize yakından bakalım. İlk olarak, HTML yapımızı oluşturalım. Bu yapı, her bir input alanı için bir , bir ve hata mesajlarının gösterileceği özel bir

içerecektir.


Şimdi JavaScript kısmına geçelim. İlk olarak, tüm form hatalarını tek bir JavaScript nesnesinde saklayacağız. Bu nesne, anahtar olarak alanın adını ve değer olarak o alanla ilgili hata mesajlarının bir dizisini tutacak.


const registrationForm = document.getElementById('registrationForm');
let formErrors = {}; // Hataları tutacak merkezi nesne

// 1. Alan bazlı doğrulama fonksiyonu
function validateField(fieldName, value, allFormData) {
  let fieldErrors = [];

  switch (fieldName) {
    case 'username':
      if (!value.trim()) {
        fieldErrors.push('Kullanıcı adı boş bırakılamaz.');
      } else if (value.length < 5) {
        fieldErrors.push('Kullanıcı adı en az 5 karakter olmalı.');
      }
      break;
    case 'email':
      if (!value.trim()) {
        fieldErrors.push('E-posta boş bırakılamaz.');
      } else if (!/\S+@\S+\.\S+/.test(value)) {
        fieldErrors.push('Lütfen geçerli bir e-posta adresi giriniz.');
      }
      break;
    case 'password':
      if (!value) {
        fieldErrors.push('Şifre boş bırakılamaz.');
      } else if (value.length < 8) {
        fieldErrors.push('Şifre en az 8 karakter olmalı.');
      } else if (!/[A-Z]/.test(value)) {
        fieldErrors.push('Şifre en az bir büyük harf içermeli.');
      } else if (!/[0-9]/.test(value)) {
        fieldErrors.push('Şifre en az bir rakam içermeli.');
      }
      break;
    case 'confirmPassword':
      if (!value) {
        fieldErrors.push('Şifre tekrarı boş bırakılamaz.');
      } else if (value !== allFormData.password) { // Şifreyle eşleşme kontrolü
        fieldErrors.push('Şifreler eşleşmiyor.');
      }
      break;
  }
  return fieldErrors;
}

// 2. Hataları UI'da gösterme fonksiyonu
function displayErrors() {
  // Tüm hata mesajlarını temizle
  document.querySelectorAll('.error-text').forEach(el => {
    el.textContent = '';
    el.classList.remove('active'); // CSS sınıfını kaldır
  });

  // Yeni hataları göster
  for (const fieldName in formErrors) {
    const errorMessages = formErrors[fieldName];
    const errorDiv = document.getElementById(error-${fieldName});
    if (errorDiv && errorMessages.length > 0) {
      errorDiv.textContent = errorMessages.join(' ');
      errorDiv.classList.add('active'); // CSS sınıfı ekle
    }
  }
}

// 3. Form inputlarına event listener ekleyerek anlık doğrulama
registrationForm.addEventListener('input', (e) => {
  const { name, value } = e.target;
  // Tüm form verisini alarak ilişkili alanları da doğrulamak için
  const allFormData = Object.fromEntries(new FormData(registrationForm).entries());
  const errors = validateField(name, value, allFormData);

  if (errors.length > 0) {
    formErrors = { ...formErrors, [name]: errors };
  } else {
    // Hata yoksa, ilgili alanı hata nesnesinden kaldır
    const { [name]: removed, ...rest } = formErrors;
    formErrors = rest;
  }
  displayErrors();
});

// 4. Form gönderildiğinde tüm formu doğrulama
registrationForm.addEventListener('submit', (e) => {
  e.preventDefault(); // Varsayılan gönderimi engelle

  const formData = new FormData(registrationForm);
  const allFormData = Object.fromEntries(formData.entries());
  formErrors = {}; // Gönderimde hataları sıfırla

  for (let [name, value] of formData.entries()) {
    const errors = validateField(name, value, allFormData);
    if (errors.length > 0) {
      formErrors = { ...formErrors, [name]: errors };
    }
  }

  displayErrors();

  if (Object.keys(formErrors).length === 0) {
    alert('Kayıt başarıyla tamamlandı!');
    // Gerçek bir uygulamada burada AJAX isteği ile sunucuya gönderim yapılır
  } else {
    alert('Lütfen formdaki hataları düzeltin.');
  }
});
    

Uzman İpucu: Anlık Doğrulama ve Performans

input olayında her tuş vuruşunda doğrulama yapmak, karmaşık doğrulama kuralları veya çok sayıda alan içeren formlarda performans sorunlarına yol açabilir. Bu tür durumlarda debouncing tekniğini kullanmak, doğrulama fonksiyonunun yalnızca kullanıcı belirli bir süre (örneğin 300ms) yazmayı bıraktığında çalışmasını sağlayarak performansı artırabilir. Bu, gereksiz işlem yükünü azaltır ve daha akıcı bir kullanıcı deneyimi sunar.

Bu adımlar, sade nesnelerle dinamik form doğrulamayı nasıl uygulayacağınızı gösteriyor. formErrors nesnesi sayesinde, formun genel geçerliliğini ve her bir alanın hata durumunu merkezi bir yerden yönetebilir, böylece kullanıcıya anında ve anlamlı geri bildirimler sunabilirsiniz. Ayrıca, doğrulama mantığını UI'dan ayırarak kodunuzun modülerliğini ve sürdürülebilirliğini artırmış olursunuz.

İleri Düzey Teknikler: Çoklu Hata Yönetimi ve Asenkron Doğrulama Stratejileri Nelerdir?

Sade nesnelerle form doğrulama, temel ihtiyaçları karşılamanın ötesinde, karmaşık senaryolar için de güçlü çözümler sunar. Çoklu hata yönetimi ve asenkron doğrulama, modern uygulamalarda sıklıkla karşılaşılan ve bu yaklaşımın esnekliğini kanıtlayan iki önemli ileri düzey tekniktir.

Çoklu Hata Yönetimi: Bir Alanda Birden Fazla Hata Nasıl Gösterilir?

Bir form alanı, aynı anda birden fazla doğrulama kuralını ihlal edebilir. Örneğin, bir şifre alanı hem boş olamaz, hem en az 8 karakter olmalı, hem bir büyük harf içermeli hem de bir sayı içermelidir. Eski yaklaşımlarda bu tür çoklu hataları göstermek zorluyken, sade nesnelerle bu oldukça basittir. Her alan için hata mesajlarını bir dizi (array) içinde tutarak, bu sorunu elegant bir şekilde çözebiliriz. Daha önceki validateField fonksiyonumuz zaten bu prensibi kullanıyordu.


// validateField fonksiyonu zaten bunu yapıyordu:
// ...
case 'password':
  if (!value) {
    fieldErrors.push('Şifre boş bırakılamaz.');
  }
  if (value && value.length < 8) {
    fieldErrors.push('Şifre en az 8 karakter olmalı.');
  }
  if (value && !/[A-Z]/.test(value)) {
    fieldErrors.push('Şifre en az bir büyük harf içermeli.');
  }
  if (value && !/[0-9]/.test(value)) {
    fieldErrors.push('Şifre en az bir rakam içermeli.');
  }
  break;
// ...
    

displayErrors fonksiyonu da bu diziye erişerek tüm hata mesajlarını tek bir yerde veya ayrı ayrı satırlarda kullanıcıya sunabilir. Bu sayede, kullanıcı tüm sorunları tek bir bakışta görebilir ve gerekli düzeltmeleri yapabilir, bu da kullanıcı deneyimini önemli ölçüde iyileştirir.

Asenkron Doğrulama Stratejileri: Sunucu Kontrolleri Nasıl Entegre Edilir?

Bazı doğrulama kuralları, istemci tarafında (tarayıcıda) kontrol edilemez. Örneğin, bir kullanıcının girdiği e-posta adresinin veritabanında daha önce kayıtlı olup olmadığını kontrol etmek veya bir ürün kodunun stokta olup olmadığını doğrulamak gibi işlemler sunucu tarafında yapılmalıdır. Bu tür "asenkron doğrulama" işlemlerini sade nesnelerle yönetmek de oldukça mümkündür.

Asenkron doğrulama genellikle şu adımları içerir:

  1. Kullanıcı bir alanı doldurur (örneğin, e-posta).
  2. İstemci, bu değeri sunucuya bir API isteği ile gönderir.
  3. Sunucu, değeri doğrular ve bir yanıt (geçerli/geçersiz, hata mesajı) döndürür.
  4. İstemci, sunucudan gelen yanıta göre hata nesnesini günceller.

Bu süreç, kullanıcının yazmayı bitirmesini beklemek için genellikle debouncing ile birleştirilir. İşte bir e-posta alanının sunucuda benzersiz olup olmadığını kontrol eden bir örnek:


let formErrors = {}; // Merkezi hata nesnesi
let debounceTimer;

async function checkEmailAvailability(email) {
  try {
    const response = await fetch(/api/check-email?email=${email});
    const data = await response.json(); // { isAvailable: boolean, message?: string }

    if (!data.isAvailable) {
      return ['Bu e-posta adresi zaten kullanılıyor.'];
    }
    return []; // Hata yok
  } catch (error) {
    console.error('E-posta kontrolünde hata:', error);
    return ['E-posta kontrol edilirken bir sorun oluştu.'];
  }
}

// Yeni bir validateField fonksiyonu (asenkron doğrulamayı da kapsayacak şekilde)
async function validateFieldAsync(fieldName, value, allFormData) {
  let fieldErrors = validateField(fieldName, value, allFormData); // Senkron doğrulamaları yap

  if (fieldName === 'email' && fieldErrors.length === 0) { // Sadece geçerli senkron emailler için async kontrol
    // Kullanıcının yazmayı bitirmesini bekle
    clearTimeout(debounceTimer);
    return new Promise(resolve => {
      debounceTimer = setTimeout(async () => {
        const asyncErrors = await checkEmailAvailability(value);
        resolve([...fieldErrors, ...asyncErrors]); // Mevcut hatalara asenkron hataları ekle
      }, 500); // 500ms sonra kontrol et
    });
  }

  return fieldErrors;
}

// Event listener'ı buna göre güncelleyelim
registrationForm.addEventListener('input', async (e) => {
  const { name, value } = e.target;
  const allFormData = Object.fromEntries(new FormData(registrationForm).entries());
  
  // Asenkron validateFieldAsync'i çağır
  const errors = await validateFieldAsync(name, value, allFormData);

  if (errors.length > 0) {
    formErrors = { ...formErrors, [name]: errors };
  } else {
    const { [name]: removed, ...rest } = formErrors;
    formErrors = rest;
  }
  displayErrors();
});

// Submit listener'ında da asenkron doğrulama beklemeyi unutmayın
registrationForm.addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = new FormData(registrationForm);
  const allFormData = Object.fromEntries(formData.entries());
  formErrors = {};

  const fieldValidationPromises = [];

  for (let [name, value] of formData.entries()) {
    fieldValidationPromises.push(
      validateFieldAsync(name, value, allFormData).then(errors => {
        if (errors.length > 0) {
          formErrors = { ...formErrors, [name]: errors };
        }
      })
    );
  }

  await Promise.all(fieldValidationPromises); // Tüm asenkron doğrulamaların bitmesini bekle

  displayErrors();

  if (Object.keys(formErrors).length === 0) {
    alert('Kayıt başarıyla tamamlandı!');
  } else {
    alert('Lütfen formdaki hataları düzeltin.');
  }
});
    

Bu örnekte, checkEmailAvailability fonksiyonu sunucuya bir istek gönderir. validateFieldAsync ise bu asenkron işlemi bekler ve sonuçları döndürür. Form gönderildiğinde, Promise.all kullanarak tüm alanların asenkron doğrulamalarının tamamlanmasını bekleriz. Bu, sade nesnelerin asenkron işlemleri de nasıl kolayca entegre edebildiğini ve karmaşık doğrulama senaryolarında dahi esneklik sağladığını göstermektedir.

Kullanıcı Deneyimi ve Erişilebilirlik: Hata Mesajları Nasıl Daha Anlaşılır Hale Getirilir?

Form doğrulama sadece teknik bir gereklilik değil, aynı zamanda kullanıcı deneyiminin (UX) ve erişilebilirliğin (Accessibility) temel bir parçasıdır. Hata mesajlarının doğru, anlaşılır ve erişilebilir bir şekilde sunulması, kullanıcıların formları sorunsuz bir şekilde tamamlamaları için kritik öneme sahiptir. Sade nesnelerle hata yönetimi, bu kontrolü geliştiricinin eline verirken, aynı zamanda kullanıcı dostu hata mesajları tasarlama fırsatı sunar.

Anlaşılır ve Eyleme Yönelik Hata Mesajları

Hata mesajları, kullanıcıyı azarlayan veya teknik jargona boğan ifadelerden ziyade, nazik, açıklayıcı ve çözüm odaklı olmalıdır. Örneğin, "Geçersiz giriş" yerine "E-posta adresiniz hatalı görünüyor, lütfen kontrol edin" veya "Şifre çok kısa" yerine "Şifreniz en az 8 karakter olmalı" demek, kullanıcıya ne yapması gerektiğini açıkça belirtir ve onların formdaki sorunları daha hızlı gidermelerine yardımcı olur.

  • Spesifik olun: Genel ifadelerden kaçının. Hatayı tam olarak neyin tetiklediğini belirtin.
  • Çözüm önerin: Kullanıcıya hatayı nasıl düzelteceği konusunda yol gösterin.
  • Nazik bir dil kullanın: Kullanıcıyı suçlayıcı bir ton yerine yardımcı ve anlayışlı bir dil tercih edin.
  • Yerleşimi düşünün: Hata mesajları, ilgili input alanının hemen altında ve kolayca görülebilecek bir yerde olmalıdır.

Erişilebilirlik (Accessibility) için ARIA Nitelikleri

Erişim teknolojileri (örneğin ekran okuyucular) kullanan kişiler için form hatalarının anlaşılır olması hayati öneme sahiptir. HTML'in ARIA (Accessible Rich Internet Applications) nitelikleri, bu konuda bize yardımcı olur:

  • aria-invalid="true": Hatalı olan input alanına bu niteliği eklemek, ekran okuyuculara bu alanın geçersiz olduğunu bildirir.
  • aria-describedby="ID_OF_ERROR_MESSAGE": Hata mesajı elementinin ID'sini input alanının aria-describedby niteliğine bağlamak, ekran okuyucunun input alanı odaklandığında ilgili hata mesajını okumasını sağlar.

Lütfen geçerli bir e-posta adresi giriniz.

Bu nitelikleri JavaScript ile dinamik olarak ekleyip kaldırarak, erişilebilirliği önemli ölçüde artırabiliriz.


function displayErrors() {
  document.querySelectorAll('input').forEach(input => {
    input.removeAttribute('aria-invalid');
  });

  document.querySelectorAll('.error-text').forEach(el => {
    el.textContent = '';
    el.classList.remove('active');
  });

  for (const fieldName in formErrors) {
    const errorMessages = formErrors[fieldName];
    const errorDiv = document.getElementById(error-${fieldName});
    const inputField = document.querySelector([name="${fieldName}"]);

    if (errorDiv && inputField && errorMessages.length > 0) {
      errorDiv.textContent = errorMessages.join(' ');
      errorDiv.classList.add('active');
      inputField.setAttribute('aria-invalid', 'true');
      inputField.setAttribute('aria-describedby', error-${fieldName}); // İlişkili hata div'ini belirt
    }
  }
}
    

Mobil Uyumlu Hata Mesajları ve Görsel Geri Bildirim

Hata mesajlarının mobil cihazlarda da düzgün görünmesi önemlidir. Küçük ekranlarda metinlerin okunabilir olması, input alanlarının hatayı belirten görsel işaretlerle desteklenmesi kullanıcı deneyimini iyileştirir. CSS ile hata durumundaki input'lara kırmızı bir kenarlık eklemek veya hata mesajlarının font boyutunu ayarlamak gibi görsel ipuçları kullanabiliriz.


/* Genel form stilleri */
.form-group {
  margin-bottom: 15px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box; /* Padding'i genişliğe dahil et */
}

/* Hata durumundaki input ve mesaj stilleri */
input[aria-invalid="true"] {
  border-color: red;
  box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.2);
}

.error-text {
  color: red;
  font-size: 0.85em;
  margin-top: 5px;
  display: none; /* Varsayılan olarak gizli */
}

.error-text.active {
  display: block; /* Hata varsa göster */
}

/* Mobil uyumluluk için media query */
@media (max-width: 600px) {
  .form-container {
    padding: 15px;
  }
  input, button {
    font-size: 1em; /* Mobil cihazlarda daha büyük font */
  }
  .error-text {
    font-size: 0.9em; /* Mobil için hata metni boyutu */
  }
}
    

Bu yaklaşımlarla, sade nesnelerle yönetilen form hataları sadece fonksiyonel olmakla kalmaz, aynı zamanda kullanıcılar için daha anlaşılır, erişilebilir ve estetik bir deneyim sunar.

Performans ve Optimizasyon: Büyük Formlarda Doğrulama Yükünü Nasıl Hafifletebiliriz?

Özellikle büyük ve karmaşık formlarda, her tuş vuruşunda veya her input değişikliğinde tüm formun veya ilgili alanın doğrulanması, performans sorunlarına yol açabilir. Gereksiz işlem yükü, kullanıcı arayüzünde takılmalara, gecikmelere ve genel olarak kötü bir kullanıcı deneyimine neden olabilir. Sade nesnelerle hata yönetimini optimize etmek için kullanabileceğimiz çeşitli stratejiler bulunmaktadır.

Debouncing ve Throttling Kullanımı

En yaygın performans optimizasyon tekniklerinden ikisi debouncing ve throttling'dir. Bu teknikler, belirli bir fonksiyonun ne sıklıkla çalıştırılacağını kontrol ederek gereksiz çağrıları engeller.

  • Debouncing: Bir fonksiyonun, belirli bir süre boyunca tekrar çağrılmadığında yalnızca bir kez çalışmasını sağlar. Örneğin, kullanıcı bir input alanına yazarken, doğrulama fonksiyonu yalnızca kullanıcı yazmayı bıraktıktan (örneğin 300-500ms) sonra tetiklenir. Bu, her tuş vuruşunda doğrulama yapılmasını engeller.
  • Throttling: Bir fonksiyonun belirli bir zaman dilimi içinde en fazla bir kez çalışmasını sağlar. Örneğin, bir scroll olayında her piksel hareketinde değil, her 100ms'de bir fonksiyonun çalışması gibi. Form doğrulamada daha az kullanılır ancak belirli senaryolarda faydalı olabilir.

// Debounce fonksiyonu
function debounce(func, delay) {
  let timeout;
  return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), delay);
  };
}

// Input event listener'ını debounce ile saralım
registrationForm.addEventListener('input', debounce(async (e) => {
  const { name, value } = e.target;
  const allFormData = Object.fromEntries(new FormData(registrationForm).entries());
  
  const errors = await validateFieldAsync(name, value, allFormData); // Asenkron validasyon da olabilir

  if (errors.length > 0) {
    formErrors = { ...formErrors, [name]: errors };
  } else {
    const { [name]: removed, ...rest } = formErrors;
    formErrors = rest;
  }
  displayErrors();
}, 300)); // 300 milisaniye gecikme
    

Bu sayede, kullanıcı hızla yazarken doğrulama fonksiyonu gereksiz yere çalışmayacak, bu da kaynak tüketimini azaltacaktır.

Tembel Doğrulama (Lazy Validation)

Her alanın her zaman doğrulanmasına gerek yoktur. "Tembel doğrulama" yaklaşımında, bir alanın doğrulama işlemi sadece belirli koşullar altında (örneğin, kullanıcı input alanından ayrıldığında, formu göndermeye çalıştığında veya hata düzeltildiğinde) tetiklenir. Bu, özellikle çok sayıda alanı olan veya karmaşık doğrulama kurallarına sahip formlarda yararlı olabilir. Kullanıcı input alanını doldurmaya başlamadan önce hata göstermemek, daha az müdahaleci bir deneyim sunar.

  • onBlur doğrulama: Kullanıcı bir input alanını terk ettiğinde (focus kaybettiğinde) doğrulama tetiklenir. Bu, alana özel anlık geri bildirim sağlar.
  • Submit üzerinde doğrulama: Formun tamamı sadece kullanıcı "Gönder" butonuna tıkladığında doğrulanır. Bu, anlık geri bildirim olmamasına rağmen, daha az işlem yükü anlamına gelebilir. Genellikle her iki yöntemin bir kombinasyonu kullanılır: onBlur ile alan bazında, onSubmit ile tüm formun nihai doğrulaması.

Parçalı Doğrulama (Partial Validation)

Çok katmanlı formlarda veya sihirbaz benzeri adımlı formlarda, her adımda sadece ilgili alanların doğrulanması ve bir sonraki adıma geçmeden önce o adımın geçerliliğinin kontrol edilmesi, performans açısından mantıklı olabilir. Tüm formun her adımda doğrulanmasına gerek yoktur. Sade nesnelerle hata yönetimi, formErrors nesnesini sadece o anki adımın alanlarını içerecek şekilde kolayca filtrelemeye olanak tanır.

Bu optimizasyon tekniklerini birleştirerek, büyük ve karmaşık formlarda bile akıcı ve performanslı bir doğrulama deneyimi sunabiliriz. Sade nesneler, bu tür teknikleri uygulamak için gereken esnek ve veri odaklı yapıyı zaten sağladığı için, geliştiricilerin işini önemli ölçüde kolaylaştırır.

Sonuç: Modern Form Doğrulama Yaklaşımının Geleceği Ne Yönde Evriliyor?

Form doğrulama, web geliştirmenin temel taşlarından biri olmaya devam ederken, uygulandığı yöntemler zamanla önemli bir evrim geçirdi. Gördüğümüz gibi, tarayıcıya bağımlı customError yaklaşımlarının kısıtlamalarından, esnek ve güçlü sade JavaScript nesnelerine geçiş, geliştiricilere daha fazla kontrol, daha iyi test edilebilirlik ve üstün bir kullanıcı deneyimi sunmuştur. Bu modern yaklaşım sayesinde, hata mesajlarının stilini ve konumunu tam olarak yönetebilir, çoklu hataları kolayca ele alabilir ve asenkron doğrulama gibi karmaşık senaryoları bile sorunsuzca entegre edebiliriz.

Sade nesnelerin kullanımı, form doğrulama mantığını UI'dan ayırarak kodun modülerliğini artırır. Bu ayrım, özellikle React, Vue veya Angular gibi modern ön yüz çerçeveleriyle çalışırken kritik öneme sahiptir. Durum yönetimiyle entegre olabilen bu yapı, form verilerinin ve hata durumlarının öngörülebilir ve yönetilebilir olmasını sağlar. Ayrıca, erişilebilirlik (ARIA nitelikleri) ve mobil uyumluluk gibi kullanıcı odaklı özelliklerin uygulanmasını da kolaylaştırır, bu da daha kapsayıcı ve kullanıcı dostu uygulamaların önünü açar.

Gelecekte, form doğrulamanın evrimi muhtemelen daha da soyutlanmış ve bildirimsel yaklaşımlara doğru devam edecektir. Yup, Zod, Joi gibi schema tabanlı doğrulama kütüphaneleri, sade nesne mantığını daha üst bir seviyeye taşıyarak, doğrulama kurallarını ayrı bir şema tanımı olarak belirlememizi sağlar. Bu kütüphaneler, hem istemci tarafında hem de sunucu tarafında aynı doğrulama mantığını kullanma olanağı sunarak "full-stack" doğrulama senaryolarını basitleştirir. Bu da, geliştirme sürecini hızlandırırken tutarlılığı artırır.

Özetle, customError'a elveda deyip sade nesnelere hoş geldiniz demek, sadece bir teknik tercih değil, daha sağlam, ölçeklenebilir ve kullanıcı odaklı web uygulamaları inşa etme yolunda atılmış stratejik bir adımdır. Bu yaklaşım, modern web geliştiricilerinin araç kutusunun vazgeçilmez bir parçası olmaya devam edecektir.

Sıkça Sorulan Sorular

  • S1: setCustomValidity tamamen kullanılmaz mı oldu?

    C: Hayır, tamamen kullanılmaz olmadı. Basit ve küçük formlar veya belirli tarayıcı varsayılan davranışlarını hızlıca kullanmak istediğiniz durumlar için hala geçerli bir seçenektir. Ancak karmaşık formlar, özel UI tasarımı veya framework entegrasyonu gerektiren senaryolarda sade nesnelerle hata yönetimi çok daha esneklik ve kontrol sağlar.

  • S2: Plain objects yaklaşımı her projeye uyar mı?

    C: Evet, sade nesnelerle hata yönetimi temel bir JavaScript prensibine dayandığı için neredeyse her projeye uyarlanabilir. İster vanilla JavaScript, ister React, Vue, Angular gibi bir çerçeve kullanın, bu yaklaşım veri odaklı yapısı sayesinde kolayca entegre edilebilir ve ölçeklenebilir.

  • S3: Asenkron validasyonun performans üzerindeki etkisi nedir?

    C: Asenkron validasyon, sunucuya yapılan ağ istekleri nedeniyle ek gecikme yaratabilir. Bu etkiyi azaltmak için debouncing kullanmak, gereksiz API çağrılarını önlemek ve sunucu tarafında hızlı yanıtlar sağlamak önemlidir. Ayrıca, kritik olmayan asenkron doğrulamaları form gönderimine kadar ertelemek de bir strateji olabilir.

  • S4: Hata mesajlarını uluslararası hale getirmek için ne yapmalıyım?

    C: Hata mesajlarını sade nesnelerde doğrudan string olarak tutmak yerine, bir anahtar (key) olarak tutabilir ve bu anahtarı bir çeviri (i18n) kütüphanesi ile eşleştirebilirsiniz. Örneğin, errors: { email: ['email.invalidFormat'] } şeklinde tutarak, email.invalidFormat anahtarını kullanıcının diline göre çeviren bir fonksiyonla render edebilirsiniz.

  • S5: React/Vue gibi framework'lerle bu yaklaşımı nasıl entegre edebilirim?

    C: Modern framework'lerde bu yaklaşım çok daha doğal durur. Hata nesnesini framework'ün durum yönetimi (state) içerisinde (örn. React'ta useState, Vue'da data) tutarsınız. Input alanlarının onChange olayında validateField fonksiyonunu çağırır, durum nesnesini günceller ve UI'ın otomatik olarak güncellenmesini beklersiniz. Form gönderiminde de tüm formu doğrulayıp nihai hata durumunu güncellemiş olursunuz. Çoğu framework zaten bu tür durum yönetimi için optimize edilmiş araçlar sunar.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.