Dojo 最佳实践 - 实现可书签化的 Ajax 应用

红薯 发布于 2010/08/30 22:10
阅读 1K+
收藏 5

可书签化的 Ajax 应用的挑战

Ajax 技术给 Web 用户带来全新的使用体验同时,也给 Web 开发人员带来了各种各样的挑战。对于浏览器书签收藏以及前进后退按钮的支持,便是其一。

对于传统的 Web 应用而言,对于支持浏览器的书签收藏以及签名后退按钮是很自然的事情,开发人员并不需要做额外的工作。每个 Web 页面都有一个唯一的 URL,书签收藏的 URL 便对应相应的页面,而用户在不同页面之间浏览时便形成一个历史记录队列,队列中每一项对应一个页面 (URL), 浏览器通过记录这个历史队列来提供前进后退按钮功能。

但在基于 Ajax 的 Web 应用中 , 情况却有所不一样。很多时候页面视图的转换是通过异步请求动态地局部刷新页面实现的,并没有重新请求一个新页面,因而页面视图对应的 URL 也不会变化,也不会形成上面情况所说的历史记录。现在的浏览器还是以传统的 Web 应用的方式来支持书签收藏及前进后退按钮。对于新兴的 Ajax Web 应用,浏览器在这方面的支持并不好。这就导致了浏览器的书签收藏,前进后退按钮功能对于 Ajax Web 应用失去了意义。


图 1. 传统 Web 应用与 Ajax Web 应用的差异
图 1. 传统 Web 应用与 Ajax Web 应用的差异

那是不是我们在现有的浏览器中只能放弃书签收藏以及前进后退功能呢?显然这样是很难让人接受的,让本来是给用户带来使用体验提升的 Ajax 技术失去一项很好的用户体验。幸好我们还有办法。接下来将介绍一种基于超链接锚点的技术来实现 Ajax Web 应用的浏览器书签收藏及前进后退按钮支持。为简单起见,我们把支持浏览器书签收藏及前进后退功能的 Ajax 应用称作可书签化的 Ajax 应用。

基于超链接锚点的可书签化的 Ajax 应用编程模式

超链接锚点本来是用作传统 Web 页面中同一页面内部内容的定位的。其表现形式便是 URL 中 # 字符后面的字符串。如 http://hostname/mypage.html#content1。如下两个 URL 对应的便是同一个页面中的不同内容部分:

URL1 http://hostname/mypage.html#content1

URL2 http://hostname/mypage.html#content2

从 URL1 跳转到 URL2,其实都是请求的服务器中同一个页面,# 后面的字符串只对浏览器端的解释起作用。利用这个特性,我们可以实现基于超链接锚的可书签化的 Ajax 应用。对于 Ajax 应用中需要书签化的不同视图,通过赋予不同的锚点来标识,这样即保证了页面不发生跳转,也实现了不同页面视图的 URL 差异,也就在浏览器中形成了一个历史记录 (IE 对于同一页面的不同锚点会认为是同一页面,而解决这个问题的办法便是使用 iframe 技术),浏览器的书签收藏与前进后退按钮功能便能起作用了。

设计可书签化的 Ajax 应用时,一般要遵循以下模式 :

  • 确定需要书签化的视图
  • 确定视图对应的状态以及 URL
  • 实现一个根据状态参数定位页面视图的方法

下面我们就结合实例来详细说明可书签化的 Ajax 应用程序的开发模式。首先,需要确定 Ajax 应用中哪些视图需要支持书签收藏功能。我们以 IBM Lotus Quickr 产品中的场所管理模块的用户界面为例来说明。下图是场所管理模块的用户界面。


图 2. 场所管理模块用户界面中支持书签收藏功能的视图
图 2. 场所管理模块用户界面中支持书签收藏功能的视图

从图中可以看出,应用的界面最顶层由几个标签页组成,它们在图例的左上角以红圈标记。标签页分别是 : 场所、模版、策略、统计信息以及搜索。这些标签页都需要支持书签收藏功能。而在每个标签页中,都可能对应有一个数据表格视图,标签页左上角的菜单(红圈标 记)对应了展现在右边数据表格中的不同数据源。最后,表格中的页面大小和当前页面序数(图中表格的上下分页栏中红圈标明的区域)也都控制着数据表格视图的 显示。我们希望用户能根据收藏的书签地址能直接访问到特定数据源的数据表格,并精确定位到表格当前页面序数及页面大小。

确定了需要支持书签收藏的视图之后,再来看定位一个视图需要的状态参数,并定义 URL。根据前面的描述,要定位需要的视图,则需要以下状态参数:

  • 标签页,tab
  • 表格数据源,filter
  • 表格页面大小 , pageSize
  • 表格当前页面序数,pageNo

当状态参数符合以下条件时,便可定位图 2 中指定的视图。

标签页为“场所”,数据源菜单为“所有场所”,页面大小为“10”以及当前页面序数为“1”。把该状态通过一个 JSON 对象描述,如下:


清单 1 JSON 格式描述的视图状态

				 
{
tab :”places”,
filter :”all_places”,
pagesize :10,
pageno :1
}

 

以上状态参数,依据一定规则转换成超链接锚点的形式。如下:

tab=places&filter=all_places&pagesize=10&pageno=1

这样,把以上的字符串作为超链接锚点加到 URL 上便可以指定我们需要的特定视图了。最后,也就是最关键的,需要实现一个 JavaScript 方法来根据前面指定的状态参数来渲染用户界面。还是以上面的场景为例,该 JavaScript 方法如下 :


清单 2 根据状态参数渲染用户界面

				 
locatePageView : function(/*object*/ viewParams){
var tab;
if(viewParams && viewParams.tab){
tab = viewParams.tab;
}
else{
// 如果 viewParms.tab 不存在,给 tab 赋予一个默认值
tab = ”places”;
}

// 根据当前 tab 值渲染标签页
RenderingTab(tab);

var filter;
if(viewParams && viewParams. filter){
filter = viewParams.filter;
}
else{
// 如果 viewParms.filter 不存在,给 filter 赋予一个默认值
filter = ”all_places”;
}
// 根据当前 filter 值渲染数据源菜单
RenderingFilter(filter);

var pageSize;
var pageNo;
if(viewParams && viewParams.pageSize){
pageSize = viewParams.pageSize;
}
else{
// 如果 viewParms.pageSize 不存在,给 pageSize 赋予一个默认值
pageSize = 10;
}

if(viewParams && viewParams.pageNo){
pageNo = viewParams.pageNo;
}
else{
// 如果 viewParms. pageNo 不存在,给 pageNo 赋予一个默认值
pageNo = 1;
}
// 根据当前 pageSize 值和 pageNo 值渲染数据表格
RenderingTable(pageSize,pageNo);
}

完成了以上步骤,便完成了实现可书签化 Ajax 应用的大部分工作。接下来,需要做的只是在视图转换时把对应的状态通过超链接锚点表现出来,把视图状态的转换记录到浏览器的历史记录队列中以及捕获浏览器 的前进后退按钮被按下时触发的事件。其实,剩下的这些工作在 Dojo 工具包中的 Dojo.back 或者是 Dojo.hash API 中都已经很好的封装了。只要根据前面的模式来设计 Ajax 应用,并根据 Dojo.back 或者 Dojo.hash API 的规范来编程,便可非常容易的实现可书签化的 Ajax 应用。

使用 Dojo.back 技术

Dojo.back 提供了一套 API,用来记录 Ajax 页面视图的状态,更新浏览器历史记录信息,同时,用户可以利用相应接口定制页面的动态刷新方法,当浏览器的后退、前进按钮被点击时,这些方法将会被执行。 下面我们就结合前面的例子来介绍如何应用 Dojo.back。

准备工作 :

在使用 Dojo.back 之前,我们需要将 Dojo.back 相关的文件加载到内存中。并在 HTML DOM 结点附着在页面之前,调用 Dojo.back.init()。我们可以在 body 标签开始处插入一段 JavaScript 脚本,如下所示:


清单 3 Dojo.back 初始代码

				 
<body>
<script type="text/javascript">
dojo.require("dojo.back");
dojo.back.init();
</script>
</body>

 

