Widgets: форма с автопроверкой

Проблема проверки форм с помощью JavaScript всегда существовала. С помощью небольшого виджета, рассматриваемого в статье, можно автоматизировать проверку данных на клиенте.

В настоящее время всё большую популярность завоёвывают особые графические элементы, так называемые «виджеты» (widgets). Началось это увлечение с Google, но теперь на очень многих сайтах можно увидеть популярные среди разработчиков элементы. Например, это может быть индикатор загрузки пользовательского файла; возможность удаления пользователем целых блоков сайта, или автоматическое изменение содержимого вкладок без перезагрузки страницы.

Выглядят подобные штуки очень привлекательно, и, зачастую, они весьма удобны посетителю. Тем не менее, если взглянуть на рунет в целом, «виджеты» используют только на самых крупных сайтах и порталах. И это понятно: на разработку подобных элементов нужны достаточно серьёзные навыки и умения, а также иногда и мощные аппаратные возможности, учитывая количество посетителей, посещающих on-line пространство.

Тем не менее, постепенно, разработчики средних и малых сайтов внедряют «виджеты» и у себя (банальный, но необходимый пример: Ajax), правда не так активно, как хотелось бы.

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

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

Ещё одним поводом для создания подобного «виджета» послужил тот факт, что часто пользователю как свидетельство об ошибке, выдаётся аварийное диалоговое окно типа alert, и всякий раз пользователю приходится нажимать на кнопку  «OK», чтобы закрыть его (например, в этом решении используются именно диалоговые окна). В разработанной нами форме ошибки будут выдаваться без диалоговых окон.

Разумеется, мы не будем предусматривать все возможные случаи проверок и ограничимся лишь самым необходимым минимумом, но его, уверяем, будет более чем достаточно для большинства форм.

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

Спрашивается, как можно узнать, следует ли проверять элемент формы? Очень просто, достаточно ввести систему обозначений, и использовать её либо в именах полей, либо в классах этих элементов.

Например, все обязательные к заполнению поля должны быть с классом «required», а, например, каждое поле, в которое необходимо ввести адрес электронной почты, должно называться или содержать слово «mail». Следуя подобному принципу, можно создать весьма удачные и полезные формы.

Наконец, осталось уточнить, каким способом выводить сообщение именно под необходимым полем. Для этого необходимо предъявить особое требование – у каждого поля формы должен быть родительский элемент. Этот элемент и можно впоследствии расширить и добавить в него необходимое сообщение об ошибке (или удалить это сообщение, если оно вдруг перестанет быть необходимым).

Впрочем, это решение может породить некоторые проблемы, поскольку в некоторых сложных формах часто под полем пишут дополнительную информацию. Однако «обернуть» поле лишним DIV’ом не слишком обременительно.

Отметим также, что сообщения об ошибках можно сделать более информативными, и зависящими, например, от названия поля. Ничего не мешает завести отдельный ассоциативный массив с названиями полей, и выводить уже сообщения, вида «Поле «ФИО» заполнено некорректно!», но можно поступить ещё проще, и хранить название поля в атрибуте title.

Итак, давайте сперва посмотрим на пример работы формы с автопроверкой:

Имя *
Должность *
E-mail *
Second mail
Пароль *
Подтверждение пароля *
Пол *
Образование
Увлечения

Как можно отметить, формой пользоваться очень удобно: сообщения об ошибках добавляются и убираются автоматически (причём все сразу), а на первое поле, содержащее ошибку, устанавливается курсор и оно выделяется.

Теперь можно переходить непосредственно к разработке формы. Взглянем на html-код.


<form class='frm' id='our_form' method='POST' action=''>
<table>

<tr>
<td>Имя <span>*</span></td>
<td><input name='name' value='' maxlength='20' class='bla small required bla2'></td>
</tr>

<tr>
<td>Должность <span>*</span></td>
<td><input name='profession' value='' maxlength='60' class='small required'></td>
</tr>

<tr>
<td>E-mail <span>*</span></td>

<td><input name='mail' class='required' value='some_example@domain.ru' maxlength='60'></td>
</tr>


<tr>
<td>Second mail</td>
<td><input name='email' value='some..wrong_example@domain.cc' maxlength='60'></td>
</tr>

<tr>
<td>Пароль <span>*</span></td>
<td><input name='password' class='required' value='' maxlength='60' type='password'></td>
</tr>

<tr>
<td>Подтверждение пароля <span>*</span></td>
<td><input name='password_confirm' class='required' value='' maxlength='60' type='password'></td>
</tr>

