Статьи

Мгновенная проверка формы с использованием JavaScript

HTML5 вводит несколько новых атрибутов для реализации проверки формы на основе браузера. Атрибут pattern представляет собой регулярное выражение, которое определяет диапазон допустимых входных данных для элементов textarea и большинства типов input . Обязательный атрибут указывает, является ли поле обязательным. Для устаревших браузеров, которые не реализуют эти атрибуты, мы можем использовать их значения в качестве основы для полизаполнения. Мы также можем использовать их для обеспечения более интересного улучшения — мгновенной проверки формы.

Здесь мы должны быть очень осторожны, чтобы не увлекаться, создавая чрезмерно агрессивную валидацию, которая нарушает естественное поведение при просмотре и мешает людям. Например, я видел формы, в которых невозможно убрать Tab из недопустимого поля — JavaScript используется (или, скорее, злоупотребляет ), чтобы заставить фокус оставаться внутри поля до тех пор, пока он не станет действительным. Это очень плохое удобство использования и прямо противоречит правилам доступности .

То, что мы собираемся сделать в этой статье, гораздо менее навязчиво. Это даже не полная проверка на стороне клиента — это лишь тонкое улучшение юзабилити, реализованное доступным способом, которое (как я обнаружил во время тестирования скрипта) практически идентично тому, что Firefox теперь делает изначально!

Основная концепция

В последних версиях Firefox, если required поле пустое или его значение не соответствует pattern поле будет отображаться красным контуром, как показано на следующем рисунке.

Недопустимая индикация поля в Firefox 16

Это не происходит сразу же, конечно. Если это так, то каждое обязательное поле будет иметь эту схему по умолчанию. Вместо этого эти контуры отображаются только после того, как вы взаимодействуете с полем, которое в основном (хотя и не совсем) аналогично событию onchange .

Вот что мы собираемся сделать, используя onchange в качестве запускающего события. В качестве альтернативы мы могли бы использовать событие oninput , которое срабатывает, как только любое значение вводится или вставляется в поле. Но это на самом деле слишком мгновенно, так как он может легко запускаться и выключаться во многих типах в быстрой последовательности при наборе текста, создавая эффект мигания, который будет раздражать или невозможно отвлекать для некоторых пользователей. И, в любом случае, oninput не oninput программным вводом, как это делает onchange , и нам может потребоваться это для обработки таких вещей, как автозаполнение из сторонних надстроек.

Определение HTML и CSS

Итак, давайте посмотрим на нашу реализацию, начиная с HTML, на котором она основана:

 <form action="#" method="post"> <fieldset> <legend><strong>Add your comment</strong></legend> <p> <label for="author">Name <abbr title="Required">*</abbr></label> <input aria-required="true" id="author" name="author" pattern="^([- \w\d\u00c0-\u024f]+)$" required="required" size="20" spellcheck="false" title="Your name (no special characters, diacritics are okay)" type="text" value=""> </p> <p> <label for="email">Email <abbr title="Required">*</abbr></label> <input aria-required="true" id="email" name="email" pattern="^(([-\w\d]+)(\.[-\w\d]+)*@([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2})$" required="required" size="30" spellcheck="false" title="Your email address" type="email" value=""> </p> <p> <label for="website">Website</label> <input id="website" name="website" pattern="^(http[s]?:\/\/)?([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2}(\/([-~%\.\(\)\w\d]*\/*)*(#[-\w\d]+)?)?$" size="30" spellcheck="false" title="Your website address" type="url" value=""> </p> <p> <label for="text">Comment <abbr title="Required">*</abbr></label> <textarea aria-required="true" cols="40" id="text" name="text" required="required" rows="10" spellcheck="true" title="Your comment"></textarea> </p> </fieldset> <fieldset> <button name="preview" type="submit">Preview</button> <button name="save" type="submit">Submit Comment</button> </fieldset> </form> - <form action="#" method="post"> <fieldset> <legend><strong>Add your comment</strong></legend> <p> <label for="author">Name <abbr title="Required">*</abbr></label> <input aria-required="true" id="author" name="author" pattern="^([- \w\d\u00c0-\u024f]+)$" required="required" size="20" spellcheck="false" title="Your name (no special characters, diacritics are okay)" type="text" value=""> </p> <p> <label for="email">Email <abbr title="Required">*</abbr></label> <input aria-required="true" id="email" name="email" pattern="^(([-\w\d]+)(\.[-\w\d]+)*@([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2})$" required="required" size="30" spellcheck="false" title="Your email address" type="email" value=""> </p> <p> <label for="website">Website</label> <input id="website" name="website" pattern="^(http[s]?:\/\/)?([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2}(\/([-~%\.\(\)\w\d]*\/*)*(#[-\w\d]+)?)?$" size="30" spellcheck="false" title="Your website address" type="url" value=""> </p> <p> <label for="text">Comment <abbr title="Required">*</abbr></label> <textarea aria-required="true" cols="40" id="text" name="text" required="required" rows="10" spellcheck="true" title="Your comment"></textarea> </p> </fieldset> <fieldset> <button name="preview" type="submit">Preview</button> <button name="save" type="submit">Submit Comment</button> </fieldset> </form> - <form action="#" method="post"> <fieldset> <legend><strong>Add your comment</strong></legend> <p> <label for="author">Name <abbr title="Required">*</abbr></label> <input aria-required="true" id="author" name="author" pattern="^([- \w\d\u00c0-\u024f]+)$" required="required" size="20" spellcheck="false" title="Your name (no special characters, diacritics are okay)" type="text" value=""> </p> <p> <label for="email">Email <abbr title="Required">*</abbr></label> <input aria-required="true" id="email" name="email" pattern="^(([-\w\d]+)(\.[-\w\d]+)*@([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2})$" required="required" size="30" spellcheck="false" title="Your email address" type="email" value=""> </p> <p> <label for="website">Website</label> <input id="website" name="website" pattern="^(http[s]?:\/\/)?([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2}(\/([-~%\.\(\)\w\d]*\/*)*(#[-\w\d]+)?)?$" size="30" spellcheck="false" title="Your website address" type="url" value=""> </p> <p> <label for="text">Comment <abbr title="Required">*</abbr></label> <textarea aria-required="true" cols="40" id="text" name="text" required="required" rows="10" spellcheck="true" title="Your comment"></textarea> </p> </fieldset> <fieldset> <button name="preview" type="submit">Preview</button> <button name="save" type="submit">Submit Comment</button> </fieldset> </form> , <form action="#" method="post"> <fieldset> <legend><strong>Add your comment</strong></legend> <p> <label for="author">Name <abbr title="Required">*</abbr></label> <input aria-required="true" id="author" name="author" pattern="^([- \w\d\u00c0-\u024f]+)$" required="required" size="20" spellcheck="false" title="Your name (no special characters, diacritics are okay)" type="text" value=""> </p> <p> <label for="email">Email <abbr title="Required">*</abbr></label> <input aria-required="true" id="email" name="email" pattern="^(([-\w\d]+)(\.[-\w\d]+)*@([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2})$" required="required" size="30" spellcheck="false" title="Your email address" type="email" value=""> </p> <p> <label for="website">Website</label> <input id="website" name="website" pattern="^(http[s]?:\/\/)?([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2}(\/([-~%\.\(\)\w\d]*\/*)*(#[-\w\d]+)?)?$" size="30" spellcheck="false" title="Your website address" type="url" value=""> </p> <p> <label for="text">Comment <abbr title="Required">*</abbr></label> <textarea aria-required="true" cols="40" id="text" name="text" required="required" rows="10" spellcheck="true" title="Your comment"></textarea> </p> </fieldset> <fieldset> <button name="preview" type="submit">Preview</button> <button name="save" type="submit">Submit Comment</button> </fieldset> </form> 

Этот пример представляет собой простую форму комментариев, в которой некоторые поля являются обязательными, некоторые проверяются, а некоторые — оба. Обязательные поля также имеют required поле aria-required , чтобы обеспечить запасную семантику для вспомогательных технологий, которые не понимают новые типы input .

Спецификация ARIA также определяет атрибут aria-invalid , и это то, что мы собираемся использовать, чтобы указать, когда поле недопустимо (для которого в HTML5 нет эквивалентного атрибута). Атрибут aria-invalid явно предоставляет доступную информацию, но его также можно использовать в качестве хука CSS для применения красного контура:

 input[aria-invalid="true"], textarea[aria-invalid="true"] { border: 1px solid #f00; box-shadow: 0 0 4px 0 #f00; } 

Мы могли бы просто использовать box-shadow и не беспокоиться о border , и, честно говоря, это выглядело бы лучше, но тогда у нас не было бы указаний в браузерах, которые не поддерживают box-shadows, таких как IE8.

Добавление JavaScript

Теперь, когда у нас есть статический код, мы можем добавить сценарии. Первое, что нам нужно, это базовая addEvent() :

 function addEvent(node, type, callback) { if (node.addEventListener) { node.addEventListener(type, function(e) { callback(e, e.target); }, false); } else if (node.attachEvent) { node.attachEvent('on' + type, function(e) { callback(e, e.srcElement); }); } } 

Далее нам понадобится функция для определения того, должно ли данное поле быть проверено, которая просто проверяет, что оно не отключено и не доступно только для чтения, и что оно имеет либо pattern либо required атрибут:

 function shouldBeValidated(field) { return ( !(field.getAttribute("readonly") || field.readonly) && !(field.getAttribute("disabled") || field.disabled) && (field.getAttribute("pattern") || field.getAttribute("required")) ); } 

Первые два условия могут показаться многословными, но они необходимы, потому что свойства элемента, disabled и readonly , не обязательно отражают состояния его атрибутов. Например, в Opera поле с жестко заданным атрибутом readonly="readonly" будет по-прежнему возвращать undefined для своего свойства readonly (свойство dot соответствует только состояниям, которые устанавливаются с помощью сценариев).

Получив эти утилиты, мы можем определить основную функцию проверки, которая проверяет поле и затем выполняет фактическую проверку, если применимо:

 function instantValidation(field) { if (shouldBeValidated(field)) { var invalid = (field.getAttribute("required") && !field.value) || (field.getAttribute("pattern") && field.value && !new RegExp(field.getAttribute("pattern")).test(field.value)); if (!invalid && field.getAttribute("aria-invalid")) { field.removeAttribute("aria-invalid"); } else if (invalid && !field.getAttribute("aria-invalid")) { field.setAttribute("aria-invalid", "true"); } } } 

Таким образом, поле является недействительным, если оно требуется, но не имеет значения, или оно имеет шаблон и значение, но значение не соответствует шаблону.

Поскольку pattern уже определяет строковую форму регулярного выражения, все, что нам нужно сделать, это передать эту строку в конструктор RegExp и это создаст объект регулярного выражения, который мы можем проверить по значению. Но мы должны предварительно протестировать значение, чтобы убедиться, что оно не пустое, так что само регулярное выражение не должно учитывать пустые строки.

После того, как мы установили, является ли поле недействительным, мы можем контролировать его атрибут aria-invalid чтобы указать это состояние — добавив его в недопустимое поле, в котором его еще нет, или удалив его из действительного поля, которое имеет. Просто! Наконец, чтобы реализовать все это, нам нужно привязать функцию проверки к событию onchange . Это должно быть так просто:

 addEvent(document, "change", function(e, target) { instantValidation(target); }); 

Однако, чтобы это onchange события onchange должны пузыриться (используя метод, который обычно называется делегированием событий ), но в Internet Explorer 8 и более ранних onchange события onchange не onchange . Мы могли бы просто проигнорировать эти браузеры, но я думаю, что это было бы позором, особенно когда проблему так просто обойти. Это просто означает немного более сложный код — мы должны получить коллекции элементов input и textarea , выполнить итерацию по ним и привязать событие onchange к каждому полю индивидуально:

 var fields = [ document.getElementsByTagName("input"), document.getElementsByTagName("textarea") ]; for (var a = fields.length, i = 0; i < a; i++) { for (var b = fields[i].length, j = 0; j < b; j++) { addEvent(fields[i][j], "change", function(e, target) { instantValidation(target); }); } } 

Вывод и дальше

Таким образом, у нас есть это — простое и ненавязчивое улучшение для мгновенной проверки формы, предоставляя доступные и визуальные подсказки, чтобы помочь пользователям заполнять формы. Вы можете проверить демо ниже:

После того, как этот сценарий будет реализован, мы на самом деле только несколько пропусков и переходов от полного заполнения. Такой сценарий выходит за рамки данной статьи, но если вы захотите развить его дальше, все основные блоки есть — проверка, нужно ли проверять поле, проверка поля по шаблону и / или обязательности, и триггер привязки События.

Я должен признаться, я не уверен, что это действительно того стоит! Если у вас уже есть это улучшение (которое работает во всех современных браузерах вплоть до IE7), и при условии, что у вас нет выбора, кроме как реализовать проверку на стороне сервера, и при условии, что браузеры, которые поддерживают pattern и required уже используют их для предварительной проверки. проверка представления — учитывая все это, есть ли смысл добавлять еще один polyfill?