After a conversation on the RichFaces forum, the following solution appeared (thanx to Brendan Healey):
RichLazyDataModel.java
public abstract class RichLazyDataModel<T> extends ExtendedDataModel<T> { private SequenceRange cachedRange; private Integer cachedRowCount; private List<T> cachedList; private Object rowKey; public abstract List<T> getDataList(int firstRow, int numRows); public abstract Object getKey(T t); public abstract int getTotalCount(); @Override public void walk(FacesContext ctx, DataVisitor dv, Range range, Object argument) { SequenceRange sr = (SequenceRange) range; if (cachedList == null || !equalRanges(cachedRange, sr)) { cachedList = getDataList(sr.getFirstRow(), sr.getRows()); cachedRange = sr; } for (T t : cachedList) { if (getKey(t) == null) { throw new IllegalStateException("found null key"); } dv.process(ctx, getKey(t), argument); } } @Override public void setRowKey(Object rowKey) { this.rowKey = rowKey; } @Override public Object getRowKey() { return rowKey; } @Override public boolean isRowAvailable() { return (getRowData() != null); } @Override public int getRowCount() { if (cachedRowCount == null) { cachedRowCount = getTotalCount(); } return cachedRowCount; } @Override public T getRowData() { for (T t : cachedList) { if (getKey(t).equals(this.getRowKey())) { return t; } } return null; } protected static boolean equalRanges(SequenceRange range1, SequenceRange range2) { if (range1 == null || range2 == null) { return range1 == null && range2 == null; } else { return range1.getFirstRow() == range2.getFirstRow() && range1.getRows() == range2.getRows(); } } @Override public int getRowIndex() { if (cachedList != null) { ListIterator<T> it = cachedList.listIterator(); while (it.hasNext()) { T t = it.next(); if (getKey(t).equals(this.getRowKey())) { return it.previousIndex() + cachedRange.getFirstRow(); } } } return -1; } @Override public void setRowIndex(int rowIndex) { int upperBound = cachedRange.getFirstRow() + cachedRange.getRows(); if (rowIndex >= cachedRange.getFirstRow() && rowIndex < upperBound) { int index = rowIndex % cachedRange.getRows(); T t = cachedList.get(index); setRowKey(getKey(t)); } } @Override public Object getWrappedData() { throw new UnsupportedOperationException("Not supported yet."); } @Override public void setWrappedData(Object data) { throw new UnsupportedOperationException("Not supported yet."); } public List<T> getCachedList() { return cachedList; }
}
ListState.java
public class ListState implements Serializable { private int page; private Map<String, Serializable> searchCriteria = new HashMap<String, Serializable>(); public int getPage() { return page; } public void setPage(int page) { this.page = page; } public Map<String,Serializable> getSearchCriteria() { return searchCriteria; }
}
CardsBean.java
@ManagedBean(name="cardsBean") public class CardsBean { @ManagedProperty("#{cardService}") private CardService cardService; private ListState state; private RichLazyDataModel<Card> cardsModel = new RichLazyDataModel<Card>() { @Override public List<Card> getDataList(int firstRow, int numRows) { MyUserDetails user = SecurityUtils.getCurrentUser(); return cardService.findUserCards(user.getUser(), firstRow, numRows, state.getSearchCriteria()); } @Override public Object getKey(Card card) { return card.getId(); } @Override public int getTotalCount() { MyUserDetails user = SecurityUtils.getCurrentUser(); return cardService.countUserCards(user.getUser(), state.getSearchCriteria()); } }; public RichLazyDataModel<Card> getCards() { return cardsModel; } public String getSearchString() { return (String)state.getSearchCriteria().get("searchString"); } public int getCurrentPage() { return state.getPage(); } public void setCurrentPage(int page) { state.setPage(page); } public void setSearchString(String searchString) { state.getSearchCriteria().put("searchString", searchString); } public void setCardService(CardService cardService) { this.cardService = cardService; } public boolean isPinned() { return Boolean.TRUE.equals(state.getSearchCriteria().get("pinned")); } public void setPinned(boolean pinned) { state.getSearchCriteria().put("pinned", pinned); } public void togglePinned() { setPinned(!isPinned()); } @PostConstruct public void init() { state = getFromSession("cardsList", null); if (state == null) { state = new ListState(); storeInSession("cardsList", state); } } public <T extends Serializable> T getFromSession(String name, T defaultValue) { T ret = (T) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(name); if (ret == null) { ret = defaultValue; } return ret; } public void storeInSession(String name, Serializable obj) { FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(name, obj); } }
cards.xhtml (partial)
... <h:form> <rich:dataGrid value="#{cardsBean.cards}" var="card" columns="2" elements="20" first="#{cardsBean.currentPage}" style="margin:0 auto;width:70em" id="cardsTable"> <f:facet name="header"> <h:inputText value="#{cardsBean.searchString}"> <a4j:ajax event="keyup" render=" cardsTable@body , cardsTable@footer "> <a4j:attachQueue requestDelay="700" ignoreDupResponses="true" /> </a4j:ajax> </h:inputText> </f:facet> <rich:panel id="cd"> <ui:include src="WEB-INF/parts/card.xhtml"> <ui:param name="card" value="#{card}"/> </ui:include> </rich:panel> <f:facet name="footer"> <rich:dataScroller page="#{cardsBean.currentPage}" /> </f:facet> </rich:dataGrid> </h:form> ...
source share