6 Replies Latest reply on Feb 2, 2008 6:08 PM by Jason Long

    Here is a Richfaces Ajax Datascroler and Seam Example

    Jason Long Master

      I use the following to view large tables where fetching the all the results is not feasible. It has about 1 million rows.

      It does assume that you object implements the interface Idable shown next.

      
      import java.io.IOException;
      import java.io.Serializable;
      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.ScopeType;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.log.Log;
      
      import my.package.Idable;
      
      public abstract class BaseExtendedDataModel<T,ID extends Serializable> extends ExtendedDataModel implements BaseExtendedDataModelDAO<T, ID>{
      
       private @Logger Log log;
      
       @Out(scope=ScopeType.CONVERSATION, required=false)
       public List<T> listRow;
      
       private ID currentId;
       private Map<ID, T> wrappedData = new HashMap<ID, T>();
       private List<ID> wrappedKeys;
       private Long rowCount; // better to buffer row count locally
      
       public abstract Long getCount();
       public abstract List<T> getList(Integer firstRow, Integer maxResults);
       public abstract T findById(ID id);
      
       public void wrap(FacesContext context, DataVisitor visitor, Range range, Object argument, List<T> list) throws IOException
       {
       wrappedKeys = new ArrayList<ID>();
       wrappedData = new HashMap<ID, T>();
       for (T row : list)
       {
       Idable idable = (Idable) row;
       ID id = (ID) idable.getId();
       wrappedKeys.add(id);
       wrappedData.put(id, row);
       visitor.process(context, id, argument);
       }
       }
      
       public boolean hasById(ID id)
       {
       for (T row : listRow)
       {
       Idable idable = (Idable) row;
       Long rowId = (Long) idable.getId();
       if (rowId.equals(id))
       {
       return true;
       }
       }
       return false;
       }
      
       @Override
       public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException
       {
       int firstRow = ((SequenceRange) range).getFirstRow();
       int maxResults = ((SequenceRange) range).getRows();
       wrap(context, visitor, range, argument, getList(firstRow, maxResults));
       }
      
       /*
       * This method normally called by Visitor before request Data Row.
       */
       @Override
       public void setRowKey(Object key)
       {
       this.currentId = (ID) key;
       }
      
       @Override
       public int getRowCount()
       {
       if(rowCount == null)
       return (rowCount = this.getCount()).intValue();
       else
       return rowCount.intValue();
       }
      
       @Override
       public boolean isRowAvailable()
       {
       if (currentId == null) {
       return false;
       } else {
       return hasById(currentId);
       }
       }
      
       /**
       * This is main way to obtain data row. It is intensively used by framework.
       * We strongly recommend use of local cache in that method.
       */
       @Override
       public Object getRowData() {
       if (currentId == null) {
       return null;
       } else {
       T ret = wrappedData.get(currentId);
       if (ret == null) {
       ret = this.findById(currentId);
       wrappedData.put(currentId, ret);
       return ret;
       } else {
       return ret;
       }
       }
       }
      
       /**
       * Unused rudiment from old JSF staff.
       */
       @Override public Object getWrappedData() { throw new UnsupportedOperationException(); }
       @Override public int getRowIndex() { throw new UnsupportedOperationException(); }
       @Override public void setRowIndex(int rowIndex) { throw new UnsupportedOperationException(); }
       @Override public void setWrappedData(Object data) { throw new UnsupportedOperationException(); }
      
       /**
       * Unused update data.
       */
      // @Override public void update() { throw new UnsupportedOperationException(); }
      
       /**
       * TODO if this is never called by the framework why is it necessary.
       */
       @Override public Object getRowKey() { if (true) throw new UnsupportedOperationException(); return currentId; }
      
      }
      
      public interface BaseExtendedDataModelDAO<T, ID> {
      
      }
      
      
      

      Idable interface
      public interface Idable
      {
       public Long getId();
      }
      


      I extend the base class as shown below.



      import java.util.List;
      
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Name;
      
      import mypackage.general.access.Access;
      
      @Name("accessExtendedDataModel")
      public class AccessExtendedDataModel extends BaseExtendedDataModel<Access, Long>
      {
       @In(create=true) AccessDAO accessDAO;
      
       @Override
       public Long getCount()
       {
       return accessDAO.getCount();
       }
      
       @Override
       public Access findById(Long id)
       {
       return accessDAO.findById(id);
       }
      
       @Override
       public List<Access> getList(Integer firstRow, Integer maxResults)
       {
       return listRow = accessDAO.getRange(firstRow, maxResults);
       }
      


      Then in the facelets page

       <a4j:form>
       <rich:datascroller for="accessList" />
       <rich:spacer height="10" />
       <rich:dataTable id="accessList"
       value="#{accessExtendedDataModel}"
       var="accessL"
       styleClass="f_table"
       rowClasses="tr0,tr1"
       rows="30">
       <rich:column id="id">
       <f:facet name="header">id</f:facet>
       <h:outputText value="#{accessL.id}" />
       </rich:column>
       <rich:column id="User">
       <f:facet name="header">User</f:facet>
       <h:outputText value="#{accessL.usr.usr}" />
       </rich:column>
       </rich:dataTable>
       </a4j:form>
      


      The scroller is now connected to the datatable. This is a first pass and was adapted for an example in the Richfaces cvs I took a while back.

      The paging is all done with Ajax and only 30 objects are loaded at a time.

      Any suggestions for improvement are most welcome.


        • 1. Re: Here is a Richfaces Ajax Datascroler and Seam Example
          Jason Long Master

          If your object does not implement Idable you can override the methods shown below. These are the only methods that depend on this. I will need to do this in some cases where I am returning a list of maps instead a list of entities.

          public void wrap(FacesContext context, DataVisitor visitor, Range range, Object argument, List<T> list) throws IOException;
          public boolean hasById(ID id);
          


          • 2. Re: Here is a Richfaces Ajax Datascroler and Seam Example
            Alexander Seitz Newbie

            Hi,

            thanks you for the code.. lazy loading with Richfaces datatable is really helpul for me. But I am not using DAO's to access my Entities. I am using the normal EntityManager.createQuery(..) method.

            I would have to make 2 queries one for getCount and one for getList (and a third for getId), correct?

            Regards,

            Alexander

            • 3. Re: Here is a Richfaces Ajax Datascroler and Seam Example
              Jason Long Master

              Yes. You should be able to just override the methods with your custom queries.

              Please let me know how it goes as I want to improve on this, but do not have the time currently to spend.

              If you have problems I will try to help.

              • 4. Re: Here is a Richfaces Ajax Datascroler and Seam Example
                Alexander Seitz Newbie

                @supernovasoftware.com
                Thanks for offering your help :-)

                As far as I understand the code, I would need a separate seam component (representing the DataModel and extending BaseExtendedDataModel) for each query of which result I want to page through the results.

                I will give it a try if I have time and keep you updated.

                Regards,

                Alexander

                • 5. Re: Here is a Richfaces Ajax Datascroler and Seam Example
                  Vassilis Petropoulos Newbie

                  I tried to run your code but i am getting NotSupportedException because setRowIndex is called. Is that expected?. Any idea?

                  Thanks,
                  V.

                  • 6. Re: Here is a Richfaces Ajax Datascroler and Seam Example
                    Jason Long Master

                    Here is a more recent version of the code. This time I have the query return a List of maps for an HQL query.

                    I just override get id and it works. Try this more recent version. Are you using Seam 2.0.x?

                    I have not seen too much interest in my method. If people like the technique employed, I can post an example when I get some time.


                    
                    import java.io.IOException;
                    import java.io.Serializable;
                    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.annotations.Logger;
                    import org.jboss.seam.log.Log;
                    
                    import com.xxx.ui.Idable;
                    
                    public abstract class BaseExtendedDataModel<T,ID extends Serializable> extends ExtendedDataModel implements BaseExtendedDataModelDAO<T, ID>{
                    
                     private @Logger Log log;
                    
                     int rowNum=-1;
                    
                     public int getRowNum()
                     {
                     return ++rowNum;
                     }
                    
                     public List<T> listRow;
                    
                     private ID currentId;
                     private Map<ID, T> wrappedData = new HashMap<ID, T>();
                     private List<ID> wrappedKeys;
                     private Long rowCount; // better to buffer row count locally
                    
                     public abstract Long getCount();
                     public abstract List<T> getList(Integer firstRow, Integer maxResults);
                     public abstract T findById(ID id);
                    
                     public ID getId(T row)
                     {
                     Idable idable = (Idable) row;
                     ID id = (ID) idable.getId();
                     return id;
                     }
                    
                     public void wrap(FacesContext context, DataVisitor visitor, Range range, Object argument, List<T> list) throws IOException
                     {
                     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(context, id, argument);
                     }
                     }
                    
                     public boolean hasById(ID id)
                     {
                     for (T row : listRow)
                     {
                     ID rowId = getId(row);
                     if (rowId.equals(id))
                     {
                     return true;
                     }
                     }
                     return false;
                     }
                    
                     @Override
                     public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException
                     {
                     int firstRow = ((SequenceRange) range).getFirstRow();
                     int maxResults = ((SequenceRange) range).getRows();
                     log.info("("+firstRow +", "+ maxResults+")");
                     wrap(context, visitor, range, argument, getList(firstRow, maxResults));
                     }
                    
                     /*
                     * This method normally called by Visitor before request Data Row.
                     */
                     @Override
                     public void setRowKey(Object key)
                     {
                     this.currentId = (ID) key;
                     }
                    
                     @Override
                     public int getRowCount()
                     {
                     if(rowCount == null)
                     return (rowCount = this.getCount()).intValue();
                     else
                     return rowCount.intValue();
                     }
                    
                     @Override
                     public boolean isRowAvailable()
                     {
                     if (currentId == null) {
                     return false;
                     } else {
                     return hasById(currentId);
                     }
                     }
                    
                     /**
                     * This is main way to obtain data row. It is intensively used by framework.
                     * We strongly recommend use of local cache in that method.
                     */
                     @Override
                     public Object getRowData() {
                     if (currentId == null) {
                     return null;
                     } else {
                     T ret = wrappedData.get(currentId);
                     if (ret == null) {
                     ret = this.findById(currentId);
                     wrappedData.put(currentId, ret);
                     return ret;
                     } else {
                     return ret;
                     }
                     }
                     }
                    
                    
                     // Unused rudiment from old JSF staff.
                     @Override public int getRowIndex() { throw new UnsupportedOperationException(); }
                     @Override public void setRowIndex(int rowIndex) { throw new UnsupportedOperationException(); }
                     @Override public Object getWrappedData() { throw new UnsupportedOperationException(); }
                     @Override public void setWrappedData(Object data) { throw new UnsupportedOperationException(); }
                    
                     // TODO if this is never called by the framework why is it necessary.
                     @Override public Object getRowKey() { throw new UnsupportedOperationException(); }
                    
                    }
                    
                    


                    
                    
                    import java.util.List;
                    import java.util.Map;
                    
                    import org.jboss.seam.ScopeType;
                    import org.jboss.seam.annotations.In;
                    import org.jboss.seam.annotations.Logger;
                    import org.jboss.seam.annotations.Name;
                    import org.jboss.seam.annotations.Out;
                    import org.jboss.seam.annotations.Scope;
                    import org.jboss.seam.log.Log;
                    
                    import com.xxx.dao.richfaces.BaseExtendedDataModel;
                    import com.xxx.search.PipeSearch;
                    
                    @Name("pipeSearchExtendedDataModel")
                    @Scope(ScopeType.CONVERSATION)
                    public class PipeSearchExtendedDataModel extends BaseExtendedDataModel<Map<String, Object>, Long>
                    {
                     private @Logger Log log;
                    
                     @In(create=true) PipeSearchDAO pipeSearchDAO;
                    
                     @In(required=false) @Out(required = false)
                     private PipeSearch pipeSearch;
                     public PipeSearch getPipeSearch() { return pipeSearch; }
                     public void setPipeSearch(PipeSearch pipeSearch) { this.pipeSearch = pipeSearch; }
                    
                     @Override
                     public Long getId(Map<String, Object> row)
                     {
                     return (Long) row.get("minId");
                     }
                    
                     @Override
                     public Long getCount()
                     {
                     return pipeSearchDAO.getCount(pipeSearch);
                     }
                    
                     @Override
                     public Map<String, Object> findById(Long id)
                     {
                     return pipeSearchDAO.findResultById(id);
                     }
                    
                     @Override
                     public List<Map<String, Object>> getList(Integer firstRow, Integer maxResults)
                     {
                     return listRow = pipeSearchDAO.getList(pipeSearch,firstRow, maxResults);
                     }
                    
                    }