注意:dojo.back.init() 必须在 DOM 结点附着在页面之前调用,否则 Dojo.back 的使用将会出现异常。

定义状态类:

Dojo.back API 中通过一个 JSON 对象来描述 Ajax 应用页面视图的状态。该 JSON 对象一般包含以下属性:

  • back(), backButton(),handle(back)

这几个方法的作用是一样的,当用户点击浏览器的后退按钮时,这些方法会被调用。在实际应用中,用户选择其中一个方法就可以。

  • forward(), forwardButton() ,handle(forward)

当用户点击浏览器的前进按钮时,这些方法会被调用。与上述的后退方法类似,在实际应用中,用户选择其中一个方法就可以。

  • changeUrl

这个属性的值会被看作当前页面视图的书签,用来唯一标识当前页面视图。

如果这个属性值被设成 true,Dojo.back 会为用户自动生成一个唯一值,将该值设置为书签,用来标识当前页面视图;如果这个属性被设定为其他值(不包括 undefined,null,0 和空字符串),那么 Dojo.back 会将这个值作为当前页面视图的书签。

根据以上描述,可以通过一个类来定义 Ajax 应用页面视图状态。


清单 4 ApplicationState 类定义

				 
dojo.declare("util.ApplicationState", null, {
changeUrl: "",
constructor: function(/* Object to store the url key/value pairs*/params){
this.params = params;
this.changeUrl = this._constructUrl(this.params);
},

/**
* Handler when press back/forward to current state.
*
* @param {Object} type
*/
handle: function(type){
// 根据当前的 params, 调用清单 2 中的 locatePageView 方法定位页面视图

},

/**
* Construct the hash URL by params
*
* @param {Object} params
*/
_constructUrl: function(params){
return dojo.objectToQuery(params);
}
});

 

构造函数中传递的 params 参数为清单 1 中所描述的页面视图状态参数,通过私有方法 _constructUrl 转换成 URL 的形式并赋给 changeUrl, 成为 URL 中的超链接锚点。params 参数与页面视图的状态是一一对应的。因而不管用户点击后退还是前进按钮回到当前页面视图,根据唯一的页面视图状态参数 parmas, 就能定位页面视图。所以,在清单 4 中只用定义 handle 方法来统一处理用户点击后退和前进按钮后的工作(调用可书签化 Ajax 应用编程模式中提到的根据视图状态参数渲染页面的方法)。

使用 Dojo.back API:

在初始化 Dojo.back 并定义状态对象后,便可以调用 Dojo.back API 来实现 Ajax 应用视图的书签化了。

  • 在应用程序初始化时,为当前的 Ajax 应用设置页面视图初始状态。

相关 API 如下所示:

dojo.back.setInitialState(state);

其中,参数 state 是前面提到的状态对象实例。

示例代码如下 :


清单 5 调用 Dojo.back API 设置 Ajax 页面视图初始状态

				 
// 获取当前超链接锚点值
var params = {};
var url = window.location.hash;
if (url.charAt(0) == "#") {
url = url.substring(1);
}
// 非 Mozilla 浏览器需要做解码当前超链接锚点值
url = dojo.isMozilla ? url : decodeURIComponent(url);

// 转换成 JSON 状态对象
params = dojo.queryToObject(url);

if(params == null || typeof params == “undefined”){
// 设定默认的页面初始状态参数

}
// 根据 params,新建状态类对象
var initialState = new util.ApplicationState(params);

// 设定初始状态
dojo.back.setInitialState(initialState);

// 根据页面视图状态对象来初始化当前页面
locatePageView(params);

 

支持书签的 Ajax 应用能根据收藏的书签地址来初始化页面。因而在上面示例中,Ajax 应用初始化时,必须获取当前超链接锚点的值,并以此来确定页面视图的初始状态。如果超链接锚点值为空,则给予默认的页面视图初始状态。接下来通过 dojo.back.setInitialState 方法设置初始状态对象 initialState。最后依据页面视图状态对象参数来渲染初始页面。

  • 当用户操作使得页面视图发生变化时,将新的页面视图状态注册到浏览器的历史记录列表中。

