Валидация ограничений
=====================

Создание веб-форм всегда было сложной задачей. Хотя сверстать саму форму достаточно просто, проверить, имеет ли каждое поле валидное значение — сложнее, а информирование пользователя о проблеме может стать настоящей головной болью. HTML5 (en-US) представил для форм новый механизм: он добавляет элементу `<input>` новые семантические типы и *constraint validation*, чтобы облегчить проверку содержимого на стороне клиента. С помощью новых атрибутов основные ограничения могут быть проверены без использования JavaScript; более сложные ограничения могут быть проверены с помощью Constraint validation API (en-US).

Базовое введение в эти концепции с примерами можно найти в разделе Валидация форм на стороне клиента.

**Примечание:** HTML5-валидация ограничений не отменяет необходимости дополнительной валидации на *стороне сервера*. Несмотря на то, что ожидается гораздо меньшее не валидных запросов формы, они всё же могут быть отправлены несовместимыми браузерами (например, не поддерживающими HTML5 или JavaScript) или злоумышленниками, которые пытаются взломать ваше веб-приложение. Следовательно, как и в случае с HTML4, вам по-прежнему нужно производить дополнительную валидацию ограничений ввода и на стороне сервера.

Внутренние и базовые ограничения
--------------------------------

В HTML5 базовые ограничения объявляются двумя способами:

* Задавать семантически наиболее подходящие значения для атрибута `type` элемента `<input>`. Например, типа `email` автоматически создаёт ограничение, которое проверяет, является ли значение валидным email-адресом.
* Использовать атрибуты валидации, позволяющие достаточно просто задать ограничения без помощи JavaScript.
### Семантические типы input

Внутренние ограничения атрибута `type`:

| Тип input | Описание ограничения | Связанное нарушение |
| --- | --- | --- |
| `<input type="URL">` (en-US) | Значение должно быть URL-адресом, как указано в URL Living Standard. | Нарушение ограничения **TypeMismatch** |
| `<input type="email">` (en-US) | Значение должно быть синтаксически правильным email-адресом, который обычно имеет формат `username@hostname.tld`. | Нарушение ограничения **TypeMismatch** |

Для обоих этих типов input, если установлен атрибут `multiple` допускается ввод нескольких разделённых запятыми значений. Если какое-то из значений не удовлетворяет условию, описанному выше, срабатывает нарушение ограничения **Type mismatch**.

Обратите внимание, что большинство типов input не имеют внутренних ограничений, так как некоторые из них недоступны для валидации ограничений или имеют алгоритм, по умолчанию преобразующий неправильные значения в правильные.

### Атрибуты валидации

В дополнение к атрибуту `type`, описанному выше, для описания основных ограничений используются следующие атрибуты:

