Skip to main content

Developing PHP the Ajax way, Part 2: Back, Forward, Reload

Mike Brittain (mike@mikebrittain.com), Director of Technology, ID Society
Mike Brittain
Mike Brittain is the director of technology at ID Society, an interactive marketing agency in New York City. He has been using open source technologies and languages to develop Web sites and applications for more than 10 years. He has taught scripting classes at the University of Denver, where he also earned his master's degree. He can be contacted at mike@mikebrittain.com.

Summary:  A major challenge of Asynchronous JavaScript and XML (Ajax)-driven Web sites is the lack of a Back button. We will use JavaScript to create a history stack for the Ajax photo gallery built in Part 1 of this two-part "Developing PHP the Ajax way" series. This history stack will closely mirror the history utility found in Web browsers, and it will be used to provide Back, Forward, and Reload buttons for the application.

View more content in this series

Date:  06 Jun 2006
Level:  Intermediate
Activity:  3961 views

Introduction

Part 1 shows how to develop a basic photo gallery using Sajax, PHP, and JavaScript. In building a history stack for the application, we will rely on client-side technologies and integrate directly into the code from Part 1. This article assumes an understanding of JavaScript and browser cookies.

Saving state in a browser

As you surf the Web, you click from page to page and site to site. As you do so, your Web browser dutifully collects a history of where you have been, creating a digital trail of breadcrumbs for you to follow to get back to where you started. The Back button can be considered the Undo button for the Web in that it takes you a step back to where you were before your last action.

The Web is a page-by-page medium. The backward and forward buttons on your browser's toolbar direct the browser from page to page. When Macromedia's Flash became all of the rage, developers and users started to see how Rich Internet Applications (RIAs) break this metaphor. You might click around a few sites, land on a Flash-based Web site, then click around in it for a few minutes. One click of the Back button and the ride is over. Rather than going one step backward within the Flash site, you completely lose your place.

The same thing goes for heavily Ajax-based Web sites, another form of RIA. Sites that attempt to allow for a lot of interaction on a single page are prone to trouble with the Back button -- or any of the history buttons, for that matter. Forward and reload buttons can be just as much trouble as the Back button.

The internal history mechanism built into Web browsers is unavoidable. For security reasons, developers are unable to tamper with browser history or any related buttons. There are also usability concerns. Consider the confusion for a user if the Back button suddenly triggered mysterious alert prompts or sent him to a new Web site.

Building a history stack

While we cannot interfere with the browser's history, we can build one of our own for use in our RIAs. Obviously, it will be somewhat disconnected from the standard navigation tools in our browser, but as we've pointed out, rich applications are somewhat disconnected from the standard page-to-page metaphor of the Web.

We'll build a stack to manage the history of events in our application, meaning that we'll be storing a list, to which we will tack on elements to the end. Stacks are used for storing data accessed in Last In, First Out (LIFO) order. Though we won't be pulling data off the top of the stack as we go back in the history, the model is close enough for our needs. In JavaScript, the stack can be managed within an array.

Working in conjunction with our stack, we'll have a pointer that identifies our current position in the stack. As we click around in the application, new events will be pushed onto the top of the stack, and the pointer will identify the last element added. When clicking the backward and forward buttons for the application, we won't be adding new events to the stack but we will be moving the pointer within the stack.

Consider what happens in your browser history when you use the Back button: The browser returns to the last viewed page, and the forward button, which was previously disabled, lights up suddenly. As you surf to new pages, the forward button again becomes dim. The later elements of the browser history are popped off the stack, and new events are pushed onto the end. We will replicate this behavior in the history stack we build.

Our goal is to create a set of functional history buttons: backward, forward, and reload, which are shown in Figure 1.


Figure 1. History buttons for back, forward, and reload are shown at left; disabled states are shown at right
History buttons

Designing for reusability

JavaScript uses a very loose method for creating objects and classes, although it is still possible to build reusability into our code. We'll start by outlining the functionality we need in our history stack, then model the stack in JavaScript. Before we integrate the history stack with the photo gallery application, we will build a simple page to test the functionality. This will ensure two aspects in our development: The test page will allow us to focus on developing and testing only the core functionality of the class; and building a separate page for testing will prevent us from mixing functionality of the history stack directly into the photo gallery functionality, ensuring reusability.

Caching in with cookies

We want the application's history to exist throughout the browser session. Our history stack object will only exist as long as the user is on the photo gallery page. Our class will copy the entire history stack into a browser cookie every time it changes. If a user leaves the page and later returns in the same browser session, he will be returned to the same point where he left off in the application.

Writing the class

Let's jump right in and talk about the sort of data, or properties, we need to store in our history stack. We've already discussed the stack (an array), and the pointer. The stack_limit property allows us to prevent overflowing the cookies with too much data (see Listing 1). In practice, you may want to store 40-50 events before letting go of the oldest ones. For the purpose of testing, we'll keep this value at 15.


Listing 1. The constructor for the history stack, which includes the properties of the class

function HistoryStack ()
{
    this.stack = new Array();
    this.current = -1;
    this.stack_limit = 15;
}

Along with these three properties, the class will have a number of methods for adding elements to the history stack, retrieving items, and saving the stack data to browser cookies. Let's first take a look at the addResource() method, which will be used to push items onto the end of the history stack (see Listing 2). Note that if the stack grows longer than stack_limit, the oldest entries will be shifted from the front of the stack.


Listing 2. The addResource() method allows for adding resources to the end of the history stack

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();
};

The next three methods we will add to the history stack class are used for getting information from the class (see Listing 3). The getCurrent() method returns the item to which the stack pointer currently identifies. This will be useful when we navigate the stack. The hasPrev() and hasNext() methods return Boolean values telling us whether there are elements preceding or following the current item, or whether we have reached the beginning or end of the stack. They are simple methods, but will be helpful in determining the state of the back and forward buttons.


Listing 3. Methods of history stack class defined

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);
};

Now we can add items to our history object and figure out where we are in the stack. But we don't yet have any way of navigating the stack. The go() method, defined in Listing 4, will allow us to move backward and forward in the stack. By passing positive and negative increments, we will move forward and backward in the stack. This is similar to JavaScript's built-in location.go() method. We're working on imitating built-in functionality, so why not model our methods on those that already exist?

As a bonus, we can allow the reload functionality to piggyback on this method. Passing positive and negative arguments to the method will navigate the stack. Passing zero will trigger a reload of the current page.


Listing 4. go() method for history stack

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();
};

Everything we've done with the new class up to this point should work just fine as long as any HistoryStack objects are still resident in the current document. We have already discussed the problem with losing data if the page is reloaded, so let's get to solving it. In Listing 5, we add methods for setting and accessing data in browser cookies. We are only going to deal with setting the name and value pair for each cookie. Since we don't want to store the cookies for longer than the browser session, we don't need to bother setting the expiration time. And for the sake of simplicity in this example, we won't deal with any of the other parameters, such as secure, domain, or path.

Note: If we needed to do anything more sophisticated with cookies for this class, it might be smarter to use an entirely separate cookie management class. Setting and reading cookies is somewhat tangential to the focus of building a history stack. If JavaScript allowed us to provide scope for the access of methods and properties, we would also probably make these private to the class.


Listing 5. Methods for setting and accessing values to and from browser cookies

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 '';
    }
};

Once we have defined the methods for managing arbitrary cookies, we can write two more classes that will handle the specific tasks of reading and writing the history stack. The save() method will convert the stack to a string and save it to a cookie, and load() will parse that string back into an array we use for managing the items in the history stack (see Listing 6).


Listing 6. The save() and load() methods

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;
    }
};

Testing the class

We can test the completed class with a simple HTML page and some JavaScript. Our test will display the history buttons at the top, with only the active buttons highlighted and clickable. Rather than building some sophisticated application for testing, this page will simply generate random numbers every time a link is clicked. These numbers will be the events we assign to the history stack. The stack will be displayed on the page, and the item identified by the current pointer will be highlighted in bold.


Listing 7. A simple HTML page for testing the history stack

<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>

To the head of this HTML page, we need to add the JavaScript code shown in Listing 8. The code first instantiates a new history stack object and loads any existing values that may have been saved to the browser's cookies.

Four do_*() functions are defined, which are event handlers that will be added to links on the backward, forward, and reload buttons, and also on the Add Random Resource link, shown in Listing 7.

The display() function will inspect the current state of the history object and generate HTML for the history buttons. It will also generate a listing of items stored in the history.


Listing 8. JavaScript used to integrate the history stack class with the testing page

<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>

Running this test page should show us how the history buttons react to the state of the history stack (see Figure 2). For example, when the page first loads, they are both grayed out. After some resources are added to the stack, the Back button becomes active. If we click backward through the stack, the forward button lights up. Notice that if we click back a few times, then click Add, the stack is truncated, and the new event is pushed onto the top of the shortened stack.


