Pull to refresh

Валидация форм в JavaScript ч.1

Reading time5 min
Views89K

Вступление


Добрый день, уважаемый читатель. В этой статье я бы хотел обратиться к теме проверки содержимого форм на стороне клиента. На заре становления языков, работающих на клиенте, эта задача была основной. Со временем эти языки обросли новыми возможностями (манипуляция DOM, управление стилями и пр.), но задача проверки форм не исчезла. Правда с появлением HTML5 стало возможным указывать такой тип поля формы, как email, и браузер сам возьмет на себя его проверку. Такая возможность на данный момент реализована в Opera, так что расчитывать на нее пока особо не приходится. Поэтому я бы и хотел рассмотреть этот вопрос основательно. В большинстве случаев валидация проводится так: каждому полю раздаётся id, и затем при submit'е вытаскиваем их, попутно проверяя содержимое. И всем хорош данный подход, кроме отсутствия в нем системности. Поэтому хочу предложить вашему вниманию свое решение данной проблемы.

Итак понеслася!


В качестве примера приведена несложная форма, содержащая поля для имени, почтового ящика и пола.
HTML-код файла:

<html>
<head>
    <style type="text/css">
        input[type="text"] {
            
            border: 1px solid #D4E2F7;
        }
        input {
            margin: 3px 0px 3px 15px;
        }

        
    </style>
    <script type="text/javascript">   
        ....
    </script>
</head>
<body>              
    <form name="form" action="6.php" method="POST">
        <input type="text" name="name" value="My name" /><br>
        <input type="text" name="email" value="email@mail.com" /><br>
        <input type="radio" name="sex" checked="checked" value="male" />male<br>
        <input type="radio" name="sex" value="female" />female<br>
        <input type="submit" value="Ok" />
    </form>
</body>
</html>

Весь остальной код будет помещаться в теге <script>.
Сначала создаем функцию, которая будет задавать все необходимые поля, предварительно снабжая их свойствами. Названия полей перечислены в массиве members.

  
function createField() {
        var members = new Array('required', 'regexp');
        for(var i = 0; i < arguments.length; i++) {
            this[members[i]] = arguments[i];
        }
    }
    // absolute regexp
    createField.prototype.regexp = /^[A-z0-9-_+. ,@]{1,}$/ig;
    createField.prototype.valid = false;
    createField.prototype.required = true;
    createField.prototype.nullify = function() {
        this.valid = false;
    };


Далее в прототипе укажем значения по умолчанию полей.
regexp — регулярка, которой должно удовлетворять значение соответствующего поля.
valid — результат проверки значения поля регулярным выражением regexp.
required — индикатор того: требуется ли данное поле (можно ли оставить поле незаполненным).
nullify() — метод, возвращающий поле valid в исходное состояние.

 
var single = new Array();
single['name'] = new createField();
single['email'] = new createField(true, /^[A-z0-9._-]+@[A-z0-9.-]+\.[A-z]{2,4}$/);
single['sex'] = new createField(true, /male$/ig);

Создаем как бы праобраз нашей формы. В ней будет 3 поля с именами name, email и sex, каждое из которых не может остаться незаполненным. Притом значения 2х последних полей должны удовлетворять указанным во втором параметре регулярном выражении.
 
var Singleton = {
        fields : single,
        regForm : false,
        nullify_values : function() {
            for(i in this.fields) {
                this.fields[i].nullify();
            }
        },
        
        ...
        };

В данном участке кода мы объявляем объект Singleton. Назначение поля fields понятно. Поле regForm — объект, содержащий форму. Через него мы и будем получать доступ к полям формы и их значениям.
Метод nullify_values() возвращает значение поля valid у «подобъектов» (т.к. fields это массив объектов) в исходное состояние.


И напоследок самое интересное. Метод submit(), который и заключает в себе основной функционал.
 
