一个经典的Rails AJAX 搜索 排序 和分页示例

[color=green]写在前头
绿色的部分是,这个介绍容易出问题部分的解释。本来就是面向零基础,才写的,所以不怕麻烦和琐碎。然而,如果你还是嫌太麻烦,或者你不希望了解细节的话。那么,我建议你直接点击[url=http://hinder.iteye.com/topics/download/fc389e2c-e6f9-38cd-ba6f-7a76247aa0d5]这里[/url],下载ajaxtable.rar的压缩包。在安装ruby和rails的环境下,到解压的目录下,运行ruby script/server直接看效果。
压缩包里有paginate的plugin,数据库也不用配置因为是文件的。万一你的系统不认sqlite3,请下载sqlite3的rar,里面有个gem包安装就可以了。
希望能够给有兴趣的,或者初学者带来帮助
[/color]

这是一个相当经典的(我认为^_^)用来展示,ajax调用排序,搜索和分页的示例。这个示例的特点是把过程写的相当的啰嗦,大妈专杀。以至于非常的好理解,非常的初级,非常的好用。

[b][size=medium]任务说明简介和重要提示[/size][/b]

在这个示例教程理,我们将要使用Rails的AJAX,来实现对结果集的如下功能:

1. 分页
2. 排序,可以对结果集的任何一个属性进行ajax排序。
3. 查询,ajax检索

在线的示例演示,请访问如下网址:(还没有准备好...)

能够部分刷新的对页面的结果集进行更新,变得越来越普遍。对于Rails来说,天生的和ajax有着完美的支持。所以,对这项功能的实现相当容易。
示例中的代码, 很大部分来源于Rails Wiki的官方说明。特别是How to make a real-time search box with the Ajax helpers 和 How to paginate with Ajax.我只是把这些代码攒到了一起,不能用的时候,稍微改了一下变量名。以便看起来更不像抄袭。

注意啦,很重要:
我既不是Rails高手,也不是AJAX大拿。在这两个领域里,我都是拥有长期经验的菜鸟。所以这个示例是面对初学者和大妈们的。你会看到简介的代码,详尽的解释,但是缺乏高效规范。幸好,虽然,我的母语是汉语,但是难免还是有错误。
任何时候,请到我的博客告诉我。

[b]应用安装和配置[/b]
在这个示例中,我们假设已经安装了当前的Rails环境(至少高于2.0)和支持数据库。在我们这里用Sqlite,当然你也可以很容易的使用其他的数据库代替。

框架生成:
我们要在目录下生成“skeleton”先:

rails ajaxtable
cd ajaxtable


在rails 2.0以后,分页的功能已经从Rails core中分离出来,成为一个独立的plugin叫作classic pagination。很不幸的很多开发者,认为will_paginate是更加流行的分页插件。但是,我并没有使用will_paginate整合我的示例。所以,我们还是首先,使用下面的命令安装classic pagination plugin

ruby script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination

[color=green]这里如果不能安装,请直接下载附件classic_pagination.rar解压缩放到verdor\plugin目录,就不用安装分页插件了。[/color]

鉴于,我们搭建的是一个非常基础的应用,所以,我们只有一个用来存储结果集的model和controller。我们将使用如下Rails的scripts生成

$ ruby script/generate model Item
$ ruby script/generate controller Item


[b]数据库准备:[/b]

简单起见,我们选择sqlite做存储。这将意味着我们将描述文件放到Rails中,并且用它生成数据库。
更新数据库配置文件:config/database.yml如下:

development:
adapter: sqlite3
database: db/development.db

然后,我们将使用如下Rails migration 工具创建数据库如下:

ruby script/generate migration database_creation


这条语句将生成类似db/migrate/20090303130724_database_creation.rb的文件。
[color=green]修改这个文件,重新定义数据库表结构[/color]

class DatabaseCreation < ActiveRecord::Migration

def self.up
create_table :items do |t|
t.column :name, :string, :limit => 30
t.column :quantity, :integer, :null => false, :default => 0
t.column :price, :integer, :null => false, :default => 0
end
end

def self.down
drop_table :items
end

end

然后,运行如下:


rake db:migrate


[color=green]这里如果,出现不能安装错误请下载附件sqlite3.rar,并放到ruby\bin目录下,执行[/color]

gem install -l c:\ruby\bin\sqlite3-ruby-1.2.3-mswin32.gem



用以创建表结构。接下来将加载数据

在db目录下应该有一个development.db的文件,这个文件就是存储sqlite数据。
你可以用如下的方法插入数据创建db/dump.sql如下:


BEGIN TRANSACTION;
INSERT INTO "items" VALUES(1, 'hoe', 3, 10);
INSERT INTO "items" VALUES(2, 'wheelbarrow', 2, 60);
INSERT INTO "items" VALUES(3, 'gherkin', 15, 3);
INSERT INTO "items" VALUES(4, 'batman', 1, 3000);
INSERT INTO "items" VALUES(5, 'fish sausage', 2, 8);
INSERT INTO "items" VALUES(6, 'sauerkraut', 9, 9);
INSERT INTO "items" VALUES(7, 'watering-can', 4, 13);
INSERT INTO "items" VALUES(8, 'dandelions', 78, 1);
INSERT INTO "items" VALUES(9, 'refrigerator', 12, 250);
INSERT INTO "items" VALUES(10, 'flying matches', 8, 145);
INSERT INTO "items" VALUES(11, 'broken accordion', 1, 18);
INSERT INTO "items" VALUES(12, 'savage whisper', 5, 7);
INSERT INTO "items" VALUES(13, 'hysterical snail', 8, 13);
COMMIT;


运行:

sqlite3 db/development.db < db/dump.sql

[color=green]注意:
1.如果运行这个命令的时候,提示找不到sqlite3,请下载sqlite3的附件,放到ruby\bin\下,并且运行时,请指明路径
2.如果你使用的数据库是mysql请自己修改,insertinto的语句格式如下 INSERT INTO items VALUES (1, 'hoe', 3, 10);[/color]那么,到现在为止,我们准备好了数据库和用到的文件

[b]创建model[/b]

就像你应该知道的,Rails程序通常会分为三层。实际上我们已经创建了/models/item.rb文件。并且我们并不需要更改。
那么,看起来,我们的第一步编码工作还不算太困难。

[b]创建view[/b]

我们应用将被分成两个部分,一个layout 另外一部分是view和partial。

Layout

Layout是页面模板用来容纳不同的几个views。Layout包含一些不变的元素例如:html的header和footer信息,导航和设计元素等。当然,这些功能完全没有能够在我们的示例中体现。因为,我们只有一个页面,
那么layout应该在app/views/layouts/item.rhtml,其中代码如下:




Ajax table manipulation attempt
<%= stylesheet_link_tag "style" %>
<%= javascript_include_tag :defaults %>




<%= @content_for_layout %>






在这段代码中,值得注意的是javascript_include_tag 将加载对应的javascript库,以便Rails可以得到AJAX功能支持。
@content_for_layout 部分,将会被生成的内容代替。

[b] view部分[/b]

view将会把controller的结果展示。逻辑部分在一节详述。根据Rails配置原则,我们view将对应item controller的listaction所以我们的view在
app/views/item/list.rhtml.
该文件的内容如下:


欢迎使用神奇的 items 列表



我们的列表是Web2.0的经典产物。



但是请注意,这里可能有太多的bug.



bug list如下






<%= text_field_tag("query", params['query'], :size => 10 ) %>


<%= image_tag("spinner.gif",
:align => "absmiddle",
:border => 0,
:id => "spinner",
:style =>"display: none;" ) %>



<%= observe_field 'query', :frequency => 2,
:update => 'table',
:before => "Element.show('spinner')",
:success => "Element.hide('spinner')",
:url => {:action => 'list'},
:with => 'query' %>


<%= render :partial => "items_list" %>



开始部分并没有什么复杂的,首先,是一段大妈专用的说明和一个检索输入框用以Filter。

然后,我们有一个id是spinner的隐藏image元素。这个image是用来AJAX有延迟调用的时候显示的(flash加载的滚动条)。当ajax的异步调用完成,可以显示数据时,这个image将再次隐藏。你可以从如下的网站得到更多的类似图片:
http://mentalized.net/activity-indicators/
[color=green]从上面的网站中下载一个gif,并重命名为spinner.gif放到public/images下。[/color]

在接下来的observe_field代码部分是最常用的AJAX代码。它的含义大概是定期检查指定区域的内容,并且当内容有变化的时候响应。

其中,使用到的变量有如下含义:
update 参数描述将要更新的
的id
url 该参数,指定响应和处理action。就是定期触发什么方法。
with 该参数,用以给url中指定的action,传递参数。在这里我们将会把observed field的检索数据传给list action
before 该方法用以指定,当AJAX的异步调用处理中将怎么执行。
sucess 当ajax的异步调用成功后,执行什么操作。

实际的操作流程相当简单,当用户在queryfield的检索框内输入要查询的东西,observerd 就会检测到监视区域的内容变化,然后,生成AJAX的请求,异步调用通过url和with的发送给服务器。注意这时页面是整体和局部都不刷新的。当请求发送的时候,before定义的操作将被执行。在我们的示例中是显示spinner图片。当请求有回复的时候,sucess的操作将被执行,在我们这里是隐藏图片。

实际上,observe_field方面发送的请求参数如下:





我们花时间来看,没一个参数选项的具体含义,是因为我们很快会再用到这些几乎每一种ajax调用都要用到的参数,

创建controller

我们的controller应该可以根据请求类别和参数的不同处理多种的请求。
Item contoller将会非常简单,我们只实现一个list的action。其他CRUD(创建、读取、更新、删除)方法将不在本示例中展示

那么,controller的内容如下:
[color=green]修改\ajaxtable\app\controllers\item_controller.rb[/color]


class ItemController < ApplicationController

def list

items_per_page = 10

sort = case params['sort']
when "name" then "name"
when "qty" then "quantity"
when "price" then "price"
when "name_reverse" then "name DESC"
when "qty_reverse" then "quantity DESC"
when "price_reverse" then "price DESC"
end

conditions = ["name LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?

@total = Item.count(:conditions => conditions)
@items_pages, @items = paginate :items, :order => sort, :conditions => conditions, :per_page => items_per_page

if request.xml_http_request?
render :partial => "items_list", :layout => false
end

end

end


本段代码的简单说明如下:

controller的唯一一个方法,用于处理各种不同请求。其中,items_per_page 参数用以定义每页显示数量。sort参数取决于同名的传入参数。出于安全考虑以此代替真正的字段名。sort 中的reverse参数用于保证再次点击的时候可以依序排列。
conditions 参数用来指定从query请求参数提供的检索条件。这个参数是类似SQL样式。
然后,我们指定@total变量用来存储符合conditions条件的记录个数。

最终,我们调用Rails的分页机制。我们需要关联分页到数据库:item,和一个可排序字段,一个符合conditions的检索条件,和一个每页的显示个数。分页机制就会返回,一个@items_pages 的对象用以分页显示。

Rails和Ajax使用XmlHttpRequest,这不同与普通的GET和POST请求。XHR的请求由javascript通过后台的Http调用触发,请求一个部分的Xhtml代码片段来更新部分的浏览器显示。这样的好处是不用重新加载整个页面。用户的使用体验,会因此变快。

那么接下来呢?

[b]创建partial[/b]

partial是用来显示部分的页面。partial的设计初衷是为了复用,满足DRY(Don‘t Repeat Yoursel)的原则,当然,这里对于AJAX也非常有用。

这里我们使用partial更新部分页面,正好满足AJAX部分更新的要求。
Partial文件的名字总是下划线开头。我们的partialapp/views/item/_items_list.rhtml,内容如下


<% if @total == 0 %>

No items found...



<% else %>

Number of items found : <%= @total %>




<% if @items_pages.page_count > 1 %>
Page :
<%= pagination_links_remote @items_pages %>
<% end %>













<% @items.each do |i| %>
">




<% end %>

>
<%= sort_link_helper "Name", "name" %>
>
<%= sort_link_helper "Quantity", "qty" %>
>
<%= sort_link_helper "Price", "price" %>
<%= i.name %> <%= i.quantity %> <%= i.price %>


<% end %>


[b]分页的helpers文件[/b]

在开始的时候,我们有一个符合条件的记录数和设定的每页显示记录数的判断。如果,符合条件的记录数小于每页可以显示的记录数,则不用分页。
相反,我们就需要要显示分页信息,虽然,Rails已经有了处理和显示的机制。可是我们希望能够实现ajax分页。那么我们需要创建pagination的helpers。
helper方法是用来帮助生成和显示view的。目的是为了将显示和逻辑分离,当然一定程度的复用和代码重构。
我们的helper文件在app/helpers/item_helper.rb. 我们的view可以读取这个文件的任何一个方法。但是,如果我们如果,希望应用中的任何view都可以使用这个文件的方法,那么我们就需要把代码放到application_helper.rb.
内容如下:


def pagination_links_remote(paginator)
page_options = {:window_size => 1}
pagination_links_each(paginator, page_options) do |n|
options = {
:url => {:action => 'list', :params => params.merge({:page => n})},
:update => 'table',
:before => "Element.show('spinner')",
:success => "Element.hide('spinner')"
}
html_options = {:href => url_for(:action => 'list', :params => params.merge({:page => n}))}
link_to_remote(n.to_s, options, html_options)
end
end

我们定义只有一个window_size的page_options的hash。这个参数标识当前页旁边的可以显示的页,如下如果window_size=1那么就会显示如下:
1 ... 5 6 7 ... 13
如果 window_size=2则显示
1 ... 4 5 6 7 8 ... 13

这样,我们就可以通过pagination_links得到对应的xhtml分页后的xhtml通过以上的连接。这的确可以用,然而,我们希望能够异步调用,实现AJAX的分页,所以我们重写pagination_links_each 方法
pagination_links_each 方法参数分析:
option和类似之前我们observe_field的参数,新的部分是params.merge,表示我们通过连接把将当前的请求参数代替之前的状态。
html_options是用来定义没有AJAX支持下的分页显示。以便分页机制在没有javascript支持下也可以用。

下面是实际的有两个页面市,第一个页面显示时的生成代码


2

sorting helpers

继续添加helpers

sort_td_class_helper代码如下:


def sort_td_class_helper(param)
result = 'class="sortup"' if params[:sort] == param
result = 'class="sortdown"' if params[:sort] == param + "_reverse"
return result
end

作用是添加一个class="sortup"用以支持逆序排列

sort_link_helper.


def sort_link_helper(text, param)
key = param
key += "_reverse" if params[:sort] == param
options = {
:url => {:action => 'list', :params => params.merge({:sort => key, :page => nil})},
:update => 'table',
:before => "Element.show('spinner')",
:success => "Element.hide('spinner')"
}
html_options = {
:title => "Sort by this field",
:href => url_for(:action => 'list', :params => params.merge({:sort => key, :page => nil}))
}
link_to_remote(text, options, html_options)
end


这个helper是上面pagination_links_remote的缩小版,它有两个参数:
text 用于显示字段的头和排序连接
param 和字段关联的请求参数
本段代码首先定义一个变量key用于保持param传递过来的参数。_reverse用于表示param是否正在排序中的参数。也就是说实现,第二次点击逆序。

接下来的代码定义了link_to_remote方法需要的参数,和paginateion_links_remote非常类似
option是哈希表,详情见上
html_options 也还是为了javascripte不支持下的功能实现。
下面是sort_link_helper以Quantity" 和 "qty" 作为 text 和 param 参数返回值的显示表格

最后,在用patial显示table的时候,如果希望能够,一行一个颜色,我们需要加入一个cycle的rails方法,来自动增加奇数和偶数的样式方法。

[b]最后了[/b]

我们已经或多或少的看了,在这个演示中用到的所有技术细节。在这个过程的最后,通过如下url享用你的劳动成果。
如果,这个文档对你有那么半点帮助,我的时间就值得欣慰了。我再重复一下,有什么问题请在我的博客反馈。
谢谢

你可能感兴趣的:(RubyOnRails,Rails,Ajax,Ruby,JavaScript,SQLite,ViewUI)