Главная страница   /   16.6. Отправка данных формы с использованием Ajax (Pro jQuery

Pro jQuery

Pro jQuery

Адам Фриман

16.6. Отправка данных формы с использованием Ajax

Отправка значений в форму исключительно проста, и в листинге 16-8 показана так же технология, которую я использовал в главе 15.

Листинг 16-8: Отправка формы с использованием Ajax
<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;
	}

	#popup {
		text-align: center;
		position: absolute;
		top: 100px;
		left: 0px;
		width: 100%;
		height: 1px;
		overflow: visible;
		visibility: visible;
		display: block;
	}

	#popupContent {
		color: white;
		background-color: black;
		font-size: 14px;
		font-weight: bold;
		margin-left: -75px;
		position: absolute;
		top: -55px;
		left: 50%;
		width: 150px;
		height: 60px;
		padding-top: 10px;
		z-index: 2;
	}
</style>
<script type="text/javascript">
	$(document).ready(function () {
		$('<div id="popup"><div id="popupContent"><img src="progress.gif"'
			+ 'alt="progress"/><div>Placing Order</div></div></div>')
			.appendTo('body');
		
		$.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, #popup').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");
		})
		
		var plurals = {
			astor: "Astors", daffodil: "Daffodils", rose: "Roses",
			peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops",
			carnation: "Carnations", lily: "Lillies", orchid: "Orchids"
		}
		
		$('<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"
		});
		
		$.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);
				}
			});
		});
		
		$('button').click(function (e) {
			e.preventDefault();
			var formData = $('form').serialize();
			$('body *').not('#popup, #popup *').css("opacity", 0.5);
			$('input').attr("disabled", "disabled");
			$('#popup').show();
			$.ajax({
				url: "http://node.jacquisflowershop.com/order",
				type: "post",
				data: formData,
				complete: function () {
					setTimeout(function () {
						$('body *').not('#popup, #popup *').css("opacity", 1);
						$('input').removeAttr("disabled");
						$('#popup').hide();
					}, 1500);
				}
			})
		})
		
		$('<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>

Я вышел за рамки того, чтобы просто сделать Ajax запрос POST, потому что я хочу добавить дополнительную информацию о том, как такие запросы могут обрабатываться в реальных проектах. Для начала, я добавил элемент, который располагается над всеми другими элементами в документе и говорит пользователю, что его заказ размещен. Есть выражения CSS и jQuery, которые создают этот эффект.

#popup {
	text-align: center;
	position: absolute;
	top: 100px;
	left: 0px;
	width: 100%;
	height: 1px;
	overflow: visible;
	visibility: visible;
	display: block;
}

#popupContent {
	color: white;
	background-color: black;
	font-size: 14px;
	font-weight: bold;
	margin-left: -75px;
	position: absolute;
	top: -55px;
	left: 50%;
	width: 150px;
	height: 60px;
	padding-top: 10px;
	z-index: 2;
}
$('<div id="popup"><div id="popupContent"><img src="progress.gif"'
	+ 'alt="progress"/><div>Placing Order</div></div></div>')
	.appendTo('body');

На удивление сложно создать элемент, который выглядит как всплывающий (pop-up) и который правильно размещен на экране, и вы видите, что для этого необходимо много CSS. По сравнению с этим, работа с HTML элементами на удивление проста, и хорошо отформатированный сгенерированный HTML выглядит так же:

<div id="popup">
	<div id="popupContent">
		<img src="progress.gif" alt="progress">
		<div>Placing Order</div>
	</div>
</div>

Указанный мной элемент img (progress.gif) является анимационным рисунком в формате GIF. Есть много сайтов, которые генерируют рисунки, отображающие прогресс, по нашей спецификации, и я воспользовался одним из них. Если вы не хотите создавать собственный рисунок, вы можете использовать таковой из этого примера. Он включен в исходный код для этой книги (бесплатно доступный на Apress.com). Вы можете увидеть, как выглядят эти элементы на рисунке 16-4, где я для ясности удалил все остальные элементы.

Рисунок 16-4: Демонстрация прогресса пользователю

Я изначально скрываю эти элементы, потому что не имеет смысла показывать пользователю отображение прогресса, пока он фактически не разместит заказ:

$('#row2, #row3, #popup').hide();

Эти элементы на своих местах и спрятаны, и мы можем вернуться к отправке формы. Я регистрирую функцию обработки для события click элемента button, вот так:

$('button').click(function (e) {
	e.preventDefault();
	var formData = $('form').serialize();
	$('body *').not('#popup, #popup *').css("opacity", 0.5);
	$('input').attr("disabled", "disabled");
	$('#popup').show();
	$.ajax({
		url: "http://node.jacquisflowershop.com/order",
		type: "post",
		data: formData,
		complete: function () {
			setTimeout(function () {
				$('body *').not('#popup, #popup *').css("opacity", 1);
				$('input').removeAttr("disabled");
				$('#popup').hide();
			}, 1500);
		}
	})
})

Прежде чем начать Ajax запрос, я показываю всплывающие элементы и делаю все остальные элементы частично прозрачными. Также я отключаю элементы ввода (input), добавляя атрибут disabled. Я это делаю, потому что я не хочу, чтобы пользователь мог менять значение любого из элементов ввода данных, пока я отправляю данные пользователю:

$('body *').not('#popup, #popup *').css("opacity", 0.5);
$('input').attr("disabled", "disabled");
$('#popup').show();

Проблема с отключением элементов input заключается в том, что их значения не будут включены в данные, отправленные на сервер. Метод serialize будет включать значения только элементов input, которые считаются действующими элементами управления (successful controls), как определено в HTML спецификации. Это исключает те элементы, которые отключены или у которых нет атрибута name. Я мог бы пройти по каждому элементу ввода сам и все равно получить значения, но проще собрать данные, которые должны быть отправлены, прежде чем отключить элементы, вот таким образом:

var formData = $('form').serialize();

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

complete: function () {
	setTimeout(function () {
		$('body *').not('#popup, #popup *').css("opacity", 1);
		$('input').removeAttr("disabled");
		$('#popup').hide();
	}, 1500);
}

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

Рисунок 16-5: Браузер во время запроса отправки формы