HistoryData非常有用,它保存比简单的URL更为复杂的AJAX地址变化状态。这是一个可选值,可以是任何JavaScript类型,例如Number、String或Object。使用该保存功能的一个例子是在一个富文本编辑器中保存所有文本(比如在用户离开当前页面时)。当用户再回到这个地址时,浏览器将会将该对象返回给历史记录变化监听器。
开发人员可以为historyData提供带有嵌套对象和表示复杂状态的数组的完整JavaScript对象;JSON (JavaScript Object Notation)所支持的在历史记录数据中都支持,包括简单数据类型和null类型。然而,DOM对象以及可用脚本编写的浏览器对象(如XMLHttpRequest)不会被保存。请注意,historyData并不随书签一起保存,当浏览器关闭,浏览器缓存被清空,或者用户清除历史记录的时候,它就会消失。
使用dhtmlHistory的最后一步是isFirstLoad()方法。在某些浏览器中,如果导航到一个Web页面,再跳转到另一个不同的页面,然后按“后退”按钮返回到起始的站点,第一页将完全重新加载,并触发onload事件。这样会对想要在第一次加载页面时用某种方式对其进行初始化(而其后则不使用这种方式重新加载该页面)的代码造成破坏。isFirstLoad()方法可以区分是第一次加载一个Web页面还是用户导航到保存在历史记录中的Web页面时触发的“假加载”事件。
在示例代码中,我们只想在第一次加载页面的时候添加历史记录事件;如果用户在加载页面后按后退按钮返回该页面,我们就不想重新添加任何历史记录事件:
window.onload = initialize; function initialize() { // initialize the DHTML History // Framework dhtmlHistory.initialize(); // subscribe to DHTML history change // events dhtmlHistory.addListener(historyChange); // if this is the first time we have // loaded the page... if (dhtmlHistory.isFirstLoad()) { debug("Adding values to browser "+ "history", false); // start adding history dhtmlHistory.add("helloworld", "Hello World Data"); dhtmlHistory.add("foobar", 33); dhtmlHistory.add("boobah", true); var complexObject = new Object(); complexObject.value1 = "This is the first value"; complexObject.value2 = "This is the second data"; complexObject.value3 = new Array(); complexObject.value3[0] = "array 1"; complexObject.value3[1] = "array 2"; dhtmlHistory.add("complexObject", complexObject); |
让我们继续使用historyStorage类。类似于dhtmlHistory,historyStorage通过一个叫historyStorage的全局对象来公开它的功能。该对象有几个模拟散列的方法,比如put(keyName、keyValue)、get(keyName)和hasKey(keyName)。键名称必须是字符串,同时键值可以是复杂的JavaScript对象甚至是XML格式的字符串。在我们的源代码例子中,在第一次加载页面时,我们使用put()将简单的XML放入historyStorage:
window.onload = initialize; function initialize() { // initialize the DHTML History // framework dhtmlHistory.initialize(); // subscribe to DHTML history change // events dhtmlHistory.addListener(historyChange); // if this is the first time we have // loaded the page... if (dhtmlHistory.isFirstLoad()) { debug("Adding values to browser " + "history", false); // start adding history dhtmlHistory.add("helloworld", "Hello World Data"); dhtmlHistory.add("foobar", 33); dhtmlHistory.add("boobah", true); var complexObject = new Object(); complexObject.value1 = "This is the first value"; complexObject.value2 = "This is the second data"; complexObject.value3 = new Array(); complexObject.value3[0] = "array 1"; complexObject.value3[1] = "array 2"; dhtmlHistory.add("complexObject", complexObject); // cache some values in the history // storage debug("Storing key 'fakeXML' into " + "history storage", false); var fakeXML = '<?xml version="1.0" ' + 'encoding="ISO-8859-1"?>' + '<foobar>' + '<foo-entry/>' + '</foobar>'; historyStorage.put("fakeXML", fakeXML); } |
然后,如果用户离开页面后又通过后退按钮返回该页面,我们可以使用get()方法提取保存的值,或者使用hasKey()方法检查该值是否存在。
window.onload = initialize; function initialize() { // initialize the DHTML History // framework dhtmlHistory.initialize(); // subscribe to DHTML history change // events dhtmlHistory.addListener(historyChange); // if this is the first time we have // loaded the page... if (dhtmlHistory.isFirstLoad()) { debug("Adding values to browser " + "history", false); // start adding history dhtmlHistory.add("helloworld", "Hello World Data"); dhtmlHistory.add("foobar", 33); dhtmlHistory.add("boobah", true); var complexObject = new Object(); complexObject.value1 = "This is the first value"; complexObject.value2 = "This is the second data"; complexObject.value3 = new Array(); complexObject.value3[0] = "array 1"; complexObject.value3[1] = "array 2"; dhtmlHistory.add("complexObject", complexObject); // cache some values in the history // storage debug("Storing key 'fakeXML' into " + "history storage", false); var fakeXML = '<?xml version="1.0" ' + 'encoding="ISO-8859-1"?>' + '<foobar>' + '<foo-entry/>' + '</foobar>'; historyStorage.put("fakeXML", fakeXML); } // retrieve our values from the history // storage var savedXML = historyStorage.get("fakeXML"); savedXML = prettyPrintXml(savedXML); var hasKey = historyStorage.hasKey("fakeXML"); var message = "historyStorage.hasKey('fakeXML')=" + hasKey + "<br>" + "historyStorage.get('fakeXML')=<br>" + savedXML; debug(message, false); } |
prettyPrintXml()是一个定义在完整示例源代码中的实用方法;此函数准备在web页面中显示以便用于调试的简单XML。
请注意,相关数据只在该页面的历史记录中进行持久化;如果浏览器被关闭,或者用户打开一个新窗口并再次键入AJAX应用程序的地址,则该历史记录数据对于新的Web页面不可用。历史记录数据只有在用到后退或前进按钮时才被持久化,当用户关闭浏览器或清空缓存的时候就会消失。如果想真正长期持久化,请参阅Ajax MAssive Storage System (AMASS)。
示例2:O'Reilly Mail
我们的第二个例子是一个简单的AJAX电子邮件模拟应用程序的示例,即O'Reilly Mail,它类似于Gmail。O'Reilly Mail描述了如何使用dhtmlHistory类来控制浏览器的历史记录,以及如何使用historyStorage对象来缓存历史记录数据。
O'Reilly Mail用户界面由两部分组成。在页面的左边是一个带有不同电子邮件文件夹和选项的菜单,例如收件箱、草稿箱等。当用户选择了一个菜单项(如收件箱),就用这个菜单项的内容更新右边的页面。在一个现实应用程序中,我们会远程获取并显示选择的信箱内容,不过在O'Reilly Mail中,我们只显示已选择的选项。
O'Reilly Mail使用RSH框架向浏览器历史记录中添加菜单变化并更新地址栏,允许用户利用浏览器的后退和前进按钮为应用程序做收藏书签和跳到上次变化的菜单。
我们添加一个特殊的菜单项——地址簿,以说明如何来使用historyStorage。地址簿是一个由联系人名称和邮件地址组成的JavaScript数组,在一个现实应用程序中,我们会从一台远程服务器取得这个数组。不过,在O'Reilly Mail中,我们在本地创建这个数组,添加几个名称和电子邮件地址,然后将其保存在historyStorage对象中。如果用户离开Web页面后又返回该页面,那么O'Reilly Mail应用程序将重新从缓存检索地址簿,而不是再次联系远程服务器。
我们用initialize()方法保存和检索地址簿:
/** Our function that initializes when the page is finished loading. */ function initialize() { // initialize the DHTML History Framework dhtmlHistory.initialize(); // add ourselves as a DHTML History listener dhtmlHistory.addListener(handleHistoryChange); // if we haven't retrieved the address book // yet, grab it and then cache it into our // history storage if (window.addressBook == undefined) { // Store the address book as a global // object. // In a real application we would remotely // fetch this from a server in the // background. window.addressBook = ["Brad Neuberg 'bkn3@columbia.edu'", "John Doe 'johndoe@example.com'", "Deanna Neuberg 'mom@mom.com'"]; // cache the address book so it exists // even if the user leaves the page and // then returns with the back button historyStorage.put("addressBook", addressBook); } else { // fetch the cached address book from // the history storage window.addressBook = historyStorage.get("addressBook"); } |
处理历史记录变化的代码也很简单。在下面的源代码中,无论用户按后退还是前进按钮,都将调用handleHistoryChange。使用O'Reilly Mail定义的displayLocation实用方法,我们可得到newLocation并使用它将我们的用户界面更新到正确的状态。
/** Handles history change events. */ function handleHistoryChange(newLocation, historyData) { // if there is no location then display // the default, which is the inbox if (newLocation == "") { newLocation = "section:inbox"; } // extract the section to display from // the location change; newLocation will // begin with the word "section:" newLocation = newLocation.replace(/section/:/, ""); // update the browser to respond to this // DHTML history change displayLocation(newLocation, historyData); } /** Displays the given location in the right-hand side content area. */ function displayLocation(newLocation,sectionData) { // get the menu element that was selected var selectedElement = document.getElementById(newLocation); // clear out the old selected menu item var menu = document.getElementById("menu"); for (var i = 0; i < menu.childNodes.length; i++) { var currentElement = menu.childNodes[i]; // see if this is a DOM Element node if (currentElement.nodeType == 1) { // clear any class name currentElement.className = ""; } } // cause the new selected menu item to // appear differently in the UI selectedElement.className = "selected"; // display the new section in the right-hand // side of the screen; determine what // our sectionData is // display the address book differently by // using our local address data we cached // earlier if (newLocation == "addressbook") { // format and display the address book sectionData = "<p>Your addressbook:</p>"; sectionData += "<ul>"; // fetch the address book from the cache // if we don't have it yet if (window.addressBook == undefined) { window.addressBook = historyStorage.get("addressBook"); } // format the address book for display for (var i = 0; i < window.addressBook.length; i++) { sectionData += "<li>" + window.addressBook[i] + "</li>"; } sectionData += "</ul>"; } // If there is no sectionData, then // remotely retrieve it; in this example // we use fake data for everything but the // address book if (sectionData == null) { // in a real application we would remotely // fetch this section's content sectionData = "<p>This is section: " + selectedElement.innerHTML + "</p>"; } // update the content's title and main text var contentTitle = document.getElementById("content-title"); var contentValue = document.getElementById("content-value"); contentTitle.innerHTML = selectedElement.innerHTML; contentValue.innerHTML = sectionData; } |
您可以下载O'Reilly Mail源代码。
结束语
现在我们已经了解了如何使用RSH API来使AJAX应用程序支持书签以及后退和前进按钮,并提供了示例代码,您可参考该示例来创建自己的应用程序。希望您能利用书签和历史记录的支持来增强AJAX应用程序。