This article will showcase an open source JavaScript library that brings bookmarks and back button support to AJAX applications. After completing this tutorial, developers will be able to get solutions to an AJAX problem (not even Google Maps and Gmail now offer the solution): A powerful, available bookmark and back forward feature that behaves like any other Web application.
This article describes the serious problems that AJAX applications face in using bookmarks and back buttons, and displays the Really simple History (RSH) Library-an open source framework that addresses the above issues, and provides several examples of running.
The main inventions of the framework shown in this paper are divided into two parts. The first is a hidden HTML form that caches client information for a large number of short sessions; This caching feature provides strong support for page navigation. followed by the combination of hyperlink anchors and hidden iframe, which are embedded in the back and forward buttons to intercept and record browser history events. Both of these techniques are packaged in a simple JavaScript library to simplify development.
problem
Bookmarks and Back buttons work very well in traditional, multiple-page Web applications. When a user browses to a Web site, its browser's address bar record is updated with the new URL, which can be pasted into an e-mail or bookmark for later use. The back and Forward buttons also work properly, allowing the user to flip forward or backward through the visited pages.
But Ajax applications are not the same, they are complex programs that run on a single Web page. Browsers are not built for such programs-such web applications are obsolete, and they need to refresh the entire page every time the mouse clicks.
In this Ajax-like software, the browser's address bar stays the same when users select features and change the state of the program, making it impossible to use bookmarks in a particular application view. In addition, if the user presses the back button to "undo" the last action, they will be surprised to find that the browser will completely leave the application's Web page.
Solution
The open source Rsh framework solves these problems by providing the ability to bookmark and control the back and forward buttons for AJAX applications. RSH is currently in beta and can run on browsers such as Firefox 1.0, Netscape 7+, Internet Explorer 6+, and Safari is not currently supported.
There are several Ajax frameworks that help with bookmarks and history issues, but these frameworks now have several significant bugs that result from implementation. In addition, many Ajax history frameworks are bound to larger libraries, such as Backbase and Dojo, which introduce a completely different programming model for AJAX applications, forcing developers to use a new way to gain historical functionality.
By contrast, rsh is a simple module that can be included in an existing AJAX system. In addition, the RSH library employs techniques to avoid bugs that affect other historical frameworks.
The RSH framework consists of two JavaScript classes: Dhtmlhistory and Historystorage.
The Dhtmlhistory class provides a history abstraction for AJAX applications. Ajax pages use the Add () method to add historical events to the browser, specifying new addresses and related historical data. The Dhtmlhistory class uses an anchor hash (such as #new-location) to update the current URL of the browser, associating the history data with the new URL. The AJAX application registers itself as a history listener, and when the user browses using the back and Forward buttons, the history event is triggered, providing a new location for the browser and any historical data saved with the Add () call.
Second class: Historystorage, which allows developers to save any number of saved history data. In a normal web page, when a user navigates to a new Web site, the browser unloads and clears all applications and JavaScript state on the Web page, and if the user returns with the Back button, all the data is lost. The Historystorage class solves such problems by using an API that contains simple hash-table methods such as put (), get (), Haskey (). The above method allows the developer to save any amount of data after the user leaves the Web page, and the history data can be accessed through the Historystorage class when the user presses the Back button back. Internally, we do this by using hidden form fields because the browser automatically saves the values in the form field, even when the user leaves the Web page.
Example
Let's start with a simple example.
First, any page that needs to use the RSH framework must contain dhtmlhistory.js scripts:
!--Load the Really Simple History Framework-->
|
The DHTML History application must also contain blank.html files in the same directory as the AJAX Web page; This file is packaged with the RSH framework and is required for Internet Explorer. Incidentally, Rsh uses a hidden iframe to track and add historical changes to Internet Explorer; This iframe requires us to specify an actual file location to work properly, which is blank.html.
The RSH framework creates a global object called Dhtmlhistory, which is the entry point for manipulating browser history. The first step in using Dhtmlhistory is to initialize the Dhtmlhistory object after the Web page has finished loading:
Window.onload = Initialize;
function Initialize () { Initialize the DHTML History Framework Dhtmlhistory.initialize (); |
The developer then uses the Dhtmlhistory.addlistener () method to subscribe to the history Change event. This method has a JavaScript callback function that receives two parameters when the DHTML history Change event occurs: The new page position and any optional history data that can be associated with the event:
Window.onload = Initialize;
function Initialize () { Initialize the DHTML History Framework Dhtmlhistory.initialize ();
Subscribe to DHTML history change Events Dhtmlhistory.addlistener (Historychange); |
The Historychange () method is simple, a function that receives newlocation and any optional historydata associated with the event after the user navigates to a new location.
/** our callback to receive history change Events. */ function Historychange (NewLocation, Historydata) { Debug ("A History change has occurred:" + "newlocation=" +newlocation + ", historydata=" +historydata, true); } |
The debug () method used above is a utility function defined in the sample source file that is packaged together with the complete sample for downloading. Debug () is used to print messages to a Web page, and the second Boolean parameter (true in the preceding code) controls whether to clear all the original messages before printing a new debug message.
Developers use the Add () method to add history events. Adding history events involves specifying a new address for historical changes, such as Edit:somepage, and providing an optional Historydata value that is saved with the event.
window.onload = Initialize; Function Initialize () { //Initialize the DHTML History //Framework Dhtmlhistory.initialize (); //Subscribe to DHTML History change //Events Dhtmlhistory.addlistener (historychange); The //If this is the the "a" 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"; 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); |
After add () is invoked, the new address is immediately displayed as an anchor value (the link address) in the browser's URL address bar. For example, after calling Dhtmlhistory.add ("HelloWorld", "Hello World Data") on an AJAX Web page with address Http://codinginparadise.org/my_ajax_app, The user will see the following address in their browser URL address bar:
Http://codinginparadise.org/my_ajax_app#helloworld
The user can then bookmark the page and, if the bookmark is used later, the AJAX application can read the #helloworld value and use it to initialize the Web page. The address value after the hash is the URL address that the rsh frame can encode and decode transparently.
Historydata is useful for saving Ajax address changes that are more complex than simple URLs. This is an optional value that can be any type of JavaScript, such as number, string, or object. An example of using this Save feature is to save all text in a rich text editor (for example, when the user leaves the current page). When the user returns to this address, the browser will return the object to the history change listener.
Developers can provide Historydata with nested objects and a complete JavaScript object that represents an array of complex states, supported by JSON (JavaScript object notation) in the history data, Includes simple data types and null types. However, Dom objects and browser objects, such as XMLHttpRequest, that can be written by the script are not saved. Note that Historydata is not saved with the bookmark, it disappears when the browser is closed, the browser cache is emptied, or when the user clears the history.
The last step in using Dhtmlhistory is the Isfirstload () method. In some browsers, if you navigate to a Web page, jump to a different page, and then press the Back button to return to the starting site, the first page will completely reload and trigger the OnLoad event. This can cause damage to code that you want to initialize in some way when the page is first loaded (and then reload the page later without using this method). The Isfirstload () method distinguishes between loading a Web page for the first time or a "fake load" event that is triggered when a user navigates to a Web page that is saved in the history.
In the example code, we just want to add the history event when the page is first loaded, and if the user returns to the page by pressing the Back button when the page is loaded, we do not want to add any history events again:
window.onload = Initialize; Function Initialize () { //Initialize the DHTML History //Framework Dhtmlhistory.initialize (); //Subscribe to DHTML History change //Events Dhtmlhistory.addlistener (historychange); The //If this is the the "a" 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"; 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); |
Let's continue using the Historystorage class. Similar to the dhtmlhistory,historystorage through a global object called Historystorage to expose its functionality. The object has several methods for simulating hashing, such as put (KeyName, keyvalue), Get (KeyName), and Haskey (KeyName). The key name must be a string, and the key value can be a complex JavaScript object or even an XML-formatted string. In our source code example, when we first load the page, we use put () to place the simple XML in the 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 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"; 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 = ' + ' encoding= ' iso-8859-1 "? >" + ' ' + ' ' + ' '; Historystorage.put ("Fakexml", fakexml); } |
Then, if the user leaves the page and returns the page via the Back button, we can use the Get () method to extract the saved value or use the Haskey () method to check whether the value exists.
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 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"; 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 = ' ' + ' ' + ' ' + ' '; 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 + " " + "Historystorage.get (' fakexml ') = " + Savedxml; Debug (message, FALSE); } |
Prettyprintxml () is a practical method that is defined in the complete sample source code, which is intended to be displayed in a Web page for debugging simple XML.
Note that the related data is persisted only in the history of the page, if the browser is closed, or if the user opens a new window and then retype the address of the AJAX application, the history data is not available for the new Web page. The history data is persisted only when the back or forward button is used, and disappears when the user closes the browser or empties the cache. If you want to be truly long-term persistent, see Ajax massive Storage System (AMASS).
Our simple example has been completed.
example 2:o ' Reilly Mail
Our second example is an example of a simple AJAX email simulation application, that is, O ' Reilly Mail, which is similar to Gmail. O ' Reilly Mail describes how to use the Dhtmlhistory class to control the history of the browser and how to use the Historystorage object to cache historical data.
The O ' Reilly Mail user interface is made up of two parts. On the left side of the page is a menu with different e-mail folders and options, such as Inbox, draft box, and so on. When a user selects a menu item (such as an inbox), it updates the page on the right with the contents of this item. In a real-world application, we will get and display the selected mailbox contents remotely, but in O ' Reilly mail we only show the options we have chosen.
O ' Reilly Mail uses the RSH framework to add menu changes to the browser history and update the address bar, allowing users to bookmark the application and skip to the last changed menu using the browser's back and forward buttons.
We add a special menu item--Address book to show how to use Historystorage. The Address Book is a JavaScript array of contact names and e-mail addresses, and in a real-world application, we get the array from a remote server. However, in O ' Reilly mail, we create this array locally, add several names and e-mail addresses, and then save it in the Historystorage object. If the user leaves the Web page and returns to the page, the O ' Reilly Mail application retrieves the address book from the cache instead of contacting the remote server again.
We use the Initialize () method to save and retrieve the Address book:
/** our function This 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"); } |
The code to handle changes in history is also simple. In the following source code, Handlehistorychange is invoked regardless of whether the user presses the back or forward button. Using the Displaylocation utility defined in O ' Reilly mail, we can get newlocation and use it to update our user interface to the correct state.
/** 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 would 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 this was selected var selectedelement = document.getElementById (newlocation);
Clear out of the old selected menu item var menu = document.getElementById ("menu"); for (var i = 0; I menu.childNodes.length i++) { var currentelement = menu.childnodes[i]; The IF 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 = " Your addressbook: "; Sectiondata + = "
";
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 + = "
- "
+ Window.addressbook[i] + " ";
}
Sectiondata + = "
"; }
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 = " This are section:" + selectedelement.innerhtml + " "; }
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; } |
Conclusion
Now that we've learned how to use the Rsh API to enable AJAX applications to support bookmarks and back and forward buttons, and to provide sample code, you can refer to the example to create your own application. I hope you can use the support of bookmarks and history to enhance AJAX applications.
This article source code: Click to download