相关 API 如下所示:

dojo.back.addToHistory(state);

其中,state 为描述新页面视图状态的 JSON 对象。

用户点击标签页 Tab,使得页面视图发生变化,示例代码如下 :


清单 6 调用 Dojo.back API 注册新的页面视图状态

				 
// 当 tab 页被点击后的处理函数
function onTabClick(/*String,tab id of clicked tab page*/ tabId){
// 根据用户点击的标签页标识来确定新的页面视图状态参数
var params = {};
params.tab = tabId;

var state = new util.ApplicationState(params);

// 注册新的页面视图状态对象
dojo.back.addToHistory(state);

// 根据状态参数渲染页面视图
locatePageView(params);
}

 

只要按照前面的编程模式步骤对 Ajax 应用进行分析与设计,并正确使用 Dojo.back API,便能实现可书签化的 Ajax 应用。

Dojo.back 的缺陷 :

当页面视图的 URL 包含超链接锚点时,如果用户手动的更改超链接锚点值以指向另一个页面视图时(如下所示),页面并不会发生迁移。

当前页面 URL http://hostname/mypage.html#tab=place

更改新 URL 到地址栏中 http://hostname/mypage.html#tab=policy

这是因为当 URL 变化时,只是超链接锚点发生了变化,而根据 HTML4 的规范,浏览器并不需要触发超链接锚点发生变化的事件(Event), 因而 Ajax 应用的页面视图没有真正发生变化。而解决这个问题的办法只能是通过一个侦听器来定时地检查超链接锚点的变化。

另外一个缺陷是当页面的 URL 真正发生改变(而非超链接锚点),页面发生了跳转,那么当通过后退按钮再次回到之前的页面时,Dojo.back 便不能正常工作了。请看下面的例子 :

页面视图状态变更序列 :

1. 初始状态 http://hostname/mypage.html#tab=place

2. 操作一次后页面视图变化 http://hostname/mypage.html#tab=policy

3. 再操作一次后页面视图变化 http://hostname/mypage.html#tab=template

4. 页面发生跳转 http://www.ibm.com

5. 按后退按钮回到视图 3 http://hostname/mypage.html#tab=template

6. 继续按后退按钮试图回到 2 http://hostname/mypage.html#tab=policy

当用户按照上面的序列操作,在第 5 步,页面还可以正确地定位到 http://hostname/mypage.html#tab=template 页面(URL 发生跳转,根据 URL 来渲染页面视图),但是如果再按后退按钮,Ajax 应用便不能正确渲染页面了(需要根据 handle 方法来渲染页面)。这是因为状态对象 ApplicationState 本质是 JavaScript 对象,而其只能在当前使用的 Ajax 应用的作用域内有效,当页面跳转到 http://www.ibm.com 后,ApplicationState 对象便不再存在了。因而用来处理后退前进按钮的 handle 方法也不再起作用。

而在下面介绍的 Dojo.hash 技术中对于这两个问题都能很好地解决,并不需要用户实现额外的代码。

使用 Dojo.hash 技术

Dojo.hash 技术始于 Dojo 1.4 版本,其原理与 Dojo.back 不太一样。Dojo.hash 提供了另外一套 API,用来侦听、获取、设置浏览器 URL 中的锚点内容,同样支持浏览器后退前进按钮以及书签化,通过 Dojo.hash 提供的这套 API,用户可以更好更快的实现 Ajax 应用程序的可书签化。Dojo.hash 的工作原理比较简单,在程序执行过程中,Dojo.hash 侦听浏览器超链接锚点的变化,一旦用户的操作(包括点击浏览器前进后退按钮)改变了浏览器的超链接锚点,Dojo.hash 将执行用户所定义的方法,根据改变后的超链接锚点来渲染页面视图。下面我们就结合具体代码来介绍如何应用 Dojo.hash。

准备工作:

在使用 Dojo.hash 之前,将 Dojo.hash 相关的文件加载到内存中。

相关 API 如下:

dojo.require("dojo.hash");

指定超链接锚点侦听函数:

侦听浏览器超链接锚点的变化,一旦发现有所改变,则执行用户定义的方法来渲染页面视图。

相关 API 如下:

dojo.subscribe("/dojo/hashchange", context, callback);

当用户通过 Dojo.hash 提供的 API 改变浏览器超链接锚点内容时,上述参数中的 callback 方法将被执行。context 用来定义 callback 方法运行的作用域。

Dojo.hash 是如何侦听到浏览器 URL 中锚点内容的变化呢?在 HTML5 的规范中,定义了新的事件 onhashchange,用来侦听浏览器 URL 中锚点内容的变化,目前,FF3.6a2, IE 8, Chrome 4.0.206.1 均支持该事件。Dojo.hash 对 onhashchange 事件进行了封装,同时对于不支持该事件的浏览器,Dojo.hash() 利用轮询的方法动态的检测浏览器 URL 中锚点部分的改变,在默认情况下,Dojo.hash() 每隔 100ms 会轮询一次,用户可以通过设置 djConfig 中的 hashPollFrequency 变量来定制轮询的时间,比如:

djConfig = {

hashPollFrequency: 200

}

同样,给出如下示例代码:


清单 7.定制侦听超链接锚点变化的函数

				 
// 订阅事件“/dojo/hashchange”,当 URL 中 hash 值改变时,
//该订阅方法会被执行。hash 值会作为第一个参数传递到该订阅方法中
dojo.subscribe("/dojo/hashchange", dojo.hitch(this, function(hashValue){
// 根据当前 hash 值,渲染页面视图
var params = dojo.queryToObject(decodeURIComponent(hashValue));

// 根据状态参数渲染页面视图
locatePageView(params);
}));

 

初始化页面视图状态

当 Ajax 应用页面第一次被访问时,因为 Dojo 还没被装载,并不能通过上面的侦听超链接锚点变化的函数来渲染页面视图,所以需要特别处理。通过 Dojo.hash 提供的 API 获取当前超链接锚点的值,并以此来确定页面视图的初始状态。如果超链接锚点值为空,则给予默认的页面视图初始状态。最后依据页面视图状态参数来渲染初始页 面。示例代码如下:


清单 8 初始化 Ajax 应用页面视图

				 
// 获取当前超链接锚点值并转换成状态参数
var params = {};

// 转换成 JSON 状态对象
params = dojo.queryToObject(decodeURIComponent(dojo.hash()));

if(params == null || typeof params == “undefined”){
// 设定默认的页面初始状态参数

}

// 根据页面视图状态对象来初始化当前页面
locatePageView(params);

 

当用户操作改变页面视图时,设置正确的锚点:

Dojo.hash 提供非常方便的 API 来获取和设置超链接锚点。

  • 获取浏览器 URL 中的锚点内容

相关 API 如下:

dojo.hash()

当没有参数传入到 dojo.hash() 中时,dojo.hash() 将返回当先浏览器 URL 中的锚点内容。通常情况下,页面视图的状态参数会存在于浏览器 URL 中锚点部分,比如 tab=place,pageno=01,pagesize=10。当浏览器 URL 的内容发生变化时,用户可以利用该 API 获取状态参数,用来渲染页面视图。

举例来说,当浏览器 URL 为以下值时,

http://hostname/mypage.html#tab%3Dplaces%26filter%3Dall_places%26pagesize%3D10%26pageno%3D1

dojo.hash() 将返回锚点内容如下:

tab%3Dplaces%26filter%3Dall_places%26pagesize%3D10%26pageno%3D1

注:从代码示例可以得知,Dojo.hash() 不会对返回值做 decoding,我们需要使用 Javascript 的全局方法 decodeURIComponent() 来对 Dojo.hash() 的返回值解码。

调用 decodeURIComponent(dojo.hash()) 结果如下 :

tab=places&filter=all_places&pagesize=10&pageno=1

  • 设置浏览器 URL 中的锚点内容

相关 API 如下:

dojo.hash(/* string */newHashValue, (optional)/*boolean*/noNewEntryInBackHistory)

