[翻译] 174 用AJAX进行分页

174: 用AJAX进行分页观看原视频

查看英文原版


在任何一个要显示一系列项的Rails程序中,最好是能够把这一系列项进行分页,这样我们就可以一次只显示一定数量的项。已经有一些你可以用来做分页的gem,其中一个叫will_paginate的gem非常不错。我们下面的程序就使用了它。


下面显示的index页面来自于一个分页显示产品的程序。点击顶部每一页的链接将刷新整个页面,显示出产品列表中的不同部分。我们当前看到的产品页的页码显示在URL的query string当中。


[翻译] 174 用AJAX进行分页_第1张图片


这种分页方式使用标准的HTML链接,但是我们更想在点击这些链接时能够触发AJAX请求,这样我们就可以只更新页面中发生变化的部分。

 

分页

will_paginate gem可以从Github进行安装,把下面的代码加到/config/environment.rb中,就将其安装到了我们的程序里。

 

config.gem 'mislav-will_paginate', :lib => 'will_paginate', :source => 'http://gems.github.com' 

在我们products controller的index action中我们使用paginate方法而不是find方法去读取我们的产品列表。 

 

 

class ProductsController < ApplicationController 
  def index 
    @products = Product.paginate(:all, :order => "name ASC", :per_page => 10, :page => params[:page]) 
  end 
end 

 

paginate方法中值得注意的参数有两个,:per_page决定每次读取多少项;:page决定页码偏移值。这里,我们每页读取10项,页码取自于query string参数。 

 

最后,在index view中我们用will_paginate来显示分页链接。将会显示“前一页”,“后一页”和每一页的链接。

 

<%= will_paginate @products %> 

 

 增加AJAX功能

为了让分页能够以AJAX的方式工作,我们需要修改分页的链接,使他们能够在点击时触发AJAX请求,而不是跳转到另外一页。要达到这样的目的,我们可以修改will_paginate的代码来改变生成链接的HTML代码,不过这样很麻烦,并且最终得到的是一个侵入式方案。一个更好的方法是,不修改原有的HTML,而是加上一些非侵入式的JavaScript代码来给这些链接加上AJAX功能。这种方法对于那些屏蔽了JavaScript的用户依然是可行的。

 

jQuery程序库,我们可以很容易地实现AJAX功能。为此,我们需要下载jQuery,在我们程序目录 /public/javascripts下,将其保存为jquery.js文件。在程序的layout文件的<head>部分,我们加入下面一行代码来引用它。 

 

<%= javascript_link_tag 'jquery' %> 

如果你愿意,你也可以使用prototype而不是jQuery,但是你需要修改我们将要写的AJAX链接的代码,使其可以在prototype下工作。

 

我们将把用来做分页的JavaScript代码放到一个新的JavaScript文件pagination.js当中,和其他JavaScript文件放到同一目录下。首先我们要往这个文件中加一些页面中分页链接的onclick事件函数。但是我们想在文档对象模型(Document Object Model)加载以后再添加这些事件函数。通过调用$函数,传入一个函数作为参数,我们可以让浏览器在DOM加载完以后来执行这个函数。为了测试一下我们是不是已经正确配置,我们在DOM加载的时候显示一个alert。 

 

$(function (){ alert('DOM has loaded.'); });

我们首先要把我们新的JavaScript文件包含在products页面中。之前在我们程序中用过Ryan Bates的很精巧的layout生成器,这里我们也可以用他提供的JavaScript辅助函数。在index view文件中,我们可以加入下面一行代码。 

 

<% javascript 'pagination' %> 

现在,当我们刷新页面的时候,pagination中的JavaScript将被加载,在页面加载时我们可以看到弹出的alert。

  
[翻译] 174 用AJAX进行分页_第2张图片
  

既然我们确信JavaScript已经能够被正确调用,我们就可以修改代码来让我们的分页链接使用AJAX。看看页面的代码,我们发现分页的链接都包含在class为pagination的一个div当中。我们可以利用这个来创建jQuery的选择器来匹配所有的链接。 

 