submit : function() {
            if(this.regForm) {
                // set property valid to false for every form field
                this.nullify_values();
                var i = null;
                // walks through the form fields, pick and if required check their values
                for(i = 0; i < this.regForm.elements.length; i++) {
                    // current field
                    var oField = this.regForm.elements[i];
                    switch (oField.type) {
                        case "button":
                        case "submit":
                        case "reset":
                            break;
                        case "checkbox":
                        case "radio":
                            if(!oField.checked) {
                                break;
                            }
                        default :
                            // javascript trim function analogue
                            oField.value = oField.value.replace(/^\s*/, '').replace(/\s*$/, '');
                            if(!oField.value) {
                                oField.value = '';
                            }

                            // if this field is out of interest
                            if(!this.fields[oField.name].required) {
                                this.fields[oField.name].valid = true;
                                this.regForm[i].style.border="";
                            }
                            // if this field is required
                            else {
                                var match = this.fields[oField.name].regexp.test(oField.value);
                                // ...  and fits regular expression
                                if(match) {
                                    this.fields[oField.name].valid = true;
                                    this.regForm[i].style.border="";
                                }
                                this.fields[oField.name].regexp.test(oField.value);
                            }
                    }
                }
                // now all we need is to check if the whole form is valid
                // we perform it by comparing number of form fields and number of valid fields
                // they should be equal
                var validForm = 0;
                var fieldsLength = 0;
                for(i in this.fields) {
                    fieldsLength++;
                    if(this.fields[i].valid) {
                        validForm++;
                    }
                    else {
                        this.regForm[i].style.border="1px solid #FF0000";
                        break;
                    }                    
                }
                if(validForm == fieldsLength) {
                    this.regForm.submit();
                }
                else {
                    this.nullify_values();
                    return false;
                }
                
            }
        }

Сначала обнуляем значения valid. Затем проходимся по полям формы. Если поле не несет особой смысловой нагрузки (типа reset) или не является помеченным галочкой — пропускаем его. Удаляем ведущие и замыкающие пробелы. И если поле является необходимым — проверяем его содержимое с помощью регулярного выражения. Если нет — идем дальше. Теперь осталось посчитать общее количество полей и количество валидных полей. И если они совпадают, то форму можно отправлять.
 
    single = null;

    window.onload = function() {
        var regForm = document.forms[0];
        Singleton.regForm = regForm;
        Singleton.regForm.onsubmit = function() {
            return Singleton.submit();
        };
    };

И в самом конце мы «занулляем» объект single, чтобы невзначай не изменить значений Singleton.fields, выхватываем форму и даем ей обработчик события submit.

Остренькое


1. Если мы зануляем single, то не уничтожим ли мы попутно и Singleton.fields? Нет. И вот почему. Присваивая переменной fields объект мы не передаем сам объект, а лишь ссылку на него. Физически объект хранится в так называемой Memory Heap. И не будет удален оттуда garbage collector'ом до тех пор, пока есть хотя бы одна ссылка на него. Таким образом мы лишь удалили ссылку на массив, и объекты не были уничтожены, т.к. существует еще одна ссылка на них, а именно Singleton.fields.

2. В случае большого количества полей, не требующих валидации, не получится ли так, что создается много объектов, свойства которых по большому счету не нужны? Нет. И вот почему. Когда мы достаем поле объекта интерпретатор JS сначала смотрит в самом объекте и если не находит — в прототипе. Таким образом значения по умолчанию хранятся в прототипе в единственном экземпляре, что не есть накладно.

3. Почему когда содержимое поля удовлетворяет регулярному выражению я делаю проверку еще раз? На это вразумительного ответа у меня нет. Опытным путем я заметил, что когда применяется функция RegExp.test(), то сначала она возвращает результат ожидаемый, а потом прямо противоположный. Попробуйте закомментировать эту строку и сами увидите, как поведение станет непредсказуемым. В обычном случае такого не наблюдается.

Цимес


Данный скрипт обладает большой гибкостью в том смысле, что при переносе придется лишь изменить имена полей (ключи массива single) и регулярные выражения для их проверки.

Посмотреть работу скрипта можно здесь.

А скачать его здесь.

Послесловие


Итак с упрощенной задачей проверки содержимого формы мы справились. Однако остались следующие вопросы:
Как задать произвольный стиль для некорректно заполненного поля?
Как добавить валидацию на другие события, например, когда проверка поля нужна по мере ввода данных?
Как выдавать сообщение, которое бы помогло пользователю понять в чем ошибка?
Что делать, если форм на странице больше одной?
Эти вопросы я постараюсь рассмотреть во второй части поста.
Tags:
Hubs:
+1
Comments34

Articles

Change theme settings