Phase-Listener Solution to JSF DataScroller Pagination Problem

I recently ran into a problem in JSF with the rich:dataScroller component. Basically, the dataScroller sets an attribute in its associated dataTable component to define which record it should start with. This allows for a simple pagination mechanism. If the dataTable is set to display 10 rows at a time, and it is supposed to start at index 0, then it shows the first page. When the page is advanced, the dataScroller tells the dataTable to start at index 10.

The problem arises when the dataTable is set to start at record n, and the model that it represents changes so that there are less than n records to display. In this situation, you’re suddenly seeing a page for which there are no records to see. You have to page back until you get to the “real” last page. As you can guess, this would probably be quite confusing to the end user.

A bug is logged for this defect here, and a workaround using Seam is described in a link from that URL. Unfortunately, I’m not using Seam and that solution doesn’t work for me. Another user proposed a workaround using a Phase-Listener. That solution did work for me, and I’ll post the details below. Hopefully, this will be useful to someone. If anyone has suggestions for improvements, please comment!

public class RenderResponsePhaseListener implements PhaseListener {
  private PhaseId phase = PhaseId.RENDER_RESPONSE;         

  public void beforePhase(PhaseEvent e)
  {
    //This is where we want to handle the pagination issue
    refreshUIDataFirstIndex();
  }         

  public void afterPhase(PhaseEvent e) {
  }         

  public void setPhase(PhaseId phase) {
    this.phase = phase;
  }         

  public PhaseId getPhaseId() {
    return phase;
  }         

  /**
  * Handles the pagination issue
  */
  private void refreshUIDataFirstIndex() {     

    ArrayList<UIData> allUIDataComponents = Util.getAllRenderedUIDataComponents();     

    for(UIData current : allUIDataComponents) {
      if(current.getFirst() > current.getRowCount()) {
        current.setFirst(0);
      }
    }     

  }         

}

public class Util {        

  /**
  * Gets the UIViewRoot
  * @return
  */
  public static UIViewRoot getUIViewRoot() {
    return FacesContext.getCurrentInstance().getViewRoot();
  }        

  /**
  * Gets all rendered UIData components in the UIViewRoot
  * @return
  */
  public static ArrayList<UIData> getAllRenderedUIDataComponents() {
    return getRenderedUIDataComponents(getUIViewRoot());
  }        

  /**
  * Gets all rendered UIData components in the UIComponent
  */
  private static ArrayList<UIData> getRenderedUIDataComponents(UIComponent currentComponent) {     

    ArrayList<UIData> allRenderedUIDataComponents = new ArrayList<UIData>();     

    if(currentComponent instanceof UIData) {     

      allRenderedUIDataComponents.add((UIData)currentComponent);     

    } else {     

      Iterator componentIterator = currentComponent.getFacetsAndChildren();
      while(componentIterator.hasNext()) {     

        UIComponent nextComponent = (UIComponent)componentIterator.next();
        if(nextComponent.isRendered()){     

          allRenderedUIDataComponents.addAll(getRenderedUIDataComponents(nextComponent));     

        }
      }
    }
    return allRenderedUIDataComponents;
  }       

}

17 Responses to “Phase-Listener Solution to JSF DataScroller Pagination Problem”

  1. bkdm Says:

    Sounds like something out of star trek…

  2. Richard Ogin Says:

    This works flawlessly. Thanks.

    Reminder: don’t forget to wire the listener in your faces config file!

  3. mugwump Says:

    Post your faces-config.xml, too, perhaps?

  4. FloorFLUX Says:

    Sorry for the slow response, but it looks like it would have been helpful for me to post the configuration for this fix as well. Basically, I like to keep some of the JSF configuration options in separate files, so I have the following in my web.xml file:

    <!-- Part of my web.xml file -->
    <context-param>
      <param-name>javax.faces.CONFIG_FILES</param-name>
      <param-value>/WEB-INF/managed-beans.xml, /WEB-INF/navigation-rules.xml, /WEB-INF/phase-listeners.xml</param-value>
    </context-param>
    ...
    

    Then, I have this in my phase-listeners.xml file:

    <!-- My phase-listeners.xml file -->
    <?xml version="1.0"?>
    
    <!DOCTYPE faces-config PUBLIC
    "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
    "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
    
    <faces-config>
    
      <lifecycle>
        <phase-listener>com.path.to.jsf.listener.RenderResponsePhaseListener</phase-listener>
      </lifecycle>
    
    </faces-config>
    

    That should wire up the listener…. Or you could just throw that lifecycle tag into your faces-config.xml file.

  5. subu Says:

    Wow!!!
    Thanks man…
    We ve been breaking our head over this for the past few weeks…
    It works simply superb…first shot….

  6. Jen Says:

    I tried the codes, and it works. but I have one more question.
    Whenever the data model change, I want to reset the scroller to page 1.
    how should I do it?

  7. FloorFLUX Says:

    That would be the ideal way for the scroller to work, but unfortunately, the UIData class doesn’t pay attention to what the model it represents does. I don’t know enough about JSF yet to know why, and as far as I know, there is no good fix like that. From what I can tell, even the real fix in the latest richfaces release doesn’t even address the issue that well (right), but I haven’t tested it yet…..

  8. Debby Says:

    Hi,

    I tried to implement the phaseListener, following the above code.
    but it seems the behavior is the same for either implement the phaselistener or not. I have tried to both scenerio for including the lifecycle tag in faces-config.xml and no including, the behavior for datascroller are the same.
    Please advise what might went wrong.

    Thanks.

  9. Mohan Says:

    Hello it will work when the entire page refreshes.
    but it is not working when you try to refresh only your list using jax call.

  10. Sam Says:

    Hi,

    Thanks for this snippet ! I ran into the opposite problem, datascroller state isn’t kept. E.g. : my datatable show 100 records at page 2, I edit the 110 record and click the save button that should bring me back to the page 2 but I always return to the first page… How can I keep datascroller state between my edit and list page ?

    Thx a lot !

  11. Ron Says:

    I had the kind of the same problem. I needed to reset the UIData after changing the datamodel. But instead of using a listener I decided to make a small util class that does this for me. As I know when the model has changed.

  12. Simon Says:

    I am French-speaking I appreciated your lesson,for more understanding on this lesson can you give me a complete version?I have a very big préocupation on this subject, thank you for your understanding

  13. Rudra Says:

    Great article…

    But still it is problematic. It resolves on issue and creating one more problm.

  14. Reid Says:

    Oh man, this was an absolute lifesaver for something I needed to fix on an older project branch stuck on richfaces 3.1.2. Thank you very much for writing this article and linking it to the original bug report.

  15. gburgalum01 Says:

    I attempted to implement this solution with no result. In my scenario, a user can do a search based on a certain category. When the category is switched, the data model is updated. I print out the number of rows in the UIComponent when the phase listener created in this article is executed. I noticed that the number of rows in the component has not been updated in accordance with the number of results loaded in the data model.

  16. Kwess Says:

    I’d fault to examine that too!

  17. Rocco Says:

    Great!!! this is the only solution that fixed my problem!!!
    Thank you thousand time!!! =D

Leave a Reply