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;
}
}
March 11th, 2008 at 10:30 am
Sounds like something out of star trek…
March 25th, 2008 at 8:14 am
This works flawlessly. Thanks.
Reminder: don’t forget to wire the listener in your faces config file!
April 2nd, 2008 at 11:41 am
Post your faces-config.xml, too, perhaps?
April 15th, 2008 at 10:18 am
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:
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.
April 20th, 2008 at 8:15 am
Wow!!!
Thanks man…
We ve been breaking our head over this for the past few weeks…
It works simply superb…first shot….
April 22nd, 2008 at 9:59 pm
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?
April 24th, 2008 at 9:05 am
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…..
May 30th, 2008 at 11:39 am
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.