Встройте Ajax в Web-приложение на основе Rails (исходники)

Джек Д. Херрингтон

Если вы не слышали о Rails, то возвращайтесь из путешествия на планету Зортон, так как только там в этом году вы могли быть и не слышать о Ruby on Rails. Rails наиболее привлекателен тем, что позволяет быстро разработать и запустить приложение со всеми функциональными возможностями. Интеграция с Rails, встроенная в библиотеку Prototype.js для Ajax, делает простой быструю разработку так называемых богатых интернет-приложений .

В этой статье будут рассмотрены шаги по созданию приложения на Rails. Затем внимание будет уделено использованию возможностей Ajax для построения JavaScript-кода, считывающего и записывающего данные на сервер.

Немного о Rails

И все-таки, что такое Rails? Rails - это платформа для Web-приложений, в основе которой лежит язык программирования Ruby. Ruby популярен уже около 10 лет. Так же как Perl и Python, он имеет открытые исходные коды и является гибким языком, предлагающим полную поддержку объектно-ориентированного программирования.

Rails - это программная оболочка, которая определяет необходимые модели Web-приложения, такие как Model-View-Controller (MVC). В данном случае, Модель (Model) представлена набором объектов ActiveRecord, которые соответствуют таблицам в базе данных. Составляющая Контроллер (Controller) - это класс Ruby с методами для каждой операции, выполняемой в данной Модели. Вид (View) обычно представлен HTML-кодом, сгенерированным с помощью шаблона ERB (ERB является встроенным в Ruby пакетом текстовых шаблонов), который, по существу, похож на код PHP или JavaServer Pages (JSP). Вид также может быть кодом XML, текстом, JavaScript-кодом, изображениями или тем, что вам нравится.

Когда пользователь запрашивает страницу Web-приложения на Rails, URL отсылается через систему маршрутизатора, которая отправляет запрос к Контроллеру. Контроллер запрашивает данные из Модели и оправляет их в Вид для форматирования.

Когда вы создаете приложение на Rails, система автоматически генерирует набор каталогов и стартовых файлов. Среди них каталоги для файлов JavaScript, которые поставляются с системой (включая библиотеку Prototype.js), каталоги для Видов, Моделей и Контроллеров и даже место для дополнительных подключаемых модулей, которые можно загрузить у других разработчиков.

Начало работы с Rails

Самый простой путь для начала разработки Rails-приложения - это использование одной из готовых сборок систем Rails. Если вы работаете в Microsoft® Windows®, я бы рекомендовал использовать Instant Rails. На Macintosh'е - я большой поклонник приложения Locomotive 2. Оба приложения написаны на языке Ruby, имеют структуру Rails, Web-серверы, и встроенный MySQL. После довольно объемной загрузки можно приступать к созданию новых приложений на Rails.

Для этой статьи я создал новое приложение - базу данных рецептов, которую назвал Recipe . В нем потребуется лишь одна таблица. В листинге 1 показана миграция базы данных для приложения Recipe.

Листинг 1. Перенос базы данных

                 class
				CreateRecipes < ActiveRecord::Migration def self.up create_table ( :recipes,
				:options => 'TYPE=InnoDB' ) do /t/ t.column :name, :string,
				:null => false t.column :description, :text, :null => false t.column
				:ingredients, :text, :null => false t.column :instructions, :text, :null
				=> false end end def self.down drop_table :recipes end end 

База данных содержит только одну таблицу: recipes . В таблице пять полей: name, description, ingredients, instructions и пятое поле - уникальный идентификатор, который автоматически поддерживает инфраструктура Rails.

Таблица готова, ее необходимо поместить в объект ActiveRecord. Этот объект показан в листинге 2.

Листинг 2. Модель Recipe

                 class Recipe
				< ActiveRecord::Base validates_presence_of :name validates_presence_of
				:description validates_presence_of :ingredients validates_presence_of :instructions
				end 

Базовый класс ActiveRecord отвечает за доступ к базе данных: запросы к таблицам, а также вставки, обновления и удаления записей. В данном случае я добавляю проверки к отдельным полям. Я сообщаю Rails, что в каждом поле должны содержаться эти данные.

Формы Ajax

На первом шаге построения приложения Recipe нужно разработать способ добавления рецептов в базу данных. Вначале я покажу стандартный метод создания базовой HTML-формы в Rails для решения данной проблемы. Он начинается с класса RecipesController, показанного в листинге 3.

Листинг 3. Recipes_controller.rb

                
				class RecipesController < ApplicationController def add @recipe = Recipe.new
				if request.post? @recipe.name = params[:recipe][:name] @recipe.description =
				params[:recipe][:description] @recipe.ingredients = params[:recipe][:ingredients]
				@recipe.instructions = params[:recipe][:instructions] @recipe.save end end end 

Задан только один метод - add, который начинается с создания пустого объекта Recipe. Затем он добавляет параметры и пытается их сохранить, если от клиента поступает соответствующий запрос.

Шаблон ERB для этой страницы показан в листинге 4.

Листинг 4. Add.rhml

                
				<html> <body> <%= error_messages_for
				'recipe' %><br/> <%= start_form_tag
				%> <table>
				<tr><td>Name</td>
				<td><%= text_field :recipe, :name
				%></td></tr>
				<tr><td>Description</td>
				<td><%= text_area :recipe, :description, :rows => 3
				%></td></tr>
				<tr><td>Ingredients</td>
				<td><%= text_area :recipe, :ingredients, :rows => 3
				%></td></tr>
				<tr><td>Instructions</td>
				<td><%= text_area :recipe, :instructions, :rows => 3
				%></td></tr> </table> <%=
				submit_tag 'Add' %> <%= end_form_tag %>
				</body> </html> 

Страница начинается с сообщений об ошибках объекта recipe. Они будут подставлены, если данные, введенные пользователем, не пройдут проверку на соответствие объекту Модели Recipe. Потом страница запускает тег <form>, объекты text_field и text_area для каждого поля, тег <submit> и окончание формы.

Это очень стандартный Rails. Это защищено, безопасно, работает в любом браузере и точно отображается в HTML для клиента. Но я хочу Web 2.0, а вместе с ним и Ajax. Итак, что мне нужно изменить?

Со стороны Контроллера код для метода add() изменяется радикально, как показано в листинге 5.

Листинг 5. Recipes_controller.rb

                
				class RecipesController < ApplicationController def add end def add_ajax
				Recipe.create( { :name => params[:recipe][:name], :description =>
				params[:recipe][:description], :ingredients => params[:recipe][:ingredients],
				:instructions => params[:recipe][:instructions] } ) end end 

Метод add() больше ничего не делает, потому что с приходящими от клиента данными теперь работает новый метод add_ajax().

Со стороны шаблона изменения минимальны, что видно в листинге 6.

Листинг 6. Заголовок add.rhtml

                
				<html> <head> <%= javascript_include_tag
				:defaults %> </head> <body> <div
				id="counter"></div> <%=
				form_remote_tag :url => { :action => 'add_ajax' },
				:complete => 'document.forms[0].reset();', :update
				=> 'counter' %> <table>
				<tr><td>Name</td> 

В начале файла я добавил новый HTML-раздел, содержащий ссылки на файлы JavaScript в Rails. Эти файлы составляют систему Prototype.js, которая выполняет большую часть работы Ajax.

После этого я добавляю тег <div>, называемый счетчиком ( counter ), который хранит возвращаемое значение запроса Ajax. В этом нет большой потребности, но является хорошим тоном, когда пользователь получает некую ответную реакцию.

И наконец, я меняю старый вызов start_form_tag на form_remote_tag. form_remote_tag принимает несколько параметров, наиболее важным из которых является url, определяющий, куда отправлять данные. Второй - конечный обработчик, содержащий код JavaScript, который выполняется по завершении запроса Ajax. В данном случае я очищаю форму, чтобы пользователь мог ввести еще один рецепт. Затем я использую параметр update, указывая Rails, куда пересылать выходные данные из add_ajax.

Также мне нужен шаблон для метода add_ajax(). Листинг 7 показывает этот шаблон.

Листинг 7. Add_ajax.rhtml

                 <%=
				Recipe.find(:all).length %> recipes now in database 

И.... это все. Нет, правда! Это конец. Это все, что было нужно для миграции стандартной формы HTML на форму Ajax в Rails. Рисунок 1 показывают готовую для заполнения форму, основанную на Ajax.

