Richfaces Datascroler, EntityQuery and pagination
jagin Feb 23, 2008 10:34 PMLast time i was trying to use rich:dataTable, rich:datascroller with large tables and I want to use for this EntityQuery and do some pagination.
I asume that i need some custom DataModel extending org.ajax4jsf.model.ExtendedDataModel (thanks to this post 'Here is a Richfaces Ajax Datascroler and Seam Example'.
So here it is - my solution:
import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import org.ajax4jsf.model.DataVisitor; import org.ajax4jsf.model.ExtendedDataModel; import org.ajax4jsf.model.Range; import org.ajax4jsf.model.SequenceRange; import org.jboss.seam.framework.EntityQuery; import pl.xxx.model.AbstractEntity; public abstract class EntityDataModel<T extends AbstractEntity, ID extends Serializable> extends ExtendedDataModel { private static final String ID_METHOD_NAME = "getId"; private EntityQuery entityQuery; private Class<T> entityClass; private ID currentId; private Map<ID, T> wrappedData = new HashMap<ID, T>(); private List<ID> wrappedKeys; public EntityDataModel() { //entityClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; Class clazz = getClass(); while(!(clazz.getGenericSuperclass() instanceof ParameterizedType)) { clazz = clazz.getSuperclass(); } entityClass = (Class<T>) ((ParameterizedType)clazz.getGenericSuperclass()).getActualTypeArguments()[0]; } public void setEntityQuery(EntityQuery entityQuery) { this.entityQuery = entityQuery; } @Override public Object getRowKey() { return currentId; } /* * This method normally called by Visitor before request Data Row. */ @Override public void setRowKey(Object key) { currentId = (ID)key; } @Override public void walk(FacesContext fCtx, DataVisitor visitor, Range range, Object argument) throws IOException { int firstResult = ((SequenceRange)range).getFirstRow(); int maxResults = ((SequenceRange)range).getRows(); entityQuery.setFirstResult(firstResult); entityQuery.setMaxResults(maxResults); List<T> list = entityQuery.getResultList(); wrappedKeys = new ArrayList<ID>(); wrappedData = new HashMap<ID, T>(); for (T row : list) { ID id = getId(row); wrappedKeys.add(id); wrappedData.put(id, row); visitor.process(fCtx, id, argument); } } @Override public int getRowCount() { return entityQuery.getResultCount().intValue(); } @Override public Object getRowData() { if (currentId == null) { return null; } else { T ret = wrappedData.get(currentId); if (ret == null) { ret = (T)entityQuery.getEntityManager().find(entityClass, currentId); wrappedData.put(currentId, ret); return ret; } else { return ret; } } } @Override public int getRowIndex() { return 0; } /** * Unused rudiment from old JSF staff. */ @Override public Object getWrappedData() { throw new UnsupportedOperationException(); } @Override public boolean isRowAvailable() { if (currentId == null) { return false; } else { for (T row : (List<T>)entityQuery.getResultList()) { ID rowId = getId(row); if (rowId.equals(currentId)) { return true; } } return false; } } /** * Unused rudiment from old JSF staff. */ @Override public void setRowIndex(int newRowIndex) { } /** * Unused rudiment from old JSF staff. */ @Override public void setWrappedData(Object data) { throw new UnsupportedOperationException(); } private ID getId(T row) { ID id; try { Method idMethod = row.getClass().getMethod(ID_METHOD_NAME, new Class[0]); Class<?> idType = idMethod.getReturnType(); id = (ID)idMethod.invoke(row, new Object[0]); } catch (Exception e) { throw new javax.faces.FacesException("Failed to obtain row id", e); } return id; } }
Next can use this abstrac EntityDataModel for our custom entity data model like this:
import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.framework.EntityQuery; import pl.xxx.model.User; @Name("userList") public class UserDataModel extends EntityDataModel<User, Long> { @In(create=true) private EntityQuery users; @Create public void create() { setEntityQuery(users); } }
In components.xml we need to define our EntityQuery:
<fwk:entity-query name="users" entity-manager="#{em}" ejbql="select u from User u" order="u.username" max-results="10"> <fwk:restrictions> <value>lower(u.username) like lower('%' || #{exampleUser.username})</value> <value>lower(u.lastName) like lower('%' || #{exampleUser.lastName})</value> </fwk:restrictions> </fwk:entity-query> <component name="exampleUser" class="pl.xxx.model.User" scope="session"/>
Now the users.xhtml:
<rich:panel header="#{messages['userList.header']}"> <h:form> <h:outputLabel value="Filter by username" for="username"></h:outputLabel> <h:inputText id="username" value="#{exampleUser.username}"> <a4j:support reRender="userList" event="onkeyup" requestDelay="1" /> </h:inputText> <a4j:status> <f:facet name="start"><h:graphicImage value="/img/spinner.gif" /></f:facet> </a4j:status> </h:form> <h:form> <rich:dataTable id="userList" value="#{userList}" var="user" rows="10"> <rich:column> <f:facet name="header">#{messages['user.username']}</f:facet> <s:link action="user"> <h:outputText value="#{user.username}" /> <f:param name="uid" value="#{user.id}" /> </s:link> </rich:column> <rich:column> <f:facet name="header">#{messages['user.firstName']}</f:facet> <h:outputText value="#{user.firstName}"/> </rich:column> <rich:column> <f:facet name="header">#{messages['user.lastName']}</f:facet> <h:outputText value="#{user.lastName}"/> </rich:column> <rich:column> <f:facet name="header">#{messages['user.email']}</f:facet> <h:outputText value="#{user.email}"/> </rich:column> <f:facet name="footer"> <rich:datascroller/> </f:facet> </rich:dataTable> </h:form> <div class="actionButtons"> <s:button action="user" value="#{messages['button.add']}"> <f:param name="uid" value=""/> </s:button> </div> </rich:panel>
I also use some ajax support for filtering data but there is one problem. Suppose i have shown all users and i'm on the last page. After doing some filtering datascroller is not reacting properly on the data range change. I don't know how to fix this.
What do you think about this solution?
Jarek