| Атрибут | Типы input, поддерживающие атрибут | Возможные значения | Описание ограничения | Связанное нарушение |
| --- | --- | --- | --- | --- |
| `pattern` | `text`, `search`, `url`,
 `tel`, `email`, `password` | Регулярное выражение JavaScript
 (без
 ECMAScript 5
 флагов `global`, `ignoreCase`, and
 `multiline` | Значение должно соответствовать шаблону. | `Нарушение ограничения
**patternMismatch**` |
| `min (en-US)` | `range`, `number` | Валидное число | Значение в поле должно быть больше или равно значению атрибута | 
 Нарушение ограничения
 **`rangeUnderflow`** |
| `date`, `month`, `week` | Валидная дата |
| `datetime`, `datetime-local`, `time` | Валидная дата и время |
| `max (en-US)` | `range`, `number` | Валидное число | Значение в поле должно быть меньше или равно значению атрибута | 
 Нарушение ограничения
 **`rangeOverflow`** |
| `date`, `month`, `week` | Валидная дата |
| `datetime`, `datetime-local`, `time` | Валидная дата и время |
| `required (en-US)` | `text`, `search`, `url`,
 `tel`, `email`, `password`,
 `date`, `datetime`, `datetime-local`,
 `month`, `week`, `time`,
 `number`, `checkbox`, `radio`,
 `file`; а также элементы `<select>`
 и `<textarea>` (en-US) | *Нет*, так как это булев атрибут: его наличие означает
 *true*, а отсутствие — *false* | В поле обязательно должно быть значение (если установлен атрибут) | **`valueMissing (en-US)`**
 constraint violation
  |
| `step (en-US)` | `date` | Целое число дней | 
 Если для шага не задан литерал `any`, значением должно быть
 **min** + целое число, кратное шагу
  | 
 Нарушение ограничения
 **`stepMismatch`** |
| `month` | Целое число месяцев |
| `week` | Целое число недель |
| `datetime`, `datetime-local`, `time` | Целое число секунд |
| `range`, `number` | Целое число |
| `minlength (en-US)` | `text`, `search`, `url`,
 `tel`, `email`, `password`; а также
 элемент `<textarea>` (en-US)/td>
  | Целое число | 
 Если атрибут не пустой, количество символов (точек кода) содержимого
 поля не должно быть меньше значения, указанного в атрибуте. Все символы
 новой строки нормализованы до одного символа (в отличие от CRLF-пар) для
 `<textarea>` (en-US).
  | 
 Нарушение ограничения
 **`tooShort`** |
| `maxlength (en-US)` | `text`, `search`, `url`,
 `tel`, `email`, `password`; а также
 элемент `<textarea>` (en-US) | Целое число | 
 Количество символов (точек кода) содержимого поля не должно превышать
 значение, указанное в атрибуте
  | 
 Нарушение ограничения
 **`tooLong`** |

Процесс валидации ограничений
-----------------------------

Валидация ограничений выполняется с помощью Constraint Validation API либо на отдельном элементе формы, либо на уровне формы, на самом элементе `<form>`. Способы валидации:

* Вызов метода `checkValidity()` или `reportValidity()` связанного с формой DOM-интерфейса, (`HTMLInputElement` (en-US), `HTMLSelectElement` (en-US), `HTMLButtonElement`, `HTMLOutputElement` (en-US) или `HTMLTextAreaElement` (en-US)), который оценивает ограничения только на данном элементе, позволяя скрипту получить эту информацию. Метод `checkValidity()` возвращает логическое значение, указывающее, соотвествует ли значение элемента его ограничениям. (Обычно это делает браузер при определении того, какой CSS-псевдоклассов применяется: `:valid` или `:invalid`.) Напротив, метод `reportValidity()` сообщает пользователю о любых нарушениях ограничений.
* Вызов метода `checkValidity()` или `reportValidity()` интерфейса `HTMLFormElement` (en-US).
* Отправка самой формы.

Вызов метода `checkValidity()` называется *статической* валидацией ограничений, а вызов метода `reportValidity()` или отправка формы называется *интерактивной* валидацией ограничений.

**Примечание:**- Если элементу `<form>` задан атрибут `novalidate`, интерактивная валидация ограничений не произойдёт.

* Вызов метода `submit()` интерфейса `HTMLFormElement` (en-US) не запускает валидацию ограничений. Другими словами, этот метод отправляет данные формы на сервер, даже если ограничения нарушены. Вместо него вызовите метод `click()` кнопки отправки формы.

Сложные ограничения с помощью Constraint Validation API
-------------------------------------------------------

С помощью JavaScript и Constraint Validation API можно реализовать более сложные ограничения, например, ограничения, касающиеся сразу нескольких полей или ограничения, включающие сложные вычисления.

По сути, идея заключается в том, чтобы при наступлении определённого события поля формы (например, **onchange**) запускать JavaScript, чтобы определить, нарушено ограничение, и затем с помощью метода `field.setCustomValidity()`, задать результат валидации: пустая строка означает, что ограничение соблюдается, а любая другая строка означает, что есть ошибка и эта строка является сообщением об ошибке, которое нужно показать пользователю.

### Ограничение, объединяющее несколько полей: Валидация почтового индекса

Формат почтового индекса в разных странах отличается. Мало того, что в большинстве стран может быть дополнительный префикс с кодом страны (например, `D —` в Германии, `F —` во Франции или Швейцарии), в некоторых странах, почтовые индексы содержат фиксированное количеством цифр; другие, например, Великобритания, имеют ещё более сложную структуру, допускающую в определённых местах ещё и наличие букв.

**Примечание:** Далее представлена не полная библиотека валидации почтового индекса, а скорее демонстрация ключевых концепций.

В качестве примера мы добавим скрипт, выполняющий валидацию органичений простой формы:


```html
<form>
  <label for="ZIP">ZIP : </label>
  <input type="text" id="ZIP" />
  <label for="Country">Country : </label>
  <select id="Country">
    <option value="ch">Switzerland</option>
    <option value="fr">France</option>
    <option value="de">Germany</option>
    <option value="nl">The Netherlands</option>
  </select>
  <input type="submit" value="Validate" />
</form>
```


Получим следующую форму:

Сначала запишем функцию, проверяющую само ограничение:


```js
function checkZIP() {
  // Для каждой страны определяем шаблон, которому должен следовать почтовый индекс
  var constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "Switzerland ZIPs must have exactly 4 digits: e.g. CH-1950 or 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "France ZIPs must have exactly 5 digits: e.g. F-75012 or 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "Germany ZIPs must have exactly 5 digits: e.g. D-12345 or 12345",
    ],
    nl: [
      "^(NL-)?\\d{4}\\s\*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
      "Nederland ZIPs must have exactly 4 digits, followed by 2 letters except SA, SD and SS",
    ],
  };

  // Прочитать id страны
  var country = document.getElementById("Country").value;

  // Получить поле NPA
  var ZIPField = document.getElementById("ZIP");

  // Создать валидатор ограничения
  var constraint = new RegExp(constraints[country][0], "");
  console.log(constraint);

  // Валидировать индекс
  if (constraint.test(ZIPField.value)) {
    // Индекс соответствует ограничению, мы используем ConstraintAPI, чтобы сообщить об этом
    ZIPField.setCustomValidity("");
  } else {
    // Индекс не соответствует ограничению , мы используем ConstraintAPI, чтобы
    // показать сообщение, описывающее формат, требуемый для данной страны
    ZIPField.setCustomValidity(constraints[country][1]);
  }
}
```


Затем мы связываем его с событием **onchange** для `<select>` и с событием **oninput** для `<input>`:


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

### Ограничение размера загружаемого файла

Ещё одним распространённым ограничением является лимит размера загружаемых файлов. Данная проверка на стороне клиента перед отправкой на сервер требует сочетания Constraint Validation API и особенно метода `field.setCustomValidity()` с другим JavaScript API, в данном случае — File API.

Вот часть HTML:


```html
<label for="FS">Select a file smaller than 75 kB : </label>
<input type="file" id="FS" />
```


Она отображает:

JavaScript считывает выбранный файл, с помощью метода `File.size()` получает его размер, сравнивает его с лимитом и вызывает Constraint Validation API, чтобы сообщить браузеру о нарушении:


```js
function checkFileSize() {
  var FS = document.getElementById("FS");
  var files = FS.files;

  // Если выбран хотя бы один файл
  if (files.length > 0) {
    if (files[0].size > 75 \* 1024) {
      // Проверить ограничение
      FS.setCustomValidity("Размер файла не должен превышать 75 kB");
      return;
    }
  }
  // Если нарушения ограничений нет
  FS.setCustomValidity("");
}
```


Наконец мы связываем метод с нужным событием:


```js
window.onload = function () {
  document.getElementById("FS").onchange = checkFileSize;
};
```

Визуальное оформление нарушения ограничений
-------------------------------------------

Помимо установки ограничений, веб-разработчики хотят управлять тем, какие сообщения показываются пользователям и как они оформлены.

### Управление внешним видом элементов

Изменять внешний вид элементов можно с помощью CSS-псевдоклассов.

#### CSS-псевдоклассы :required и :optional

Псевдоклассы `:required` и `:optional` позволяют писать селекторы, соответствующие элементам формы, которые имеют или не имеют атрибут `required`

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

Смотрите `:placeholder-shown`

#### CSS-псевдоклассы :valid и :invalid

Псевдоклассы `:valid` и `:invalid` используются для представления элементов <input>, содержимое которых является валидным или не валидным соответственно. Эти классы позволяют пользователю оформлять валидные или не валидные элементы формы, чтобы облегчить определение элементов, которые имеют корректный или некорректный формат.

### Управление текстом нарушения ограничений

Следующее может помочь с управлением текстом нарушения ограничений:

* Метод element.setCustomValidity(message) (en-US) для элементов:
    + `<fieldset>`. Примечание: Добавление кастомного сообщения валидации на элемент fieldset не предотвращает отправку формы в большинстве браузеров.
    + `<input>`
    + `<output>`
    + `<select>`
    + Кнопка отправки формы в виде элемента `<button>` или `input` `submit` с указанным типом submit (en-US). Другие типы кнопок не участвуют в валидации ограничений.
    + `<textarea>` (en-US)
* Интерфейс `ValidityState` описывает объект, возвращаемый свойством validity (en-US) перечисленных выше типов элементов. Он представляет разные способы определения не валидности значения. Вместе они помогают объяснить, почему значение элемента не проходит валидацию, если оно не валидно.