Using Dojo Back Button and Bookmarks
by Chris Keene

Ajax applications are sometimes a bit like super models - visually impressive but not always well behaved.

In particular, Ajax applications have a spotty record with respect to supporting basic web concepts like the browser back button and the ability to bookmark a particular place in the application and return to it later.

Fortunately, Dojo has a nifty resource, dojo.back, that takes care of both the back button and bookmarks with one fell swoop. Unfortunately, there are not many “real world” examples for how to use dojo.back. Most examples consider it sufficient to have the back button generate alerts - not a particularly useful “state” in a real application.

Sample Application - Ajax Showcase

The use case for this project is an Ajax showcase application that manages a list of applications in a grid and then displays a set of screenshots for that application. The following screenshot shows the application (you can also see it live here)

Because the whole point of the application is to showcase cool Dojo apps, this application had a core requirement that a user could bookmark a particular application that they like and share it with a friend. For example, a bookmark like the following should bring up the screenshots for the ehealth application:

http://community.wavemaker.com/Showcase/#ehealth

In addition, it is important that the application responds properly to the user pressing the back button.

Enter Dojo.Back

Dojo has a set of functions in the core module that are accessed by including dojo.back in an application using dojo.require:

dojo.require("dojo.back");

The trick with dojo.back is to create a class to hold the state of the application at a particular point in time. For this application, the state is either “home” - meaning that the application should show a list of all the sample applications, or “appName” - meaning that the application should show the screenshots for the application specified by appName.

Defining this state object is a two part process:

  1. first define the data stored by the state and
  2. specify the function to call when the back or forward button is pressed. Defining the state is straightforward. Note that the changeUrl becomes the bookmark displayed in the url - neat!
 backStateObject: function(appName) {     // layerName will be used as app state     this.appName = appName;     // changeUrl is a bookmark that Dojo adds to the app url     this.changeUrl = appName; },

Next, add a function to call when the back or forward button is pressed. First use dojo.hitch to create a function that is bound to the local context. Next, use dojo.extend to add a back function element to the backStateObject and pass it the magically hitched function. Interestingly, you can use the local context to pass a parameter to the back function (e.g., this.appName) but you cannot assign back to this.getScreenShotsByName.

 // dojo.hitch returns a handle to func that does not require "this" var backLayerFunc = dojo.hitch(this,"getScreenShotsByName"); // Add the back function to the backStateObject using dojo.extend dojo.extend(this.backStateObject, {     // Specify function to call when browser back button is pushed     back: function(){         // Can't use "this" in the function name, but can use it for func parameter         backLayerFunc(this.appName);     } })

The getScreenShotsByName function triggers an hql query that returns a json structure with the requested screenshots. That’s basically it! Now you can create state objects each time you switch application context.

 // Save the app state var newState = new this.backStateObject(inName); // Add new state to the back button history dojo.back.addToHistory(newState);

Using Bookmarks

Since our implementation of dojo.back is automatically creating bookmarks for us, the only thing that remains is to use the same bookmark syntax to call the application url and force it to a particular state. Here is a decidedly inelegant function that checks the app url onAppLoad to see if a bookmark has been included in the url. If a bookmark is found, it calls the trusty getScreenShotsByName function to retrieve the appropriate screenshots and navigate to the screenshot display layer.

 // Check if the app url has a bookmark #appName checkForBookMark: function( paramName ) {     // Get full url     var appUrl= window.location.href;     var results = new Array();     // Split url - bookmark starts with the # character     results = appUrl.split("#");     // console.log(results)     // results[1] should hold the bookmark     if( results[1] != null ) {         // Lookup the screenshots specified by the bookmark         this.getScreenShotsByName(results[1]);     } },

Here is a screenshot showing the application using a bookmark to automatically navigate to the appropriate screenshot page for the ehealth application:

Using WaveMaker To Build Dojo Apps

The Showcase application was built using WaveMaker, a visual tool for building Dojo apps on top of standard Java servers. WaveMaker is sort of like an updated, open version of MS Access. You specify a database to talk to, then WaveMaker automatically generates Hibernate mappings and CRUD operation queries based on the data schema. WaveMaker also generates custom data widgets for each table that can display that table’s data as a grid or editable form.

After importing the data schema, you use the visual editor to drag widgets onto the page and drop to Javascript when necessary on the client and/or Java on the server. WaveMaker generates standard Java projects that work with existing Java IDEs like Eclipse and Java servers like Websphere.

The WaveMaker community includes over 10,000 developers. Customers include Cisco, Macy’s and National City Bank; and partners include IBM and KANA. For more information, see www.wavemaker.com

Known Issues/Things I Was Too Lazy or Ignorant To Figure Out

This doesn’t seem to work particularly well on ie (ie gets confused after pressing back button a few times - sigh) - interestingly, it works best on Chrome.

It would be relatively easy to create a more generalized version of the back state that could keep track of the layer/page + primary key data for whatever data to load on that page. Maybe we’ll even get around to automating this and adding it to Wavemaker…someday ;-)

Appendix - Source Code

Here is the source code for the Showcase app. Note that most of the widgets are defined in a separate widget file that is generated automatically by WaveMaker, so this contains only the unique logic for implementing the necessary added functionality. You can also download the WaveMaker project file here.

