Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSF, RichFaces, pagination

I know there are quite a number of posts regarding JSF paginatin here, but none of them satisfed me.

To split pretty big data into pages I was going to use RichFaces Data scroller component.

It appeared to be suitable for that, but it looks like it does "artificial" pagination.

What i don't like here is that it loads all data and then merely displays part of it. At least it appears to do so. Correct me if i'm wrong.

Rich faces have nice demos, but by some reason they skip bean definitions- only xhtml. I saw some mention of ExtendedDataModel, but the only example i found didn't impress me. Why am i suposed to have 5 screen code just do display subset of data?

Honestly such a simple question as pagination happened to be very complex in JSF and i don't really get why.

So my questions are: 1. is it possible in Richfaces to load only one page of data 2. without storing it to session (i mean actual data not list state- current page, etc)

I'm interested only in richfaces lib as already use it in my project and don't what to introduce dependency on somethin else

thanx in advance

like image 227
Alexey Pilipchuk Avatar asked Aug 13 '12 15:08

Alexey Pilipchuk


1 Answers

After talking on RichFaces forum came up this the following solution (thanx to Brendan Healey):

RichLazyDataModel.java

/**
 * Extended data model
 * @author Brendan Healey
 */
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) {
            /*
            * the 2nd param is used to build the client id of the table
            * row, i.e. mytable:234:inputname, so don't let it be null.
            */
            throw new IllegalStateException("found null key");
        }
        dv.process(ctx, getKey(t), argument);
    }

}


/*
* The rowKey is the id from getKey, presumably obtained from
* dv.process(...).
*/
@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();
    }
}



/*
* get/setRowIndex are used when doing multiple select in an
* extendedDataTable, apparently. Not tested. Actually, the get method is
* used when using iterationStatusVar="it" & #{it.index}.
*/
@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

/**
 * Holds list state
 */
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>
...
like image 91
Alexey Pilipchuk Avatar answered Sep 22 '22 17:09

Alexey Pilipchuk