Monday, 6 June 2011

Enhance Web 2.0 applications navigation using dojo hash function

Web developers face navigation & usability issues with Web 2.0 applications that allow dynamic content change without page refresh. Those problems are summarized as follows:
  1. Browser back button doesn't function properly. Instead of updating page state, it refreshes and changes the whole page to the previous page or just doesn't make any effect (depending on how the page is designed)
  2. The page title doesn't change with different states
  3. It is not possible to copy the URL and send it over to someone to check specific content or may be include a link in e-mail notification that takes the user to certain state.
Those three issues can be resolved by using the elegant dojo.hash function in combination with a proposed controller that renders the correct state based on the supplied hash value.

This is shown in the attached controller.js in function handlePageHashState(). The provided sample is simplified and made self explanatory as much as possible.

First save the two attached files and start index.html and navigate to view1 and view 2 and notice how the browser links change as well as page title in the browser's bar. Then try to use the browser back button and you can easily notice that page content changes appropriately without refresh. Even better, you can copy the URL to any specific view and open it from a different browser and you will get the right view directly without having to navigate from main list.

Hint: The concept works on both Firefox and IE including IE 6.0

index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" type="text/javascript"></script>
<script type="text/javascript" src="controller.js"></script>
<title>Main page</title>
<script type="text/javascript">
dojo.addOnLoad(init);
</script>
</head>
<body>
<h1>Dojo hash sample - main view</h1>

<div id="mainview">
<ul>
<li><a href="#view1">View 1</a></li>
<li><a href="#view2">View 2</a></li>
</ul>
</div>
<div id="childview" style="display:none">
The child view is empty
</div>
</body>
</html>


controller.js
dojo.require("dojo.hash");
var pageInitialized=false;

function init(){
    console.debug("init");
    dojo.subscribe("/dojo/hashchange", this, handlePageHashState);
    handlePageHashState(dojo.hash());
}

function handlePageHashState(hashState) {
    console.debug("handlePageHashState ", hashState);
    if (!pageInitialized) {
        //This is one time initialization for all views
        //For specific view initialization, execute it from the proper render function
        console.debug("Perform any required initialization");
        pageInitialized=true;
    }
   
    if (hashState == null || hashState === "" || hashState === "default") {
        resetPageTitle();
        defaultRender();
    } else if(hashState=="view1"){
        console.debug("Manage view1 state");
        updatePageTitle("view1");
        view1Render();
    }else if(hashState=="view2"){
        console.debug("Manage view2 state");
        updatePageTitle("view2");
        view2Render();
    }else{
        console.debug("Calling a default action in case you can't find a proper hash state");
        //This will force the execution of pageHandleHashState with default as hash value
        //This is one the nice features with the hash controller that you can force executing it
        //with specific hash value
        //This will force an update in URL hash so the undefined one is replaced
        //This can happen if user manually modified the hash value with wrong one
        dojo.hash("default");
    }
}

function view1Render(){
    console.debug("view1 render");
    dojo.attr("mainview", { style : { display : "none" } });
    dojo.attr("childview",{innerHTML:"This is view 1<br><br><a href='#default'>Back to default</a>",style : { display : "block" } });
}

function view2Render(){
    console.debug("view2 render");
    dojo.attr("mainview", { style : { display : "none" } });
    dojo.attr("childview",{innerHTML:"This is view 2<br><br><a href='#default'>Back to default</a>",style : { display : "block" } });
}

function defaultRender(){
    console.debug("default render");
    dojo.attr("mainview", { style : { display : "block" } });
    dojo.attr("childview",{style : { display : "none" } });
}
function resetPageTitle(){
    console.debug("resetPageTitle");
    document.title="Main page";
}
function updatePageTitle(view){
    console.debug("updatePageTitle ",view);
    document.title="Main page - "+view;
}

Comments on sample implementation above:
Namespaces - All you functions and variables are global.  You really should namespace everything.  Example:
dojo.provide("ibm.foo.bar");
ibm.foo.bar = {
    localVarA : "hello",

    x : function() {
        console.log( "localVarA = ", this.localVarA);
    }
};
- dijit StackContainer, and StackController
    This is doing essentially what your controller code is doing, but In a  much more manageable fashion.

No comments:

Post a Comment