Ajaxified Body
I've often wondered if it was possible to use Ajax to reload the main content of a web application without reloading the header, menu and footer. SiteMesh allows you to move these common elements to a decorator that gets wrapped around each page. Below is a diagram of how SiteMesh works.
You can read the Introduction to SiteMesh article if you're interested in learning more about SiteMesh's basic features. By default, SiteMesh decorates text/html
responses and ignores any other content type (e.g. image/gif
). It also contains an <excludes> configuration element that allows you to turn off decoration when a URL matches a certain pattern. For example, the following allows you to disable decoration when "ajax=true" is passed in the URL.
<excludes> <pattern>**ajax=true</pattern> </excludes>
To optimize the loading of an application so the common elements aren't loaded for each page, it should be possible to create an Ajaxified Body where the primary content area (PCA) of the site is loaded via Ajax rather than refreshing the whole page. The header, footer and navigation bar often stays the same from page-to-page, so it doesn't seem to make a whole lot of sense to load them each time the page loads. The image below shows the PCA (of an AppFuse Light application) as a grey square with a red border.
Implementing an Ajaxified Body consists of the following steps:
- Adding SiteMesh and moving common elements to a decorator.
- Remove common elements from each individual page (if you're using includes).
- Configure SiteMesh so decoration is disabled when the requested URL contains "ajax=true".
- Write JavaScript that modifies all <a href=""> links (and buttons with onclick='location.href') in the PCA to have an onclick handler.
- The onclick handler should call a JavaScript function that loads the link's URL + ajax=true using XMLHttpRequest (XHR).
- Add XHR success handling to replace the PCA with the loaded content.
- Add XHR error handling to go to the URL normally when response.status != 200.
- Inspect the response HTML for <title> element and replace document.title if exists.
- Inspect the response HTML for <head> element and append to current if exists.
- Inspect the response HTML for <script> and <link> elements (JavaScript and CSS) and evaluate them if they exist.
As a proof of concept, I created a prototype using AppFuse Light (Prototype/Scriptaculous for Ajax). You can see a demo at the following URL. You can also download a patch or the source for this project.
http://demo.raibledesigns.com/ajaxifiedbody
Below are a number of things I discovered while writing this prototype:
- The hardest part of implementing this seems to be coding the exceptions. It's possible you'll have some links with existing onclick handlers and you may have to disable "ajaxifying links" for those links.
- A progress indicator is important or the page might load so fast that the user doesn't visually detect it changed. This can lead to a worse user experience because they don't see the flash of the blank page they're used to when a page load occurs.
- While forms can be submitted via Ajax, there's no harm in leaving existing form behavior in place where the full site is reloaded after submitting a form.
- If a particular page needs to change the common elements (header, menu, footer), it should be possible to do that with JavaScript after the PCA content loads.
- If the success/error indicator is outside the PCA, it may need to be populated and displayed/hidden with JavaScript after the PCA loads.
I'm sure my implementation can be improved, but I'm also curious to see what you think of this idea. I know it's not revolutionary, but it's something I'm considering adding by default to AppFuse and AppFuse Light. Do any Ajax frameworks do something like this out-of-the-box?
Update: Thanks to everyone for the great feedback - keep it coming. I agree that adding history support is a must. I'll try to do that in the next day or two. This post has also been featured on Javalobby and Ajaxian.
Update 2: Added history support.
Very cool idea! I could definitely see a use for this in AppFuse along with some standardized JS that deals with the exceptions as well as the URL formatting (I'm a big Prototype fan, so using that framework would work well for me, but I could see others wanting the same solutions in JQuery and the like).
I suppose you could argue that is a bit difficult to debug without a tool to see the DOM in its current state along with the Get requests (such as FireBug) as the view source shows the initial page (understandably so since the request is asynchronous).
Either way, interesting approach ... have to play around a bit with it :-)
Posted by Johnny Wey on October 03, 2008 at 09:27 PM MDT #
Be very wary of passing too much pre-rendered HTML back from an XHR call, especially if you're (for example) passing it inside a JSON string along with other data.
You'll end up discovering all kinds of new and interesting ways that Antivirus / Antispam / Security utilities can muck with the server's response and cause problems. The latest generation of these utilities actually rewrites HTML content between the server and the browser, including XHR requests!
The worst part is, it'll work just fine on every test browser you try, then will fail when Johnny Random accesses the site with the very same browser -- only with Comcast's horrible free security product installed.
I'm actually right in the middle of a major site rebuild that's pushing quite a bit of the dynamic HTML rendering down to the Javascript layer and simply passing the data from the server in JSON.
Posted by Brian Landers on October 03, 2008 at 10:21 PM MDT #
Yes, AjaxAnywhere does this, and does a real good job. It supports the notion of named zones (area between <aa:zone name="listGrid"> for example) and you can specify which zone you want refreshed when making an ajax submit.
The idea and implementation is very clever and works seamlessly with JSP's. It essentially has a servlet filter that reads the zone name of the area to refresh from the request, and then after the JSP for the entire page is rendered on the server, only returns the fragment of content that falls within the zone to refresh. On the client side, it refreshes that area. So having just one zone for the entire "body" will provide results similar to yours.
We used this at work to Ajaxify our JSP based application and it worked extremely well with little effort.
Disclaimer : I am a project member of AjaxAnywhere. I didn't contribute a whole lot of though, all credit goes to Vitaliy. The project doesn't have a super active forum anymore but I view the project as complete. It is solid and just works.
Along with AjaxAnywhere, I used the code from here to do the url href fixing.
Posted by Sanjiv Jivan on October 03, 2008 at 10:54 PM MDT #
hmm, I just realized you had blogged about AjaxAnywhere a while ago so I must be missing something as you were aware of it. Does the Axaxified Body approach differ from AjaxAnwhere?
http://raibledesigns.com/rd/entry/ajaxanywhere
Posted by Sanjiv Jivan on October 04, 2008 at 10:27 AM MDT #
I tried this using WordPress (PHP) and ran into a few issues that are raised by this approach:
The first two could be solved by implementing a JavaScript "history" solution [1], but I'm not sure about the third. It would be interesting to know how search engines would handle this type of setup.
Just some things to consider. Also, I know Google Chrome is in beta, but it doesn't handle the ajaxifiedbody demo very well ;)
[1] http://www.stilbuero.de/jquery/history/
Posted by Eric Martin on October 04, 2008 at 02:18 PM MDT #
Posted by Matt Raible on October 04, 2008 at 03:36 PM MDT #
ah yes, I remember that DisplayTag blog now. It's been a while. Sorry for the noise :)
Posted by Sanjiv Jivan on October 05, 2008 at 01:36 AM MDT #
Posted by Lars Behnke on October 05, 2008 at 04:25 PM MDT #
Posted by Matt Raible on October 05, 2008 at 04:52 PM MDT #
Posted by Rui Campos on October 06, 2008 at 11:27 AM MDT #
Posted by Jordi Hernandez on October 06, 2008 at 03:52 PM MDT #
You may find RSH helpful when resolving bookmarkability:
http://code.google.com/p/reallysimplehistory/
It's the same principle used by GMail and others.
Posted by Ignacio Coloma on October 06, 2008 at 07:48 PM MDT #
Posted by Raible Designs on October 07, 2008 at 06:17 AM MDT #
Posted by Mario on October 08, 2008 at 12:38 AM MDT #
What software did you use to do your diagrams.
OmniGraffle (Mac only).
Posted by Matt Raible on October 08, 2008 at 12:55 AM MDT #
Posted by Richard Cowin on October 09, 2008 at 09:56 AM MDT #
Posted by Matt Raible on October 09, 2008 at 04:38 PM MDT #
Posted by Antony Stubbs on October 09, 2008 at 05:29 PM MDT #
Posted by Enid on October 26, 2008 at 07:22 AM MDT #