<div class="pagination">
  <a href="/products?page=1" class="prev_page" rel="prev start">&laquo; Previous</a> 
  <a href="/products?page=1" rel="prev start">1</a> 
  <span class="current">2</span> 
  <a href="/products?page=3" rel="next">3</a> 
  <!-- Other links omitted --> 
  <a href="/products?page=3" class="next_page" rel="next">Next &raquo;</a> 
</div>

匹配到所有的链接后,我们就可以用click函数来改变他们的行为。我们修改后的分页代码如下所示。

  

$(function () { 
  $('.pagination a').click(function () { 
    $.get(this.href, null, null, 'script'); 
    return false; 
  }); 
});

当页面的DOM加载以后,我们的$函数依然会被调用,不再弹出一个alert,它现在做了一些其他事情。首先,它调用$函数,传入一个字符串来找到class为pagination的div中的所有锚标签。这些标签的click事件将被修改,当他们被点击时,就会发出AJAX请求。

 

这个AJAX请求是一个GET请求,所以我们可以使用jQuery的$.get方法。这个方法可以传入几个参数。第一个是请求的URL。我们想让我们的AJAX请求锚链接上同样的URL,所以我们可以用this.href来将href属性值传入函数。

 

第二个参数用来传入我们需要的任意数据。我们可以传入页码值,但是它已经放到URL的query string里面了,我们这里可以用null。同样,第三个参数是一个回调函数,我们不需要用它所以也可以传入null。

 

最后一个参数很重要。它告诉jQuery如果处理我们AJAX请求的返回内容。我们想要取回JavaScript代码让浏览器来执行,所以我们给这个参数传入’script’。

 

最后,函数返回false,这样的话链接的缺省行为就不会被触发,否则将会重新刷新页面,使得我们的AJAX请求变得毫无意义。

 

修改Controller

点击一个分页链接触发的AJAX请求会调用在没有加入JavaScript代码前同样的一个action:ProductController的index action。

   

def index 
  @products = Product.paginate(:all, :order => "name ASC", :per_page => 10, :page => params[:page]) 
end 

虽然这个action同样可以正确响应AJAX请求,但是与其关联的view文件返回的却是HTML。我们需要创建另外一个view以JavaScript的形式来返回一系列的产品项。为此,我们在目录/app/views/products下创建一个叫index.js.erb的文件。这个新view包含了分页链接被点击的时候返回的我们想让浏览器执行的JavaScript。

 

下面我们给这个view加一个简单的alert来测试我们的AJAX代码是不是可行。

  

alert("This is an AJAX request."); 

如果你再次刷新页面,并且点击这些分页链接,你将看到弹出的alert。

 
[翻译] 174 用AJAX进行分页_第3张图片
  

现在浏览器运行的就是index action响应AJAX请求时返回的JavaScript。

 

毫无疑问,当分页链接被点击时,我们不只是想看到一个alert。我们想替换页面中显示链接和产品项的部分。为此,我们需要把链接和产品列表的部分抽取到一个partial当中。

 

index view的代码如下所示:

  

<% title "Products" %> 
<% javascript 'pagination' %> 
<%= will_paginate @products %> 
<% @products.each do |product| %> 
<div class="product"> 
  <h3> 
    <%= link_to product.name, product %> 
    <%= number_to_currency(product.price, :unit => "&pound;") %>   
  </h3> 
  <div class="actions"> 
    <%= link_to "Edit", edit_product_path(product) %> | 
    <%= link_to "Destroy", product, :confirm => "Are you sure?", :method => :delete %> 
    </div> 
  </div> 
<% end %> 
<%= will_paginate @products %> 
<p><%= link_to "New Product", new_product_path %></p> 

我们要从view中抽取出来的是包含在两个有will_paginate的行之间的部分。我们把这一部分代码放到一个叫_product.html.erb的partial文件中,然后再在index中引用它。

  

<% title "Products" %> 
<% javascript 'pagination' %> 
<div id="products"> 
  <%= render 'products' %> 
</div> 
<p><%= link_to "New Product", new_product_path %></p>

另外一个需要改动的地方是把partial放到一个div中,我们好在JavaScript中引用这个包含了内容的外层元素。也就是说,我们可以确切地知道我们要替换页面中的哪一部分。从Rails2.3开始,我们可以通过把一个partial的名字作为字符串传给render来显示它。

 

