Primefaces custom data scroller component

Goal

Creation of a data scroller composite component that displays more data on demand

Description

In a recent project, I had a requirement to load a list of items in a lazy loaded way. Primefaces data scroller with lazy loading seemed the right choice. However, when I addded a command link to be able to interact with each of the items in the list, I didn’t see the action being invoked. After a little research, I found a couple of links (link1, link2, …) which basically, state that it won’t be possible to make an action be invoked from an item within a list that was loaded in a lazy manner.

In one of those links, there was also a suggestion where it would be possible to have a lazy loaded data list but the component would be more or less self made. In this recipe, I’ll explain the steps towards the creation of a custom made lazy loaded data scroller which may be used in other projects, through the creation of a custom facelets composite component.

How to

This recipe consists on the creation of:

  1. The composite component, in the file src/main/webapp/resources/myproject/data_scroller.xhtml, whose code follows:
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    xmlns:composite="http://java.sun.com/jsf/composite"
    xmlns:lnk="http://www.linkare.com/setvaluehandler/tags">
    
    <composite:interface>
    <!-- Serves as the backing bean instance -->
    <composite:attribute name="value"
    shortDescription="The instance of the datascroller data"
    type="com.linkare.myproject.web.component.IDataScrollerData"
    required="true" />
    <!-- It will be used as the variable to iterate through the list. Overriding the var attribute is only permitted through the tag library component that will be shown next... -->
    <composite:attribute name="var"
    shortDescription="The name of the dataScroller var attribute"
    default="entry" />
    <!-- To be able to configure the name of the load more label -->
    <composite:attribute name="loadMoreLabel" shortDescription="The label of the load more link" default="Load more" />
    <!-- To be able to specify which part of the page should be updated -->
    <composite:attribute name="update" shortDescription="The region to update" default="@form" />
    <!-- To be able to define the name of blockUI widget -->
    <composite:attribute name="blockUIWidget" shortDescription="The name of the block ui widget" required="true" />
    </composite:interface>
    <composite:implementation>
    <h:panelGroup id="dataScrollerGroup" layout="block">
    <p:dataScroller value="#{cc.attrs.value.records}">
    <ui:remove>
    Set the var attribute with the configured value. This is necessary because var only accepts a string.
    See http://marcus-christie.blogspot.pt/2007/03/setting-var-attribute-of-datatable-in.html
    </ui:remove>
    <!-- This is a very important trick: the var attribute in the component does not accept an expression language, but only a string value. Therefore, we set the value of the var attribute through the call to method setVar of the component, through a specific tag library method -->
    <lnk:setValue methodName="setVar" value="#{cc.attrs.var}" />
    <composite:insertChildren />
    <f:facet name="loader">
    <h:outputText value="" />
    </f:facet>
    </p:dataScroller>
    <div class="row">
    <div class="col-md-12">
    <p:commandLink value="#{cc.attrs.loadMoreLabel}" process="@this" update="#{cc.attrs.update}" action="#{cc.attrs.value.loadMore()}" rendered="#{cc.attrs.value.totalCount gt cc.attrs.value.recordsSize}" onstart="PF('#{cc.attrs.blockUIWidget}').show()" oncomplete="PF('#{cc.attrs.blockUIWidget}').hide()" />
    </div>
    </div>
    <p:blockUI block="dataScrollerGroup">
    <!-- Same trick to specify the widgetVar attribute of the blockUI component -->
    <lnk:setValue methodName="setWidgetVar" value="#{cc.attrs.blockUIWidget}" />
    </p:blockUI>
    </h:panelGroup>
    </composite:implementation>
    </html>
    
  2. The interface that exposes the necessary behaviour one wants to see attached to the cmponent (a possible implementation will be shown next):
    package com.linkare.myproject.web.component;
    
    import java.util.List;
    
    /**
     *
     * @author Paulo Zenida - Linkare TI
     *
     */
    public interface IDataScrollerData<T> {
      public void search();
      public void loadMore();
      public long getTotalCount();
      public List<T> getRecords();
      public long getRecordsSize();
    }
    
  3. The tag library class:
    package com.linkare.myproject.web.component;
    
    import java.io.IOException;
    import java.lang.reflect.Method;
    
    import javax.el.ELException;
    import javax.faces.FacesException;
    import javax.faces.component.UIComponent;
    import javax.faces.view.facelets.FaceletContext;
    import javax.faces.view.facelets.FaceletException;
    import javax.faces.view.facelets.TagAttribute;
    import javax.faces.view.facelets.TagConfig;
    import javax.faces.view.facelets.TagHandler;
    
    /**
    *
    * http://marcus-christie.blogspot.pt/2007/03/setting-var-attribute-of-datatable-in.html
    *
    * @author Paulo Zenida - Linkare TI
    *
    */
    public class SetValueTagHandler extends TagHandler {
    
      private final TagAttribute methodName;
      private final TagAttribute value;
    
      public SetValueTagHandler(TagConfig config) {
        super(config);
        this.methodName = this.getRequiredAttribute(&quot;methodName&quot;);
        this.value = this.getRequiredAttribute(&quot;value&quot;);
      }
      public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException, ELException {
        try {
          Method m = parent.getClass().getMethod(this.methodName.getValue(ctx), new Class[] { String.class });
          m.invoke(parent, new Object[] { this.value.getValue(ctx) });
        } catch (Exception e) {
          e.printStackTrace();
        }
        this.nextHandler.apply(ctx, parent);
      }
    }
    
  4. The tag library definition, in file src/main/webapp/WEB-INF/setvaluehandler.taglib.xml:
    <xml version="1.0"?>
    <!DOCTYPE facelet-taglib PUBLIC
    "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
    "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
    <facelet-taglib>
    <namespace>http://www.linkare.com/setvaluehandler/tags</namespace>
    <tag>
    <tag-name>setValue</tag-name>
    <handler-class>com.linkare.myproject.web.component.SetValueTagHandler</handler-class>
    </tag>
    </facelet-taglib>
    
  5. The definition of the tag library in web.xml:
    ...
    <context-param>
    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
    <!-- Use ';' in case more than one tag library file is necessary -->
    <param-value>/WEB-INF/setvaluehandler.taglib.xml</param-value>
    </context-param>
    ...
    
  6. As an example, a possible implementation that displays a list of participants (the code snipptet was simplified a lot to try to display only the relevant parts regarding this recipe):
    package com.linkare.myproject.web.controller.mission;
    
    import java.util.ArrayList;
    import java.util.List;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.linkare.myproject.domain.Participant;
    import com.linkare.myproject.service.ParticipantService;
    import com.linkare.myproject.web.component.IDataScrollerData;
    ...
    
    /**
    *
    * @author Paulo Zenida - Linkare TI
    *
    */
    @ViewScoped
    @Named("participantController")
    public class ParticipantController implements IDataScrollerData<Participant> {
    
    private static final long serialVersionUID = -1042585460939100606L;
    private static final Logger log = LoggerFactory.getLogger(ParticipantController.class);
    private List<Participant> participants;
    private long totalCount;
    private int start;
    private static final int CHUNK_SIZE = 2;
    @Inject
    private ParticipantService participantService;
    
    @PostConstruct
    public void init() {
    search();
    }
    @Override
    public void search() {
    start = 0;
    totalCount = participantService.count();
    participants = new ArrayList<Participant>((int) totalCount);
    loadMore();
    }
    @Override
    public void loadMore() {
    List<Participant> newParticipants = new ArrayList<Participant>(CHUNK_SIZE);
    try {
    newParticipants = participantService.find(start, CHUNK_SIZE);
    participants.addAll(newParticipants);
    start = participants.size();
    } catch (Exception e) {
    log.error("Problems loading current missions. Returning empty list", e);
    }
    }
    @Override
    public long getTotalCount() {
    return totalCount;
    }
    @Override
    public List<Participant> getRecords() {
    if (participants == null) {
    participants = new ArrayList<>();
    }
    return participants;
    }
    @Override
    public long getRecordsSize() {
    return getRecords().size();
    }
    ... methods to remove, edit, ...
    }
    
  7. And finally, the xhtml page that uses the composite component:
    <!DOCTYPE html>
    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    xmlns:myproject="http://java.sun.com/jsf/composite/myproject">
    ...
    <myproject:data_scroller value="#{participantController}" var="participant" blockUIWidget="participantsLoaderBUI">
    <div class="participant">
    <div class="row">
    <div class="col-md-12">
    <h6 class="panel-title">
    <h:outputText value="#{participant.person.name}" />
    <p:commandLink oncomplete="PF('confirmRemoveParticipantWidget').show()" process="@this">
    <f:setPropertyActionListener target="#{missionController.participantHelper.participantToRemove}" value="#{participant}" />
    <i class="fa fa-times pull-right" />
    </p:commandLink></h6>
    </div>
    </div>
    <div class="row">
    <div class="col-md-12">
    <h:outputText value="#{participant.person.department.name}" />
    </div>
    </div>
    </div>
    </myproject:data_scroller>
    ...
    </ui:composition>
    

And the result is the following (any time the Load more option is chosen, more data is fetched).

participants_data_scroller

Explanations

Nothing further to explain here, I suppose, since all explanations were made in place (in the code).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s