Wednesday, August 26, 2009

Back and Forward Buttons working in RichFaces using Dojo

Recently I have been doing some work with RichFaces and encountered the biggest problem with AJAX based applications - the BACK button is not working. BTW, I cetainly recommend RichFaces, specifically its Ajax4JSF module, which make AJAX so simple. Anyways start doing some research and ultimately got working by integrating Dojo's back button support into my RichFaces application.

Basically there are 3 part of the solution:
  1. Adding the server side support to keep a history of the AJAX calls being made.
  2. Add and initialize Dojo to your web application.
  3. Send back button click events to load the previous state of the UI on the server side.
You can download the code from here.

So the first step is to create a session-scoped History bean that will maintain a queue of the states. Define 4 simple apis:
  • addToBack - adds the previous state to the history
  • addToForward - adds the current state to the history
  • doBack - go to the previous state
  • doForward - go to the state ahead
    public class HistoryBean {
private List _backHistory = new ArrayList();
private List _forwardHistory = new ArrayList();
public HistoryBean() {
super();
}
public void doBack() {
if (_backHistory.size() == 0) return;
State state = _backHistory.remove(0);
addToForward(state);
}
public void doForward() {
if (_forwardHistory.size() == 0) return;
State state = _forwardHistory.remove(0);
addToBack(state);
}
public void addToBack(State state) {
_backHistory.add(0, state);
}
public void addToForward(State state) {
_forwardHistory.add(0, state);
}
public State getCurrentState() {
if (_backHistory.size() == 0) return null;
State state = _backHistory.get(0);
return state;
}
}


Next add and intitialize the back button module from Dojo into your application web pages. You can find more information on Dojo and back button here.

    <script type="text/javascript"  src="js/dojo/dojo.js"
djConfig="preventBackButtonFix: false">
</script>

<script type="text/javascript">
// load the dojo.back package
dojo.require("dojo.back");
dojo.back.init();
</script>

Now create a state object to insert into the browser's history. The state provides 2 functions, one for back and the other for forward. Finally set the initial state when the page is first loaded.

    <script type="text/javascript">
// define a state for the back button clicks
var state = {
back: function() {
invokeBack();
},
forward: function() {
invokeForward();
}
};
// load the initial state
dojo.back.setInitialState(state);
</script>

Now add two A4J based JavaScript functions that are invoked from the state functions above. These functions will changes the state at the server side when the back or forward buttons are clicked.

    <a4j:jsFunction name="invokeBack"
action="#{history.doBack}"
reRender="__my_tabs"/>
<a4j:jsFunction name="invokeForward"
action="#{history.doForward}"
reRender="__my_tabs"/>

In these A4J javascript functions you are doing two things, first asking the server's session to switch to teh previous or next state in the queue. Secondly re-rendering the necessary components through the "reRender" attribute.

That's all you need to get the back button functioning correctly in an application built Ajax4JSF/RichFaces. I am pretty sure you can apply the same to other JSF implementation like MyFaces and JSF RI to get it to work.

You can download an example app that shows the back/forward button in action from here.

Things that you should be careful about in the server side history implementation.
  • Avoid loops in the state management. For example, when back in invoked it should call a setter that will call back into the history to add a back state.
  • Try to store objects with very small footprints in the state to conserve memory. You can also implement a FIFO model to remove very old back states.
  • Add an api like loadState() to the State interface (see sample code) that will allow you to implement the state loading in different parts of your application.

3 comments:

  1. Very cool! It's just what I need, but the downloadable code samples from MediaFire don't seem to be available.

    A couple quick questions:

    1. Should the class for the State variable in the HistoryBean be org.ajax4jsf.component.UIAjaxStatus.State?

    2. Does the state var in the JavaScript need to be initialized via a function invoked from the page body's onload event?

    ReplyDelete
  2. Here is the link to the file - http://www.mediafire.com/file/ei1gygwjlm2/rf.zip. Sorry about that.

    Greg, for the State I just defined a simple interface that can be implemented to define the state of the application in different parts of your application.

    ReplyDelete