Рисунок 1. Форма в Ajax
Форма в Ajax

Следующий шаг - поэкспериментировать с более динамичной интерактивностью, например, с динамическим поиском при помощи Ajax.

Динамический поиск в Ajax

Prototype.js обеспечивает функциональность просмотра полей и форм на странице. Я использую эту функциональность для работы с текстовым полем поиска, в которое я могу ввести часть рецепта. Затем файл запускает метод поиска в RecipesController, который выдает результаты в теге <div> под текстовым полем. Давайте начнем с обновленного RecipesController, показанного в листинге 8.

Листинг 8. Recipes_controller.rb

                
				class RecipesController < ApplicationController ... def index end def
				search_ajax @recipes = Recipe.find( :all, :conditions => [ "name
				LIKE ?", "%#{params[:recipe][:name]}%" ] ) render
				:layout=>false end end 

Метод index() интерпретирует форму HTML. Метод search_ajax() находит рецепты, соответствующие параметру поиска, и отсылает данные в шаблон ERB для форматирования. Шаблон index.rtml показан в листинге 9.

Листинг 9. Index.rhtml

                
				<html> <head> <%= javascript_include_tag
				:defaults %> </head> <body> <%= form_tag
				nil, { :id => 'search_form' } %> <%=
				text_field 'recipe', 'name' %> <%=
				end_form_tag %> <div id="recipe">
				</div> <%= observe_form :search_form, :frequency => 0.5,
				:update => 'recipe', :url => { :action =>
				'search_ajax' } %> </body>
				</html> 

В начале листинга 9 я снова подключаю библиотеки JavaScript. После этого я создаю форму form с полем поиска и тегом <div> для отображения результатов поиска. И в конце я вызываю вспомогательный метод observe_form(), который создает код JavaScript, отслеживающий изменения в форме и отсылающий данные из формы в метод search_ajax(). Затем результаты работы метода помещаются в recipe <div>.

Код для формы search_ajax.rhtml показан в листинге 10.

Листинг 10. Search_ajax.rhtml

                
				<% @recipes.each { /r/ %> <h1><%= r.name
				%></h1> <p><%= r.description
				%></p> <% } %> 

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

Когда я открываю сайт в браузере и набираю в поисковой строке apple, я нахожу рецепт яблочного коктейля (apple cobbler), как показано на рисунке 2.

Рисунок 2. Динамический поиск в Ajax
Динамический поиск в Ajax

Это все, что касается основ. Но что если я хочу получить больше информации о рецепте яблочного коктейля? Могу ли я использовать Ajax для получения списка ингредиентов и инструкций по требованию? Я рад, что вы спросили. Конечно, могу!

Добавление контента по запросу

Иногда есть смысл в том, чтобы предоставить пользователю возможность загружать дополнительную информацию, а не отображать ее на странице. Обычно разработчики используют скрытый тег <div>, содержащий информацию и отображающий ее по запросу пользователя. В Rails есть более элегантная возможность, использующая Ajax для получения данных по запросу.

Листинг 11 показывает шаблон рецепта с добавленным вызовом вспомогательного метода link_to_remote().

Листинг 11. Search_ajax.rhtml

                
				<% @recipes.each { /r/ %> <h1><%= r.name
				%></h1> <p><%= r.description
				%></p> <div id="extra_<%= r.id
				%>"></div> <%= link_to_remote
				'Extra', :url => { :action =>
				'get_extra_ajax', :id => r.id }, :update =>
				"extra_#{r.id}" %> <% } %> 

Метод link_to_remote() добавляет на страницу код JavaScript вместе с тегом-якорем (<a>), содержащим заданный текст. Когда пользователь нажимает на ссылку, страница создает запрос Ajax для получения нового содержимого и заменяет им текст в якоре.

Для получения большего количества информации я добавляю еще один метод в RecipesController - get_extra_ajax(). Этот метод показан в листинге 12.

Листинг 12. Recipes_controller.rb

                
				class RecipesController < ApplicationController ... def get_extra_ajax
				@recipe = Recipe.find( params[:id] ) render :layout=>false end end 

И наряду с этим мне нужен шаблон для дополнительной информации, называемый get_extra_ajax.rhtml. Листинг 13 показывает этот шаблон.

Листинг 13. Get_extra_ajax.rhtml

                
				<blockquote><%= simple_format @recipe.ingredients
				%></blockquote> <p><%= simple_format
				@recipe.instructions %></p> 

И теперь, заходя на страницу и набирая apple в поле поиска, я вижу что-то похожее на рисунок 3.

Рисунок 3. Ссылка на дополнительную информацию, включающую в себя ингридиенты и инструкции
ссылка на дополнительную информацию

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

Рисунок 4. Дополнительная информация о рецептах
Дополнительная информация о рецептах

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

Возможно, самой горячей возможностью Web 2.0 являются текстовые поля с автозаполнением. Какое же рассмотрение Ajax будет полным без одного из них?

Поля с автозаполнением

Rails делает создание полей с автозаполнением до смешного простым. Во-первых, я добавляю новые элементы в шаблон index.rhtml. Обновленная версия показана листинге 14.

Листинг 14. Обновление index.rhtml

                
				<html> <head> <%= javascript_include_tag
				:defaults %> <style> div.auto_complete { width: 300px;
				background: #fff; } div.auto_complete ul { border: 1px solid #888; margin: 0px;
				padding: 0px; width: 100%; list-style-type: none; } div.auto_complete ul li {
				margin: 0px; padding: 3px; } div.auto_complete ul li.selected { background-color:
				#ffb; } div.auto_complete ul strong.highlight { color: #800; margin: 0px; padding:
				0px; } </style> </head> <body> <%=
				form_tag nil, { :id => 'search_form' } %>
				<p><%= text_field 'recipe',
				'name', :autocomplete => 'off'
				%></p> <div class="auto_complete"
				id="recipe_name_auto_complete"></div>
				<%= auto_complete_field :recipe_name, :url => {
				:action=>'autocomplete_recipe_name' }, :tokens =>
				',' %> <%= end_form_tag %> ... 

Блок каскадных стилей (CSS) в начале файла используется для создания выпадающего списка автозаполняемых элементов. После этого я сделал маленькое добавление в text_field для выключения автозаполнения в браузерах. Еще добавил <div> для хранения информации для выпадающих списков и вызов метода auto_complete(). Вспомогательный метод auto_complete() создает клиентский код JavaScript, который вызывает с сервера метод autocomplete_recipe_name() с текущим содержимым текстового поля recipe name в качестве аргумента.

Метод autocomplete_recipe_name() в RecipesController запускает поиск по имени, как показано в листинге 15.

Листинг 15. Recipes_controller.rb

                
				class RecipesController < ApplicationController ... def
				autocomplete_recipe_name @recipes = Recipe.find( :all, :conditions => [
				"name LIKE ?", "%#{params[:recipe][:name]}%" ] )
				render :layout=>false end end 

Этому коду для построения списка требуется еще один шаблон ERB, как показано в листинге 16.

Листинг 16. Autocomplete_recipe_list.rb

                 <ul
				class="autocomplete_list"> <% @recipes.each { /r/
				%> <li class="autocomplete_item"><%=
				r.name %></li> <% } %> </ul> 

Система автозаполнения ищет список HTML (<ul>), в котором каждый элемент представляет собой одну опцию. Их форматирует CSS на странице index.rhtml (или указанный вами лист стилей).

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

Рисунок 5. Выпадающий список автозаполнения
Выпадающий список автозаполнения

Я могу использовать стрелки "вверх" и "вниз" для выбора элемента и кнопку "ввод" для подтверждения выбора. Выбранный элемент помещается в текстовое поле.

Это быстрое решение и - благодаря архитектуре Rails - легко осуществимое.

Вердикт

Я не постесняюсь сказать: "Я люблю Rails!" Конечно, я спотыкался, начиная его использовать. Насколько я видел в сети, спотыкалось большинство разработчиков. А почему бы и нет? Rails позволяет с невероятной легкостью строить высокоинтерактивные Web-приложения.

Даже если вы не собираетесь покупать приложение Rails, я бы порекомендовал вам загрузить Instant Rails или Locomotive и опробовать их. Вам понравится - и вы узнаете о многом таком, что могли бы использовать в своих приложениях на Java PHP или Microsoft .NET. Может даже случиться так, что вы захотите полностью перейти на Rails.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=7161