Обновление нескольких элементов при помощи одного Ajax-запроса
Проблема
Вы уже видели, как работающие с формой Ajax-пoмoщники позволяют обновлять часть рабочей страницы результатами удаленного действия. Для определения идентификатора НТМL-элемента, который должен быть обновлен данными, полученными от удаленного действия, многие Аjах-действия используют параметр :update.
Для большинства ситуаций этот механизм очень удобен и вполне достаточен. Если к списку нужно добавить какую-нибудь запись, то полученной с сервера измененной версией обновляется только HTML этого списка. То же самое будет происходить и при местном редактировании формы.
Но если нужно одним щелчком или действием на странице провести обновление нескольких, заведомо разобщенных элементов, этот шаблон уже не сработает. Возможность решить эту задачу, использовав параметр : update, довольно призрачна и проблематична.
Решение
В Rails 1.1 появился новый тип шаблонов, названный Remote JavaScript, или RJS. Так же как Builder-шаблоны, файлы которых имели расширение .rxml, шаблоны с расширением файловых имен .rjs автоматически обрабатывались как RJS-шаб-лоны.
RJS предоставляет простые, лаконичные Ruby-методы, генерирующие многословный код JavaScript. Вы вызываете метод
-
page.hide ‘element-id’
и RJS генерирует JavaScript, устанавливающий режим отображения названного элемента в попе, а затем направляет этот JavaScript браузеру. Содержимое возвращается браузеру с указанием в Content-type типа text/javascript. Распространяемая вместе с Rails JavaScript-библиотека Prototype распознает этот Content-type и вызывает JavaScript-функцию eval( ) с возвращенным содержимым.
Чтобы увидеть все это в действии, давайте взглянем на небольшой пример. Предположим, у нас уже есть сгенерированное приложение, для которого мы сгенерируем новый контроллер, с которым и будем иметь дело:
> ruby script/generate controller AjaxFun
exists app/controllers/
…
Затем в файле index.rhtml мы создадим для этого контроллера простое представление, которое станет испытательным полигоном для нашего Ajax. С учетом важности идентификатора HTML-элемента содержимое файла index.rhtml должно выглядеть следующим образом:
-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-
<html>
-
<head>
-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-
<title>Проба Ajax</title>
-
<%= javascript_include_tag :defaults %>
-
</head>
-
<body>
-
<h2 id="header">Проба Ajax</h2>
-
<div>
-
Эта страница первоночально была загружена в <%= Time.now %>
-
</div>
-
<div>
-
Эта страница была обновленна в <span id="time_updated"><%= Time.now %></span>
-
</div>
-
<ul id="the_list">
-
<li>Изначально первый элемент</li>
-
<li>Другой элемент</li>
-
<li id="item_to_remove">Этот элемент будет удален</li>
-
</ul>
-
<div id="initially_hidden" style="display:none;">
-
Этот текст изначально не видим
-
</div>
-
<%= link_to_remote "Магия Ajax", :url => {:action => "change"} %><br />
-
</body>
-
</html>
Мы скрупулезно пометили элементы, подлежащие динамическому обновлению HTML-атрибутом ID. Ссылка на удаленный ресурс, расположенная в конце страницы, запускает запрос XMLHttpRequest, направляемый методу контроллера change( ). Именно с этой ссылки и начнется наше развлечение. Заметьте, что ссылке link_to_remote( ) не передан параметр update. Давайте сначала взглянем на контроллер:
-
class AjaxFunController < ApplicationController
-
def change
-
@rails_version = Rails::VERSION::STRING
-
end
-
end
В нем всего лишь устанавливается значение переменной экземпляра под названием @rails_version, которая будет востребована в представлении. Вся настоящая работа будет происходить в представлении, созданном для этого действия, размещенном в файле change.rjs:
-
page.replace_html ‘time_updated’, Time.now.to_s
-
page.visual_effect :shake, ‘time_updated’
-
-
page.insert_html :top, ‘the_list’, ‘<li>Король вершины</li>’
-
page.visual_effect :highlight, ‘the_list’
-
-
page.show ‘initially_hidden’
-
-
page.delay(3) do
-
page.alert @rails_version
-
end
-
-
page.remove ‘item_to_remove’
Можно заметить, что RJS в неявном виде поставляет объект под названием page, который обеспечивает все методы генерации JavaScript. В первой строке содержимое HTML span-тега с идентификатором time-updated заменяется показанием текущего времени. В следующей строке обеспечивается предупреждение пользователя легкой вибрацией изображения, показывающей, что время было обновлено.
В четвертой строке обеспечивается вставка нового значения списка в его не предписанный элемент, за этим следует вызов эффекта под названием 37signals-coined Yellow Fade Technique. Учтите, что каждый из методов, insert_html() и replace_html( ), может воспринять либо строку, как в данном примере, либо те же параметры, которые воспринимаются методом render( ). Поэтому в страницу можно, к примеру, вставить результат отправки фрагментарного шаблона представления. В седьмой строке вызывается отображение скрытых элементов страницы.
Противоположным действием обладает метод hide( ), который не нужно путать с методом remove( ), использованным в тринадцатой строке для реального удаления элемента с HTML-страницы.
И наконец, в девятой строке мы используем довольно необычный метод delay( ), вызывающий появление всплывающего предупреждения JavaScript через три секунды после того, как страница будет загружена. Метод delay( ) генерирует JavaScript-функцию timeout, которая выполняет любой JavaScript-код, сгенерированный внутри предоставленного ему блока.
Заметьте, что в методе alert( ) используется переменная экземпляра @rails_ version, значение которой было присвоено в контроллере. Переменные экземпляра и вспомогательные методы доступны в RJS-шаблонах, так же как и в любом другом представлении. Уже упоминалось, что RJS-шаблоны генерируют JavaScript и отправляют его браузеру на выполнение. Конкретно для этого RJS-шаблона сгенерированный JavaScript будет выглядеть следующим образом:
-
try {
-
-
Element.update("time_updated", "Fri Sep 05 17:11:28 +0300 2008");
-
-
new Effect.Shake("time_updated",{});
-
-
new Insertion.Top("the_list", "\u003Cli\u003E\u041a\u043e\u0440\u043e\u043b\u044c \u0432\u0435\u0440
-
-
\u0448\u0438\u043d\u044b\u003C/li\u003E");
-
-
new Effect.Highlight("the_list",{});
-
-
Element.show("initially_hidden");
-
-
setTimeout(function() {
-
-
;
-
-
alert("2.0.2");
-
-
}, 3000);
-
-
Element.remove("item_to_remove");
-
-
} catch (e) { alert(‘RJS error:\n\n‘ + e.toString()); alert(‘Element.update(\"time_updated\", \"Fri Sep
-
-
05 17:11:28 +0300 2008\");\nnew Effect.Shake(\"time_updated\",{});\nnew Insertion.Top(\"the_list\",
-
-
\"\\u003Cli\\u003E\\u041a\\u043e\\u0440\\u043e\\u043b\\u044c \\u0432\\u0435\\u0440\\u0448\\u0438\\u043d
-
-
\\u044b\\u003C/li\\u003E\");\nnew Effect.Highlight(\"the_list\",{});\nElement.show(\"initially_hidden
-
-
\");\nsetTimeout(function() {\n;\nalert(\"2.0.2\");\n}, 3000);\nElement.remove(\"item_to_remove\");’
-
-
); throw e }
Вот, собственно, и все. Проще простого. С появлением RJS Ajax перестал быть чем-то излишне сложным.
P.S.
Тип содержимого RJS-шаблона — Content-type — должен быть установлен в text/javascript. Обработчик RJS делает это за вас, но, если в приложении имеется код, который явным образом указывает Content-type, может обнаружиться, что RJS-шаблоны не работают. Если возникает подозрение, что RJS-шаблоны ровным счетом ничего не делают, проверьте, нет ли в приложении фильтра «после» (after) устанавливающего значение Content-type.
Вы должны знать еще об одной особенности применения RJS-шаблонов. Поскольку они генерируют JavaScript для выполнения в браузере, возникают трудности с определением ошибок. Например, если вы допустили синтаксическую ошибку в контроллере или представлении, Rails вернет свою обычную запись стека в HTML-формате. Проблема состоит в том, что код выполняется без видимых признаков, и если код JavaScript не может быть выполнен, то в конечном счете в браузере ничего не произойдет. Поэтому, если вы уставились на безжизненный экран браузера в бесконечном ожидании завершения работы усиленного RJS действия Ajax, нужно проверить регистрационные записи, чтобы понять, что запрос закончился ошибкой.

За такие посты надо награды давать, на полном серьезе!