Making Your DHTML or AJAX Application Bookmarkable

As more and more websites are embracing “web 2.0″ standards and evolving into more responsive dynamic applications many of them are losing basic functionality such as bookmark-ability.

In order to present a richer interface or to reduce page load times and improve responsiveness it can be beneficial to alter the content on a page dynamically, using client-side JavaScript and optionally XMLHTTPRequests, without making the browser request a whole new page. This approach is a core technique of the “web 2.0″ umbrella approach to site design and interaction.

For a basic example, envision a product page with the product image and high level information at the top and multiple tabs below that. The default tab might include a detailed product description and color options. The next tab might show user reviews. When the user clicks on the Reviews tab, the displayed tab changes using JavaScript, but the browser does not request a new page (although the review data could be loaded via an AJAX request to speed the initial page load time – although note that this would keep the reviews from being seen by user agents which do not support JavaScript, like the Googlebot).

Assume I am a user of the site: I read through the reviews and see one that is particularly useful. I’d like to bookmark this page, or perhaps e-mail this page to a friend of mine who has been looking for a product like this. The URL in the browser hasn’t changed from the initial page state, so when I bookmark or share the link with a friend, the Reviews tab isn’t selected and I have to include instructions to my friend to click on the Reviews tab after opening the link I have sent. Now this may not seem like a significant burden, but now assume that the reviews are paginated within the tab in groups of 10, and the review I want my friend to read is on “page” 7 of the reviews. Or assume that the product page allows you to select different SKUs (different colors or versions for instance), and the data displayed and reviews are loaded based on the SKU that was selected. In either case, I now have to include detailed click path instructions to my friend, or remember them myself if I am bookmarking the page.

The solution is to make use of the URL’s hash or anchor feature to store page state without forcing a page reload. The hash is the part of some URLs at the end prefixed with a hash sign: #. This is commonly used for anchors when navigating a long page, which is a means of storing page state in the URL, we just want to take it to another level.

There are two parts to this. The first is that we need to store the state data in the URL’s hash when the state changes. The second is that when the page loads for the first time we have to check for state data in the URL’s hash, and if it exists setup the page state accordingly.

Let’s start with the basic example of the product page with two tabs above. When the user selects the Reviews tab, some JavaScript is being called in order to change the displayed content from the default tab to the Reviews tab. This could be as basic as a link with an onclick JavaScript attribute, or it could be driven by one of the many JavaScript frameworks that support tabs (Dojo, RichFaces, jQuery, etc…). For this post I’ll assume it’s using the onclick attribute of a standard link as it’s the most basic case. If you are using a specific framework you’ll need to read the documentation on binding a method to the tab change or reading an event, etc…

If the Reviews tab link looks like this:

<a href=”javascript:void(0)” onclick=”javascript:swapTab(‘Reviews’);”>Reviews!</a>

You can store the state data in the URL’s hash by changing the link to this:

<a href=”javascript:void(0)” onclick=”javascript:swapTab(‘Reviews’);window.location.hash=’Reviews’;”>Reviews!</a>

Now, when the link is clicked, the Reviews tab is displayed, and the URL in the browser is rewritten from:

http://www.mysite.com/product.jsp?prodId=1234

to:

http://www.mysite.com/product.jsp?prodId=1234#Reviews

Now we’ve successfully stored the page state data in the URL in a way that is bookmark-able and shareable. The next step is to check for and act on that state data when the page loads.

Assuming most of your javascript for that page is in an external file called product.js, you would add a new function call handleHashOnLoad which might look like this:

function handleHashOnLoad() {
	// This method looks for anchor tags, or hashes, in the URL and sets up the tabs appropriately
	var hash = location.hash;
	//alert("hash:"+hash);
	// if we have a hash value from the URL
	if(hash != "") {
		// We need to strip off the "#" which is included in the hash value
		var tabName = hash.substring(1);
		// Now we call the function to setup the correct tab
		swapTab(tabName);
	}
}

Now you just need to call that new function after the page loads. Since the swapTab function probably manipulates the DOM tree, it is important to not call this function until after the DOM tree is complete. You can do this using a onload call, or your 3rd party JavaScript library may have a better solution (such as Dojo’s addOnLoad feature).

Now you have a page with dynamic tabs with a URL which can be bookmarked or sent out via e-mail or IM. This was a very simple example and on many pages there may be more than one piece of data defining the state. For instance, if the Reviews are paginated you might have a pagination start point, or if the product has dynamically selectable SKUs you might need to store the selected SKU. This can be done by loading the data into the hash with prefixes and separators.

http://www.mysite.com/product.jsp?prodId=1234#sku555:tabReviews:reviewStart35

Then in your handleHashOnLoad function you parse out the various values by splitting on the seperator (in this case the colon) and handling each value separately based on the prefix.

The two places you need to be careful in this approach are:

Ensure that you’re setting up the data correctly and are not overwriting the location.hash with just the one state point that was changed by the click, but are overwriting the particular value you wish to change.

Ensure that your handleHashOnLoad function is calling the DOM altering JavaScript functions in the correct order. For instance if the selected SKU changes the reviews that are loaded into the Reviews tab, it would be important to handle the selected SKU before you handle the displayed tab and the pagination of the reviews within that tab.

I may write up some basic JavaScript to assist with these areas of risk, and if so I will share it here. There may be some good options out there already but, as I’m currently 37,500 feet up, I can’t Google for them.

