İçeriğe geç
academia.sh

Ders 25 / 25

Form Erişilebilirliği

Hata iletilerinin alana bağlanması, sunucudan dönen hata özetinin kurulması, odak yönetimi ve klavyeyle kullanımın gerekleri.

İçindekiler

Form bu noktada veri topluyor, doğruluyor ve dosya alabiliyor. Geriye bir soru kalıyor: gönderim reddedildiğinde kullanıcı ne görüyor?

Yerleşik Doğrulama dersinde tarayıcının kendi iletisinin iki sınırı belirtilmişti: tek seferde tek alan gösterir ve kısa sürede kaybolabilir. Sunucudan dönen hata ise yeni bir belgeyle gelir ve kullanıcı, hangi alanların düzeltilmesi gerektiğini bulmak zorundadır.

Bu ders hata bildirimini belgede kurar. Kurulacak yapı betik gerektirmez; sunucu tarafından üretilen işaretlemedir ve bu nedenle her koşulda çalışır.

Bir Hata Bildiriminin Karşılaması Gerekenler

Dört gereklilik vardır ve dördü de işaretlemeyle karşılanır.

Bulunabilirlik. Kullanıcı, sayfanın neresine döndüğünü ve kaç hata olduğunu görmelidir. Sayfanın ortasında tek bir kırmızı satır, o satıra bakmayan kullanıcıya hiçbir şey söylemez.

Bağlılık. Her ileti, ait olduğu alana bildirimle bağlanmalıdır. Alanın altına yerleştirilmiş bir metin görsel olarak bağlıdır; erişilebilirlik ağacında bağlı değildir.

Duyurulabilirlik. Alana odaklanıldığında hata iletisi de duyurulmalıdır.

Erişilebilirlik. İletiye ulaşmak, hatanın hangi alanda olduğunu tahmin etmeyi gerektirmemelidir.

Rengin tek başına bir bildirim olmadığı da eklenmelidir: kırmızı bir kenarlık, rengi ayırt edemeyen kullanıcıya bir şey söylemez. Durum metinle de bildirilir.

Sunucudan Dönen Hata Özeti

Aşağıdaki betik, sunucunun doğrulama sonrasında ürettiği belgeyi kurar. Girdi, alan tanımları ve hata listesidir; çıktı, gönderilecek işaretlemedir.

// erisilebilirlik.mjs — sunucuda uretilen hata ozeti ve alan-hata baglari
const ALANLAR = [
  { kimlik: "istasyon", ad: "istasyon", etiket: "İstasyon kodu", tur: "text" },
  { kimlik: "sicaklik", ad: "sicaklik", etiket: "Sıcaklık (°C)", tur: "number" },
  { kimlik: "eposta", ad: "eposta", etiket: "Bildirim adresi", tur: "email" },
];

const ILETI = {
  valueMissing: "Bu alan zorunludur.",
  patternMismatch: "Yalnızca küçük harf ve tire kullanın.",
  rangeOverflow: "Değer en çok 60 olabilir.",
  typeMismatch: "Geçerli bir e-posta adresi yazın.",
};

