Отправка данных формы с использованием 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: Браузер во время запроса отправки формы