Photo by Ei! Kumpel

10MinuteMail Updates

I just pushed a new version of 10MinuteMail. Here are the notable updates:

  1. Removed the Ad-Aware links and text. No one was clicking on them anyhow.
  2. Added some translation fixes.
  3. Implemented AJAX based (RichFaces) refreshing of the list of e-mails in your inbox.
  4. Added smtp client throttling (in Postfix) to limit the number of messages accepted from a single source within 60 seconds. This seems to have already fixed the negative impact of high volume spammers on the function of the site.
  5. Removed the “Get Another E-Mail” feature. While this was a user request, I discovered that it was being abused by spammers.
  6. Added a Forward feature to allow you to forward a received e-mail to your home account for storage.

Enjoy! If you have any issues with the AJAX refreshes, let me know, but I think it should work better now.

Apache Proxy Breaks RichFaces

I’ve run into this twice now, so I wanted to document it here to help other folks, and to see if anyone knows the root cause of the issue.

When using RichFaces with Seam, things work just fine on my local development JBoss instance. But when I deploy the same EAR file up to my production JBoss instance, which is sitting behind an Apache proxy, everything works EXCEPT the rich/ajax stuff.

The issue was that the JavaScript located here: ContextRoot/a4j_3_1_4.GAorg.ajax4jsf.javascript.AjaxScript

would not load.

My Apache proxy was configured like this:

	ProxyPass /10MinuteMail balancer://mycluster/10MinuteMail/
	ProxyPass /10MinuteMail/* balancer://mycluster/10MinuteMail/
	ProxyPassReverse /10MinuteMail http://127.0.0.1:8080/10MinuteMail

With mycluster defined like this:

        
                AddDefaultCharset off
                Order deny,allow
                Allow from all

		BalancerMember http://127.0.0.1:8080
                #Allow from .example.com
        

Again, this configuration worked fine for everything EXCEPT that RichFaces JavaScript.

Since I am only using one node for 10MinuteMail, there is no real need for a load balancer configuration, so I replaced the configuration with this:

	ProxyPass /10MinuteMail http://127.0.0.1:8080/10MinuteMail
	ProxyPass /10MinuteMail/ http://127.0.0.1:8080/10MinuteMail/
        ProxyPassReverse /10MinuteMail/ http://127.0.0.1:8080/10MinuteMail/

Which works, and fixed the RichFaces reference.

So there’s your solution. However I have no idea what the actual root cause is.

Displaying and Rerendering a RichFaces ModalPanel from a commandLink

Let’s say that when a user clicks on something, perhaps selecting something from a DataTable or DataGrid, you want to call some server side code, and then popup a modal panel which needs to show content based on the new state setup by your server side call. In my case I have a DataGrid showing a number of items, and when a user clicks on an item I want to set the current selected item on a Seam component on the server, and then popup a modal panel to display detailed information about the item, and provide the user some actions within the modal panel.

Looking at the solution it seems so simple, but for some reason it took me hours of trying different combinations, orders, tags, JavaScripts, etc… before I could get this to work correctly. I had the panel appearing with the old data, or appearing with the old data and then disappearing completely, only to show the so called new data the next time I clicked on an item, or rendering on the page itself below the footer, or not popping up at all, or popping up with null data, or…. So I’m posting it up here for myself and anyone else who runs into issues.

First, create your modal panel (rich:modalPanel). I do this in a separate .xhtml file, as I will use the same model panel in various pages throughout the site. In your modal-panel.xhtml file (or whatever you choose to name it), start with a <ui:composition> tag, define your RichFaces xml namespace (and any other JSF libs you’ll be using), and then setup your <rich:modalpanel>

You need to give the modalPanel a unique id, in this case we’ll call it “myModalPanel”.

<rich:modalpanel id="myModalPanel" autosized="true" zindex="2000">

Inside your modalPanel tag, you need to define an inner div. This is the div which you will be rerendering after the server action is complete. I use tags, as I’m using Seam. Ensure that any references to backing beans or data that you want refreshed are within these div tags. You must give this div a unique id as well, in this case we’ll call it “myModalDiv”.

<s:div id="myModalDiv">

The one limitation that I’ve run into here, and if you know how to solve this, please let me know, is that the <f:facet> tags for your header text and controls can’t be inside this div, and you can’t put the div around the whole modalPanel (or it makes the panel disappear when you rerender). As such you can’t make anything from the <f:facet>s be updated on rerender. This is preventing me from having the modal panel header displaying the current selected item’s name. Not great, but not the end of the world.

So inside this div you have your dynamic output. I have lots of <h:outputtext value="#{backingBean.selectedItem.property}"> type entries in mine.

On your actual page where you will be displaying the modal panel from you need to include the modal-panel.xhtml file, I use a:

<a:include viewid="/modal-panel.xhtml">

Then, I have my page, which includes a rich:dataGrid, and inside it has an <a:commandLink> which creates the AJAX clickable item. This commandLink has to call the backend action, display the modal panel, and rerender your div.

<a:commandlink id="showItem" action="#{backingBean.setSelectedItem(item)}" oncomplete="Richfaces.showModalPanel('myModalPanel',{width:550, top:200});" rerender="myModalDiv">
Click Here!
</a:commandlink>

This makes the panel display, and then the new content is rendered in the panel. It seems to work just how I wanted it to. With the minor annoyance of having to use a static modalPanel header.