/*** Showcase - this application demonstrates how to implement the back button and* bookmarking in Dojo using WaveMaker.** Back button - this application shows how to create a state object, backStateObject* to store the application state for the back button. This object is extended to* add the back function using dojo.extend.** Bookmarking - when the application is started, it checks to see if the url encodes* a layer to goto.**/// Note: WaveMaker defines each page as a Dojo widget using dojo.declaredojo.declare("Main", wm.Page, {    start: function() {        this.backInit();        this.checkForBookMark();    },    // Check if the app url has a bookmark #appName    checkForBookMark: function( paramName ) {        // Get full url        var appUrl= window.location.href;        var results = new Array();        // Split url - bookmark starts with the # character        results = appUrl.split("#");        // console.log(results)        // results[1] should hold the bookmark        if( results[1] != null ) {            // Lookup the screenshots specified by the bookmark            this.getScreenShotsByName(results[1]);        }    },    // backStateObject is a class that stores the app state and sets bookmark    backStateObject: function(appName) {        // layerName will be used as app state        this.appName = appName;        // changeUrl is a bookmark that Dojo adds to the app url        this.changeUrl = appName;    },    // backInit initializes the back button state    backInit: function(inSender) {        // Ensure that dojo.back class is loaded        dojo.require("dojo.back");        // dojo.hitch returns a handle to func that does not require "this"        var backLayerFunc = dojo.hitch(this,"getScreenShotsByName");        // Add the back function to the backStateObject using dojo.extend        dojo.extend(this.backStateObject, {            // Specify function to call when browser back button is pushed            back: function(){                // Can't use "this" in the function name, but can use it for func parameter                backLayerFunc(this.appName);            },            forward: function(){                // Use the same function to get back to page w forward button                backLayerFunc(this.appName);            }        });        var initState = new this.backStateObject("home");        dojo.back.setInitialState(initState);    },    // Function called when a data grid is selected    thumbsDataGridSelected: function(inSender, inIndex) {        var appName = this.thumbsDataGrid.selectedItem.getData().shortname;        this.getScreenShotsByName(appName);    },    // Calls Hibernate query service (queryAppName) for specified application    getScreenShotsByName: function(inName) {        // Special case - if the app name == "home" just go to the home tab        if (inName =="home") {            this.gotoHomeTab.update();        } else {            // Set input parameter for hibernate query service            this.queryAppSvcVar.input.setValue("inputName", inName );            // Invoke the service by calling update() on the service variable            this.queryAppSvcVar.update();        }        // Save the app state        var newState = new this.backStateObject(inName);        // Add new state to the back button history        dojo.back.addToHistory(newState);    },    // Called when the Hibernate query returns results    queryAppSvcVarResult: function(inSender, inData) {        if (this.queryAppSvcVar.getData().length ==0) {            alert("Requested app not found!");        } else {            // Navigate to the screens tab            this.gotoScreensTab.update();        }    },    // Called to return to the home page    toHomeButtonClick: function(inSender, inEvent) {        this.getScreenShotsByName("home");    },    // Show screenshots for featured apps    appNatCityClick: function(inSender) {        this.getScreenShotsByName("natcity");    },    appEHealthClick: function(inSender) {        this.getScreenShotsByName("ehealth");    },    appWmStudioClick: function(inSender) {        this.getScreenShotsByName("wmstudio");    },    appGoogMapClick: function(inSender) {        this.getScreenShotsByName("googmap");    },    appLotusLvClick: function(inSender) {        this.getScreenShotsByName("lotuslv");    },    _end: 0});

Tags: , ,

This entry was posted on Sunday, May 17th, 2009 at 4:26 pm and is filed under Dojo Cookies, Dojo in action, Great websites, Intermediate, Tutorials. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

4 Responses to “Using Dojo Back Button and Bookmarks”

  1. andyhot Says:

    IE can be made to work by adding:

    dojo.back.init();

    directly inside

    For this to reliably work, i’d also have to add

    just after including http://ajax.googleapis.com/ajax/libs/dojo/1.3.1/dojo/dojo.xd.js and ofcourse correctly
    setup djConfig.dojoIframeHistoryUrl (but that’s only for xdomain)

  2. andyhot Says:

    IE can be made to work by adding:
    dojo.back.init();
    directly inside BODY

    For this to reliably work, i’d also have to add
    http://ajax.googleapis.com/ajax/libs/dojo/1.3.1/dojo/back.js
    just after including http://ajax.googleapis.com/ajax/libs/dojo/1.3.1/dojo/dojo.xd.js and ofcourse correctly
    setup djConfig.dojoIframeHistoryUrl (but that’s only for xdomain)

  3. ckeene Says:

    Oops, I forgot to put that in the article. Yes I did that too - all it seems to do is make the back button work a few times - ie still eventually gets confused.

  4. Tendrid Says:

    In a land of semi-useless dojo.back explanations, I really wish this article would have had usable code to copy/paste and play with. Instead, I saw only commented pseudo code and an ad for wavemaker

Leave a Reply

You must be logged in to post a comment.