Validasi manual pada chapter sebelumnya bekerja, tetapi ada pekerjaan yang sebenarnya sudah dikerjakan browser: aturan seperti “kolom ini wajib” atau “isi harus cocok pola ini” begitu umum sehingga HTML menyediakan atributnya, dan browser siap memeriksa setiap form dikirim. Membiarkan aturan dasar berpindah ke HTML mengurangi kode yang harus kita rawat.

Chapter ini membedah Constraint Validation API: atribut validasi HTML, object validity yang melaporkan detail kegagalan, method checkValidity() dan reportValidity(), serta setCustomValidity() untuk aturan yang hanya kita ketahui. Form yang kita pakai adalah form penukaran kupon di toko Budi — kolom kode kupon dengan pola tertentu dan kolom email.

Atribut Validasi dan Pemeriksaan Otomatis #

Atribut berikut diperiksa browser secara otomatis setiap form dikirim:

AtributAturan yang dijaga
requiredkolom tidak boleh kosong
minlength, maxlengthjumlah karakter minimal dan maksimal
min, maxbatas nilai untuk kolom angka dan tanggal
type="email", type="url"format harus sesuai jenisnya
patternisi harus cocok dengan regex yang diberikan

Selama atribut-atribut ini terpasang, browser menjadi penjaga pertama: bila ada yang gagal, pesan bawaannya tampil dan peristiwa submit tidak dikirim — handler kita tidak berjalan untuk data yang belum lolos. Atribut pattern cocok terhadap seluruh isi, seolah pola diapit ^ dan $; kita memakai [A-Z]{3}-[0-9]{3} sehingga ABC-123 lolos, tetapi xyz-777 tidak, karena pola itu peka huruf besar-kecil.

html
<form id="form-produk">
  <label for="kode">Kode Kupon</label>
  <input id="kode" name="kode" type="text" required pattern="[A-Z]{3}-[0-9]{3}">
  <p id="galat-kode" hidden></p>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" required minlength="6">
  <p id="galat-email" hidden></p>

  <button type="submit">Tukar Kupon</button>
</form>

Objek validity: Laporan Detail Tiap Kolom #

Setiap kontrol form membawa properti validity — object berisi satu boolean per jenis kegagalan, ditambah valid yang bernilai true bila semuanya lolos. Daftar lengkapnya:

FlagAktif ketika
valueMissingkolom required ditinggal kosong
typeMismatchisi tidak sesuai jenis kolom (email, url)
patternMismatchisi tidak cocok dengan atribut pattern
tooShort, tooLongisian melewati minlength/maxlength yang berlaku
rangeUnderflow, rangeOverflowangka di luar batas min/max
stepMismatchangka tidak sesuai kelipatan step
badInputbrowser tidak bisa mengubah isian menjadi angka
customErrorsetCustomValidity() sedang menandai kegagalan
validtidak ada flag lain yang aktif

Membaca flag yang relevan jauh lebih informatif daripada sekadar tahu “tidak valid”, karena pesan yang kita tampilkan bisa sesuai penyebabnya — dan pemetaan flag ke pesan Bahasa Indonesia hanya butuh satu object kecil.

// validasi-bawaan.js — validity state, checkValidity, dan setCustomValidity
const form = document.querySelector("#form-produk");
const kode = form.querySelector("#kode");
const email = form.querySelector("#email");

// aturan tambahan milik toko: kupon XYZ-777 sudah pernah ditukar
function periksaKupon() {
  if (kode.value === "XYZ-777") {
    kode.setCustomValidity("Kupon XYZ-777 sudah pernah ditukar.");
  } else {
    kode.setCustomValidity(""); // string kosong = tanpa aturan khusus
  }
}

// peta flag validity menjadi pesan Bahasa Indonesia
const PESAN = {
  valueMissing: "Kolom wajib diisi.",
  patternMismatch: "Format tidak sesuai pola.",
  typeMismatch: "Format email belum benar.",
  tooShort: "Isian terlalu pendek.",
  customError: "Kupon sudah pernah ditukar.",
};

function pesanUntuk(kolom) {
  for (const [flag, teks] of Object.entries(PESAN)) {
    if (kolom.validity[flag]) return teks;
  }
  return "";
}

// --- kondisi awal: kedua kolom kosong ---
console.log("kode  valid:", kode.checkValidity());
console.log("email valid:", email.checkValidity());
console.log("form  valid:", form.checkValidity());

// isi salah, lalu periksa flag satu per satu
kode.value = "xyz-777";
email.value = "budi@@salah";
periksaKupon();
console.log("kode patternMismatch:", kode.validity.patternMismatch);
console.log("pesan kode           :", pesanUntuk(kode));
console.log("email typeMismatch   :", email.validity.typeMismatch);
console.log("reportValidity kode  :", kode.reportValidity());

// format benar, tetapi kupon masuk daftar yang sudah dipakai
kode.value = "XYZ-777";
email.value = "[email protected]";
periksaKupon();
console.log("customError          :", kode.validity.customError);
console.log("pesan kode           :", pesanUntuk(kode));
console.log("kode  valid          :", kode.checkValidity());

// ganti dengan kupon yang masih tersedia
kode.value = "ABC-123";
periksaKupon();
console.log("kode  valid          :", kode.checkValidity());
console.log("form  valid          :", form.checkValidity());

// --- bukti: submit hanya terpicu bila seluruh form valid ---
let terkirim = 0;
form.addEventListener("submit", function (event) {
  event.preventDefault();
  terkirim += 1;
});
form.requestSubmit();
console.log("submit terpicu       :", terkirim);

Output yang Dihasilkan (console browser):

text
kode  valid: false
email valid: false
form  valid: false
kode patternMismatch: true
pesan kode           : Format tidak sesuai pola.
email typeMismatch   : true
reportValidity kode  : false
customError          : true
pesan kode           : Kupon sudah pernah ditukar.
kode  valid          : false
kode  valid          : true
form  valid          : true
submit terpicu       : 1

Mari kita baca alurnya:

  1. Baris 31–34 (kondisi awal): kedua kolom kosong sehingga valueMissing aktif; checkValidity() mengembalikan false tanpa menampilkan apa pun, dan versi untuk seluruh form tersedia lewat form.checkValidity().
  2. Baris 36–43 (isian salah): xyz-777 gagal pola karena huruf kecil, dan budi@@salah gagal format email. Object PESAN dipetakan ke flag lewat pesanUntuk(), sehingga pesan selalu sesuai penyebab kegagalan.
  3. Baris 43 (reportValidity): method ini memeriksa seperti checkValidity() dan sekaligus melaporkan hasilnya — di browser sungguhan, pesan bawaan tampil di dekat kolom dan kolom mendapat fokus. Nilai kembaliannya tetap boolean: false berarti ada yang gagal.
  4. Baris 45–51 (aturan khusus): format sudah benar, tetapi setCustomValidity("Kupon XYZ-777 sudah pernah ditukar.") menandai kolom gagal dengan flag customError. Method ini dipanggil ulang pada setiap pemeriksaan karena browser tidak tahu kapan aturan khusus kita kedaluwarsa.
  5. Baris 53–66 (lolos penuh): kupon baru dan email yang benar membuat form.checkValidity() bernilai true, lalu form.requestSubmit() berhasil memicu submit — bukti bahwa validasi bawaan berlaku sebagai gerbang sebelum handler kita.

checkValidity dan reportValidity #

Keduanya berbagi pekerjaan pemeriksaan dan berbeda pada laporannya:

checkValidity()reportValidity()
Memeriksa keabsahanyaya
Nilai kembalianbooleanboolean
Menampilkan pesan di layartidakya (UI browser)
Memindahkan fokus ke kolomtidakya

checkValidity() cocok untuk keputusan di dalam kode tanpa efek tampilan; reportValidity() dipakai saat kita ingin browser menyampaikan pesan bawaannya sendiri. jsdom, sebagai lingkungan uji, hanya menjalankan sisi logikanya: nilai kembalian boolean sama, tanpa gelembung pesan yang tampak.

setCustomValidity: Aturan yang Hanya Kita Tahu #

Atribut HTML tidak bisa menilai “kupon ini sudah pernah dipakai” — informasi itu hidup di data toko. Method setCustomValidity(teks) menambahkan lapisan aturan atas apa pun yang sudah diperiksa browser: teks non-kosong membuat kolom gagal dengan flag customError, sedangkan string kosong menghapusnya. Dua hal perlu dibiasakan sejak awal. Pertama, panggil setCustomValidity pada setiap putaran pemeriksaan — tandanya tidak dihitung ulang otomatis saat isian berubah, dan hanya panggilan berikutnya yang menggantinya; itulah sebabnya periksaKupon() selalu dipanggil setiap kali isian diperbarui. Kedua, cabut tandanya dengan string kosong begitu syarat terpenuhi — bila tidak, kolom yang sudah benar tetap gagal tanpa alasan yang terlihat.

form.novalidate adalah saklar kebalikan dari semua yang dibahas di sini: atribut novalidate pada <form> mematikan validasi bawaan sehingga handler submit menerima semua isian. Saklar ini dipakai ketika kita sepenuhnya mengambil alih tampilan pesan — misalnya menampilkan galat inline ala chapter sebelumnya — sambil tetap memanfaatkan validity untuk tahu apa yang salah. Pada chapter berikutnya, gerbang validasi ini kita hubungkan ke pengiriman data yang lengkap.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

validasi-bawaan.js

Unduh berkas
javascript
// validasi-bawaan.js — validity state, checkValidity, dan setCustomValidity
const form = document.querySelector("#form-produk");
const kode = form.querySelector("#kode");
const email = form.querySelector("#email");

// aturan tambahan milik toko: kupon XYZ-777 sudah pernah ditukar
function periksaKupon() {
  if (kode.value === "XYZ-777") {
    kode.setCustomValidity("Kupon XYZ-777 sudah pernah ditukar.");
  } else {
    kode.setCustomValidity(""); // string kosong = tanpa aturan khusus
  }
}

// peta flag validity menjadi pesan Bahasa Indonesia
const PESAN = {
  valueMissing: "Kolom wajib diisi.",
  patternMismatch: "Format tidak sesuai pola.",
  typeMismatch: "Format email belum benar.",
  tooShort: "Isian terlalu pendek.",
  customError: "Kupon sudah pernah ditukar.",
};

function pesanUntuk(kolom) {
  for (const [flag, teks] of Object.entries(PESAN)) {
    if (kolom.validity[flag]) return teks;
  }
  return "";
}

// --- kondisi awal: kedua kolom kosong ---
console.log("kode  valid:", kode.checkValidity());
console.log("email valid:", email.checkValidity());
console.log("form  valid:", form.checkValidity());

// isi salah, lalu periksa flag satu per satu
kode.value = "xyz-777";
email.value = "budi@@salah";
periksaKupon();
console.log("kode patternMismatch:", kode.validity.patternMismatch);
console.log("pesan kode           :", pesanUntuk(kode));
console.log("email typeMismatch   :", email.validity.typeMismatch);
console.log("reportValidity kode  :", kode.reportValidity());

// format benar, tetapi kupon masuk daftar yang sudah dipakai
kode.value = "XYZ-777";
email.value = "[email protected]";
periksaKupon();
console.log("customError          :", kode.validity.customError);
console.log("pesan kode           :", pesanUntuk(kode));
console.log("kode  valid          :", kode.checkValidity());

// ganti dengan kupon yang masih tersedia
kode.value = "ABC-123";
periksaKupon();
console.log("kode  valid          :", kode.checkValidity());
console.log("form  valid          :", form.checkValidity());

// --- bukti: submit hanya terpicu bila seluruh form valid ---
let terkirim = 0;
form.addEventListener("submit", function (event) {
  event.preventDefault();
  terkirim += 1;
});
form.requestSubmit();
console.log("submit terpicu       :", terkirim);