#175 AJAX History and Bookmarks
- Download:
- source codeProject Files in Zip (123 KB)
- mp4Full Size H.264 Video (17.1 MB)
- m4vSmaller H.264 Video (11.5 MB)
- webmFull Size VP8 Video (30.3 MB)
- ogvFull Size Theora Video (24.9 MB)
上一集我们给产品列表的分页加上了AJAX功能。与通常的HTML链接不同,通过非侵入式的JavaScript在分页链接上绑定click事件,他们就可以在点击时发出AJAX请求,从而将页面中的主体部分替换为一页新的产品内容。
AJAX带来的问题
因为我们使用了AJAX来更新页面,所以当新的一页产品被加载时URL不会发生改变。这就意味着,当我们刷新整个页面、或者收录书签后再打开那一页的话,不管之前我们在哪一页,都只会显示第一页的产品。另外一个使用AJAX的后果是,翻页以后上一页并没有被记到浏览器的历史记录中,所以我们没法用返回、前进按钮在我们之前看过的页面中前后浏览。
这些是使用了AJAX方法的问题;那么我们有什么解决办法吗?这将是本集我要告诉你们的内容。要说明的是,因为我们要解决的问题主要是在客户端,所以本集会更多地讨论JavaScript、而不是Ruby on Rails。
有很多JavaScript程序库可以用来解决这个问题。在Google code上我们可以找到一个最早的叫reallysimplehistory的。Yahoo也有一个叫Browser History Manager的。Yahoo有很多JavaScript程序库,所以这个很值得一看。还有一个是Asual的SWFAddress,可以用于Flash、也可以用于JavaScript。
所有上面提到的这些程序库都是支持纯JavaScript代码,但是在我们的程序中使用了jQuery,所以最好能找到一个能支持它的。上面提到的Asual曾经发布过一个叫jQuery Address的插件,可以用。但是我们要用的是另外一个插件,叫jQuery URL Utils plugin。这个插件虽然小(压缩后不到4KB),但是功能很丰富,也有很好的文档。
我们先下载这个插件,在程序目录/public/javascripts
下保存为jquery.ba-url.js
,然后我们在产品index页面中引用它。因为需要在pagination.js
之前引用它,所以我们需要修改引用JavaScript的那行代码,把它加到pagination
的前面。
<% javascript 'jquery.ba-url', 'pagination' %>
改进一下我们的分页代码
在我们开始修改代码来用这个插件之前,我们先看一下上集中我们写的分页的代码。
$(function () { $('.pagination a').live("click", function () { $('.pagination').html('Page is loading...'); $.get(this.href, null, null, 'script'); return false; }); });
我们可以对这个代码稍稍做些改进。不再使用$.get
而是用$.getScript
来发起AJAX请求,传入的参数是我们要调用的URL。
$.getScript(this.href);
$.getScript
函数向作为参数传入的URL发起一个GET请求,然后执行返回的JavaScript代码,功能和$.get
一样,但是代码更简洁。
为每一页指定一个唯一的URL
现在来给我们的程序实现一些新的功能。我们要做的是每当一个分页链接被点击后,用JavaScript来更新URL。这样每一个产品页都有一个唯一的URL,我们就可以把它加到书签里了。我们需要给我们所在产品页对应的唯一URL加上一个锚标签。例如,对于第二页,如果程序是在本地运行,URL将会表示为:
http://localhost:3000/#page=2
我们可以利用之前下载的插件来设置这个URL。插件有一个叫$.setFragment
的方法,能够修改当前URL的锚标签部分。在我们的pagination代码中可以这样来用:
$(function () { $('.pagination a').live("click", function () { $.setFragment({ page: $.queryString(this.href).page }); $('.pagination').html('Page is loading...'); $.getScript(this.href); return false; }); });
工作原理是这样的,给$.setFragment
传入一个JavaScript的原值对象,它会把他们转成象querystring一样的键值对。我们希望得到一个形如page=x
的锚标签,所以我们传入一个键是page
的对象。但是我们如何得到页码值呢?其实,这个页码值已经在我们AJAX调用的URL的 querystring里面了。要得到这个值,我们可以用$.queryString
函数把querystring中的键值对转化为JavaScript对象,然后从page
项中就可以得到这个值。
我们现在可以试一下我们修改后的pagination代码。加载完我们的产品页面,然后点击“下一页”,随着AJAX请求的发出,URL也发生了变化。我们看到浏览器的返回按钮现在也是可用的,说明上一页已经被记录到浏览器的历史记录中了。
搞定返回按钮
事情还没有完。虽然返回和前进按钮可以用了,但是点击它们时并不能改变页面的内容。还有就是,当我们重新打开我们收录的书签页,或者刷新页面,那么,不管URL中锚标签的页码值是多少,显示的都是第一页的产品项。
我们先来解决返回和前进按钮。为了让它们能工作,我们需要侦听URL中锚标签变化时的一个事件。这是我们用的这个插件所支持的功能。在我们的pagination代码中增加下面一行JavaScript代码,可以激活这个功能。
$.fragmentChange(true);
现在我们就可以侦听到这个事件了。我们可以调用$(document).bind
函数,传入这个我们要侦听的事件的名字。如果我们再传入我们关注的那个锚标签的名字,那么这个事件就只会在那个锚标签发生改变时被触发。我们还需要传入一个绑定的函数,在事件被触发时能够被执行。现在,我们只是弹出一个alert
来测试一下是不是万事俱备。
$(document).bind("fragmentChange.page", function () { alert('fragment changed!'); });
现在我们刷新页面并且点其中的一个链接,我们可以看到弹出的alert,同时也看到URL中的锚标签也变化了。当我们点击返回或者前进按钮时,我们也可以看到alert弹出来。
现在点击任意一个链接都可以捕获到fragmentChange
事件,我们就可以把AJAX请求搬到那个响应事件的函数中去了。那样的话,点击返回按钮也可以发出AJAX请求了。但是,这给我们带来了一点小问题。下面是我们要搬过去的那行代码。
$.getScript(this.href);
上面的代码中要引用的分页链接已经没法再得到了,我们不得不用其他方法得到页码值。所幸,点击链接时运行的代码会改变URL中的锚标签,所以我们可以从那里得到页码值。
$.getScript( $.queryString( document.location.href, { 'page':$.fragment().page } ) );
这块代码有点复杂,所以我们把它分解成多行使其更清晰一些。为了得到正确的URL,我们使用了$.queryString
函数,传入参数document.location.href
来获得页面URL的主体部分,传入一个键值为page
的JavaScript对象来构造querystring。通过调用$.fragment().page
,我们可以从URL的锚标签部分得到正确的页码值。构造出正确的URL,我们就可以把它作为$.getScript
的参数来发起AJAX请求了。
如果现在我们刷新页面并且点击这些链接,返回按钮依然可用。但是现在再点击返回按钮,fragmentChange
事件将被触发,AJAX请求会更新产品列表,从而显示出正确的产品页。
书签收录和页面刷新
大功即将告成,不过页面上还有一点不如人意的地方。如果我们在浏览某一产品页时刷新了页面,我们依然只能看到第一页产品。如果我们收录了某一页到书签中,然后再打开它,也会有同样的问题。
解决这个问题的方法是,当页面的URL中存在锚标签时,在页面刷新时能够触发fragmentChange
事件。为此,我们需要在pagination中在加入几行JavaScript代码。
if ($.fragment().page) { $(document).trigger("fragmentChange.page"); }
这段代码在页面刷新的时候看URL中是否有一个锚标签,如果有就触发fragmentChange
事件,这样就可以加载正确的产品页了。
最终完整的pagination代码如下所示:
$(function () { $('.pagination a').live("click", function () { $.setFragment({ page: $.queryString(this.href).page }); $('.pagination').html('Page is loading...'); return false; }); $.fragmentChange(true); $(document).bind("fragmentChange.page", function () { $.getScript($.queryString(document.location.href, { 'page': $.fragment().page })); }); if ($.fragment().page) { $(document).trigger("fragmentChange.page"); } });
现在我们可以通过AJAX的方式来对产品列表翻页,并且支持浏览器的前进和返回按钮,而且能够将其中某一页收录到书签里。
在我们的程序中,我们使用jQuery开发了分页功能,其实还有很多用户是使用Prototype的。如果你用ProtoType,Prototypextensions程序库中也有类似的支持AJAX浏览记录的功能。