现在,我们回到刚才的index.js.erb文件,替换掉显示alert的代码,然后加入代码来更新那个包含了partial内容、id为products的div。所有这些只需要一行jQuery代码。

 

$("#products").html("<%= escape_javascript(render("products")) %>");

上面的代码从products partial中读取HTML,然后用escape_javascript来安全地将其插入到一个JavaScript字符串当中,然后将id为products的div中的内容替换成paritial的输出。

 

当我们现在刷新页面,并且点击“下一页”链接的时候,页面内容已经更新为下一页的产品项,但是页面的URL没有改变,但是当我们再次点击,页面又会全部刷新,URL也会改变。接下来的点击结果会在AJAX更新和整页刷新之间交替出现。  


[翻译] 174 用AJAX进行分页_第4张图片
  

这是怎么回事?哦,原来是我们分页的JavaScript是在页面DOM加载完成的时候将分页链接绑定到click事件上的。当链接和内容被AJAX请求返回的内容替换掉时,事件绑定丢失了,所以链接行为又回到了原来的样子。我们点击的下一个链接刷新了整个页面,使得链接的click事件又被重新绑定,这些绑定的事件在另一个链接被点击、AJAX更新发生时又会失效。

 

幸亏,jQuery可以轻松搞定这个问题。不使用click方法将click事件绑定到链接上,我们可以利用live函数。这个函数多出的那一个参数是指定你要绑定的事件的名字,也就是click。

 

$(function () { 
  $('.pagination a').live("click", function () { 
    $.get(this.href, null, null, 'script'); 
    return false; 
  }); 
});  

使用live(“click”, function() { … } 而不是click(),jQUery会使用事件代理来保证任何新的匹配的元素都将响应这个事件。如果我们现在刷新整个页面并且点击链接,每隔一次的页面刷新不见了,整个页面将始终是通过AJAX来更新的。

 
[翻译] 174 用AJAX进行分页_第5张图片
  

增加一个加载标示

结束本集之前,我们加一个标示,当用户点击链接时能够告诉他们页面正在加载。因为我们是在本机运行这个程序,所以响应极快,但是在真的环境中不一定会这样,所以我们需要给用户一个反馈。

 

为了在本地模拟一定的延时,我们在index action中sleep两秒钟。

 

def index 
  sleep 2 
  @products = Product.paginate(:all, :order => "name ASC", :per_page => 10, :page => params[:page]) 
end   

如果我们现在点一个分页链接,页面更新的时候会有一个很短的停顿,但是没有任何地方显示说下一组数据正在被读取。

 

我们可以在我们pagination JavaScript中加这个标示。有很多方法可以来实现这个功能;其中最常见的方法是用一个转动的图片来显示操作正在进行中,这个图片通常是被隐藏的,必要时才显示出来。简单起见,当点了链接后,包含分页内容的pagination div将被一个消息串替代。

 

$(function () { 
  $('.pagination a').live("click", function () { 
    $('.pagination').html('Page is loading...'); 
    $.get(this.href, null, null, 'script'); return false;
  }); 
});

 

现在,当我们点击一个分页链接,在读取的结果返回之前,分页链接部分会被一个“页面刷新中…”的消息串替换。

 
[翻译] 174 用AJAX进行分页_第6张图片
  

最后一点提示

如果你用的是更早版本的jQuery,你可能需要再加一行JavaScript代码使得AJAX请求能够在服务器端被正确处理。否则,可能返回HTML页面而不是JavaScript代码。如果你遇到这种情况,你需要添加下面的代码:

  

jQuery.ajaxSetUp({ 'beforeSend': function (xhr) {xhr.setRequestHeader('Accept', text/javascript')} });

 

本集内容就是这些。希望它能够让你很好地了解如何用jQuery来处理各种页面请求,不仅仅是分页,包括任何你想加到你的网站的各种AJAX功能。

下一集,我们将扩展本集的内容。如果你现在刷新页面,显示的产品列表会回到第一页,而不是刚才的地方。另外,我们没办法将一页收录到书签,或者用浏览器的返回按钮回到前一页。下一集将讨论这些内容并增强我们程序的可用性。

 

你可能感兴趣的:(JavaScript,jquery,Ajax,浏览器,Rails)