Главная страница   /   7.1. Использование Ajax с jQuery (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

7.1. Использование Ajax с jQuery

Использование JavaScript в веб-приложениях становится все более важным из-за необходимости обеспечить максимальное удобство работы с интерфейсом для пользователя. К сожалению, работа с сырым JavaScript-кодом весьма затруднительна. Различные браузеры имеют свои возможности и ограничения, которые значительно усложняют написание кросс-браузерного JavaScript-кода (например, Internet Explorer использует уникальный механизм добавления событий к элементам). Навигация и манипулирование HTML DOM ("объектная модель документа", Document Object Model. Это иерархия объектов, которые представляют собой все элементы страницы) также очень трудозатратны и сложны. Избежать всего вышеперечисленного можно, используя библиотеки JavaScript.

В настоящий момент существует много популярных библиотек JavaScript (в том числе jQuery, Prototype, MooTools и Dojo). Все они делают работу с JavaScript проще и помогают нормализовать его кросс-браузерную функциональность. Здесь в примерах мы будем использовать открытую библиотеку jQuery (http://jQuery.com).

jQuery был выпущен Джоном Резигом в 2006 году и стал одной из самых популярных библиотек JavaScript благодаря простому, но мощному механизму взаимодействия с HTML DOM. Впрочем, на самом деле jQuery обязан своей популярностью Microsoft, который добавил несколько функций к его кодовой базе, обеспечил официальную поддержку и включил в ASP.NET MVC как часть шаблона проектов по умолчанию.

В этом разделе мы рассмотрим основы работы с jQuery и то, как он может использоваться для отправки на сервер асинхронных вызовов, обрабатываемых ASP.NET MVC. Затем мы узнаем, как обеспечить работу нашего сайта в браузерах с выключенным JavaScript. Наконец, мы научимся использовать jQuery для отправки данных формы на сервер в фоновом режиме.

Основы jQuery

Работа с jQuery в основном сводится к работе с функциями (начинаются со значка $), которые могут выполнять различные операции в зависимости от содержания. Например, чтобы с помощью jQuery найти все теги <div />, на определенной странице и добавить к каждому класс CSS, вы можете использовать следующий код:

$('div').addClass('foo');

Следующую за $ строку jQuery будет рассматривать как CSS-селектор и попытается найти все элементы на данной странице, которые ему соответствуют. В данном случае, он найдет все теги <div /> на странице. Аналогично, функция $('#foo') найдет все элементы с идентификатором foo, а функция $('table.grid td') найдет все теги <td /> в таблицах класса grid.

Результатом вызова этой функции будет объект jQuery, который содержит коллекцию DOM-элементов, соответствующих селектору. Благодаря этому в jQuery поддерживаются цепочки вызовов, с помощью которых можно выполнять сложные операции с DOM-элементами в очень сжатой форме. В предыдущем примере вызывается метод AddClass, который добавляет указанный CSS-класс к каждому элементу данного объекта (в данном примере все теги <div /> на странице).

Аналогичным образом вы можете добавить события к элементам. Например, чтобы вывести сообщение после нажатия на кнопку, можно разместить JavaScript в событии onclick:

<button id="myButton" onclick="alert('I was clicked!')">
	Click me!
</button>

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

<button id="myButton">Click me!</button>
<script type="text/javascript">
	$('button#myButton').click(function() {
		alert('I was clicked!');
	});
</script>

В этом примере показана страница, которая содержит код JavaScript. Он передает в jQuery инструкцию найти любой элемент <button /> с id myButton и запустить функцию при нажатии кнопки. В этом случае браузер просто выведет сообщение о том, что кнопка была нажата.

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

Аналогично тому, как мы добавляем события к элементам, мы можем добавить событие ready к целой странице. Оно запустится только тогда, когда сформируется иерархия DOM страницы – это самый первый момент, подходящий для безопасного взаимодействия с HTML-элементами. Таким образом, лучше всего, если события и другой jQuery-код будут содержаться в обработчике ready:

$(document).ready(function() {
	$('button#myButton').click(function() {
		alert('Button was clicked!');
	});
});

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

Хотя jQuery - это тема для целой книги, изложенные здесь основные понятия облегчат вам понимание следующих далее примеров. Чтобы получить более глубокие знания о jQuery, можно прочитать «jQuery в действии. Второе издание» Бера Бибо и Иегуды Каца, а также книги издательства Manning.

Создание Ajax-запросов с помощью jQuery

Чтобы продемонстрировать, как создавать Ajax-запросы с помощью jQuery, мы создадим новый проект ASP.NET MVC на базовом шаблоне Internet Application. Добавим в него простой контроллер с двумя действиями, оба из которых будут демонстрировать представление - Index и PrivacyPolicy.

Действие Index содержит гиперссылку, при нажатии на которую будет отправлен запрос на сервер, чтобы получить информацию о политике конфиденциальности и загрузить ее на страницу Index. Желаемый результат показан на рисунке 7.1.

Рисунок 7-1: При нажатии на ссылку будет загружена информация о политике конфиденциальности

Код этого контроллера показан в следующем листинге.

Листинг 7-1: Простой контроллер
public class CustomAjaxController : Controller
{
	public ActionResult Index()
	{
		return View();
	}
	public ActionResult PrivacyPolicy()
	{
		return PartialView();
	}
}

Строка 9: Возвращает частичное представление

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

Частичное представление PrivacyPolicy содержит некоторые элементы базовой разметки:

<h2>Our Commitment to Privacy</h2>
...privacy policy goes here...

Содержание представления Index показано в следующем листинге.

Листинг 7-2: Представление Index со ссылками на скрипты
@section head {
	<script type="text/javascript"
		src="@Url.Content("~/scripts/AjaxDemo.js")">
	</script>
}

@Html.ActionLink("Show the privacy policy", "PrivacyPolicy", null, new { id = "privacyLink" })
<div id="privacy"></div>

Строка 1: Тег раздела head

Строки 2-4: Ссылка на код

Строка 7: Ссылка на действие

Строка 8: Контейнер для результатов

Начнем с определения секции head (строка 1). Во все новые проекты MVC автоматически включается последняя версия jQuery. Для этого используется NuGet, который также позволяет очень просто обновлять jQuery. На момент написания данной книги последней версией был jQuery 1.7.2, и соответствующие скрипты находятся в подкаталоге Scripts. Мы включаем в ссылку вызов Url.Content, а не создаем абсолютный маршрут. Это позволит избежать ошибок во время исполнения, независимо от того, запущена ли страница из коневого каталога сайта или из подкаталога.

Далее следует скриптовая ссылка, указывающая на JavaScript-файл под названием AjaxDemo.js, который мы еще не создали. Этот файл будет содержать пользовательский код jQuery.

Далее мы создаем стандартную в ASP.NET MVC ссылку на действие (строка 7). В ней указываем следующие параметры: текст гиперссылки, собственно действие, к которому мы ее привязываем (в данном случае действие PrivacyPolicy), любые дополнительные параметры маршрута (в данном случае их нет, поэтому указываем null), и анонимный тип, определяющий дополнительные HTML-атрибуты (в данном случае мы просто назначаем ссылке ID).

В конце мы создаем div с id равным "privacy", в который будет загружена информация о политике конфиденциальности после запроса Ajax.

Теперь мы создадим файл AjaxDemo.js в каталоге Scripts. В этот файл добавим код jQuery для перехвата нажатия по ссылке privacyLink, что показано в следующем листинге.

Листинг 7-3: Код jQuery в файле AjaxDemo.js
$(document).ready(function () {
	$('#privacyLink').click(function (event) {
		event.preventDefault();
		var url = $(this).attr('href');
		$('#privacy').load(url);
	});
});

Вначале мы создаем обработчик, который будет вызван сразу после загрузки DOM. В этом обработчике мы создаем инструкцию для jQuery найти ссылку с идентификатором privacyLink и добавить функцию к событию click.

Обработчик click принимает ссылку на событие как параметр. Здесь мы вызываем метод preventDefault, чтобы предотвратить выполнение сценария по умолчанию для этой ссылки (то есть, переход на страницу, указанную в атрибуте href). Вместо этого мы извлекаем значение атрибута href и сохраняем его в переменной url.

Последняя строка обработчика содержит собственно запрос Ajax. Эта строка содержит инструкцию для jQuery найти на странице элемент с id privacy (т. е. тег <div />, который мы создали в листинге 7.2), а затем загрузить в этот элемент содержание, которое мы извлекли по ссылке. В результате будет создан запрос Ajax, который асинхронно обращается к URL и загружает полученные данные в DOM.

Теперь, когда вы запустите приложение и нажмите на ссылку, вы увидите, что содержимое Privacy policy загружается на страницу. Если вы используете браузер Firefox с установленным расширением Firebug (http://getfirebug.com), вы можете проследить отправку запроса Ajax, как показано на рисунке 7.1.

Это пример ненавязчивого JavaScript - весь код содержится не на странице, а в отдельном файле.

Прогрессивное улучшение

Предыдущий пример также иллюстрирует такой принцип, как прогрессивное улучшение (progressive enhancement). Прогрессивное улучшение предполагает, что мы начинаем с базовой функциональности (в данном случае простая гиперссылка), а затем добавляем дополнительные функции (с помощью Ajax). Таким образом, если пользователь выключит JavaScript в браузере, дополнительная функциональность будет урезана, и нажатие по ссылке отправит пользователя на страницу Privacy policy без использования Ajax, как показано на рисунке 7.2.

Рисунок 7-2: Браузер переходит на страницу Privacy Policy, если Javascript отключен.

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

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

Листинг 7-4: Использование метода IsAjaxRequest для изменения действия
public ActionResult PrivacyPolicy()
{
	if(Request.IsAjaxRequest())
	{
		return PartialView();
	}
	return View();
}

Строка 3: Проверяет, был ли отправлен Ajax запрос

Теперь действие PrivacyPolicy проверяет, был ли отправлен запрос Ajax, вызывая метод расширения IsAjaxRequest в свойстве контроллера Request. Если метод возвращает результат true, то действие было вызвано запросом Ajax, в этом случае мы выводим частичное представление; если запроса Ajax к странице не было, он возвращает обычное представление.

Сейчас, если вы нажмете на ссылку в браузере с отключенным JavaScript, страница будет отображаться полностью, как показано на рисунке 7.3.

Рисунок 7-3: Отображение страницы Privacy policy полностью в браузере с отключенным JavaScript

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

Выше мы рассмотрели, как можно эффективно использовать JQuery для получения данных от сервера при нажатии на ссылку. Сейчас мы рассмотрим, как можно отправить данные на сервер асинхронно. Чтобы проиллюстрировать это, добавим на страницу из предыдущего примера список комментариев пользователей. Конечный результат такой страницы показан на рисунке 7.4.

Рисунок 7-4: Форма отправлена посредством Ajax, и результат добавлен к списку.

Для начала добавим список комментариев в контроллер в статическом поле. Когда поступит запрос к действию Index, этот список будет передан в представление. Мы также добавим действие AddComment, которое позволит пользователю добавить комментарий. Расширенный контроллер показан в листинге 7.5.

Листинг 7-5: Действие AddComment
public class CustomAjaxController : Controller
{
	private static List<string> _comments = new List<string>();
	public ActionResult Index()
	{
		return View(_comments);
	}
	[HttpPost]
	public ActionResult AddComment(string comment)
	{
		_comments.Add(comment);
		if (Request.IsAjaxRequest())
		{
			ViewBag.Comment = comment;
			return PartialView();
		}
		return RedirectToAction("Index");
	}
}

Строка 3: Содержит список комментариев

Строка 6: Отправляет комментарии в представление

Строка 9: Принимает комментарий как параметр

Строка 11: Сохраняет новый комментарий

Строка 14: Отправляет комментарий в представление

Строка 17: Переадресовывает к действию Index

Сначала добавим в наш контроллер список строковых данных, который будет содержать комментарии. Эти комментарии передаются в представление Index как модель. Мы также создаем новое действие AddComment, которое принимает комментарий в качестве параметра. Атрибут HttpPost здесь гарантирует, что это действие может быть вызвано только как результат отправки формы.

Действие AddComment добавляет комментарий к списку и затем, если оно было вызвано запросом Ajax, передает его в ViewBag и возвращает частичное представление. Если у пользователя отключен JavaScript, AddComment переадресовывает к действию Index, в результате чего страница полностью обновляется.

Примечание

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

Частичное представление, возвращаемое действием AddComment, отображает комментарий в списке:

<li>@ViewBag.Comment</li>

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

Листинг 7-6: Представление Index с формой добавления комментариев
@model IEnumerable<string>
@section head {
	<script type="text/javascript"
		src="@Url.Content("~/scripts/AjaxDemo.js")">
	</script>
}
<h4>Comments</h4>
<ul id="comments">
@foreach (var comment in Model) {
	<li>@comment</li>
}
</ul>
<form method="post" id="commentForm" action="@Url.Action("AddComment")">
	@Html.TextArea("Comment", new { rows = 5, cols = 50 })
	<br />
	<input type="submit" value="Add Comment" />
</form>

Строка 1: Определяет строгий тип представления

Строки 8-12: Создает список комментариев

Строки 13-17: Создает форму добавления комментария

Модифицированное представление Index начинается с указания, что оно строго типизировано IEnumerable <string>, который соответствует списку комментариев, переданных от контроллера в представление. Далее следует ссылка на файл AjaxDemo c jQuery-кодом.

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

Наконец мы добавляем форму, которая обращается к действию AddComment и содержит текстовое поле, где пользователь может добавить комментарий.

На данный момент, если вы запустите страницу и отправите форму, комментарий будет добавлен в список, но, чтобы увидеть обновленные комментарии, придется полностью обновить страницу.

Теперь нам нужно изменить jQuery код в файле AjaxDemo.js, чтобы отправлять форму с помощью Ajax, как показано в следующем листинге.

Листинг 7-7: Отправка формы с помощью Ajax
$(document).ready(function () {
	$('#commentForm').submit(function (event) {
		event.preventDefault();
		var data = $(this).serialize();
		var url = $(this).attr('action');
		$.post(url, data, function (response) {
			$('#comments').append(response);
		});
	});
});

Строка 2: Добавляет обработчик событий

Строка 4: Сериализует данные в строку

Строка 6: Отправляет данные на сервер

Строка 7: Добавляет результат в список комментариев

Как и в примере со ссылкой, мы сначала добавляем функцию $(document).ready, которая будет вызвана после загрузки DOM. Внутри этой функции мы сообщаем jQuery найти форму с идентификатором commentForm и добавить к ней обработчик для события отправки формы. Далее мы снова вызываем event.preventDefault, чтобы не отправлять форму. Вместо этого мы сериализуем содержимое формы в строку, вызывая метод serialize к элементу формы. Эта строка содержит закодированные в URL пары ключ-значение, представляющие поля внутри формы. В этом случае, если мы ввели текст hello world в поле для комментариев, преобразованные данные будут представлены значением "Comment=hello+world".

Когда содержимое формы представлено в виде строки, оно может быть отправлено с помощью Ajax. Чтобы увидеть, куда мы должны отправить данные, получим результат атрибут action формы и сохраним его в переменной url. Далее мы используем метод jQuery post для отправки этих данных к серверу. Метод post принимает несколько аргументов: URL, по которому должны быть размещены данные, сами данные и функция обратного вызова, которая будет запущена при получении ответа сервера.

В этом случае ответ сервера будет содержать частичное представление AddComment, содержащее комментарий в виде элемента списка. Мы добавляем его в конец списка комментариев с помощью метода JQuery append.

Теперь, когда вы откроете страницу и добавите комментарий, вы сможете проследить отправку запроса Ajax в Firebug и добавление результата к списку, как показано на рисунке 7.4.

JavaScript и ключевое слово this

Так как в JavaScript функции используются как объекты, не всегда очевидно, на что указывает ключевое слово this, то есть оно является контекстно-зависимым. В листинге 7.7 this является ссылкой внутри обработчика события, следовательно, оно указывает на элемент, где произошло событие (в данном случае, форма).