9 Replies Latest reply on Jun 12, 2013 12:03 AM by Yanlin Ye

    Why is pagination that complex in JSF?

    Alexey Pilipchuk Newbie

      In my project i need absolutelly common thing: split big data into pages and create links to switch the pages.

       

      All of a sudden it happened to be very complex task in JSF and RichFaces in particular. I searched piles of discussions dating back to early 2000 to today.

       

      All solutions i saw were very cumbersome: people load all data,  store it somewhere in session or client side and then do pagination. I don't really get why it is so heavy and resource demanding.

       

      RichFaces is a very nice framework it has all the stuff i need including data scroller. But it looks like it worships the same approach- load all show part.

       

      Example with SerializableDataModel didn't impress me. Why do i need 5 screen code just to display the result of query selest * from table limit 1, 10?

       

      Am i missing something or it is the only way to avoind load all problem in RichFaces?

       

      Wouldn't it be nice to have something like that:

       

      <foo:dataTable value="#{recordSource}" var="record" ...>

      ...

           <foo:datascroller maxPages="5" />

      </foo:dataTable>

       

      where recordSource would implement interface like that:

       

      interface Foo {

           List<Item> getData(int curPage, int pageSize, Object additionalOptionalStuffLikeSearchQueryOrSortOrder);

      }

       

      wher gatData(...) would be called upon page change

       

      It's just a sketch i don't pretend to final design.

       

      So the question is:

       

      Is there any simple way in RichFaces to achieve pagination without loading all data into memory storing it in a session or "hacking" tags with binding and custom data models?

       

      Don't get me wrong am not lazy to write a couple of lines of code i just think that such a common thing as a pagination can have standard and simple implementation

       

      Looking forward to hear any ideas and opinions

        • 1. Re: Why is pagination that complex in JSF?
          Brendan Healey Master

          To an extent a lot of the complexity is unavoidable but I think that there's a lot of room for improvement in the documentation.

          Based initially on the example from Practical Richfaces 2 but with quite a lot of changes I've written a generic abstract class

          to make this a bit easier and this requires only that you override 3 abstract methods:

           

          • getDataList - return a List of the generic type of the concrete instantiation of the abstract class. If you're using JPA you'd

                  typically be using something like: query.setFirstResult(firstRow).setMaxResults(numRows).getResultList();

          • getKey - return the row id.
          • getTotalCount - the overall number of records in the view.

           

          I normally use this by instantiating an anonymous inner class as shown in the comments in the code. I've had to modify this

          code to handle using iterationStatusVar and using row level ajax updates (render="table:@rows(...):...", so it ought to be

          reasonably mature.

           

          Regards,

          Brendan.

           

          RichLazyDataModel.java

          ------------------------------------

          package uk.co.myco.jsfbeans.helper;

           

          import java.util.List;

          import java.util.ListIterator;

          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;

           

          /*

          * @author Brendan Healey

          *

          * Works with a RichFaces rich:extendedDataTable, like this:

          *

          * private RichLazyDataModel<Event> model;

          *

          * model = new RichLazyDataModel<Event>() {

          *

          *      @Override

          *      public List<Event> getDataList(int firstRow, int numRows) {

          *          return selectedEntity.getEventList(firstRow, numRows);

          *      }

          *

          *      @Override

          *      public Object getKey(Event t) {

          *          return t.getId();

          *      }

          *

          *      @Override

          *      public int getTotalCount() {

          *          return selectedEntity.getEventListCount().intValue();

          *      }

          * };

          *

          */

           

          public abstract class RichLazyDataModel<T> extends ExtendedDataModel<T> {

           

              private SequenceRange cachedRange = null;

              private Integer cachedRowCount = null;

              private List<T> cachedList = null;

              private Object rowKey;

           

              public abstract List<T> getDataList(int firstRow, int numRows);

              public abstract Object getKey(T t);

              public abstract int getTotalCount();

           

              public RichLazyDataModel() {

                  super(); // shouldn't be needed try removing

              }

           

              @Override

              public void walk(FacesContext ctx, DataVisitor dv, Range range, Object argument) {

           

                  SequenceRange sr = (SequenceRange) range;

           

                  if (cachedList == null || !equalRanges(cachedRange, sr)) {

           

                      int firstRow = sr.getFirstRow();

                      int numRows = sr.getRows();

           

                      //Log.log("firstRow: " + firstRow + " numRows: " + numRows);

           

                      cachedList = getDataList(firstRow, numRows);

                      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;

              }

          }

          1 of 1 people found this helpful
          • 2. Re: Why is pagination that complex in JSF?
            Puspendu Banerjee Newbie

            Server Side Pagination was always complex, but now a day, a number of framework e.g. JBoss Seam has made life a lot easier.

            Give it a try!

            • 3. Re: Why is pagination that complex in JSF?
              Brendan Healey Master

              Puspendu, could you be more specific as to how Seam is able to help in this respect? Any personal experience you

              have is likely to be of interest to the wider community, but perhaps provide more details,

               

              Regards,

              Brendan.

              • 4. Re: Why is pagination that complex in JSF?
                Puspendu Banerjee Newbie

                My Personal Experience with seam is poor though you may read this blog.

                • 5. Re: Why is pagination that complex in JSF?
                  Alexey Pilipchuk Newbie

                  Thanx Brendan,

                   

                  i was thinking about nearly the same approach- create some abstract wrapper around ExtendedDataModel not to invent a wheel every time i need a paged list.

                   

                  My final solution is to utilize your RichLazyDataModel and store list state (current page, search criteria, sort orde) in the session

                   

                  I just wonder why such a simple and needed solution is not a part of the framework. All you need is a standard way of storing list state and pagable data model

                   

                  Had there been something like RichLazyDataModel and clear example how to implement simple paged list with it i wouldn't have wasted several days.

                   

                  Do you mind if i repost your code on stackoverflow with a refference to you?

                  • 6. Re: Why is pagination that complex in JSF?
                    Alexey Pilipchuk Newbie

                    Hi Puspendu,

                     

                    my main concern was not not being able to implement paged list in jsf, but unnecessary complexity of existing solutions.

                     

                    I heard of Seam many time and would love to try it in the future projects, but it looks excessive introducing dependency on whole big framework just to split list into pages

                    • 7. Re: Why is pagination that complex in JSF?
                      Brendan Healey Master

                      >Do you mind if i repost your code on stackoverflow with a refference to you?

                       

                      Please feel free.

                       

                      Regards,

                      Brendan.

                      • 8. Re: Why is pagination that complex in JSF?
                        Puspendu Banerjee Newbie

                        Alexy,

                        Well, Seam is not very large at it's core but your dependency graph becomes larger as you start to use more features.

                         

                        Regards,

                        Puspendu

                        • 9. Re: Why is pagination that complex in JSF?
                          Yanlin Ye Newbie

                          Hello Brendan,

                           

                          Thank you very much for posting the code for an abstract lazy loading data model.  The code looks clean and straightforward.  I was trying to use the class in my project but got an error.  See below for the abridged error message:

                           

                          java.lang.IllegalArgumentException: Page size must not be less than or equal to zero!

                              at org.springframework.data.domain.PageRequest.<init>(PageRequest.java:74)

                              at my.beans.JobFilter.filterJobs(JobFilter.java:247)

                              at my.beans.JobsBean$DataModel.getDataList(JobsBean.java:103)

                              at util.RichLazyDataModel.walk(RichLazyDataModel.java:65)

                              at org.richfaces.model.ArrangeableModel.initializeRowKeys(ArrangeableModel.java:225)

                              at org.richfaces.model.ArrangeableModel.arrange(ArrangeableModel.java:194)

                              at org.richfaces.component.UIDataTableBase.createExtendedDataModel(UIDataTableBase.java:265)

                              at org.richfaces.component.UIDataAdaptor.getExtendedDataModel(UIDataAdaptor.java:459)

                              at org.richfaces.component.UIDataAdaptor.getRowCount(UIDataAdaptor.java:510)

                              ...

                              at org.richfaces.DataScrollerUtils.eval(DataScrollerUtils.java:86)

                              at org.richfaces.DataScrollerUtils.getRowCount(DataScrollerUtils.java:81)

                              at org.richfaces.event.DataTablePreRenderListener.processEvent(DataTablePreRenderListener.java:122)

                           

                          The exception was thrown by Spring Data because the specified page size (or numRows as the second parameter of RichLazyDataModel.getDataList()) was -1, which was originated from ArrangeableModel (line 225, RichFaces 4.3.2):

                           

                          originalModel.walk(context, new DataVisitor() {

                              public DataVisitResult process(FacesContext context, Object rowKey, Object argument) {

                                  originalModel.setRowKey(rowKey);

                                  if (originalModel.isRowAvailable()) {

                                      rowKeys.add(rowKey);

                                  }

                                  return DataVisitResult.CONTINUE;

                              }

                          }, new SequenceRange(0, -1), null);

                           

                          I am not sure why RichFaces called walk() to figure out the row count and why -1 was used instead of my specified page size (by the "rows" attribute of the data table).  Here is my code extending the lazy data model, as a static inner class of my view scoped bean:

                           

                          /**

                          * Data model for supporting server side pagination.

                          */

                          private static class DataModel extends RichLazyDataModel<AbstractJob> implements Serializable {

                              private static final long serialVersionUID = 1L;

                              @Override

                              public List<AbstractJob> getDataList(int firstRow, int numRows) {

                                  return FacesHelper.findBean(JobFilter.class).filterJobs(firstRow, numRows);

                              }

                              @Override

                              public Object getKey(AbstractJob t) {

                                  return t.getId();

                              }

                              @Override

                              public int getTotalCount() {

                                  return FacesHelper.findBean(JobFilter.class).countJobs();

                              }

                          }

                           

                          In my JSF page, I used <rich:extendedDataTable>.  I also tried dataTable with no different result.

                           

                          <rich:extendedDataTable

                              id="table" value="#{jobsBean.dataModel}" var="r"

                              rows="20" rowClasses="odd,even">

                              ...

                              <f:facet name="footer">

                                  <rich:dataScroller maxPages="9" fastControls="hide"/>

                              </f:facet>

                          </rich:extendedDataTable>

                           

                          Did I miss something or do something wrong?  I am developing on a Win7 box with Tomcat 7, if that matters at all.

                           

                          Thanks a lot for your help,

                           

                          Yanlin