结合AJAX的PHP开发之后退、前进和刷新

来源:互联网 发布:lumberyard 知乎 编辑:程序博客网 时间:2024/04/29 16:33

简介 第1部分介绍了如何用Sajax、PHP和java script开发基本的相册。在为应用程序建立历史堆栈的过程中,我们将依靠客户端技术,并将其直接与第1部分的代码结合在一起。本文假设读者了解java script 和浏览器cookie。在浏览器中保存状态 在网上冲浪的时候,总是从一个..

 

简介

    第1部分介绍了如何用Sajax、PHP和java script开发基本的相册。在为应用程序建立历史堆栈的过程中,我们将依靠客户端技术,并将其直接与第1部分的代码结合在一起。本文假设读者了解java script 和浏览器cookie。

在浏览器中保存状态

    在网上冲浪的时候,总是从一个页面到另一个页面,从一个站点到另一个站点。在这个过程中,Web浏览器忠实地记录了您曾经到过何处的历史记录,创建了一条面包屑型(breadcrumbs)数字轨迹,沿着这条轨迹能够一步一步地回到出发点。后退按钮允许您回到上一个动作之前所在的位置,从这个意义上说它就是Web上的撤销按钮。

    Web是一种按页划分的的媒体。浏览器工具栏中的后退和前进按钮指引着浏览器从一个页面移动到另一个页面。当Macromedia的Flash风行一时的时候,开发人员和用户发现富互联网应用程序(Rich Internet Application,RIA)打破了这种模式。用户可以在几个站点上浏览,然后登录一个基于 Flash 的网站,在这个网站上消磨几分钟。当用户单击后退按钮时,游戏结束了。用户没有回到先前的那个Flash站点,完全不知道到了什么地方。

    对于完全基于Ajax的网站——RIA的另一种形式,情况也是如此。允许用户与一个页面进行多次交互的网站很容易受到后退按钮的困扰,或者受到任何历史记录按钮的困扰(就此而言)。前进和重载按钮的问题与后退按钮的问题一样。Web浏览器内置的内部历史记录机制是一个不可逃避的问题。出于安全的原因,开发人员不能篡改浏览器历史记录或者任何相关按钮。还有可用性的问题。设想一下,如果后退按钮突然弹出一个神秘的警告提示或者用户被打发到一个新的网站上去,用户该是多么困惑。

构建历史堆栈

    虽然不能改变浏览器历史记录,但是可以自己构建一个在RIA中使用的历史记录。显然,它在某种程度上应该与浏览器的标准导航工具分开,但正如前面所说的,富应用程序在一定程度上背离了Web的页面到页面的标准模式。

    我们将建立一个堆栈来管理应用程序的历史事件记录,也就是说存储一个列表,在表的最后添加元素。堆栈用于按照后进先出(LIFO)的顺序存储数据。虽然回退的时候并没有删除堆栈顶部的数据,但这个模型跟我们的需要非常接近。在java script中,堆栈可以用数组来管理。

    与堆栈在一起的还有一个指针,指示我们在堆栈中的当前位置。当我们在应用程序中单击的时候,新的事件将被压入堆栈顶部,指针指向最后添加的元素。单击应用程序的后退和前进按钮时,不会在堆栈中添加新的事件,而是移动堆栈的指针。 想一想使用后退按钮时历史堆栈中会发生什么:浏览器返回上一次查看的页面,原来不能用的前进按钮突然之间变得可用了。浏览新的页面时,前进按钮再次变成灰色。浏览器历史记录中较晚保存的元素将被弹出堆栈,新的事件被压入堆栈顶部。我们将在自己创建的历史堆栈中再现这种行为。

    我们的目标是创建一组可用的历史记录按钮:后退、前进和刷新。

可重用的设计

    java script使用非常宽松的方法创建对象和类,但仍然能够建立可重用的代码。首先列出历史堆栈需要的功能,然后用java script建立堆栈模型。在把历史堆栈集成到相册应用程序之前,首先要建立一个简单的页面来测试其功能。这样做有两方面的好处:测试页有助于将精力集中到开发和测试类的核心功能上,建立单独的测试页可以避免混淆历史堆栈和相册的功能,从而确保可重用性。

用cookie缓冲

    我们需要应用程序的历史记录在整个浏览器会话中都存在。只要用户仍在查看相册页面,历史堆栈对象就会一直存在。每当发生更改的时候,这个类就会将整个历史记录复制到浏览器cookie中。如果用户在同一个浏览器会话中离开该页之后又返回,那么将返回他离开该应用程序时所在的同一个位置。

编写类

    我们来看看历史堆栈中需要存储的数据或属性。前面已经讨论了堆栈(数组)和指针。stack_limit属性可以防止因为数据过多而造成的cookie溢出(参见清单 1)。在实践中,我们希望在删除最老的记录之前能够存储40-50个事件。出于测试的目的,我们将该值设置为15。

    清单  1  历史堆栈的构造,包括类的属性

