论坛首页 编程语言技术论坛

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

浏览 4211 次
精华帖 (0) :: 良好帖 (2) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-03-01   最后修改:2009-03-05
写在前头
     绿色的部分是,这个介绍容易出问题部分的解释。本来就是面向零基础,才写的,所以不怕麻烦和琐碎。然而,如果你还是嫌太麻烦,或者你不希望了解细节的话。那么,我建议你直接点击这里,下载ajaxtable.rar的压缩包。在安装ruby和rails的环境下,到解压的目录下,运行ruby script/server直接看效果。
     压缩包里有paginate的plugin,数据库也不用配置因为是文件的。万一你的系统不认sqlite3,请下载sqlite3的rar,里面有个gem包安装就可以了。
    希望能够给有兴趣的,或者初学者带来帮助


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

任务说明简介和重要提示

在这个示例教程理,我们将要使用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大拿。在这两个领域里,我都是拥有长期经验的菜鸟。所以这个示例是面对初学者和大妈们的。你会看到简介的代码,详尽的解释,但是缺乏高效规范。幸好,虽然,我的母语是汉语,但是难免还是有错误。
任何时候,请到我的博客告诉我。

应用安装和配置
在这个示例中,我们假设已经安装了当前的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

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

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


数据库准备:

简单起见,我们选择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的文件。
修改这个文件,重新定义数据库表结构
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


这里如果,出现不能安装错误请下载附件sqlite3.rar,并放到ruby\bin目录下,执行
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

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

创建model

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

创建view

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

Layout

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

<html>
<head>
  <title>Ajax table manipulation attempt</title>
  <%= stylesheet_link_tag "style" %>
  <%= javascript_include_tag :defaults %>
</head>
<body>

<div id="content">
<%= @content_for_layout %>
</div>

</body>
</html>


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

view部分

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

 <h1>欢迎使用神奇的 items 列表</h1>

<p>我们的列表是Web2.0的经典产物。</p>

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

<h2>bug list如下</h2>

<p>
<form name="sform" action="" style="display:inline;">
<label for="item_name">Filter on item name  : </label>
<%= text_field_tag("query", params['query'], :size => 10 ) %>
</form>

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

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

<div id="table">
<%= render :partial => "items_list" %>
</div>


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

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

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

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

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

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

<script type="text/javascript">
//<![CDATA[
new Form.Element.Observer('query', 2, function(element, value) {Element.show('spinner'); new Ajax.Updater('table', '/item/list', {asynchronous:true, evalScripts:true, onSuccess:function(request){Element.hide('spinner')}, parameters:'query=' + value})})
//]]>
</script>


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

创建controller

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

那么,controller的内容如下:
修改\ajaxtable\app\controllers\item_controller.rb

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代码片段来更新部分的浏览器显示。这样的好处是不用重新加载整个页面。用户的使用体验,会因此变快。

那么接下来呢?

创建partial

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

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

<% if @total == 0 %>

<p>No items found...</p>

<% else %>

<p>Number of items found : <b><%= @total %></b></p>

<p>
<% if @items_pages.page_count > 1 %>
Page&nbsp;:
<%= pagination_links_remote @items_pages %>
<% end %>
</p>


<table>
  <thead>
    <tr>
      <td <%= sort_td_class_helper "name" %>>
        <%= sort_link_helper "Name", "name" %>
      </td>
      <td <%= sort_td_class_helper "qty" %>>
        <%= sort_link_helper "Quantity", "qty" %>
      </td>
      <td <%= sort_td_class_helper "price" %>>
        <%= sort_link_helper "Price", "price" %>
      </td>
    </tr>
  </thead>
  <tbody>
    <% @items.each do |i| %>
    <tr class="<%= cycle("even","odd") %>">
      <td><%= i.name %></td>
      <td><%= i.quantity %></td>
      <td><%= i.price %></td>
    </tr>
    <% end %>
  </tbody>
</table>

<% end %>


分页的helpers文件

在开始的时候,我们有一个符合条件的记录数和设定的每页显示记录数的判断。如果,符合条件的记录数小于每页可以显示的记录数,则不用分页。
相反,我们就需要要显示分页信息,虽然,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支持下也可以用。

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

<a href="/item/list?page=2" onclick="Element.show('spinner'); new Ajax.Updater('table', '/item/list?page=2', {asynchronous:true, evalScripts:true, onSuccess:function(request){Element.hide('spinner')}}); return false;">2</a>

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方法,来自动增加奇数和偶数的样式方法。

最后了

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


   
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics