Solution using Seam to pagination issue with JSF on DataModel updates
November 13, 2007
In an implementation using the JBoss RichFaces rich:dataScroller component, I noticed the first attribute of a UIData component is maintained even if the DataModel it is displaying has changed. It turns out, this is an issue with JSF. The UIData component does not track the DataModel it represents. Thus, when the DataModel is updated it has no effect on the first attribute of the UIData component. In defense of JSF, it would be difficult to know when a DataModel update should trigger an update of the UIData as this would require making some assumptions about the model.
As you can imagine, this can lead to consistency issues with data versus the state of the UI when pagination is used. For example, if I have a DataModel that wraps a List of 100 elements that are the results of a search. In the display, I can show 10 elements per page and allow the user to paginate freely with the rich:dataScroller component. If the user paginates to the fifth listing, the first attribute of the UIData component now points to 50. If the user then narrows the search (by changing some search criteria and re-submitting) the result set is reduced to 10. Now we have an issue. The user sees no results because the UIData is looking for index 50 to start the List. The first attribute does not get updated to reflect the change in the model.
So how can we resolve this? A simple Seam interceptor would do the trick (if you are already using stereotypes this will be a snap). Assuming you have not yet created a stereotype for your Action classes, the following steps can be taken,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Interceptors(ActionInterceptor.class)
@Inherited
public @interface Action {}
This will ensure that any @Action class will be intercepted by the ActionInterceptor. Now create a method-level annotation similar to the following:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Search {
String dataTableId();
}
The dataTableId is the component ID of the DataTable in your page. Note: This will require that you specify unique IDs for your components which is good practice in JSF anyway. Next, you must provide the interceptor which does the work of reseting the first attribute for the component. When an @Search action is executed that resets the DataModel, we want to reset the UIData:
@Interceptor(stateless=true)
public class ActionInterceptor implements Serializable
{
...
@AroundInvoke
public Object aroundInvoke(InvocationContext invocation)
throws Exception
{
Method method = invocation.getMethod();
if(method.isAnnotationPresent(Search.class))
{
UIComponent component = FacesContext
.getCurrentInstance().getViewRoot()
.findComponent(searchAnnotation.dataTableId());
if(component != null && component instanceof UIData)
{
UIData dataTable = (UIData) component;
dataTable.setFirst(0);
}
}
result = invocation.proceed();
}
...
You would probably want to add your own exception handling to this as well. Finally, we can use it in our application:
@Name("myAction")
@Stateful
@Action
public class MyActionBean implements MyAction {
...
@DataModel
List<myentity> searchResults;
...
@Search(dataTableId="myForm:searchResults")
public void search() throws Exception
{
searchResults = entityManager.createQuery(...)
.getResultList();
}
...
As I mentioned before, if stereotypes are already being used, this becomes a snap. Simply add the @Search annotation and add the code to handle the dataTable reset in your existing ActionInterceptor.








Posted in 


content rss
December 20th, 2007 at 2:50 am
great article !! it solved the problem in a breeze !!!!
January 29th, 2008 at 2:52 am
Wonderful idea.
Just a question: is it needed that the component ‘myAction’ be @Stateful?
January 29th, 2008 at 8:51 am
No, it is simply required that the
@DataModelbe available from the context (i.e. PAGE or CONVERSATION). So if you wanted to makemyAction @Stateless, just make sure you outject to the appropriate context and you will be all set.March 7th, 2008 at 11:42 pm
Why not just have a refresh() method that resets the results and the position. Since you are re-performing the search why not make the index reset a part of the research?
However, it is a great example on the power of creating your own stereotypes and using annotations/interceptors to neatly solve problems.
March 8th, 2008 at 8:18 am
Thanks for the comment Andy!
Yes, this is certainly another option, but I thought it was a nice example (outside of the usual auditing, logging, security, etc) to demonstrate using stereotypes and get others thinking. As you mention, you could always abstract the repeated JSF invocation out to some helper method and invoke it in a refresh(), but that’s just not quite as exciting