view source
print?
1function HistoryStack (){
2 this.stack = new Array();
3 this.current = -1;
4 this.stack_limit = 15;
5}

    除了这三个属性外,该类还需要一些方法来添加元素、检索堆栈数据以及将堆栈数据保存到浏览器cookie 中。首先看一看addResource()方法,它用于将记录压入历史堆栈的堆栈顶部(参见清单 2)。注意,如果堆栈的长度超过了stack_limit,那么最老的记录将从堆栈中移走。

    清单 2  addResource() 方法,向历史堆栈的堆栈顶部添加记录

view source
print?
01HistoryStack.prototype.addResource = function(resource){
02 if (this.stack.length > 0) {
03this.stack = this.stack.slice(0, this.current + 1);
04 }
05 this.stack.push(resource);
06 while (this.stack.length > this.stack_limit) {
07this.stack.shift();
08 }
09 this.current = this.stack.length - 1;
10 this.save();
11};

    给历史堆栈添加的以下三个方法用于从该类中获取信息(参见清单 3)。getCurrent()返回堆栈指针指向的当前记录,这在堆栈中导航的时候非常有用。hASPrev()和 hasNext()方法返回Boolean值,告诉我们当前记录之前或之后是否还有记录,或者指示我们到达了堆栈顶部或堆栈尾部。这些方法很简单,但是确定后退和前进按钮的状态时很有用。

    清单 3  历史堆栈定义的方法

view source
print?
01HistoryStack.prototype.addResource = function(resource)
02HistoryStack.prototype.getCurrent = function (){
03 return this.stack[this.current];
04};
05 
06HistoryStack.prototype.hasPrev = function(){
07 return (this.current > 0);
08};
09 
10HistoryStack.prototype.hasNext = function(){
11 return (this.current < this.stack.length - 1 && this.current > -1);
12};

    现在就可以向历史堆栈中添加记录并确定所在的位置了。但还是无法在堆栈中导航。清单 4中定义的go() 方法允许我们在堆栈中来回移动。通过传递正或负的增量就可以在堆栈中向前或向后移动。这与java script内置的location.go()方法类似。既然模仿内置功能,为何不根据这些已有的方法建立模型呢?

    此外,我们还可用该方法实现刷新功能。可以通过传递正或负的参数在堆栈中导航。传递零时则会刷新当前页面。

    清单 4  历史堆栈的 go() 方法

view source
print?
01HistoryStack.prototype.go = function(increment){
02 // Go back...
03 if (increment < 0) {
04this.current = Math.max(0, this.current + increment);
05 
06// Go forward...
07 } else if (increment > 0) {
08this.current = Math.min(this.stack.length - 1,this.current + increment);
09// Reload...
10 } else {
11location.reload();
12 }
13 this.save();
14};

    到目前为止,只要 HistoryStack对象存在于当前文档中,这个新建的类就能正常工作。我们已经讨论了刷新页面会造成数据丢失的问题,现在来解决它。清单 5 中添加了在浏览器cookie中设置和访问数据的方法。所要做的只是设置每个cookie的名称值对。因为只需要在浏览器会话中保存cookie,而不需要设置有效期。为了简化示例,我们不考虑其他参数,如secure、domain和path。

    注意:如果该类需要对cookie做复杂处理,更明智的办法是使用完全独立的cookie管理类。建立和读取cookie有点偏离历史堆栈的正题。如果java script允许指定方法和属性访问的作用域,也可以将这些方法设成私有的。

    清单 5  建立和访问浏览器 cookie 的方法

view source
print?
01HistoryStack.prototype.setCookie = function(name, value){
02 var cookie_str = name + "=" + escape(value);
03 document.cookie = cookie_str;
04};
05 
06HistoryStack.prototype.getCookie = function(name){
07 if (!name) return '';
08 var raw_cookies, tmp, i;
09 var cookies = new Array();
10 raw_cookies = document.cookie.split('; ');
11 for (i=0; i < raw_cookies.length; i++) {
12tmp = raw_cookies[i].split('=');
13cookies[tmp[0]] = unescape(tmp[1]);
14 }
15 if (cookies[name] != null) {
16return cookies[name];
17 } else {
18return '';
19 }
20};

    定义了管理任何cookie的方法之后,可以编写另外两个类专门处理历史堆栈的类。save()方法将堆栈转化成字符串并保存到cookie中,load() 重新将字符串解析成用于管理历史堆栈的数组(参见清单 6)。

    清单 6  save() 和 load() 方法

view source
print?
01HistoryStack.prototype.save = function(){
02 this.setCookie('CHStack', this.stack.toString());
03 this.setCookie('CHCurrent', this.current);
04};
05 
06HistoryStack.prototype.load = function(){
07 var tmp_stack = this.getCookie('CHStack');
08 if (tmp_stack != '') {
09this.stack = tmp_stack.split(',');
10 }
11 
12 var tmp_current = parseInt(this.getCookie('CHCurrent'));
13 if (tmp_current >= -1) {
14this.current = tmp_current;
15 }
16};

测试类

    可以用简单的HTML页面和一些java script来测试完成的类。测试页面将在上方显示历史记录按钮,只有活动的按钮是突出显示并且可以单击的。我们没有建立复杂的测试应用程序,该页面在每次单击链接时仅仅生成随机数。这些数字就是记录到历史堆栈中的事件。堆栈也在页面上显示,指针标记的当前记录用粗体显示。

    清单 7  测试历史堆栈的简单 HTML 页面

view source
print?
1<br><html><br><head><br><title></title><br></head><br><body><br><div id="historybuttons"></div><br><div><br><a href="#" onclick="do_add(); return false;">Add Random<br>Resource</a><br></div><br><div id="output" style="margin-top:40px;"></div><br></body><br></html><br>

    在该 HTML 页面的头部需要添加清单 8所示的java script代码。这段代码首先实例化一个新的历史堆栈对象,并载入可能已经保存到浏览器 cookie 中的所有数据。

    我们定义了四个do_*()函数,这些事件处理程序将添加到后退、前进和刷新按钮的链接中,此外还有Add Random Resource链接,如清单 7 所示。

    display() 函数检查历史记录对象的当前状态,并为历史记录按钮生成HTML。它还生成历史记录中存储的项目列表。

    清单 8  集成历史记录类和测试页面的java script

view source
print?
01<script type="text/java script" src="history.js"></script>
02<script type="text/java script"
03 
04var myHistory = new HistoryStack();
05myHistory.load();
06 
07function do_add()
08{
09 var num = Math.round(Math.random() * 1000);
10 myHistory.addResource(num);
11 display();
12 return false;
13}
14 
15function do_back()
16{
17 myHistory.go(-1);
18 display();
19}
20 
21function do_forward()
22{
23 myHistory.go(1);
24 display();
25}
26 
27function do_reload()
28{
29 myHistory.go(0);
30}
31 
32function display()
33{
34 // Display history buttons
35 var str = '';
36 if (myHistory.hASPrev()) {
37str += '<a href="#" onclick="do_back(); return false;">'
38+ '<img src="icons/back_on.gif" ';
39 } else {
40str += '<img src="icons/back_off.gif" /> ';
41 }
42 if (myHistory.hasNext()) {
43str += '<a href="#" onclick="do_forward(); return false;">'
44+ '<img src="icons/forward_on.gif" />'
45+ '</a> ';
46 } else {
47str += '<img src="icons/forward_off.gif" /> ';
48 }
49 str += '<a href="#" onclick="do_reload(); return false;">'
50+ '<img src="icons/reload.gif" = str;
51 
52 // Display the current history stack, highlighting the current
53 // position.
54 var str = '<div>History:</div>';
55 for (i=0; i < myHistory.stack.length; i++) {
56if (i == myHistory.current) {
57 str += '<div><b>' + myHistory.stack[i] +
58'</b></div>';
59} else {
60 str += '<div>' + myHistory.stack[i] + '</div>';
61}
62 }
63 document.getElementById("output").innerHTML = str;
64}
65 
66window.onload = function () {
67 display();
68};
69</script>

    运行该测试页面,可以看到历史记录按钮反映了历史堆栈的状态。比如,第一次加载页面时,按钮都是灰色的。向堆栈中添加一些记录后,后退按钮就变成活动的了。如果在堆栈中回退,前进按钮就变亮了。还要注意的是,如果单击几次后退然后再单击Add,那么堆栈会被截掉一部分,新的事件被压入缩短的堆栈顶部。

    测试完该类后,就可以进入最激动人心的阶段了。

集成历史记录对象和相册

    我们将从第 1 部分留下的问题开始,直接从相册页面调用历史堆栈。不需要修改任何 PHP 文件。

    首先需要添加一个 div 标记来存放历史记录按钮。如清单 7 所示。

view source
print?
1<div id="historybuttons"></div>

    历史堆栈代码被保存到一个 .js 文件中,该文件将链接到相册页面。

view source
print?
1<script type="text/java script" src="history.js"></script>

    需要实例化历史堆栈对象并从缓冲区加载它们。这些操作可以添加到相册页面上已有脚本的前面。

view source
print?
1var myHistory = new HistoryStack();
2myHistory.load();

    在针对历史堆栈的测试应用程序中,只存储随机数作为事件。我们可以在历史记录中存储需要的任何信息,但是要记住,当用户单击应用程序的后退按钮时,还要确定历史堆栈中的内容是什么。应用程序只有两个动作与x_get_table()和x_get_image()函数有关。因此对于每个表链接,可以存储名称table再加上start和step值作为事件标识符,比如table-10-5。类似地,可以存储名称image和将被查看图像的index,如image-20。

    在第1部分中,相册中的每个链接都是由get_table_link()和get_image_link()两个函数生成的。通过编辑这些函数,可以在调用Sajax函数之前让该函数先调用历史堆栈。清单 9 以粗体显示了这些变化。

    清单 9  get_table_link() 和 get_image_link() 函数的更新版本

view source
print?
01function get_table_link ( $title, $start, $step ) {
02 $link = "myHistory.addResource('table-$start-$step'); "
03 ."x_get_table($start, $step, to_window); "
04 ."return false;";
05 return '<a href="#" onclick="' . $link . '">' . $title.'</a>';
06}
07 
08function get_image_link ( $title, $index ) {
09 $link = "myHistory.addResource('image-$index'); "
10 ."x_get_image($index, to_window); "
11 ."return false;";
12 return '<a href="#" onclick="' . $link . '">' . $title .'</a>';
13}

    当应用程序进行Sajax调用时,to_window()作为回调函数在页面上重新生成HTML。在测试应用程序中,我们用函数display()(清单 8)完成了两项任务:更新页面输出和更新历史记录按钮的状态。现在将在已有的to_window()函数体中添加下列函数调用:

view source
print?
1display_history_buttons();

    该函数的定义如清单 10 所示。

    清单 10  display_history_buttons() 函数

view source
print?
1<br>function display_history_buttons()<br>{<br> var str = '';<br> if (myHistory.hASPrev()) {<br>str += '<a href="#" onclick="do_back(); return false;"><br><img src="icons/back_on.gif" /></a>';<br> } else {<br>str += '<img src="icons/back_off.gif" />';<br> }<br> if (myHistory.hasNext()) {<br>str += '<a href="#" onclick="do_forward(); return false;"><br><img src="icons/forward_on.gif" /></a>';<br> } else {<br>str += '<img src="icons/forward_off.gif" />';<br> }<br> str += '<a href="#" onclick="do_reload(); return false;"><br><img src="icons/reload.gif" /></a>';<br> document.getElementById("historybuttons").innerHTML = str;<br>} <br>

    在开始跟踪相册应用程序的历史记录之前,只需要在页面加载过程中调用x_get_table()函数即可。这样就可以调用通过Sajax显示的初始表。

    现在已经有了历史堆栈,但是我们不希望每次打开该应用程序时都要从头开始。相反,我们希望从离开的地方开始。因此需要添加load_current()函数以扩展应用程序,加载页面时会调用该函数。添加后退和前进按钮处理程序时,还将调用该函数,根据保存到历史堆栈中的事件ID来更新页面。

    清单 11  load_current() 函数

view source
print?
01function load_current()
02{
03 // No existing history.
04 if (myHistory.stack.length == 0) {
05x_get_table(to_window);
06myHistory.addResource('table-0-5');
07 
08// Load from history.
09 } else {
10var current = myHistory.getCurrent();
11var params = current.split('-');
12if (params[0] == 'table') {
13 x_get_table(params[1], params[2], to_window);
14} else if (params[0] == 'image') {
15 x_get_image(params[1], to_window);
16}
17 }
18}

    onload处理程序需要进行相应的修改:

view source
print?
1window.onload = function () {
2 load_current();
3};

    最后,添加清单 12中的历史记录按钮处理例程。注意处理程序和测试应用程序的相似性。

    清单 12  历史记录按钮事件处理程序

view source
print?
01function do_back(){
02 myHistory.go(-1);
03 load_current();
04}
05 
06function do_forward(){
07 myHistory.go(1);
08 load_current();
09}
10 
11function do_reload(){
12 myHistory.go(0);
13}

    至此就完成了历史堆栈到相册应用程序的集成。

    打开应用程序并单击链接,就会看到存储在浏览器cookie中的历史堆栈和指针。

view source
print?
1CHCurrent = 4
2CHStack = table-0-5%2Cimage-1%2Cimage-2%2Cimage-3%2Ctable-3-5

    如果正在运行Mozilla Firefox并下载了Web Developer Toolbar扩展,那么这些操作就很容易实现。

结束语

    我们介绍了如何创建一个自定义的历史堆栈来跟踪Ajax应用程序中的事件。可以在应用程序中添加Web浏览器上常见的后退、前进和刷新按钮来导航自定义的历史堆栈。

    为解决这一难题,我们确定了问题所在,创建了能应用于其他应用程序的可重用解决方案。我们没有直接在相册应用程序中建立历史堆栈,而是用一个简单的页面测试这个类。这样做有助于建立不会严格绑定到某个应用程序的解决方案,该解决方案可用于其他Ajax应用程序来解决同样的问题。

原创粉丝点击