Ders 05 / 25
Form Kalıpları
Alan gruplamanın ölçütü, çok adımlı formda odak ve duyuru yönetimi, kaydetme ile geri alma davranışı ve iki düzenin kural denetimiyle karşılaştırılması.
İçindekiler
Önceki dört ders bileşenleri tek tek belirtti. Ödünç alma formu ise bir düğme, üç alan, bir seçenek grubu ve bir açılır liste taşır; her biri kendi başına doğru kurulmuş olsa bile formun bütünü çalışmayabilir. Bir alanın hangi gruba ait olduğu, üç adıma bölünmüş bir formda adımın değiştiğinin nasıl bildirileceği ve gönderimin geri alınıp alınamayacağı bileşen belirtimlerinde yazmaz.
Bu ders formu bir bütün olarak belirtir. Ölçülen şey, tek tek doğru bileşenlerin bir arada üretebildiği kusurlardır.
Bölünme Ölçütü
Bir form, alan sayısı çok olduğu için bölünmez. Bölünme ölçütü, alanların birbirinden bağımsız karar kümeleri oluşturmasıdır: ödünç alma formunda kayıt seçimi, üye bilgisi ve süre kararı ayrı kümelerdir; her biri kendi başına doğrulanabilir ve kullanıcı birini tamamlamadan diğerine geçmez.
Ölçüt sağlanmıyorsa bölmek zarar verir. Beş alanlı bir düzeltme bildirimi formunu üç sayfaya yaymak, kullanıcının tamamını bir arada görmesini engeller ve her geçişte odak yönetimi gerektiren bir yük ekler. Kısa formlar tek görünümde kalır.
Alan Gruplama
Gruplamanın ölçütü, alanların ortak bir soruya yanıt vermesidir. “İade tarihi” ile “Ödünç süresi” ortak bir soruya yanıt verir; “Üye numarası” ile “Ödünç süresi” vermez. Ekranda birbirine yakın durmak gruplama gerekçesi değildir.
Ortak sorusu olan alanlar fieldset ve legend ile sarılır; grup adı sorunun kendisidir.
Yalnız bir alan taşıyan bir grup kurulmaz — o alanın etiketi zaten sorudur ve ek bir grup adı
duyuruyu iki kat uzatır.
<h2 id="adim-2" tabindex="-1">Üye bilgisi</h2> <p id="adim-durum" role="status">Adım 2 / 3</p> <form action="/odunc/adim-2" method="post"> <fieldset> <legend>Bildirim yöntemi</legend> <p><label><input type="radio" name="bildirim" value="eposta" checked> E-posta</label></p> <p><label><input type="radio" name="bildirim" value="yok"> Bildirim yok</label></p> </fieldset> <p><label for="uye-no">Üye numarası</label> <input id="uye-no" name="uye-no" required inputmode="numeric" aria-describedby="uye-yardim"> <span id="uye-yardim">Kart üzerindeki 8 haneli numara.</span></p> <p><button type="submit">İleri</button> <button type="button" name="geri">Geri</button></p> </form>
Düğmelerin sırası ve türü belirtimin parçasıdır. “İleri” düğmesi formun ilk gönderim
düğmesidir; bir metin alanındayken giriş tuşuna basan kullanıcı bu düğmeyi çalıştırır.
“Geri” düğmesi type="button" taşır; taşımazsa örtük gönderimde formu geriye götürür ve
kullanıcı bir adım kaybeder.
Adım Değişimi Sayfa Değişimi Değildir
Sayfa yenilenerek yapılan bir geçişte odak belgenin başına döner ve kullanıcı yeni belgeye baştan girer. Sayfa yenilenmeden yapılan bir geçişte hiçbir şey kendiliğinden olmaz: ekranda içerik değişir, odak eski düğmede kalır, ekranı görmeyen kullanıcı için hiçbir şey olmamış gibi görünür.
Belirtim üç şey ister: odak yeni adımın başlığına taşınır, geçiş kibar öncelikli bir durum
bölgesiyle duyurulur, adım göstergesi kaçıncı adımda olunduğunu metinle söyler. Başlığın
tabindex="-1" taşıması, sekme sırasına girmeden odak alabilmesi içindir.
Aşağıdaki betik aynı senaryoyu iki düzende koşturur ve dört kuralı denetler.
// form.mjs — cok adimli formda odak yonetimi, duyuru ve deger korumasinin denetimi const ADIMLAR = [ { ad: "Kayıt onayı", baslik: "adim-1", alanlar: [ { id: "kayit", etiket: "Kayıt numarası", zorunlu: true, kural: (v) => /^K-\d{3}$/.test(v), ileti: "Kayıt numarası K-000 biçiminde olmalı." }, ] }, { ad: "Üye bilgisi", baslik: "adim-2", alanlar: [ { id: "uye-no", etiket: "Üye numarası", zorunlu: true, kural: (v) => /^\d{8}$/.test(v), ileti: "Üye numarası 8 hane olmalı." }, { id: "eposta", etiket: "Bildirim adresi", zorunlu: false, kural: (v) => v === "" || v.includes("@"), ileti: "Geçerli bir e-posta adresi yazın." }, ] }, { ad: "Süre ve onay", baslik: "adim-3", alanlar: [ { id: "sure", etiket: "Ödünç süresi", zorunlu: true, kural: (v) => ["14", "21", "28"].includes(v), ileti: "Bir ödünç süresi seçin." }, ] }, ]; const baslangic = () => ({ adim: 0, degerler: {}, hatalar: [], odak: "input#kayit", duyuru: "", bitti: false, }); function dogrula(adim, degerler) { const h = []; for (const a of ADIMLAR[adim].alanlar) { const v = degerler[a.id] ?? ""; if (a.zorunlu && v === "") h.push([a.id, a.etiket, "Bu alan zorunludur."]); else if (v !== "" && !a.kural(v)) h.push([a.id, a.etiket, a.ileti]); } return h; } // duzen: odagiTasi, gecisiDuyur, degerleriKoru function makine(duzen) { const odak = (hedef, eski) => (duzen.odagiTasi ? hedef : eski); const duyur = (m) => (duzen.gecisiDuyur ? m : ""); return (d, e) => { if (e.tur === "yaz") return { ...d, degerler: { ...d.degerler, [e.id]: e.deger } }; if (e.tur === "geri") { if (d.adim === 0) return d; const y = d.adim - 1; return { ...d, adim: y, hatalar: [], degerler: duzen.degerleriKoru ? d.degerler : {}, odak: odak("h2#" + ADIMLAR[y].baslik, d.odak), duyuru: duyur(`Adım ${y + 1} / ${ADIMLAR.length}: ${ADIMLAR[y].ad}`) }; } if (e.tur === "ileri" || e.tur === "gonder") { const hatalar = dogrula(d.adim, d.degerler); if (hatalar.length > 0) { return { ...d, hatalar, odak: odak("div#hata-ozeti", d.odak), duyuru: duyur(`${hatalar.length} alan düzeltilmeli`) }; } if (e.tur === "gonder" && d.adim === ADIMLAR.length - 1) { return { ...d, hatalar: [], bitti: true, odak: odak("h2#sonuc", d.odak), duyuru: duyur("Ödünç alma isteği kaydedildi") }; } const y = Math.min(d.adim + 1, ADIMLAR.length - 1); return { ...d, adim: y, hatalar: [], odak: odak("h2#" + ADIMLAR[y].baslik, d.odak), duyuru: duyur(`Adım ${y + 1} / ${ADIMLAR.length}: ${ADIMLAR[y].ad}`) }; } return d; }; } const SENARYO = [ { tur: "yaz", id: "kayit", deger: "K-118" }, { tur: "ileri" }, { tur: "yaz", id: "uye-no", deger: "1234" }, { tur: "ileri" }, { tur: "yaz", id: "uye-no", deger: "10045512" }, { tur: "ileri" }, { tur: "geri" }, { tur: "ileri" }, { tur: "yaz", id: "sure", deger: "21" }, { tur: "gonder" }, ]; const ODAKLANABILIR = new Set([ "input#kayit", "div#hata-ozeti", "h2#adim-1", "h2#adim-2", "h2#adim-3", "h2#sonuc", ]); function kosum(baslik, duzen, izYaz) { const sonraki = makine(duzen); let d = baslangic(); const iz = []; console.log(baslik); if (izYaz) console.log(" eylem".padEnd(24) + "adim".padEnd(18) + "odak".padEnd(18) + "duyuru"); for (const e of SENARYO) { const onceki = d; d = sonraki(d, e); iz.push({ e, onceki, d }); if (!izYaz) continue; const etiket = e.tur === "yaz" ? `yaz ${e.id}` : e.tur; const adimAdi = d.bitti ? "sonuc" : `${d.adim + 1}/${ADIMLAR.length} ${ADIMLAR[d.adim].ad}`; console.log(" " + etiket.padEnd(22) + adimAdi.padEnd(18) + d.odak.padEnd(18) + (d.duyuru || "-")); } const bulgular = []; for (const { e, onceki, d: s } of iz) { if (e.tur === "yaz") continue; if (s.odak === onceki.odak && s.adim !== onceki.adim) bulgular.push(`${e.tur}: adim degisti, odak tasinmadi`); if (s.hatalar.length > 0 && s.odak !== "div#hata-ozeti") bulgular.push(`${e.tur}: hata var, odak ozete tasinmadi`); if (!ODAKLANABILIR.has(s.odak)) bulgular.push(`${e.tur}: odak hedefi odaklanabilir degil (${s.odak})`); if (s.duyuru === "") bulgular.push(`${e.tur}: gecis duyurulmadi`); for (const k of Object.keys(onceki.degerler)) { if (onceki.degerler[k] !== s.degerler[k]) bulgular.push(`${e.tur}: ${k} alaninin degeri kayboldu`); } } console.log(` bulgu: ${bulgular.length}`); for (const b of bulgular) console.log(" " + b); return iz; } kosum("kusurlu duzen (odak tasinmiyor, gecis duyurulmuyor, geri gidiste degerler siliniyor):", { odagiTasi: false, gecisiDuyur: false, degerleriKoru: false }, false); console.log(""); const iz = kosum("duzeltilmis duzen:", { odagiTasi: true, gecisiDuyur: true, degerleriKoru: true }, true); const hatali = iz.find(({ d }) => d.hatalar.length > 0).d; console.log("\nhatali gecisteki ozet baglantilari:"); for (const [id, etiket, ileti] of hatali.hatalar) { console.log(` <a href="#${id}">${etiket}: ${ileti}</a>`); }
kusurlu duzen (odak tasinmiyor, gecis duyurulmuyor, geri gidiste degerler siliniyor):
bulgu: 14
ileri: adim degisti, odak tasinmadi
ileri: gecis duyurulmadi
ileri: hata var, odak ozete tasinmadi
ileri: gecis duyurulmadi
ileri: adim degisti, odak tasinmadi
ileri: gecis duyurulmadi
geri: adim degisti, odak tasinmadi
geri: gecis duyurulmadi
geri: kayit alaninin degeri kayboldu
geri: uye-no alaninin degeri kayboldu
ileri: hata var, odak ozete tasinmadi
ileri: gecis duyurulmadi
gonder: hata var, odak ozete tasinmadi
gonder: gecis duyurulmadi
duzeltilmis duzen:
eylem adim odak duyuru
yaz kayit 1/3 Kayıt onayı input#kayit -
ileri 2/3 Üye bilgisi h2#adim-2 Adım 2 / 3: Üye bilgisi
yaz uye-no 2/3 Üye bilgisi h2#adim-2 Adım 2 / 3: Üye bilgisi
ileri 2/3 Üye bilgisi div#hata-ozeti 1 alan düzeltilmeli
yaz uye-no 2/3 Üye bilgisi div#hata-ozeti 1 alan düzeltilmeli
ileri 3/3 Süre ve onay h2#adim-3 Adım 3 / 3: Süre ve onay
geri 2/3 Üye bilgisi h2#adim-2 Adım 2 / 3: Üye bilgisi
ileri 3/3 Süre ve onay h2#adim-3 Adım 3 / 3: Süre ve onay
yaz sure 3/3 Süre ve onay h2#adim-3 Adım 3 / 3: Süre ve onay
gonder sonuc h2#sonuc Ödünç alma isteği kaydedildi
bulgu: 0
hatali gecisteki ozet baglantilari:
<a href="#uye-no">Üye numarası: Üye numarası 8 hane olmalı.</a>
Kusurlu düzen on dört bulgu üretiyor ve bunların ikisi zincirleme kusurdur: geri gidişte değerler silindiği için sonraki iki geçiş de doğrulamadan geçemiyor. Ekranda görülen tek şey “formun geri dönünce boşalması”dır; kalan on iki bulgu ekranda hiç görünmez.
Düzeltilmiş koşumun izinde iki satır dikkat gerektirir. Dördüncü satırda doğrulama başarısız oluyor ve odak hata özetine gidiyor; adım numarası değişmiyor. Yedinci satırda geri gidiş de bir adım değişimidir ve ileri gidişle aynı odak ve duyuru işlemini gerektirir; geri gidişin sessiz bırakılması sık atlanan bir durumdur.
Klavye Sözleşmesi
| Tuş | Davranış |
|---|---|
Tab / Shift+Tab |
Alanlar arasında kaynak sırasıyla gezer. |
Enter (tek satırlık alanda) |
Formun ilk gönderim düğmesini çalıştırır. |
Enter (çok satırlı alanda) |
Satır sonu ekler; form göndermez. |
Enter (düğmede) |
Odaktaki düğmeyi çalıştırır. |
Sözleşmenin kısıtı, örtük gönderimin hangi düğmeyi çalıştıracağıdır. Formun ilk gönderim
düğmesi “Geri” ise, alanda giriş tuşuna basan kullanıcı bir adım geriye düşer. Gönderim
niteliği taşımayan her düğme type="button" yazılır.
Kaydetme ve Geri Alma
Ödünç alma isteği bir kayıt üretir; kullanıcının kendi verisi üzerinde bağlayıcı bir işlemdir. 3.3.4 Hata Önleme ölçütü bu tür gönderimlerde üç yoldan en az birini ister: gönderim geri alınabilir olmalı, girilen veri denetlenip düzeltilebilmeli, ya da gönderim öncesinde onaylanmalıdır.
Üç adımlı formda ikinci yol kendiliğinden kurulur: son adım, girilen bütün değerleri özet olarak gösterir ve her satırın yanında ilgili adıma dönen bir bağlantı taşır. Özetin salt metin olması yetmez; kullanıcı düzeltmek istediği alana bir eylemle ulaşabilmelidir.
İki ek kural belirtimde yazılır. Kaydedilmemiş değişiklik varken sayfadan ayrılmak uyarıyla karşılanır; uyarı bir kip pencere olduğunda odak yönetimi gerektirir ve bu, sonraki konunun sorumluluğudur. Oturum zaman aşımı varsa 2.2.1 Zamanlamanın Ayarlanabilirliği devreye girer: kullanıcı süreyi uzatabilmeli ya da veri korunmalıdır. Yirmi dakikada dolan bir oturumun uzun bir formu sessizce silmesi bu ölçütü kaldırır.
Ölçülebilir Kısıtlar
3.3.2 Etiketler ve Yönergeler her alanda etiket ya da yönerge ister; 3.3.1 Hata Belirleme hatalı alanın metinle belirtilmesini; 3.3.3 Hata Önerisi biliniyorsa düzeltme önerisinin verilmesini ister. Denetimdeki “Üye numarası 8 hane olmalı” iletisi bu üçüncü ölçütün karşılığıdır: hatayı bildirmekle kalmaz, doğru biçimi söyler.
1.3.5 Girdi Amacını Belirleme, kullanıcının kendi bilgisini toplayan alanlarda amacın program yoluyla bildirilmesini ister; ödünç alma formunda ad ve bildirim adresi alanları bu kapsamdadır.
2.4.6 Başlıklar ve Etiketler adım başlıklarının içeriği gerçekten tanımlamasını ister: “Adım 2” bir başlık değildir, “Üye bilgisi” başlıktır ve adım numarası ayrı bir metinle verilir.
Sık Yapılan Hata
Adım göstergesinin yalnızca görsel olması. Dolan bir çubuk ya da renklenen üç nokta, ekranı görmeyen kullanıcıya hiçbir şey söylemez. Saptaması, göstergenin metin karşılığının ağaçta bulunup bulunmadığının denetlenmesidir.
Hata özetinin bulunduğu yerde bırakılması. Özet oluşturulup odak taşınmadığında kullanıcı gönderimin başarısız olduğunu öğrenemez. Saptaması denetimin ikinci kuralıdır: hata varken odağın özete taşınıp taşınmadığı sınanır.
Geri gidişin sessiz bırakılması. İleri gidişte kurulan odak ve duyuru düzeni geri gidişte de gereklidir. Saptaması, iki yönün aynı kural kümesinden geçirilmesidir.
Tek alanlı grup kurulması. Bir alanı fieldset içine almak, alanın adını grup adıyla
birlikte iki kez duyurur. Saptaması, grup içindeki alan sayısının sayılmasıdır.
Özet
- Form, alan sayısına göre değil bağımsız karar kümelerine göre bölünür; kısa formlar tek görünümde kalır.
- Gruplamanın ölçütü alanların ortak bir soruya yanıt vermesidir; tek alanlı grup kurulmaz.
- Adım değişimi üç işlem gerektirir: odağın yeni adım başlığına taşınması, geçişin kibar öncelikle duyurulması ve adım göstergesinin metinle verilmesi. Geri gidiş de bir adım değişimidir.
- Formun ilk gönderim düğmesi örtük gönderimde çalışır; gönderim niteliği taşımayan
düğmeler
type="button"yazılır. - 3.3.4 bağlayıcı gönderimlerde geri alma, düzeltme ya da onaylama yollarından en az birini ister; son adımdaki düzeltilebilir özet ikinci yolu kurar.
- Denetimin ürettiği bulguların çoğu ekranda görünmez; odak ve duyuru kusurları yalnızca kural sınamasıyla ortaya çıkar.
Sonraki Adım
Bu konudaki bütün bileşenler bir eylemi çalıştırdı: aramayı koşturmak, süzgeci uygulamak, formu göndermek. Sonuç tablosundaki kayıt başlıkları ise farklı bir iş yapar — kullanıcıyı başka bir kaynağa götürür. İkisi ekranda birbirine benzetilebilir, ancak klavye sözleşmeleri, ağaçtaki rolleri ve kullanıcının onlardan beklediği şey ayrıdır. Sonraki ders bu ayrımı kurala bağlar ve karışmanın hangi somut kırılmaları ürettiğini gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.