对于完全基于 Ajax 的网站 —— RIA 的另一种形式,情况也是如此。允许用户与一个页面进行多次交互的网站很容易受到后退按钮的困扰,或者受到任何历史记录按钮的困扰(就此而言)。前进和重载按钮的问题与后退按钮的问题一样。
Web 浏览器内置的内部历史记录机制是一个不可逃避的问题。出于安全的原因,开发人员不能篡改浏览器历史记录或者任何相关按钮。还有可用性的问题。设想一下,如果后退按钮突然弹出一个神秘的警告提示或者用户被打发到一个新的网站上去,用户该是多么困惑。
构建历史堆栈
虽然不能改变浏览器历史记录,但是可以自己构建一个在 RIA 中使用的历史记录。显然,它在某种程度上应该与浏览器的标准导航工具分开,但正如前面所说的,富应用程序在一定程度上背离了 Web 的页面到页面的标准模式。
我们将建立一个堆栈来管理应用程序的历史事件记录,也就是说存储一个列表,在表的最后添加元素。堆栈用于按照后进先出(LIFO)的顺序存储数据。虽然回退的时候并没有删除堆栈顶部的数据,但这个模型跟我们的需要非常接近。在 JavaScript 中,堆栈可以用数组来管理。
与堆栈在一起的还有一个指针,指示我们在堆栈中的当前位置。当我们在应用程序中单击的时候,新的事件将被压入堆栈顶部,指针指向最后添加的元素。单击应用程序的后退和前进按钮时,不会在堆栈中添加新的事件,而是移动堆栈的指针。
想一想使用后退按钮时历史堆栈中会发生什么:浏览器返回上一次查看的页面,原来不能用的前进按钮突然之间变得可用了。浏览新的页面时,前进按钮再次变成灰色。浏览器历史记录中较晚保存的元素将被弹出堆栈,新的事件被压入堆栈顶部。我们将在自己创建的历史堆栈中再现这种行为。
我们的目标是创建一组可用的历史记录按钮:后退、前进和刷新,如图 1 所示。
图 1. 后退、前进和刷新的历史记录按钮显示在左侧,不可用状态显示在右侧
可重用的设计
JavaScript 使用非常宽松的方法创建对象和类,但仍然能够建立可重用的代码。首先列出历史堆栈需要的功能,然后用 JavaScript 建立堆栈模型。在把历史堆栈集成到相册应用程序之前,首先要建立一个简单的页面来测试其功能。这样做有两方面的好处:测试页有助于将精力集中到开发和测试类的核心功能上,建立单独的测试页可以避免混淆历史堆栈和相册的功能,从而确保可重用性。
用 cookie 缓冲
我们需要应用程序的历史记录在整个浏览器会话中都存在。只要用户仍在查看相册页面,历史堆栈对象就会一直存在。每当发生更改的时候,这个类就会将整个历史记录复制到浏览器 cookie 中。如果用户在同一个浏览器会话中离开该页之后又返回,那么将返回他离开该应用程序时所在的同一个位置。
编写类
我们来看看历史堆栈中需要存储的数据或属性。前面已经讨论了堆栈(数组)和指针。stack_limit 属性可以防止因为数据过多而造成的 cookie 溢出(参见清单 1)。在实践中,我们希望在删除最老的记录之前能够存储 40-50 个事件。出于测试的目的,我们将该值设置为 15。
清单 1. 历史堆栈的构造,包括类的属性
function HistoryStack () { this.stack = new Array(); this.current = -1; this.stack_limit = 15; } |
除了这三个属性外,该类还需要一些方法来添加元素、检索堆栈数据以及将堆栈数据保存到浏览器 cookie 中。首先看一看 addResource() 方法,它用于将记录压入历史堆栈的堆栈顶部(参见清单 2)。注意,如果堆栈的长度超过了 stack_limit,那么最老的记录将从堆栈中移走。
清单 2. addResource() 方法,向历史堆栈的堆栈顶部添加记录
HistoryStack.prototype.addResource = function(resource) { if (this.stack.length > 0) { this.stack = this.stack.slice(0, this.current + 1); } this.stack.push(resource); while (this.stack.length > this.stack_limit) { this.stack.shift(); } this.current = this.stack.length - 1; this.save(); }; |
给历史堆栈添加的以下三个方法用于从该类中获取信息(参见清单 3)。getCurrent() 返回堆栈指针指向的当前记录,这在堆栈中导航的时候非常有用。hasPrev() 和 hasNext() 方法返回 Boolean 值,告诉我们当前记录之前或之后是否还有记录,或者指示我们到达了堆栈顶部或堆栈尾部。这些方法很简单,但是确定后退和前进按钮的状态时很有用。
清单 3. 历史堆栈定义的方法
HistoryStack.prototype.addResource = function(resource) HistoryStack.prototype.getCurrent = function () { return this.stack[this.current]; };
HistoryStack.prototype.hasPrev = function() { return (this.current > 0); };
HistoryStack.prototype.hasNext = function() { return (this.current < this.stack.length - 1 && this.current > -1); };
|
回首页
现在就可以向历史堆栈中添加记录并确定所在的位置了。但还是无法在堆栈中导航。清单 4 中定义的 go() 方法允许我们在堆栈中来回移动。通过传递正或负的增量就可以在堆栈中向前或向后移动。这与 JavaScript 内置的 location.go() 方法类似。既然模仿内置功能,为何不根据这些已有的方法建立模型呢?
此外,我们还可用该方法实现刷新功能。可以通过传递正或负的参数在堆栈中导航。传递零时则会刷新当前页面。
清单 4. 历史堆栈的 go() 方法
HistoryStack.prototype.go = function(increment) { // Go back... if (increment < 0) { this.current = Math.max(0, this.current + increment);
// Go forward... } else if (increment > 0) { this.current = Math.min(this.stack.length - 1, this.current + increment);
// Reload... } else { location.reload(); }
this.save(); };
|
到目前为止,只要 HistoryStack 对象存在于当前文档中,这个新建的类就能正常工作。我们已经讨论了刷新页面会造成数据丢失的问题,现在来解决它。清单 5 中添加了在浏览器 cookie 中设置和访问数据的方法。所要做的只是设置每个 cookie 的名称值对。因为只需要在浏览器会话中保存 cookie,而不需要设置有效期。为了简化示例,我们不考虑其他参数,如 secure、domain 和 path。
注意:如果该类需要对 cookie 做复杂处理,更明智的办法是使用完全独立的 cookie 管理类。建立和读取 cookie 有点偏离历史堆栈的正题。如果 JavaScript 允许指定方法和属性访问的作用域,也可以将这些方法设成私有的。
清单 5. 建立和访问浏览器 cookie 的方法
HistoryStack.prototype.setCookie = function(name, value) { var cookie_str = name + "=" + escape(value); document.cookie = cookie_str; };
HistoryStack.prototype.getCookie = function(name) { if (!name) return '';
var raw_cookies, tmp, i; var cookies = new Array();
raw_cookies = document.cookie.split('; '); for (i=0; i < raw_cookies.length; i++) { tmp = raw_cookies[i].split('='); cookies[tmp[0]] = unescape(tmp[1]); }
if (cookies[name] != null) { return cookies[name]; } else { return ''; } };
|
定义了管理任何 cookie 的方法之后,可以编写另外两个类专门处理历史堆栈的类。save() 方法将堆栈转化成字符串并保存到 cookie 中,load() 重新将字符串解析成用于管理历史堆栈的数组(参见清单 6)。
清单 6. save() 和 load() 方法
HistoryStack.prototype.save = function() { this.setCookie('CHStack', this.stack.toString()); this.setCookie('CHCurrent', this.current); };
HistoryStack.prototype.load = function() { var tmp_stack = this.getCookie('CHStack'); if (tmp_stack != '') { this.stack = tmp_stack.split(','); }
var tmp_current = parseInt(this.getCookie('CHCurrent')); if (tmp_current >= -1) { this.current = tmp_current; } };
|
回首页
测试类
可以用简单的 HTML 页面和一些 JavaScript 来测试完成的类。测试页面将在上方显示历史记录按钮,只有活动的按钮是突出显示并且可以单击的。我们没有建立复杂的测试应用程序,该页面在每次单击链接时仅仅生成随机数。这些数字就是记录到历史堆栈中的事件。堆栈也在页面上显示,指针标记的当前记录用粗体显示。
清单 7. 测试历史堆栈的简单 HTML 页面
<html> <head> <title></title> </head>
<body>
<div id="historybuttons"></div> <div> <a href="#" onclick="do_add(); return false;">Add Random Resource</a> </div> <div id="output" style="margin-top:40px;"></div>
</body> </html>
|
在该 HTML 页面的头部需要添加清单 8 所示的 JavaScript 代码。这段代码首先实例化一个新的历史堆栈对象,并载入可能已经保存到浏览器 cookie 中的所有数据。
我们定义了四个 do_*() 函数,这些事件处理程序将添加到后退、前进和刷新按钮的链接中,此外还有 Add Random Resource 链接,如清单 7 所示。
display() 函数检查历史记录对象的当前状态,并为历史记录按钮生成 HTML。它还生成历史记录中存储的项目列表。
清单 8. 集成历史记录类和测试页面的 JavaScript
<script type="text/javascript" src="history.js"></script> <script type="text/javascript">
var myHistory = new HistoryStack(); myHistory.load();
function do_add() { var num = Math.round(Math.random() * 1000); myHistory.addResource(num); display(); return false; }
function do_back() { myHistory.go(-1); display(); }
function do_forward() { myHistory.go(1); display(); }
function do_reload() { myHistory.go(0); }
function display() { // Display history buttons var str = ''; if (myHistory.hasPrev()) { str += '<a href="#" onclick="do_back(); return false;">' + '<img src="icons/back_on.gif" alt="Back" /></a> '; } else { str += '<img src="icons/back_off.gif" alt="" /> '; } if (myHistory.hasNext()) { str += '<a href="#" onclick="do_forward(); return false;">' + '<img src="icons/forward_on.gif" alt="Forward" />' + '</a> '; } else { str += '<img src="icons/forward_off.gif" alt="" /> '; } str += '<a href="#" onclick="do_reload(); return false;">' + '<img src="icons/reload.gif" alt="Reload" /></a>'; document.getElementById("historybuttons").innerHTML = str;
// Display the current history stack, highlighting the current // position. var str = '<div>History:</div>'; for (i=0; i < myHistory.stack.length; i++) { if (i == myHistory.current) { str += '<div><b>' + myHistory.stack[i] + '</b></div>'; } else { str += '<div>' + myHistory.stack[i] + '</div>'; } } document.getElementById("output").innerHTML = str; }
window.onload = function () { display(); }; </script>
|
运行该测试页面,可以看到历史记录按钮反映了历史堆栈的状态(见图 2)。比如,第一次加载页面时,按钮都是灰色的。向堆栈中添加一些记录后,后退按钮就变成活动的了。如果在堆栈中回退,前进按钮就变亮了。还要注意的是,如果单击几次后退然后再单击 Add,那么堆栈会被截掉一部分,新的事件 被压入缩短的堆栈顶部。
图 2. 历史堆栈的测试页面
测试完该类后,就可以进入最激动人心的阶段了。
回首页
集成历史记录对象和相册
我们将从第 1 部分留下的问题开始,直接从相册页面调用历史堆栈。不需要修改任何 PHP 文件。
首先需要添加一个 div 标记来存放历史记录按钮。如清单 7 所示。
<div id="historybuttons"></div> |
历史堆栈代码被保存到一个 .js 文件中,该文件将链接到相册页面。
<script type="text/javascript" src="history.js"></script> |
需要实例化历史堆栈对象并从缓冲区加载它们。这些操作可以添加到相册页面上已有脚本的前面。
var myHistory = new HistoryStack(); myHistory.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() 函数的更新版本
function get_table_link ( $title, $start, $step ) { $link = "myHistory.addResource('table-$start-$step'); " ."x_get_table($start, $step, to_window); " ."return false;"; return '<a href="#" onclick="' . $link . '">' . $title .'</a>'; } function get_image_link ( $title, $index ) { $link = "myHistory.addResource('image-$index'); " ."x_get_image($index, to_window); " ."return false;"; return '<a href="#" onclick="' . $link . '">' . $title . '</a>'; } |
当应用程序进行 Sajax 调用时,to_window() 作为回调函数在页面上重新生成 HTML。在测试应用程序中,我们用函数 display()(清单 8)完成了两项任务:更新页面输出和更新历史记录按钮的状态。现在将在已有的 to_window() 函数体中添加下列函数调用:
display_history_buttons(); |
该函数的定义如清单 10 所示。
清单 10. display_history_buttons() 函数
function display_history_buttons() { var str = ''; if (myHistory.hasPrev()) { str += '<a href="#" onclick="do_back(); return false;"> <img src="icons/back_on.gif" alt="Back" /></a>'; } else { str += '<img src="icons/back_off.gif" alt="" />'; } if (myHistory.hasNext()) { str += '<a href="#" onclick="do_forward(); return false;"> <img src="icons/forward_on.gif" alt="Forward" /></a>'; } else { str += '<img src="icons/forward_off.gif" alt="" />'; } str += '<a href="#" onclick="do_reload(); return false;"> <img src="icons/reload.gif" alt="Reload" /></a>'; document.getElementById("historybuttons").innerHTML = str; }
|
在开始跟踪相册应用程序的历史记录之前,只需要在页面加载过程中调用 x_get_table() 函数即可。这样就可以调用通过 Sajax 显示的初始表。
现在已经有了历史堆栈,但是我们不希望每次打开该应用程序时都要从头开始。相反,我们希望从离开的地方开始。因此需要添加 load_current() 函数以扩展应用程序,加载页面时会调用该函数。添加后退和前进按钮处理程序时,还将调用该函数,根据保存到历史堆栈中的事件 ID 来更新页面。
清单 11. load_current() 函数
function load_current() { // No existing history. if (myHistory.stack.length == 0) { x_get_table(to_window); myHistory.addResource('table-0-5');
// Load from history. } else { var current = myHistory.getCurrent(); var params = current.split('-'); if (params[0] == 'table') { x_get_table(params[1], params[2], to_window); } else if (params[0] == 'image') { x_get_image(params[1], to_window); } } } |
onload 处理程序需要进行相应的修改:
window.onload = function () { load_current(); };
|
最后,添加清单 12 中的历史记录按钮处理例程。注意处理程序和测试应用程序的相似性。
清单 12. 历史记录按钮事件处理程序
function do_back() { myHistory.go(-1); load_current(); }
function do_forward() { myHistory.go(1); load_current(); }
function do_reload() { myHistory.go(0); }
|
至此就完成了历史堆栈到相册应用程序的集成。完成后的产品如图 3 所示。
图 3. 与相册应用程序结合的历史记录按钮
打开应用程序并单击链接,就会看到存储在浏览器 cookie 中的历史堆栈和指针。
CHCurrent = 4 CHStack = table-0-5%2Cimage-1%2Cimage-2%2Cimage-3%2Ctable-3-5 |
如果正在运行 Mozilla Firefox 并下载了 Web Developer Toolbar 扩展,那么这些操作就很容易实现。
分享到:
相关推荐
为了解决这个问题,开发人员引入了“保存浏览器历史记录”的概念,如“RSH(Replace State History)”技术,来模拟浏览器的历史状态,确保AJAX操作也能被正确地记录在历史记录中。 **AJAX与浏览器历史记录** 在...
5. 处理Ajax错误和状态 6. 跨域请求与CORS 7. 响应式Ajax设计 8. 高级话题,如Promise和Fetch API 9. 实战项目:构建一个Ajax驱动的Web应用 通过这个视频教程,学习者不仅可以理解Ajax的工作原理,还能掌握实际应用...
3. **页面历史管理**:传统的前进/后退按钮可能无法正确处理Ajax导致的页面变化。 4. **用户感知**:由于页面没有刷新,用户可能不清楚后台在做什么。 **Ajax的应用场景** 1. **表单验证**:在提交前验证用户输入...
5. **历史记录存储**:聊天记录可以被保存在服务器上,用户可以在需要时查看过去的对话。 6. **状态管理**:用户可以设置自己的在线状态,如在线、离线、忙碌等。 **学习价值:** 1. **PHP编程**:通过研究Ajax ...
当用户拖放节点时,前端会发送一个AJAX请求,携带新的工作流布局信息,服务器端接收到请求后,会更新工作流模型,保存新的状态配置。 此外,为了实现工作流的逻辑控制,比如根据当前节点判断下一步的走向,可能需要...
4. **历史记录管理**:Ajax加载的内容无法自动添加到浏览器的历史记录,不利于用户回溯。 **五、Ajax进阶技术** 1. **jQuery和库的使用**:jQuery简化了Ajax的使用,提供了`$.ajax()`、`$.get()`、`$.post()`等...
- 数据存储:聊天记录通常需要保存,以便用户刷新页面或新用户加入时仍能看到历史对话。这可能涉及到数据库操作,如SQL Server或NoSQL数据库。 2. "www.pudn.com.txt" - 这个文件名看起来像是来源于一个网站URL,...
在探讨 AJAX 如何实现文件上传之前,我们首先简要回顾一下 AJAX 的历史背景及其带来的变革。AJAX(Asynchronous JavaScript and XML)是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。这项技术的...
它提供了pushState和replaceState两个方法,可以将当前页面的状态保存到浏览器的历史记录中。 * pushState方法:将当前页面的状态推送到浏览器的历史记录中。 * replaceState方法:将当前页面的状态替换到浏览器的...
1. **前进后退功能失效**:由于AJAX请求不会导致整个页面的重载,因此浏览器不会将新的状态添加到历史记录中,这使得用户无法通过前进和后退按钮回到之前的状态。 2. **移动设备兼容性**:许多小型移动设备(如智能...
- **GET**: 由于数据出现在URL中,容易被截获或保存在浏览器历史记录中,不适合传输敏感信息。 - **POST**: 数据不会出现在URL中,相对安全。 4. **缓存机制**: - **GET**: 浏览器通常会缓存GET请求的结果,因此...
对于“保存”或“提交”按钮,AJAX可以实现无刷新提交表单,用户操作后立即给出成功或失败的提示,保持页面状态。 5. **切换视图**: 通过按钮切换不同的视图或数据集,如“日视图”、“周视图”、“月视图”按钮...
然而,这也会带来一个问题,即使用Ajax操作后,浏览器的历史记录无法正确处理后退和前进操作。本文将详细介绍如何利用JavaScript中的`history`对象API解决这个问题。 `history`对象提供了`pushState()`和`...
**1.2 浏览器历史** - **早期浏览器:** - Netscape Navigator:首个流行的图形界面浏览器。 - Internet Explorer:微软推出的浏览器,与Windows操作系统紧密集成。 - Firefox:开放源代码的浏览器,以安全性...
此模式通过保存历史状态并在用户点击回退时恢复这些状态,确保Ajax操作不影响浏览器的历史记录。 3. **Update-In-Place Pattern** 此模式允许在不重新加载页面的情况下替换或更新页面的一部分。通过使用Ajax请求,...
3. **前进/后退按钮**:Ajax操作可能会破坏浏览器的前进/后退功能,可以通过保存历史状态或使用HTML5的History API来解决。 总之,Ajax是现代Web开发中的关键技术,它极大地提升了Web应用的交互性和效率。掌握Ajax...
1. **导航后退和前进按钮**:为了保持浏览器的历史记录功能,Ajax应用可以通过存储每次Ajax请求的状态来实现后退和前进功能。当用户点击这些按钮时,应用可以根据存储的状态恢复到之前或之后的页面状态。 2. **书签...
为解决这一问题,开发者通常会保存每个操作的状态,以便在用户点击后退时恢复。 此外,AJAX依赖JavaScript,这意味着如果用户的浏览器禁用了JavaScript,或者使用不支持AJAX的浏览器,那么这些应用可能无法正常工作...