Hacking JSF State Management with AJAX

I was working on an “Export-to-Excel” feature for the datatable components we use in our project. The requirement is when the client clicks on the excel icon on a datatable, a window opens and responses the data as an excel file.

There is an excellent library created by my colleague Mert called JSFExcelCreator that takes the id of the data table and gives the excel, however after integrating this library to our custom data table renderer, we’ve realized that it only works when the state is hold at server. The reason is obvious; a phaselistener traverses the component tree in the view, finds the datatable and outputs the excel after examining it. When the state saving method is client, the newly opened window responsible for giving the excel cannot find the component tree since the view stayed as an hidden variable at the first page which fired the javascript event to open a new excel window.

The solution I’ve thought is to use AJAX and include the view state to the AJAX request, therefore JSF takes the view state, restores the view, rebuilds the component tree with no postback. This results the view to be available available in an AJAX request. Following are the javascript functions I’ve written to handle the AJAX stuff;

var http = createRequestObject();

function createRequestObject() {
    var requestObject;
    var browserName = navigator.appName;
    if(browserName == “Microsoft Internet Explorer”) {
        requestObject = new ActiveXObject(“Microsoft.XMLHTTP”);
    }
    else {
        requestObject = new XMLHttpRequest();
    }
    return requestObject;
}

function sendExcelReq(tableId) {
    var parameters = getAjaxExcelRequestParams(tableId);
    var url = document.forms[0].action;
    http.open(‘POST’, url, true);
    http.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’);
    http.onreadystatechange = function() {
        if(http.readyState == 4) {
            document.getElementById(tableId+’:excelIcon’).style.display=’inline’;
            document.getElementById(tableId+’:statusText’).style.display=’none’;
            window.open(postbackURL+’?excelResponseTableId=’ + tableId);
        } else {
            //I envy gmail’s loading text, while waiting for the excel response, make the status text visible
            document.getElementById(tableId+’:excelIcon’).style.display=’none’;
            document.getElementById(tableId+’:statusText’).style.display=’inline’;
        }
    }
    http.send(parameters);
}

function isStateClient() {
    var viewState = document.getElementsByName(‘com.sun.faces.VIEW’)[0];
    if(viewState == null)
        return false;
    else
        return true;
}

function getAjaxExcelRequestParams(tableId) {
    var params = null;
    if(isStateClient( ) ) {
        var viewStateValue = encodeURI(document.getElementsByName(‘com.sun.faces.VIEW’)[0].value);
        var regExp = new RegExp(“\\+”,”g”);
        var encodedViewState = viewStateValue.replace(regExp, “\%2B”);
        params = ‘com.sun.faces.VIEW=’ + encodedViewState + ‘&excelRequestTableId=’ + tableId;
    }
    else {
        params = ‘excelRequestTableId=’ + tableId;   
    }
    return params;
}

When the state saving is server, there is no need to include the viewstate to the Ajax request, but when it is on client, params must contain the encoded view state which will be used to restore the component tree at Restore View phase. The whole thing is this actually, including the view state to the Ajax request imitating that it is a normal JSF request and gain access to the component tree when the state saving is method client.

The phaselistener responsible for handling the request and the response of excel requests is the following(excel creation with poi code omitted);

  
    
    

    public class AjaxPhaseListener implements PhaseListener {

  public void afterPhase(PhaseEvent phaseEvent) {
    FacesContext facesContext = phaseEvent.getFacesContext();
    Map requestMap = facesContext.getExternalContext().getRequestParameterMap();
    String excelRequestTableId = (StringrequestMap.get("excelRequestTableId");
    String excelResponseTableId = (StringrequestMap.get("excelResponseTableId");
    if (excelRequestTableId != null) {
      handleExcelRequest(facesContext, excelRequestTableId);
    else if(excelResponseTableId != null) {
      handleExcelResponse(facesContext, excelResponseTableId);
    }
  }

  private void handleExcelRequest(FacesContext facesContext, String excelRequestTableId) {
    UIComponent component = findComponentInRoot(excelRequestTableId);
    if (component != null && component instanceof HtmlDataTable) {
      HttpServletResponse response = (HttpServletResponsefacesContext.getExternalContext().getResponse();
      Map session = facesContext.getExternalContext().getSessionMap();

      HSSFWorkbook generatedExcel = null;
      generatedExcel = generateExcel(facesContext,(HtmlDataTablecomponent);
      if (generatedExcel != null) {
        session.put(excelRequestTableId, generatedExcel);
      }
    }
    facesContext.responseComplete();
  }
  
  private void handleExcelResponse(FacesContext facesContext, String excelResponseTableId) {
    Map session = facesContext.getExternalContext().getSessionMap();
    HSSFWorkbook excelWorkBook = (HSSFWorkbook)session.get(excelResponseTableId);
    HttpServletResponse response = (HttpServletResponsefacesContext.getExternalContext().getResponse();
    
    response.setContentType("application/vnd.ms-excel");
    try {
      excelWorkBook.write(response.getOutputStream());
    catch (IOException e) {
      e.printStackTrace();
    }
    session.remove("excelResponseTableId");
    facesContext.responseComplete();
  }

  public void beforePhase(PhaseEvent phaseEvent) {

  }

  public PhaseId getPhaseId() {
    return PhaseId.RESTORE_VIEW;
  }
      

Long story short, using this way an one can access the viewstate even it is saved on the client and the request is of type ajax. Exporting the data to as an excel is just the outcome of this approach for demonstrating a use case. Enough of text and here are two screenshots showing the export-to-excel support in a JSF datatable. After clicking the excel icon, user is informed by a text displaying “loading”. Reminds gmail a bit:) Yes, I admit, it is inspired by gmail. After the excel is ready, the excel icon is displayed again.

Free Image Hosting at www.ImageShack.us Free Image Hosting at www.ImageShack.us 

3 Responses to Hacking JSF State Management with AJAX

  1. Russ Wright says:

    This doesn’t seem to work with Oracle ADF Faces and the way they store client state (by using a token). Any ideas how to address that?

  2. bm. says:

    dfghdfghdfh

  3. Hanson Song says:

    Great article for my reference.
    Pardon me but, the isStateClient() would definitely return true as I’ve tested in my JSF1.1_02(SUN-RI) environment, even I set the follows in web.xml:

    javax.faces.STATE_SAVING_METHOD
    server


    The “com.sun.faces.VIEW” would contain a special id (I’d rather call it serverSideSavedViewStateId) like “_id1:_id2”, which would be posted back to server telling which view state in session to restore.
    I am using it to hack a AJAX implementation now.

%d bloggers like this: