5

我在延迟加载Primefaces Datascroller组件时遇到问题。

我有一个 jsf 页面,应该在页面加载时显示 10 个事件。如果用户想看更多,他/她可以点击更多按钮来加载并显示接下来的 10 个事件。对于每个事件行,都有一个链接可用于显示事件的详细信息。

    <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>

初始搜索工作正常,也就是说,当我单击查看事件详细信息链接时,我的支持 bean 被调用,我看到收到的索引和事件对应于我单击的行。

但是,一旦我加载包含 1 个额外事件的下一个块,页面会显示 11 个事件,但单击查看事件详细信息链接会发送正确的索引,但不会发送正确的事件。例如,如果我单击索引 0 处的事件,我将获得索引 10 处的事件,如果我单击索引 1 处的事件,则不会调用我的支持 bean。

当我单击更多按钮时,数据滚动器似乎忘记了最后 10 个事件,但我的惰性数据模型仍然记得。

支持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;
}}

由于页面显示正确的信息并且接收到的索引始终有效,因此我当前的解决方法是通过索引在惰性数据模型中获取事件。

但是,我想了解为什么收到的事件不是我点击的那个。

我做错了什么还是这就是滚动条的实现方式?

在 Mojarra 2.2、Tomcat 7、Primefaces 5、Omnifaces 1.8 上运行

4

3 回答 3

2

我在此链接http://www.theserverside.com/news/thread.tss?thread_id=44186中找到了关于请求范围行为的一个很好的解释

如果您在请求范围内使用 ManagedBeans,则会遇到 DataTables 中的 CommandLinks 问题。DataTables 是我非常喜欢 JSF 的一件事,而且 CommandLinks 也经常派上用场。但是,当您将 CommandLink 放入 DataTable 中时,例如,选择 CommandLink 所在行的条目时,您会被咬。也就是说,如果您想要具有请求范围的 ManagedBeans。应该由 CommandLink 触发的动作永远不会触发,页面只是再次呈现。出现这种行为的原因是 DataTable 在渲染期间修改了 CommandLink 的 id,但 CommandLink 不知道它是用不同的 id 渲染的。在对通过单击 CommandLink 触发的请求进行解码期间,ComandLinkRenderer 会查看隐藏的表单参数。如果该表单参数的值等于 CommandLink 的 id,则操作将排队。如果没有,则什么也不做。由于DataTable改变了ids,所以隐藏表单参数的值与CommandLink的id不匹配。

根据上述情况,您需要将范围注释从@ViewScoped更改为 @SessionScope,您的问题将自动解决。这似乎是比编写额外代码更好的解决方案,除非您需要保留 @ViewScopped

于 2016-06-07T22:57:34.770 回答
1

一种解决方法是使用 PrimeFaces 远程命令,通过 rc([name: 'paramName', value: someParamValue]) 传递参数。这些参数应该可以使用 #{param['paramName']} EL 表达式

例子:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:body>

        <ui:composition>
            <p:dataTable id="#{id}" widgetVar="#{id}"
                         value="#{requestCache.getLazy(id, () -> dataSource)}" var="rpo"
                         selectionMode="single" selection="#{selection}"
                         lazy="true" paginator="true" rows="#{pageSizeController.pageSize}"
                         pageLinks="10" paginatorPosition="top"
                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
                         currentPageReportTemplate="(#{label.Page} {currentPage} #{label.of} {totalPages}, #{label.Row} {startRecord} - {endRecord} #{label.of} {totalRecords})"
                         rowsPerPageTemplate="5 10 20 30 40 50 100"
                         scrollable="true" scrollHeight="#{empty scrollHeight ? 300 : scrollHeight}"
                         resizableColumns="true" emptyMessage="#{label.Table_is_empty}">
    <p:column>
                    <h:outputText value="#{rpo.decreeSequence.code}" />
                    <p:commandLink id="displayAdditionalInfoCommandLink" type="link" style="float: right; text-decoration: none"
                                   onclick="displayAdditionalInfo([{name: 'refundPaymentOrderId', value: #{rpo.id}}])"
                                   title="#{label.Additional_information}">
                        <h:outputLabel for="displayAdditionalInfoCommandLink" styleClass="fa fa-info-circle"
                                       onmouseover="jQuery(this).addClass('fa-lg').css('cursor', 'pointer')"
                                       onmouseout="jQuery(this).removeClass('fa-lg')"/>
                    </p:commandLink>
                </p:column>
</p:dataTable>
            <p:remoteCommand name="displayAdditionalInfo" process="@this" update="@parent">
                <f:setPropertyActionListener target="#{refundPaymentOrderCache.refundPaymentOrder}"
                                             value="#{refundPaymentOrderRepo.find(requestCache.toLong(param['refundPaymentOrderId']))}" />
                <f:actionListener binding="#{dialog.displayInputForm('RPO_ADDITIONAL_INFO')}" />
            </p:remoteCommand>
</ui:composition>
    </h:body>
</html>
于 2017-08-16T13:47:02.407 回答
0

我终于有时间花在这个问题上,我找到了解决方法。这是一个 hack,所以也许正确的解决方案是使用不同的组件或创建我自己的组件。

将 DataScroller 与 LazyDataModel 一起使用时,似乎会出现 Primefaces DataScroller 限制。似乎该组件并非旨在执行此操作。

为了避免这个问题,我实现了自己的延迟加载,除了新添加的元素之外,还返回相同的列表实例。

这是我之前为实现这种新的延迟加载模式而修改的示例:

html页面:

<h:form id="mainForm" >
    <p:dataScroller value="#{backing.events}" var="event" rowIndexVar="index">
        #{event.name}
        <p:commandLink class="view-trigger"
            value="View Event Details"
            action="#{backing.initViewEventDetails(index, event)}"/>

        <f:facet name="loader"><h:outputText value=""/></f:facet>
    </p:dataScroller>

    <p:commandLink value="More" process="@form" update="@form" 
        action="#{backing.loadMore()}"
        visible="#{backing.totalCount gt backing.events.size()}"
        rendered="#{backing.totalCount gt backing.events.size()}"/>
</h:form>

DataScroller 不再具有lazy="true"、chunkSize="10",使用名为 events 的列表作为值并声明一个空的加载器方面(以避免在到达列表底部时自动加载更多)。我使用了一个调用 backing.loadMore() 并更新表单以替换加载器方面的 commandLink。

支持bean:

@Named("backing")
@ViewScoped
public class DataScrollerBacking implements Serializable {

private static final long serialVersionUID = 4012320411042043677L;

private static final Logger LOGGER = Logger.getLogger(DataScrollerBacking.class);

private static final Integer CHUNK_SIZE = 10;

@DataSource
@Inject
private String dataSource;

private WebEventDAO webEventDAO;

private List<Event> events;
private Integer totalCount;
private Date startDate;
private Date endDate;

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

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

    try {
        totalCount = webEventDAO.getSearchByPeriodRaceTypeAndRaceStatusForCompanyCount(Collections.singletonList(1), startDate, endDate, null, null);
        events = new ArrayList<Event>(totalCount);
        loadMore();
    } catch (DAOException e) {
        LOGGER.error("An error occurred while retrieving events.", e);
    }
}

public void loadMore() {
    List<Event> newEvents = new ArrayList<Event>(CHUNK_SIZE);
    try {
        newEvents = webEventDAO.searchByPeriodRaceTypeAndRaceStatusForCompany(Collections.singletonList(1), startDate, endDate, null, null, true, events.size(), CHUNK_SIZE);
        events.addAll(newEvents);
    } catch (DAOException e) {
        LOGGER.error("An error occurred while retrieving events.", e);
    }

}

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 List<Event> getEvents() {
    return events;
}

public void setEvents(List<Event> events) {
    this.events = events;
}

public Integer getTotalCount() {
    return totalCount;
}

public void setTotalCount(Integer totalCount) {
    this.totalCount = totalCount;
}}

在支持 bean 中,搜索方法计算事件总数,保存该信息并调用 loadMore() 以加载事件列表中的前 10 个事件。

单击更多按钮时,将再次调用 loadMore() 并将接下来的 10 个事件附加到事件列表的末尾。

现在,当我单击新加载的元素时,commandLink 会调用具有正确值的支持 bean。

于 2014-10-06T18:56:57.757 回答