Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CommandLink not Working on a Lazy Loaded Primefaces Datascroller

I'm having an issue with lazy loading a Primefaces Datascroller component.

I have a jsf page that should display 10 events on page load. If the user wants to see more he/she can click the more button to load and display the 10 next events. For each of the event rows, there is a link that can be used to display the event's details.

    <h:form id="mainForm" >
        <p:dataScroller value="#{backing.lazyModel}" var="event" lazy="true" chunkSize="10" rowIndexVar="index">
            #{event.name}
            <p:commandLink class="view-trigger"
                value="View Event Details"
                actionListener="#{backing.initViewEventDetails(index, event)}"/>
            <f:facet name="loader">
                <p:outputPanel 
                        visible="#{backing.lazyModel.rowCount gt 10}"
                        rendered="#{backing.lazyModel.rowCount gt 10}">
                    <p:commandLink value="More" />
                </p:outputPanel>
            </f:facet>
        </p:dataScroller>
    </h:form>

The initial search works fine, that is, when I click the view event details link, my backing bean is invoked and I see that the index and event received correspond to the row I clicked on.

However, once I load the next chunk, which consists of 1 extra event, the page displays 11 events but clicking a view event details link sends the proper index but does not send the proper event. For example, if I click on event at index 0, I get the event at index 10, if I click on event at index 1 my backing bean is not invoked.

It looks like the datascroller forgets about the last 10 events when I click on the more button but my lazy data model still remembers.

The backing bean:

@ManagedBean(name="backing")
@ViewScoped
public class DataScrollerBacking implements Serializable {

private static final long serialVersionUID = 4012320411042043677L;
private static final Logger LOGGER = Logger.getLogger(DataScrollerBacking.class);

@ManagedProperty("#{settings.dataSource}")
private String dataSource;

private WebEventDAO webEventDAO;

private LazyDataModel<Event> lazyModel;

@PostConstruct
public void init() {
    webEventDAO = CommonDAOFactory.getInstance(dataSource).getWebEventDAO();
    search();
}

public void search() {
    DateTime start = new DateTime(2014, 1, 1, 0, 0 ,0);
    final Date startDate = start.toDate();
    final Date endDate = start.plus(Years.ONE.toPeriod()).minus(Seconds.ONE.toPeriod()).toDate();

    lazyModel = new LazyDataModel<Event>() {

        private static final long serialVersionUID = 1231902031619933635L;
        private LinkedHashSet<Event> eventCache; // Ordered set of all retrieved events so far.
        // I'm using a set because the load method is called twice on page load (any idea why???) and I don't want duplicates in my cache.

        @Override
        public List<Event> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
            List<Event> events = new ArrayList<Event>(10);
            try {
                if(eventCache == null){
                    int count = webEventDAO.getSearchByPeriodRaceTypeAndRaceStatusForCompanyCount(Collections.singletonList(1), startDate, endDate, null, null);
                    this.setRowCount(count);
                    eventCache = new LinkedHashSet<Event>(count);
                }
                events = webEventDAO.searchByPeriodRaceTypeAndRaceStatusForCompany(Collections.singletonList(1), startDate, endDate, null, null, true, first, pageSize);
                eventCache.addAll(events);
            } catch (DAOException e) {
                LOGGER.error("An error occurred while retrieving events.", e);
            }
            return events;
        }
    };

}

public void initViewEventDetails(Integer index, Event event){
    LOGGER.info("index=" + index + " eventname=" + event.getName());
}

public String getDataSource() {
    return dataSource;
}

public void setDataSource(String dataSource) {
    this.dataSource = dataSource;
}

public LazyDataModel<Event> getLazyModel() {
    return lazyModel;
}

public void setLazyModel(LazyDataModel<Event> lazyModel) {
    this.lazyModel = lazyModel;
}}

Since the page displays the proper information and the index received is always valid, my current workaround is to go fetch the Event in the lazy data model by index.

However, I would like to understand why the received event is not the one I clicked on.

Am I doing something wrong or this is just how the scroller is implemented?

Running on Mojarra 2.2, Tomcat 7, Primefaces 5, Omnifaces 1.8

like image 755
Etienne Dufresne Avatar asked Nov 01 '22 18:11

Etienne Dufresne


1 Answers

I found a good explanation about the behavior of request scope in this link http://www.theserverside.com/news/thread.tss?thread_id=44186

If you are using ManagedBeans in request scope, you get problems with CommandLinks inside DataTables. DataTables are one thing I really like about JSF, and CommandLinks often come in handy as well. But when you put a CommandLink inside a DataTable, e. g., to select the entry of the row in which the CommandLink is, you get bitten. That is, if you want ManagedBeans with request scope. The action which should be triggered by the CommandLink is never triggered, the page is simply rendered again. The reason for this behaviour is that the DataTable modifies the id of the CommandLink during renderering, but the CommandLink does not know that it was rendered with a different id. During the decoding of the request which was triggered by clicking the CommandLink, the ComandLinkRenderer looks at a hidden form parameter. If the value of that form parameter equals the id of the CommandLink, an action is queued. If not, nothing is done. Since the DataTable changes the ids, the value of the hidden form parameter does not match the id of the CommandLink.

Based on above context, you need to change the scope annotations from @ViewScoped to @SessionScope, and your problem will be solved automatically. It seems to be a better solution than write additional code, unless you need to keep the @ViewScopped

like image 168
Cassio Seffrin Avatar answered Nov 13 '22 05:11

Cassio Seffrin