该 API 将当前浏览器 URL 的锚点内容设置为参数 newHashValue 的值。如果不指定参数 noNewEntryInBackHistory 的值或将其值设置为 false,那么 Dojo.hash 在改变浏览器 URL 内容的同时,将当前页面设图的状态存储到浏览器历史记录堆栈中。如果参数 noNewEntryInBackHistory 设置为 true,那么 Dojo.hash 只改变浏览器 URL 的内容,不会将当前页面设图的状态存储到浏览器历史记录堆栈中。

举例来说,当运行以下代码时,

dojo.hash(encodeURIComponent(“tab=places&filter=alert_places&pagesize=20&pageno=2”));

浏览器 URL 的值将被设置如下 :

http://hostname/mypage.html#tab%3Dplaces%26filter%3Dalert_places%26pagesize%3D20%26pageno%3D2

同时,当前页面视图的状态将被存储到浏览器的历史记录队列中。

同样,Dojo.hash(value) 不会对传入值做 encoding。value 参数可能包含 URL 中的保留字符,因而我们需要使用 encodeURIComponent() 对传入值进行编码。

当用户点击标签页 Tab,使得页面视图发生变化,示例代码如下 :


清单 9 调用 Dojo.hash API 设置锚点

				 
// 当 tab 页被点击后的处理函数
function onTabClick(/*String,tab id of clicked tab page*/ tabId){
// 根据用户点击的标签页标识来确定新的页面视图状态参数
var params = {};
params.tab = tabId;

// 设置锚点
dojo.hash(encodeURIComponent(dojo.objectToQuery(params)));
}

小结

本文介绍了如何使用超链接锚点技术编程模式来分析和设计可书签化的 Ajax 应用。在基于以上的设计和分析后,分别结合实例讲解了如何使用 Dojo.back 技术和 Dojo.hash 技术来实现可书签化的 Ajax 应用。

与 Dojo.back 技术相比,Dojo.hash 技术有以下两项明显的优点:

  • Dojo.hash 的机制适用性更强。它将浏览器超链接锚点的变化作为判断页面视图变换的唯一根据,这样无论是用户在页面视图上的操作,或是点击浏览器的前进后退按钮,或是 直接在浏览器输入 URL,只要是造成了浏览器超链接锚点的变化,Dojo.hash 都会执行用户的定义的方法来渲染页面视图。就算页面发生真正的 URL 跳转,当回退时,Dojo.hash 机制仍然能正确工作。而 Dojo.back 只适用于用户在 URL 不发生跳转 ( 不包括锚点 ) 前提下,点击浏览器前进后退按钮的情况。
  • 利用 Dojo.hash 实现书签化的 Ajax 应用程序,实现步骤更加简单直接。

在实现可书签化的 Ajax 应用程序的时候,Dojo.hash 技术将会得到更加广泛的应用。因此,只要是基于 Dojo 1.4 及以上版本开发 Ajax 应用,Dojo.hash 技术将是不二选择。

文中所展示的大部分示例代码都来自附件中的 demo 程序,建议读者下载并运行该 demo 程序,相信这样会帮助读者更好的理解如何使用 Dojo.back 技术和 Dojo.hash 技术实现可书签化的 Ajax 应用。

加载中
0
曾建凯
曾建凯

红薯老大的帖子,不顶不行

但是单就这个案例而言,其实需要一个良好的View层面结构和清晰的分离,最简单的模式也是能实现。

===============================

繁体字貌似出现字符出错了。

0
红薯
红薯

引用来自#2楼“曾建凯”的帖子

红薯老大的帖子,不顶不行

但是单就这个案例而言,其实需要一个良好的View层面结构和清晰的分离,最简单的模式也是能实现。

===============================

繁体字貌似出现字符出错了。

哪是我的啊,IBM开发者园地上的:)

0
曾建凯
曾建凯

哦哦。我以为你喜欢Dojo呢,dojo需要很大的精力去学啊。

0
孙思邈
孙思邈

Thank you 红薯 我一直在找这个,dojo接触了一年了,但用的不是特别灵活,来取经了

返回顶部
顶部