Figure 2. The testing page for the history stack
The testing page for the history stack

Having tested the class, we're ready for prime time.

Integrating the history object with the photo gallery

We're going to pick up directly from where Part 1 left off, adding calls to the history stack directly into the photo gallery page. We will not need to touch any of the PHP files.

First, we need to add a div tag to house the history buttons. We saw this tag in Listing 7.

<div id="historybuttons"></div>

The history stack code should be saved to a .js file, which will be linked into the photo gallery page:

<script type="text/javascript" src="history.js"></script>

The history stack object will need to be instantiated and loaded from cache. This can be added at the top of the existing script tag on the photo gallery page:

var myHistory = new HistoryStack();
myHistory.load();

In the testing application for the history stack, we only stored random numbers as events. We can store just about whatever we want in the history, but remember that we also need to figure out what is in the history when someone clicks on our application's Back button. The only two actions in the application are related to the x_get_table() and x_get_image() functions. So for each table link, we can store the name table, along with the start and step values as an event identifier -- for example, table-10-5. Similarly, for images, we can store the name image with the index of the image to be viewed -- for example, image-20.

In Part 1, you saw how each link in the photo gallery application is generated by one of the two get_table_link() and get_image_link() functions. We can edit these functions to include calls to our history stack just before making calls to the Sajax functions. Listing 9 shows these changes in bold.


Listing 9. Updated versions of get_table_link() and get_image_link() functions

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>';
}

When Sajax calls are made in the application, the to_window() function is used as a callback to regenerate HTML on the page. In our test application, we used the function display() (Listing 8), which handled two tasks: updating the output for the page and updating the state of the history buttons. We now add the following function call to the body of the existing to_window() function:

display_history_buttons();

This function is defined in Listing 10.


Listing 10. The display_history_buttons() function

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;
}

Before we began tracking history in the photo gallery application, we simply needed to call the x_get_table() function during the onload event for the page. This called the initial table to be displayed via Sajax.

Now that we have a history stack, however, we don't want to start from scratch each time this application is loaded. Instead, we want to restart from where we left off. We will extend the application by creating a load_current() function that will be called when the page is loaded. When adding the backward and forward button handlers, you will also see that this function is called to update the page from the event IDs we have been saving to the history stack.


Listing 11. The load_current() function

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);
        }
    }
}

The onload handler will be updated accordingly:

window.onload = function () {
    load_current();
};

Finally, we add the handlers for the history buttons in Listing 12. Note the similarities to the handlers in our test application.


Listing 12. Event handlers for the history button

function do_back()
{
    myHistory.go(-1);
    load_current();
}

function do_forward()
{
    myHistory.go(1);
    load_current();
}

function do_reload()
{
    myHistory.go(0);
}

And with that, we're finished with integrating the history stack into the photo gallery application. The finished product is shown in Figure 3.


Figure 3. Active history buttons integrated with photo gallery application
Active history buttons integrated with photo gallery application

If you open the application and click around, you'll see our history stack and pointer stored in the browser's cookies.

CHCurrent = 4
CHStack = table-0-5%2Cimage-1%2Cimage-2%2Cimage-3%2Ctable-3-5

This is particularly easy if you are running Mozilla Firefox and have downloaded the Web Developer Toolbar extension.

Summary

We have shown how a custom history stack can be created to track events within an Ajax application. Backward, forward, and reload buttons common to Web browsers can be added to our applications to navigate our custom history stack.

In addressing this challenge, we identified the problem and created a reusable solution for it that can be applied to other applications. Rather than building the history stack directly into the photo gallery, we generated a simple page to test our class. This allowed us to build a solution not tightly bound to one application and can be reused in other Ajax applications to solve the same problem.



Download

DescriptionNameSizeDownload method
Part 2 source codeos-php-rad2.code.zip6.5KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

  • Chris Pederick's Web Developer Toolbar allows for viewing and managing cookies, CSS, images, forms, and other goodies, and is one of the most useful extensions in Mozilla.

  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

Discuss

About the author

Mike Brittain

Mike Brittain is the director of technology at ID Society, an interactive marketing agency in New York City. He has been using open source technologies and languages to develop Web sites and applications for more than 10 years. He has taught scripting classes at the University of Denver, where he also earned his master's degree. He can be contacted at mike@mikebrittain.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=123440
ArticleTitle=Developing PHP the Ajax way, Part 2: Back, Forward, Reload
publish-date=06062006
author1-email=mike@mikebrittain.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers