Pro jQuery

Pro jQuery

Адам Фриман

Добавление валидации формы

На следующем этапе мы добавляем некоторую валидацию для элементов ввода данных. В листинге 16-6 показаны необходимые дополнения.

Листинг 16-6: Добавление валидации формы
<!DOCTYPE html>
<html>
<head>
	<title>Example</title>
	<script src="jquery-1.7.js" type="text/javascript"></script>
	<script src="jquery.tmpl.js" type="text/javascript"></script>
	<script src="jquery.validate.js" type="text/javascript"></script>
	<link rel="stylesheet" type="text/css" href="styles.css" />
	<style type="text/css">
		a.arrowButton {
			background-image: url(leftarrows.png);
			float: left;
			margin-top: 15px;
			display: block;
			width: 50px;
			height: 50px;
		}

		#right {
			background-image: url(rightarrows.png);
		}

		h1 {
			min-width: 0px;
			width: 95%;
		}

		#oblock {
			float: left;
			display: inline;
			border: thin black solid;
		}

		form {
			margin-left: auto;
			margin-right: auto;
			width: 885px;
		}

		#bbox {
			clear: left;
		}

		#error {
			color: red;
			border: medium solid red;
			padding: 4px;
			margin: auto;
			width: 300px;
			text-align: center;
			margin-bottom: 5px;
		}

		.invalidElem {
			border: medium solid red;
		}

		#errorSummary {
			border: thick solid red;
			color: red;
			width: 350px;
			margin: auto;
			padding: 4px;
			margin-bottom: 5px;
		}
	</style>
	<script type="text/javascript">
		$(document).ready(function () {
			
			$.ajaxSetup({
				timeout: 5000,
				converters: {
					"text html": function (data) { return $(data); }
				}
			})
			
			$(document).ajaxError(function (e, jqxhr, settings, errorMsg) {
				$('#error').remove();
				var msg = "An error occurred. Please try again"
				if (errorMsg == "timeout") {
					msg = "The request timed out. Please try again"
				} else if (jqxhr.status == 404) {
					msg = "The file could not be found";
				}
				$('<div id=error/>').text(msg).insertAfter('h1');
			}).ajaxSuccess(function () {
				$('#error').remove();
			})
			
			$('#row2, #row3').hide();
			
			var flowerReq = $.get("flowers.html", function (data) {
				var elems = data.filter('div').addClass("dcell");
				elems.slice(0, 3).appendTo('#row1');
				elems.slice(3).appendTo("#row2");
			})
			
			var jsonReq = $.getJSON("additionalflowers.json", function (data) {
				$('#flowerTmpl').tmpl(data).appendTo("#row3");
			})
			
			$('<div id=errorSummary>Please correct the following errors:</div>')
				.append('<ul id="errorsList"></ul>').hide().insertAfter('h1');
			
			$('form').validate({
				highlight: function (element, errorClass) {
					$(element).addClass("invalidElem");
				},
				unhighlight: function (element, errorClass) {
					$(element).removeClass("invalidElem");
				},
				errorContainer: '#errorSummary',
				errorLabelContainer: '#errorsList',
				wrapper: 'li',
				errorElement: "div"
			});
			
			var plurals = {
				astor: "Astors", daffodil: "Daffodils", rose: "Roses",
				peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops",
				carnation: "Carnations", lily: "Lillies", orchid: "Orchids"
			}
			
			$.when(flowerReq, jsonReq).then(function () {
				$('input').each(function (index, elem) {
					$(elem).rules("add", {
						required: true,
						min: 0,
						digits: true,
						messages: {
							required: "Please enter a number for " + plurals[elem.name],
							digits: "Please enter a number for " + plurals[elem.name],
							min: "Please enter a positive number for " + plurals[elem.name]
						}
					})
				}).change(function (e) {
					if ($('form').validate().element($(e.target))) {
						var total = 0;
						$('input').each(function (index, elem) {
							total += Number($(elem).val());
						});
						$('#total').text(total);
					}
				});
			});
			
			$('<a id=left></a><a id=right></a>').prependTo('form')
				.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
			$('#right').appendTo('form');
			
			var total = $('#buttonDiv')
				.prepend("<div>Total Items: <span id=total>0</span></div>")
				.css({ clear: "both", padding: "5px" });
			$('<div id=bbox />').appendTo("body").append(total);
			
			function handleArrowMouse(e) {
				var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
				$(this).css("background-position", propValue);
			}
			
			function handleArrowPress(e) {
				var elemSequence = ["row1", "row2", "row3"];
				var visibleRow = $('div.drow:visible');
				var visibleRowIndex = jQuery.inArray(visibleRow.attr("id"), elemSequence);
				var targetRowIndex;
				if (e.target.id == "left") {
					targetRowIndex = visibleRowIndex - 1;
					if (targetRowIndex < 0) { targetRowIndex = elemSequence.length - 1 };
				} else {
					targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
				}
				visibleRow.fadeOut("fast", function () {
					$('#' + elemSequence[targetRowIndex]).fadeIn("fast")
				});
			}
		});
	</script>
	<script id="flowerTmpl" type="text/x-jquery-tmpl">
		<div class="dcell">
			<img src="${product}.png" />
			<label for="${product}">${name}:</label>
			<input name="${product}" value="0" />
		</div>
	</script>
</head>
<body>
	<h1>Jacqui's Flower Shop</h1>
	<form method="post" action="http://node.jacquisflowershop.com/order">
		<div id="oblock">
			<div class="dtable">
				<div id="row1" class="drow"></div>
				<div id="row2" class="drow"></div>
				<div id="row3" class="drow"></div>
			</div>
		</div>
		<div id="buttonDiv">
			<button type="submit">Place Order</button></div>
	</form>
</body>
</html>

В этом листинге я импортировал библиотеку JavaScript для плагина валидации и определил некоторые базовые стили, которые будут использованы для отображения ошибок валидации. Затем я вызываю метод validate для элемента form, чтобы настроить валидацию формы, определяя один отчет о валидации. Эта методика полностью взята из главы 13.

Теперь использование Ajax для генерирования элементов цветочной продукции создает для меня проблему. Естественно, это касается асинхронных вызовов, то есть я не могу делать предположения о наличии элементов ввода в документе в выражениях, которые следуют за вызовами Ajax. Это общеизвестная ловушка, которую я описал в главе 14; и если браузер выполнит мою выборку элементов ввода прежде чем завершатся оба Ajax запроса, тогда я не найду ни одного элемента (потому что они вот только должны быть созданы и добавлены в документ), и моя валидация "провалится". Чтобы избежать этого, я использовал методы when и then, которые являются частью функционала отложенных объектов jQuery, о чем я расскажу в главе 35. Вот соответствующие выражения:

$.when(flowerReq, jsonReq).then(function () {
	$('input').each(function (index, elem) {
		$(elem).rules("add", {
			required: true,
			min: 0,
			digits: true,
			messages: {
				required: "Please enter a number for " + plurals[elem.name],
				digits: "Please enter a number for " + plurals[elem.name],
				min: "Please enter a positive number for " + plurals[elem.name]
			}
		})
	}).change(function (e) {
		if ($('form').validate().element($(e.target))) {
			var total = 0;
			$('input').each(function (index, elem) {
				total += Number($(elem).val());
			});
			$('#total').text(total);
		}
	});
});

Я не хочу забегать вперед, но jqXHR объекты, возвращаемые всеми методами Ajax, могут быть переданы в качестве аргументов методу when, и если оба запроса пройдут успешно, будет выполнена функция, переданная методу then.

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

После того как выбраны элементы input, я добавляю функцию обработки для события change, которое запускается, если меняется значение, введенное в поле. Обратите внимание, что я вызываю метод element:

...
if ($('form').validate().element($(e.target))) {
...

Таким образом запускается валидация для измененных элементов, и результатом этого метода является boolean, показывающий валидность введенного значения. Если использовать это в блоке if, можно избежать добавления индивидуальных значений к текущей итоговой сумме выбранных элементов.

Добавление удаленной валидации

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

Плагин валидации также поддерживает удаленную валидацию, когда значение, введенное пользователем, отправляется на сервер и уже там применяются правила. Это полезно, если вы не хотите отправлять данные браузеру, потому что их слишком много, потому что это может быть небезопасно или потому что вы хотите выполнить проверку в отношении последних данных.

Внимание

При использовании удаленной валидации нужно быть осторожным, потому что нагрузки на сервер могут быть значительными. В этом примере я использую удаленную валидацию каждый раз, когда пользователь меняет значение элемента input, но в реальном приложении это может генерировать много запросов. Более гибким подходом обычно является выполнение удаленной валидации только в качестве предшественника отправки формы.

Я не объяснял удаленную валидацию в главе 13, потому что она основывается на JSON и Ajax, и я не хотел вникать в эти темы слишком рано. В листинге 16-7 показано добавление удаленной валидации в примере документа, где я использую ее, чтобы убедиться, что пользователь не сможет заказать больше продукции, нежели, как считает сервер, имеется в наличие.

Листинг 16-7: Выполнение удаленной валидации
$.when(flowerReq, jsonReq).then(function () {
	$('input').each(function (index, elem) {
		$(elem).rules("add", {
			required: true,
			min: 0,
			digits: true,
			remote: {
				url: "http://node.jacquisflowershop.com/stockcheck",
				type: "post",
				global: false
			},
			messages: {
				required: "Please enter a number for " + plurals[elem.name],
				digits: "Please enter a number for " + plurals[elem.name],
				min: "Please enter a positive number for " + plurals[elem.name]
			}
		})
	}).change(function (e) {
		if ($('form').validate().element($(e.target))) {
			var total = 0;
			$('input').each(function (index, elem) {
				total += Number($(elem).val());
			});
			$('#total').text(total);
		}
	});
});

Настройка удаленной валидации теперь, когда вы видели поддержку jQuery для Ajax, является довольно простым делом. Мы указываем правило remote и настраиваем его таким образом, чтобы оно было стандартным объектом настроек Ajax. В этом примере я использовал настройку url, чтобы указать URL, который будет вызываться для выполнения удаленной валидации, настройку type, чтобы указать, что мне нужен POST запрос, и настройку global, чтобы отключить глобальные события.

Я отключил глобальные события, потому что я не хочу, чтобы ошибки, связанные с валидацией, обрабатывались так же, как и общие ошибки, с которыми пользователь может справиться сам. Вместо этого, я хочу, чтобы все проходило тихо, на основе того, что сервер будет проводить дальнейшую валидацию, когда отправляется форма (скрипт Node.js не выполняет никакой валидации, но важно, чтобы реальное веб приложение выполняло, как я упоминал в главе 13).

Плагин валидации использует наши настройки Ajax, чтобы сделать запрос к указанному URL, отправляя name элемента input и значение, введенное пользователем. Если ответ сервера – это слово true, тогда значение валидно. Любой другой ответ считает сообщением об ошибке, которое будет отображено пользователю. Как используются эти сообщения, можно увидеть на рисунке 16-3.

Рисунок 16-3: Отображение сообщений об удаленной валидации
или RSS канал: Что новенького на smarly.net