function kac(metin) {
  return metin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function formYaz(degerler, hatalar) {
  const satirlar = [];
  if (hatalar.length > 0) {
    satirlar.push('<div role="alert" tabindex="-1" id="hata-ozeti">');
    satirlar.push("  <h2>Form gönderilemedi: " + hatalar.length + " alan düzeltilmeli</h2>");
    satirlar.push("  <ul>");
    for (const [alanKimligi, kod] of hatalar) {
      const alan = ALANLAR.find((a) => a.kimlik === alanKimligi);
      satirlar.push(
        '    <li><a href="#' + alan.kimlik + '">' + kac(alan.etiket) + ": " + kac(ILETI[kod]) + "</a></li>",
      );
    }
    satirlar.push("  </ul>");
    satirlar.push("</div>");
  }
  satirlar.push('<form action="/olcum" method="post" novalidate>');
  for (const alan of ALANLAR) {
    const hata = hatalar.find(([kimlik]) => kimlik === alan.kimlik);
    satirlar.push('  <p><label for="' + alan.kimlik + '">' + kac(alan.etiket) + "</label>");
    const ozellikler = [
      'id="' + alan.kimlik + '"',
      'name="' + alan.ad + '"',
      'type="' + alan.tur + '"',
      'value="' + kac(degerler[alan.ad] ?? "") + '"',
    ];
    if (hata) {
      ozellikler.push('aria-invalid="true"');
      ozellikler.push('aria-describedby="' + alan.kimlik + '-hata"');
    }
    satirlar.push("     <input " + ozellikler.join(" ") + ">");
    if (hata) {
      satirlar.push(
        '     <span id="' + alan.kimlik + '-hata" class="hata">' + kac(ILETI[hata[1]]) + "</span>",
      );
    }
    satirlar.push("  </p>");
  }
  satirlar.push('  <p><button type="submit">Kaydı gönder</button></p>');
  satirlar.push("</form>");
  return satirlar.join("\n");
}

const degerler = { istasyon: "Kuzey Yamac", sicaklik: "95", eposta: "[email protected]" };
const hatalar = [["istasyon", "patternMismatch"], ["sicaklik", "rangeOverflow"]];
console.log(formYaz(degerler, hatalar));
<div role="alert" tabindex="-1" id="hata-ozeti">
  <h2>Form gönderilemedi: 2 alan düzeltilmeli</h2>
  <ul>
    <li><a href="#istasyon">İstasyon kodu: Yalnızca küçük harf ve tire kullanın.</a></li>
    <li><a href="#sicaklik">Sıcaklık (°C): Değer en çok 60 olabilir.</a></li>
  </ul>
</div>
<form action="/olcum" method="post" novalidate>
  <p><label for="istasyon">İstasyon kodu</label>
     <input id="istasyon" name="istasyon" type="text" value="Kuzey Yamac" aria-invalid="true" aria-describedby="istasyon-hata">
     <span id="istasyon-hata" class="hata">Yalnızca küçük harf ve tire kullanın.</span>
  </p>
  <p><label for="sicaklik">Sıcaklık (°C)</label>
     <input id="sicaklik" name="sicaklik" type="number" value="95" aria-invalid="true" aria-describedby="sicaklik-hata">
     <span id="sicaklik-hata" class="hata">Değer en çok 60 olabilir.</span>
  </p>
  <p><label for="eposta">Bildirim adresi</label>
     <input id="eposta" name="eposta" type="email" value="[email protected]">
  </p>
  <p><button type="submit">Kaydı gönder</button></p>
</form>

Üretilen işaretleme, dört gerekliliğin her birini karşılar.

Özet, formdan önce durur ve kaç alanın düzeltileceğini bir başlıkla söyler. Belgenin başında bulunması, kullanıcının ona ek bir gezinme yapmadan varmasını sağlar; role="alert" bildirimi ise bölümü ivedi duyurulacak bir bölge olarak işaretler. Belge yüklenirken duyurulup duyurulmayacağı gerçekleştirime bağlıdır, bu nedenle özet yalnızca duyuruya dayanmaz: konumu ve başlığıyla da bulunabilir.

Her hata bir bağlantıdır ve hedefi ilgili alanın kimliğidir. Bağlantı metni yalnızca “bu alan zorunludur” demez; hangi alan olduğunu da söyler — Bağlantılar dersindeki “bağlamdan koparıldığında anlaşılır olma” ölçütü burada da geçerlidir.

Alanlar aria-invalid="true" taşır. Bu, erişilebilirlik ağacına geçersizlik bildirimidir ve rengin taşımadığı bilgiyi taşır.

İleti alana aria-describedby ile bağlıdır. Etiket–Alan İlişkisi dersinde kurulan düzenek burada hata iletisi için kullanılır: alana odaklanıldığında ad, sonra tür, sonra açıklama duyurulur.

Girilen değerlerin korunduğuna dikkat edilmelidir. Geçersiz değerler bile alanlara geri yazılır; kullanıcı ne yazdığını görür ve düzeltir. Formu boşaltarak geri döndürmek, uzun formlarda kullanıcının çalışmasını yok eder.

Kaçırma işlevi de gereklidir: alan değerleri kullanıcıdan gelir ve Etiketler, Öznitelikler ve Varlıklar dersinde belirtildiği gibi <, & ve tırnak karakterleri kaçırılmadan belgeye yazılırsa yapı bozulur.

Odak Yönetimi

Özetin tabindex="-1" özniteliği taşıması, ona program yoluyla odaklanılabilmesi içindir. Değer eksi bir olduğunda öge sekme sırasına girmez ama odak alabilir.

Sunucudan dönen bir belgede odak, belgenin başındadır ve özet zaten oradadır; bu durumda ek bir işlem gerekmez. Sayfa yenilenmeden yapılan denetimlerde odağın özete taşınması gerekir, yoksa kullanıcı yeni eklenen bölümden habersiz kalır.

Özetteki bağlantıya tıklandığında odak hedef alana gider. Bunun için alanın odaklanabilir olması yeterlidir; girdi alanları zaten odaklanabilirdir.

tabindex özniteliğinin sıfırdan büyük değerleri kullanılmaz. Bu değerler ögeyi sekme sırasının başına alır ve belgenin geri kalanının sırasını bozar; sıra, belgedeki kaynak sırasından gelmelidir. Odak sırası ile görsel sıra ayrıştığında klavye kullanıcısı ekranda sıçrar.

Sayfa Yenilenmeden Bildirim

Denetim sayfa yenilenmeden yapılıyorsa, ekranda beliren bir iletinin duyurulması gerekir. Bir canlı bölge (live region), içeriği değiştiğinde ekran okuyucunun duyurduğu bölümdür.

<div role="alert" id="hata-ozeti" tabindex="-1"></div>

role="alert" bildirimi ivedi duyuru ister ve kullanıcının o anki okumasını böler; hata bildirimleri için uygundur. Daha az ivedi bilgiler —“kayıt gönderiliyor” gibi— için kesintisiz duyuru yapan bir canlı bölge kullanılır.

Canlı bölgenin belgede önceden bulunması gerekir. Belgeye sonradan eklenen bir canlı bölgenin içeriği bazı gerçekleştirimlerde duyurulmaz; boş bir kap önceden konur ve içeriği sonradan doldurulur.

Klavyeyle Kullanım

Bir formun klavyeyle kullanılabilmesi, belgede hiçbir şey yapılmadığı sürece kendiliğinden sağlanır: input, select, textarea ve button ögeleri odaklanabilirdir, sekme sırası kaynak sırasıdır ve gönderim, alanlardan birindeyken giriş tuşuyla yapılabilir.

Bu davranışın bozulduğu yerler, dilin ögelerinin yerine başkalarının konduğu yerlerdir. Bir div ögesine tıklama davranışı eklenerek yapılmış bir düğme odaklanabilir değildir, klavyeyle basılamaz ve ekran okuyucuda düğme olarak duyurulmaz. Bunları geri kazanmak için gereken bildirimler —odaklanabilirlik, rol, tuş işleme— button ögesinde zaten vardır.

Odak göstergesi de kaldırılmaz. Odaklanılan ögenin görünür bir işareti olmadığında, klavye kullanıcısı belgenin neresinde olduğunu bilemez. Gösterge sunum katmanında değiştirilebilir; kaldırılamaz.

Bitmiş Form

Kurs boyunca geliştirilen belgenin son bölümü:

<section aria-labelledby="bildirim">
  <h2 id="bildirim">Ölçüm Düzeltmesi Bildir</h2>

  <div role="alert" id="hata-ozeti" tabindex="-1"></div>

  <form action="/olcum/duzeltme" method="post" enctype="multipart/form-data">
    <fieldset>
      <legend>Ölçüm bilgisi</legend>

      <p><label for="istasyon">İstasyon kodu</label>
         <input id="istasyon" name="istasyon" type="text" value="kuzey-yamac"
                required maxlength="20" pattern="[a-z-]+"
                aria-describedby="istasyon-yardim">
         <span id="istasyon-yardim">Yalnızca küçük harf ve tire.</span></p>

      <p><label for="tarih">Ölçüm tarihi</label>
         <input id="tarih" name="tarih" type="date" required></p>

      <p><label for="deger">Ölçülen değer (°C)</label>
         <input id="deger" name="deger" type="number"
                required min="-60" max="60" step="0.1"></p>
    </fieldset>

    <fieldset>
      <legend>Ölçüm kaynağı</legend>
      <p><label><input type="radio" name="kaynak" value="otomatik" checked> Otomatik</label></p>
      <p><label><input type="radio" name="kaynak" value="elle"> Elle girildi</label></p>
    </fieldset>

    <fieldset>
      <legend>Ham kayıt</legend>
      <p><label for="kayit">Ölçüm dosyası</label>
         <input id="kayit" name="kayit" type="file"
                accept=".csv,text/csv" aria-describedby="kayit-yardim">
         <span id="kayit-yardim">Virgülle ayrılmış değer dosyası, en çok 5 MB.</span></p>
    </fieldset>

    <p><button type="submit">Düzeltmeyi gönder</button></p>
  </form>
</section>

Her alanın bir etiketi, her grubun bir adı, her kısıtın bir bildirimi ve her açıklamanın bir bağı vardır. Belgede tek bir betik yoktur.

Özet

  • Bir hata bildirimi bulunabilir, alana bağlı, duyurulabilir ve ulaşılabilir olmalıdır; renk tek başına bildirim değildir.
  • Sunucudan dönen hata özeti formdan önce durur, hata sayısını söyler ve her hatayı ilgili alana bağlanan bir bağlantı olarak sunar.
  • Alanlar aria-invalid ile geçersizlik durumunu, aria-describedby ile hata iletisini bildirir; girilen değerler korunur ve kaçırılarak yazılır.
  • tabindex="-1" ögeyi sekme sırasına sokmadan odaklanabilir yapar; sıfırdan büyük değerler belgenin odak sırasını bozar.
  • Canlı bölgeler belgede önceden bulunmalıdır; sonradan eklenen bir bölgenin içeriği duyurulmayabilir.
  • Klavyeyle kullanım, dilin kendi ögeleri kullanıldığı sürece kendiliğinden sağlanır; odak göstergesi değiştirilebilir ama kaldırılamaz.

Kurs Kapanışı

Bu kurs tek bir soruyla başladı: sunucudan gelen yanıt gövdesi nedir? Yanıt bir belgedir ve belge, baytlardan bir düğüm ağacına çevrilir.

Dört konu bu ağacı kurdu. Web Nasıl Çalışır, baytların karaktere, karakterlerin sözcük birimine, birimlerin ağaca dönüşümünü izledi; ayrıştırıcının kurtarma kurallarını ve kaynak yüklemenin bu zinciri nasıl beklettiğini gösterdi. Belge Yapısı, kök yapıdan tablolara kadar belgenin iskeletini kurdu ve her etiketin bir yapı bildirimi olduğunu, görünüm kararı olmadığını yineledi. Anlamsal İşaretleme ve Medya, belgeye bir üst düzey harita ekledi ve görsel, ses, video ile gömülü içeriği erişilebilir biçimde yerleştirdi. Formlar, tek yönlü belgeyi iki yönlü kıldı ve kullanıcıdan gelen verinin sunucuya nasıl ulaştığını gerçek gövdeler üzerinden gösterdi.

Kurs boyunca izlenen ölçüm istasyonu belgesi, iki satırlık bir iskeletten bölümlenmiş, çizelgeli, medya taşıyan ve doğrulanabilir bir form içeren bütün bir sayfaya dönüştü. Belgede tek bir sunum kararı yoktur: hiçbir yerde bir renk, bir ölçü, bir konum bildirilmemiştir.

Bu eksiklik bilinçlidir. İlk derste tanımlanan üç katmandan yalnızca birincisi kuruldu. Belge şu anda bir tarayıcıda açıldığında okunabilir ve gezilebilir — çünkü tarayıcının her öge için taşıdığı varsayılan biçemler vardır. Ancak o görünüm belgenin değil, tarayıcının kararıdır.

Sonraki kurs, CSS ile Görsel Sunum, ikinci katmanı kurar: hangi ögeye hangi kuralın uygulanacağını belirleyen seçicileri, çakışan kuralların nasıl çözüldüğünü, kutu modelini ve akış düzenini. O kursun üzerine çalışacağı belge budur — ve iyi kurulmuş bir yapı, iyi bir sunumun ön koşuludur: bir ögeye biçem vermek, önce o ögenin var olmasını gerektirir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat