Валідація обмежень

Створення вебформ завжди було складною задачею. Хоч розмітка самої форми – це просто, та перевірка того, чи має кожне поле дійсне і змістовне значення, – уже складніше, а сповіщення користувача про проблему може стати головним болем. HTML5 ввів нові механізми для форм: він додав нові семантичні типи для елемента <input> та валідацію обмежень для спрощення роботи з перевіркою вмісту форми на клієнтській стороні. Базові, звичні обмеження можна перевірити без необхідності використання JavaScript, задавши нові атрибути; складніші обмеження можна перевірити за допомогою API валідації обмежень.

Базове знайомство з цими концепціями, з прикладами, дивіться в Підручнику з валідації форм.

[!NOTE] Валідація обмежень HTML не усуває потреби валідації на серверній стороні. Навіть попри те, що слід очікувати куди менше недійсних запитів форми, такі запити все одно можуть бути надіслані багатьма способами:

  • Шляхом внесення змін до HTML через інструменти розробника в браузері.
  • Ручним формуванням запиту HTTP, без використання форми.
  • Програмним вписуванням вмісту до форми (певні валідації обмежень спрацьовують лише щодо користувацького введення, але не тоді, коли значення поля форми задається засобами JavaScript).

Таким чином, слід завжди валідувати дані форми на серверній стороні, аналогічним чином щодо валідації на клієнтському боці.

Внутрішні та базові обмеження

У HTML базові обмеження оголошуються двома способами:

  • Шляхом вибору найбільш семантично відповідного значення атрибута type для елемента <input>, наприклад, якщо вибрати тип email, то це автоматично створює обмеження, що перевіряє, чи є значення дійсною адресою електронної пошти.
  • Шляхом задання значень атрибутам, що стосуються валідації, дозволяючи описувати базові обмеження простим способом, без необхідності використання JavaScript.

Семантичні типи полів

Внутрішні обмеження атрибута type такі:

Тип поля Опис обмеження Пов'язані порушення
<input type="URL"> Значення повинно бути абсолютним URL, як визначено в Живому стандарті URL. Порушення обмеження TypeMismatch
<input type="email"> Значення повинно бути синтаксично дійсною адресою електронної пошти, що зазвичай має формат [email protected], але також може бути локальною – виду username@hostname. Порушення обмеження TypeMismatch

В обох цих типах полів, якщо задано атрибут multiple, то можна задати декілька значень, у вигляді розділеного комами списку. Якщо будь-яке зі значень такого списку не задовольняє умовам, описаним тут, то спрацьовує порушення обмеження Type mismatch.

Зверніть увагу на те, що більшість типів полів не мають внутрішніх обмежень, оскільки деякі з них позбавлені валідації обмежень або мають алгоритм санації, що перетворює неправильні значення на правильні усталені.

Атрибути, що стосуються валідації

На додачу до атрибута type, описаного вище, для опису базових обмежень використовуються наступні атрибути:

Атрибут Типи полів, що його підтримують Можливі значення Опис обмеження Пов'язане порушення
pattern text, search, url, tel, email, password Регулярний вираз JavaScript (скомпільований з вимкненими позначками global, ignoreCase і multiline) Значення повинно давати збіг з заданим патерном. Порушення обмеження patternMismatch
min range, number Дійсне число Значення повинно бути більшим або рівним значенню. Порушення обмеження rangeUnderflow
date, month, week Дійсна дата
datetime-local, time Дійсні дата з часом
max range, number Дійсне число Значення повинно бути меншим або рівним значенню Порушення обмеження rangeOverflow
date, month, week Дійсна дата
datetime-local, time Дійсні дата з часом
required text, search, url, tel, email, password, date, datetime-local, month, week, time, number, checkbox, radio, file, а також елементи <select> і <textarea> Жодного, адже це булів атрибут: його присутність означає true, а відсутність – false Повинно бути якесь значення (якщо цей атрибут задано). Порушення обмеження valueMissing
step date Ціле число днів Якщо крок не заданий у вигляді літерала any, то значення повинно дорівнювати min + ціле число кроків. Порушення обмеження stepMismatch
month Ціле число місяців
week Ціле число тижнів
datetime-local, time Ціле число секунд
range, number Ціле число
minlength text, search, url, tel, email, password, а також на елементі <textarea> Довжина – ціле число Число символів (кодових точок) повинно бути не менше значення атрибута, якщо значення поля не є порожнім. Для <textarea> – всі символи нового рядка нормалізуються до одиничного символу (на противагу парам CRLF). Порушення обмеження tooShort
maxlength text, search, url, tel, email, password, а також на елементі <textarea> Довжина – ціле число Кількість символів (кодових точок) не повинна перевищування значення цього атрибута. Порушення обмеження tooLong

Процес валідації обмежень

Валідація обмежень виконується за допомогою API валідації обмежень – або на окремому елементі форми, або на рівні всієї форми, на самому елементі <form>. Валідація обмежень виконується такими способами:

  • Шляхом виклику методу checkValidity() або reportValidity() на пов'язаному з формою інтерфейсі DOM (HTMLInputElement, HTMLSelectElement, HTMLButtonElement, HTMLOutputElement або HTMLTextAreaElement), що обчислює обмеження лише на відповідному елементі, даючи сценарію змогу отримати цю інформацію. Метод checkValidity() повертає булеве значення, що вказує, чи пройшло значення елемента його обмеження. (Це, як правило, робиться користувацьким агентом при визначенні того, який з псевдокласів CSS, :valid або :invalid, застосовується.) На противагу йому, метод reportValidity() повідомляє користувачу про будь-які порушення обмежень.
  • Шляхом виклику методу checkValidity() або reportValidity() на інтерфейсі HTMLFormElement.
  • Шляхом подання всієї форми.

Виклик checkValidity() називається статичною валідацією обмежень, а виклик reportValidity() або подання форми називається інтерактивною валідацією обмежень.

[!NOTE]

  • Якщо на елементі <form> задано атрибут novalidate, то інтерактивна валідація обмежень не відбувається.
  • Виклик методу submit() на інтерфейсі HTMLFormElement не спричиняє валідації обмежень. Інакше кажучи, цей метод надсилає дані форми на сервер навіть тоді, коли ці дані не задовольняють обмеженням. Замість цього викликайте метод click() на кнопці подання.
  • Обмеження minlength і maxlength перевіряються лише щодо введення з боку користувача. Вони не перевіряються, якщо значення задано програмно, навіть коли явно викликати checkValidity() або reportValidity().

Складні обмеження з допомогою API валідації обмежень

За допомогою JavaScript та API обмежень можна реалізувати складніші обмеження, наприклад, обмеження, що поєднують декілька полів, або такі, що включають складні обчислення.

По суті ідея полягає в тому, щоб за певною подією поля форми (наприклад, onchange) спрацьовував JavaScript для обчислення того, чи порушено обмеження, а потім використовувався метод field.setCustomValidity(), аби задати результат валідації: порожній рядок означає, що обмеження задовольняється, а будь-який інший рядок означає, що є помилка, і цей рядок є повідомленням про помилку, яке відображається користувачеві.

Обмеження з поєднанням декількох полів – валідація поштового індексу

Формат поштового індексу в різних країнах відрізняється. Річ не лише в тому, що більшість країн дозволяє необов'язковий префікс з кодом країни (наприклад, D- в Німеччині, F- в Франції або Швейцарії), але також у тому, що деякі країни мають поштові індекси лише з фіксованою кількістю цифр, а інші, наприклад, Велика Британія, мають складнішу структуру, що дозволяє на деяких конкретних позиціях літери.

[!NOTE] Це не вичерпна бібліотека валідації поштових індексів, а лише демонстрація ключових концепцій.

Як приклад, додаймо сценарій, що перевіряє валідацію обмежень для цієї простої форми:

<form>
  <label for="ZIP">Поштовий індекс : </label>
  <input type="text" id="ZIP" />
  <label for="Country">Країна : </label>
  <select id="Country">
    <option value="ch">Швейцарія</option>
    <option value="fr">Франція</option>
    <option value="de">Німеччина</option>
    <option value="nl">Нідерланди</option>
  </select>
  <input type="submit" value="Валідувати" />
</form>

Це виводить наступну форму:

По-перше, напишімо функцію, що перевіряє саме обмеження:

function checkZIP() {
  // Для кожної країни визначмо патерн, котрому повинен відповідати індекс
  const constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "Швейцарські індекси повинні мати рівно 4 цифри: наприклад, CH-1950 або 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "Французькі індекси повинні мати рівно 5 цифр: наприклад, F-75012 або 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "Німецькі індекси повинні мати рівно 5 цифр: наприклад, D-12345 або 12345",
    ],
    nl: [
      "^(NL-)?\\d{4}\\s*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
      "Нідерландські індекси повинні мати рівно 4 цифри, після яких – 2 літери, що не є SA, SD і SS",
    ],
  };

  // Отримати ідентифікатор країни
  const country = document.getElementById("Country").value;

  // Отримати поле NPA
  const ZIPField = document.getElementById("ZIP");

  // Сформувати перевірник обмеження
  const constraint = new RegExp(constraints[country][0], "");
  console.log(constraint);

  // Перевірити!
  if (constraint.test(ZIPField.value)) {
    // Якщо індекс відповідає обмеженню, використовується API обмежень, щоб про це сповістити
    ZIPField.setCustomValidity("");
  } else {
    // Якщо індекс не відповідає обмеженню, використовується API обмежень, щоб
    // надати повідомлення про формат, що вимагається для відповідної країни
    ZIPField.setCustomValidity(constraints[country][1]);
  }
}

Потім ця функція зв'язується з подією onchange елемента <select> і подією oninput елемента <input>:

window.onload = () => {
  document.getElementById("Country").onchange = checkZIP;
  document.getElementById("ZIP").oninput = checkZIP;
};

Лімітування розміру файлу перед його відвантаженням

Іще одне поширене обмеження – лімітування розміру файлу до відвантаження. Перевірка цього на клієнтській стороні перед тим, як файл буде переданий на сервер, вимагає поєднання API обмежень, і особливо методу field.setCustomValidity(), з іншим API JavaScript, тут – API файлів.

Ось частина мовою HTML:

<label for="FS">Оберіть файл, менший за 75 кБ : </label>
<input type="file" id="FS" />

Це виводить:

JavaScript зчитує вибраний файл, використовує метод File.size() для отримання його розміру, порівнює його з (жорстко закодованим) лімітом і використовує API обмежень, щоб повідомити браузер про наявність або відсутність порушення:

function checkFileSize() {
  const FS = document.getElementById("FS");
  const files = FS.files;

  // Якщо є (щонайменше) один вибраний файл
  if (files.length > 0) {
    if (files[0].size > 75 * 1024) {
      // Перевірити умову
      FS.setCustomValidity("Вибраний файл не повинен бути більшим за 75 кБ");
      FS.reportValidity();
      return;
    }
  }
  // Немає порушення власного обмеження
  FS.setCustomValidity("");
}

Врешті решт, цей метод чіпляється до відповідної події:

window.onload = () => {
  document.getElementById("FS").onchange = checkFileSize;
};

Візуальне оформлення валідації обмежень

Окрім задання обмежень, веброзробники хочуть контролювати те, які повідомлення виводяться користувачам і як ці повідомлення оформлені.

Контроль вигляду елементів

Вигляд елементів можна контролювати за допомогою псевдокласів CSS.

Псевдокласи CSS :required та :optional

Псевдокласи :required та :optional дозволяють писати селектори, що дають збіг з елементами форми, що мають атрибут required і тими, що його не мають.

Псевдоклас CSS :placeholder-shown

Дивіться :placeholder-shown.

Псевдокласи CSS :valid :invalid

Псевдокласи :valid та :invalid використовуються для представлення елементів <input>, чий вміст проходить і провалює валідацію, відповідно, згідно з налаштуваннями типу поля. Ці класи дають користувачам змогу оформлювати дійсні та недійсні елементи форми, щоб полегшити визначення елементів, які відформатовані правильно або неправильно.

Контроль тексту порушення обмеження

З контролем тексту порушення обмеження можуть допомогти наступні інструменти:

  • Метод setCustomValidity(message) на наступних елементах:

    • <fieldset>. Примітка: Задання власного повідомлення про валідність на елементах fieldset в більшості браузерів не запобігає поданню форми.
    • <input>
    • <output>
    • <select>
    • Кнопках подання (створених або за допомогою елемента <button> з типом submit, або елемента input з типом submit. Інші типи кнопок не беруть участі в валідації обмежень.
    • <textarea>
  • Інтерфейс ValidityState описує об'єкт, повернений властивістю validity типів елементів, перерахованих вище. Він представляє різні шляхи, за якими введене значення може бути недійсним. Разом вони допомагають пояснити, чому значення елемента не проходить валідацію, якщо є недійсним.