<tr>
<td>Пол <span>*</span></td>
<td>

<select name='gender' class='required'>
<option value=''></option>
<option value='10'>Мужской</option>

<option value='1'>Не мужской</option>
</select>
</tr>

<tr>
<td>Образование</td>

<td>
<select name='education'>
<option value=''></option>
<option value='10'>Высшее</option>

<option value='1'>Среднее</option>
<option value='100'>Просто хорошее</option>
</select>
</tr>

<tr>
<td>Увлечения</td>
<td><textarea name='hobby'></textarea></td>
</tr>

<tr>
<td colspan='2' align='center'><input name='blablabla' type='hidden'></td>
</tr>

<tr>
<td colspan='2' align='center'><input class='sbm' name='sub' value='Отправить' type='submit'></td>
</tr>

</table>
</form>

<script type='text/javascript'>
<!--
    new formValidate('our_form');

-->
</script>


Форма, для простоты, содержит таблицу из двух столбцов. Заголовки тех полей, которые необходимо заполнить, отмечены звёздочками, сами же поля содержат класс required.

Ещё один момент заключается в том, что название поля «Подтверждение пароля» содержит приставку _confirm. Эта приставка означает, что существует поле, значение которого должно в точности повторяться в текущем поле.

И последнее, что необходимо отметить, это непосредственное инстанцирование JavaScript-объекта. Таким образом устанавливается обработчик OnSubmit, который сработает при попытке отправить форму. Считается, что лушче устанавливать обработчики исключительно через JavaScript-код:


document.getElementById('some_element').onclick = someFunc;

Таким образом достигается разделение представления и логики на клиенте. Единственный минус такого подхода заключается в том, что если до попытки установки обработчика какому-либо элементу произошла ошибка, он не будет установлен.

Теперь у нас впереди остаётся самое главное: разбор объекта formValidate. Перед тем, как увидеть JS-код, отметим, что мы будем использовать несколько общих функций, например, широко известную функцию $(), заимствованную из Prototype. Кроме того, мы будем использовать функцию str_replace() – это аналог функции replace(), но без использования регулярных выражений. Наконец, ещё одна функция, которая понадобится нам для работы, getParent(), которая корректно находит родительский элемент для текущего.


function $(obj) {
  	if (typeof obj == 'object')
  	return obj;
	if (document.getElementById)
		return (document.getElementById(obj));
	else  if (document.all) 
		return document.all(obj);

    return null;
}

String.prototype.str_replace = function(srch, rpl)
{
	var ar = this.split(srch);
	return ar.join(rpl);
}


function getParent(el) {

	return ((el.parentElement) ? el.parentElement : ((el.parentNode) ? el.parentNode : null));

}

function createMessage(el, i, message, parent)
{
	if (!$('id' + i))
	{
		var newB = document.createElement("B");
		newB.setAttribute('id', 'id' + i);
		newB.appendChild(document.createElement("BR"));
		newB.appendChild(document.createTextNode(message));				
		if (parent)
			parent.appendChild(newB);
	}
}

function removeMessage(el, i, parent)
{
	if ($('id' + i))
	{ 
		var b = $('id' + i); 
		if (typeof  parent != 'undefined' && b) parent.removeChild(b);
	}
}

function checkMail(el) {
	if (el.name.indexOf('mail') != -1)
	{
		var mail = "^([A-Za-z0-9]+([-_\.]?[A-Za-z0-9]+)?@[A-Za-z0-9]+(\.?[A-Za-z0-9]+?)\.[A-Za-z]{2,9})$";
		var re = new RegExp(mail);
		return !re.test(el.value + "");
	}
	return false;
}

function checkConfirm(el, frm)
{
	var pos = el.name.indexOf('_confirm');
	if (pos != -1)
	{
		var master = el.name.substr(0, pos);
		if (frm.elements[master] && frm.elements[master].value)
		{
			if (frm.elements[master].value != el.value)
				return true;		
		}
	}
	return false;
}

function isRequired(el)
{
	return ((el.className.indexOf('required') != -1) && ((typeof  el.value != 'undefined') && (el.value == '')));	
}

function isMailField(el)
{
	return (el.name.indexOf('mail') != -1);
}

function formValidate(form)
{
	this.form_ = $(form);
	this.queue = new Array();
	this.queueText = new Array();
	this.empty_message = 'Внимание! Вы не заполнили обязательное поле!';
	this.addCheck(checkMail, 'Адрес электронной почты введён неверно!');
	this.addCheck(checkConfirm, 'Подтверждение не совпадает!');

	this.setValidate(form);	
};

formValidate.prototype.processError = function(el, num, message, parent)
{
	createMessage(el, num, message, parent);
	if (this.first == 0)
		this.first = el;
	this.return_val = false;
}

formValidate.prototype.unsetError = function(el, i, parent)
{
	removeMessage(el, i, parent);	
}

formValidate.prototype.validate = function(form)
{
	this.return_val = true;
	this.first = 0;

	if (typeof (form.elements) != 'undefined')
	{
		var els = form.elements;
		if (els && els.length && els.length > 0)
		{
			for (var i = 0; i < els.length; i++)
			{
				var parent = getParent(els[i]);
				el.className = el.className.str_replace('selected', '');
				if (isRequired(els[i]))
				{
					this.processError(els[i], i, this.empty_message, parent);
				} 
				else  
				{
					this.unsetError(els[i], i, parent);

					if (els[i].value == '')
						continue;

					for (j = 0; j < this.queue.length; j++)
					{
						if (this.queue[j].call(this, els[i], this.form_))
						{
							this.processError(els[i], i, this.queueText[j], parent);
							break;
						}
						else 
						{
							this.unsetError(els[i], i, parent);
						}
					}	
				}

			}
		}
	}
	if (this.return_val === false && this.first)
	{
		this.first.className += ' selected';
		this.first.focus();
	}
	
	return this.return_val;
};



formValidate.prototype.setValidate = function (form)
{
	obj = this;
	if ($(form)) $(form).onsubmit = function () { return obj.validate($(form));};
};

formValidate.prototype.addCheck = function (fn, message) {
	this.queue[this.queue.length] = fn;
	this.queueText[this.queueText.length] = message;
};

Итак, главная часть приведённого выше кода, это объект formValidate. В его конструкторе инициализируются массивы для хранения функций для обработки элементов формы (queue), а также тексты ошибок, выдаваемых в случае необходимости (queueText).

В конструкторе с помощью метода addCheck() также добавляются все необходимые проверки (хотя, конечно, их можно добавлять и в любом другом месте, например, после инстанцирования объекта formValidate). В этот метод передаются: фукнция для проверки утверждения, и текст, выводимый в случае ошибки.

Наконец, в конструкторе происходит установка обработчика на форму.

Основная функция проверки validate() вызывается в случае попытки отправки формы. Она действует достаточно просто: проходит по всем элементам формы, выявляет требуемые к заполнению и незаполненные поля, и генерирует ошибку, с помощью метода processError(). Если поле заполнено, то запускается обход массива дополнительных проверок, в результате которого либо устанавливается ошибка для этого поля (сам цикл после нахождения первой же ошибки прерывается), либо снимается ошибка с поля, если она уже была обнаружена ранее (функция unsetError()). Если же поле не обязательно к заполнению и не заполнено, цикл дополнительных проверок не запускается.

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

Проверки должны быть устроены следующим образом: они должны возвращать true в случае, если произошла ошибка и false в любом другом случае. Также, в них задаются условия, какие из полей необходимо проверять. Это может быть особый критерий, связанный с именем поля или с классом, или ещё с чем-нибудь.

Вообще, данный подход достаточно гибок и расширяем. Ничего не мешает, например, добавлять функции, проверяющие данные с помощью асинхронных запросов. Можно также в конструкторе «навесить» на поля формы дополнительные обработчики, например, на те поля, в которые разрешено вводить только цифры. Очень легко изменить и вывод ошибок, сделать его более информативным, например, определив для разного рода ошибок соответствующие пиктограммы. Да и сам код можно всецело поменять, подогнав его под свои нужды.

Использовать же данный класс очень просто. Достаточно предварительно в коде формы создать необходимые имена и классы, и далее использовать следующий код:


// После кода формы или в OnLoad всей страницы следует написать
new formValidate('our_form');
// где 'our_form' - ID формы, желательно, не должно совпадать с name 
// из-за всеми любимого IE

Вот, пожалуй, и всё. Осталось лишь посоветовать чаще использовать возможности JavaScript, не забывая, конечно, и о программировании на серверной части.

Прилагаемый для скачивания js-код содержит в себе небольшие изменения, для использования его в старых версиях IE.

Скачать исходный код

20 января - 2 февраля 2008 года


Оценка материала:

 
На данный момент нет голосовавших

Число просмотров: 2
ilmarinen, 22.04.2008, 14:23

Странно, что эта форма устроена не по такому принципу:)

admin, 22.04.2008, 16:23

Лень было переделывать.

Имя *

Цифровой ящик

Комментарии *

Top.Mail.Ru