p:datatable 在 ajax 刷新后丢失排序列和顺序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10032356/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 10:44:27  来源:igfitidea点击:

p:datatable loses sort column and order after ajax refresh

ajaxsortingjsf-2primefacesdatatable

提问by jjross

I have a button on a page that causes my data table to refresh via an AJAX request. Something like this:

我在页面上有一个按钮,可以通过 AJAX 请求刷新我的数据表。像这样的东西:

<h:form id="datatable">
<p:dataTable/>
</h:form>
<p:commandButton update=":datatable">

This is all fine an dandy except that when the table is refreshed it reverts to not sorting anything while still showing that it's sorting based on the previous value. In other words, the header is still highlighted and the arrow is still pointing in the sort direction but no sort is actually being performed. Obviously this isn't ideal.

这一切都很好,只是当表格刷新时,它恢复为不排序任何内容,同时仍显示它是根据前一个值排序的。换句话说,标题仍然突出显示,箭头仍然指向排序方向,但实际上没有执行排序。显然这并不理想。

Ideally I'd like the component to keep it's sort order in the view state and then submit the proper parameters during the AJAX request (so that the sort is correctly defined). Am I missing a parameter or something? Does anyone else have this issue?

理想情况下,我希望组件在视图状态中保持其排序顺序,然后在 AJAX 请求期间提交正确的参数(以便正确定义排序)。我错过了一个参数还是什么?还有其他人有这个问题吗?

From what I can tell when the table is expecting a sort back it posts the following options:

据我所知,表何时需要排序,它会发布以下选项:

<componentID>_sortDir
<componentID>_sortKey
<componentID>_sorting
<componentID>_updateBody

When I refresh the form this doesn't happen. It also doesn't happen if I just refresh the table (thought I could work around things by updating the component directly). Is there a way to get the table to refresh correctly?

当我刷新表单时,这不会发生。如果我只是刷新表格也不会发生这种情况(我认为我可以通过直接更新组件来解决问题)。有没有办法让表格正确刷新?

回答by srbs

I wrote an extension to @truemmer's solution. His reverts the sorting order back to the default, where as mine will reverts to the previous sort the user selected.

我为@truemmer 的解决方案写了一个扩展。他将排序顺序恢复为默认值,而我的将恢复到用户选择的先前排序。

function postAjaxSortTable(datatable)
{
    var selectedColumn = datatable.jq.find('.ui-state-active');
    if(selectedColumn.length <= 0)
    {
        return;
    }
    var sortorder = "ASCENDING";
    if(selectedColumn.find('.ui-icon-triangle-1-s').length > 0)
    {
        sortorder = "DESCENDING";
    }
    datatable.sort(selectedColumn, sortorder);
}

Updating the same table as truemmer's works like this:

更新与 truemmer 相同的表是这样的:

<p:commandButton value="refresh" action="#{tableController.refreshPrices}" update="myTable" oncomplete="postAjaxSortTable(myTableWidget)" />

EDIT: Primefaces 4.0 MultiSort support

编辑:Primefaces 4.0 MultiSort 支持

function postAjaxSortTable(datatable) {
    var selectedColumn = undefined;

    // multisort support
    if(datatable && datatable.cfg.multiSort) {
        if(datatable.sortMeta.length > 0) {
            var lastSort = datatable.sortMeta[datatable.sortMeta.length-1];
            selectedColumn = $(document.getElementById(lastSort.col));
        }
    } else {
        selectedColumn = datatable.jq.find('.ui-state-active');
    }

    // no sorting selected -> quit
    if(!selectedColumn || selectedColumn.length <= 0) {
        return;
    }

    var sortorder = selectedColumn.data('sortorder')||"DESCENDING";
    datatable.sort(selectedColumn, sortorder, datatable.cfg.multiSort);
}

回答by truemmer

First of all, there is an open ticket on the PrimeFaces Issue Tracker, which targets this question but was recently labeled as "won't fix".

首先,PrimeFaces Issue Tracker上有一张公开票,它针对这个问题,但最近被标记为“无法修复”。

Nevertheless, I found a nice workaround. The sorting of PrimeFaces tables can be triggered by calling an undocumented JavaScript method on the datatable's widget. The following might not work for future releases of PrimeFaces, but it does for 3.4.2:

不过,我找到了一个很好的解决方法。PrimeFaces 表的排序可以通过调用数据表小部件上未记录的 JavaScript 方法来触发。以下内容可能不适用于 PrimeFaces 的未来版本,但适用于 3.4.2:

Just add the following to your component, which triggers the update:

只需将以下内容添加到您的组件中,即可触发更新:

oncomplete="myTableWidget.sort($(PrimeFaces.escapeClientId('#{p:component('sortColumnId')}')), 'ASCENDING')"

The table might look like this:

该表可能如下所示:

<p:dataTable id="myTable"
             widgetVar="myTableWidget"
             value="#{myArticles}"
             var="article"
             sortBy="#{article.price}"
             sortOrder="ASCENDING">
    ...

    <p:column id="price" sortBy="#{article.price}">
        <f:facet name="header">
            <h:outputText value="Price" />
        </f:facet>
        <h:outputText value="#{article.price}" />
    </p:column>

</p:dataTable>

So updating the table could work like this.

所以更新表格可以这样工作。

<p:commandButton value="refresh" action="#{tableController.refreshPrices}" update="myTable" oncomplete="myTableWidget.sort($(PrimeFaces.escapeClientId('#{p:component('price')}')), 'ASCENDING')" />

回答by Fallup

EDIT:

编辑:

Solution posted below (LazyTable) works for the p:dataTable backed with LazyDataModel. Overriden loadmethod is called after every update/refresh on the desired table and it handles sort properly. The problem with simple p:dataTable is that it performs predefined sort only on the first load, or after the click on sort column. This is a normal behaviour of a simple table.

下面发布的解决方案 (LazyTable) 适用于由 LazyDataModel 支持的 p:dataTable。每次更新/刷新所需表后都会调用覆盖加载方法,它会正确处理排序。简单 p:dataTable 的问题在于它仅在第一次加载时或在单击排序列之后执行预定义的排序。这是简单表的正常行为。

So what are your possibilities for simple table :

那么简单表格有哪些可能性:

  • Don't sort the table after update, but remove the sort column so end user is not missinformed. Add this to your action listener or action method for your update button :

    UIComponent table  = FacesContext.getCurrentInstance().getViewRoot().findComponent(":dataTablesForm:dataTableId");
    table.setValueExpression("sortBy", null);
    
  • Update the sort of the table manually by script. This is not the best solution, but primefaces doesn't provide any client side function for "resorting" the table. Basically you know that only one column at a time can be sorted and this column has a .ui-state-active. You can use it in a script and trigger 2 clicks on that column (1. click - other sort order, 2. click - back to current sort order).

     <h:form id="mainForm">  
    <div id="tableDiv">
       <p:dataTable id="dataTable" var="item" value="#{testBean.dummyItems}">
          .
          .
          .
       </p:dataTable> 
       <p:commandButton value="Refresh" oncomplete="refreshSort();" update=":mainForm:dataTable"/>
     </div>
    

    And script function :

    function refreshSort(){
    jQuery('#tableDiv').find('.ui-state-active').trigger('click');
    jQuery('#tableDiv').find('.ui-state-active').trigger('click');
    }
    
  • 更新后不要对表进行排序,而是删除排序列,以免误导最终用户。将此添加到更新按钮的操作侦听器或操作方法中:

    UIComponent table  = FacesContext.getCurrentInstance().getViewRoot().findComponent(":dataTablesForm:dataTableId");
    table.setValueExpression("sortBy", null);
    
  • 通过脚本手动更新表的排序。这不是最好的解决方案,但 primefaces 不提供任何客户端功能来“重新定位”表。基本上您知道一次只能对一列进行排序,并且该列具有 .ui-state-active。您可以在脚本中使用它并在该列上触发 2 次单击(1. 单击 - 其他排序顺序,2. 单击 - 返回当前排序顺序)。

     <h:form id="mainForm">  
    <div id="tableDiv">
       <p:dataTable id="dataTable" var="item" value="#{testBean.dummyItems}">
          .
          .
          .
       </p:dataTable> 
       <p:commandButton value="Refresh" oncomplete="refreshSort();" update=":mainForm:dataTable"/>
     </div>
    

    和脚本功能:

    function refreshSort(){
    jQuery('#tableDiv').find('.ui-state-active').trigger('click');
    jQuery('#tableDiv').find('.ui-state-active').trigger('click');
    }
    

I know this is not the best workaround, but it works. You can use it as an inspiration to make something better.

我知道这不是最好的解决方法,但它有效。你可以用它作为灵感来做出更好的东西。

LazyTable

懒人表

IMHO the most proper way is to update directly the component you want. So for example :

恕我直言,最正确的方法是直接更新您想要的组件。例如:

<h:form id="dataTableForm">
  <p:dataTable id="dataTableToUpdate">
    .
    .
    .
  </p:dataTable>
  <p:commandButton value="Refresh" update=":dataTableForm:dataTableToUpdate" />
</h:form>

It should work fine in this scenario (I suppose it is your purpose) : Open the .xhtml with your p:dataTable, sort some column (keep the facelet opened), update dataTables data from another .xhtml for example, click on refresh button. The dataTable should show your added value in correct (previously chosen) sort order - sorting was performed after update.

在这种情况下它应该可以正常工作(我想这是你的目的):用你的 p:dataTable 打开 .xhtml,对一些列进行排序(保持 facelet 打开),例如从另一个 .xhtml 更新 dataTables 数据,点击刷新按钮. dataTable 应该以正确的(先前选择的)排序顺序显示您的附加值 - 排序是在更新后执行的。

Hope it helped !

希望有帮助!

回答by Rares Oltean

Add this PhaseListener to your application and both sorting and filtering will be kept after updating the DataTable.

将此 PhaseListener 添加到您的应用程序中,并且在更新 DataTable 后将保留排序和过滤。

public class DataTableUpdatePhaseListener implements PhaseListener {

    private static final long serialVersionUID = 1L;

    @Override
    public void afterPhase(PhaseEvent event) {
        // Nothing to do here
    }

    @Override
    public void beforePhase(PhaseEvent event) {
        FacesContext facesContext = event.getFacesContext();
        if (!facesContext.isPostback()) {
            return;
        }

        PartialViewContext partialViewContext = facesContext.getPartialViewContext();
        if (partialViewContext != null) {
            Collection<String> renderIds = partialViewContext.getRenderIds();

            for (String renderId : renderIds) {
                UIComponent component = facesContext.getViewRoot().findComponent(renderId);
                if (component instanceof DataTable) {
                    DataTable table = (DataTable) component;
                    if (!table.isLazy()) {
                        updateDataTable(table);
                    }
                }
            }
        }
    }

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }

    private void updateDataTable(DataTable table) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext == null || table == null) {
            return;
        }

        // Reapply filtering
        if (!table.getFilters().isEmpty()) {
            FilterFeature filterFeature = new FilterFeature();
            filterFeature.decode(facesContext, table);
        } else {
            table.setFilteredValue(null);
        }

        // Reapply sorting
        ValueExpression tableSortByVE = table.getValueExpression("sortBy");
        if (tableSortByVE != null) {
            String tableSortByExpression = tableSortByVE.getExpressionString();

            // Loop on children, that are the columns, to find the one whose order must be applied.
            for (UIComponent child : table.getChildren()) {
                Column column = (Column) child;
                ValueExpression columnSortByVe = column.getValueExpression("sortBy");
                if (columnSortByVe != null) {
                    String columnSortByExpression = columnSortByVe.getExpressionString();

                    if (tableSortByExpression != null && tableSortByExpression.equals(columnSortByExpression)) {
                        // Now sort table content
                        SortFeature sortFeature = new SortFeature();
                        sortFeature.sort(facesContext, table, tableSortByVE,
                                SortOrder.valueOf(table.getSortOrder().toUpperCase(Locale.ENGLISH)),
                                table.getSortFunction());

                        break;
                    }
                }
            }
        }
    }
}

This is for non-lazy data tables. Data tables using a lazy model do not require this, as the lazy model will take care of sorting and filtering. For non-lazy data tables, this should work with both single and multiple column sorting but there is a bug in Primefaces that makes DataTable loose its MultiSortMeta between postbacks when updating the table. This means that the columns selected for sorting before postback are completely lost from FacesContext and component state and cannot be retrieved anymore upon postback. I'll look more into this later and provide an update if I manage to find a workaround or maybe Primefaces will fix it soon.

这适用于非惰性数据表。使用惰性模型的数据表不需要这样做,因为惰性模型将负责排序和过滤。对于非惰性数据表,这应该适用于单列和多列排序,但 Primefaces 中存在一个错误,使 DataTable 在更新表时在回发之间失去其 MultiSortMeta。这意味着在回发之前选择用于排序的列从 FacesContext 和组件状态中完全丢失,并且在回发时无法再检索。如果我设法找到解决方法,或者 Primefaces 很快会修复它,我将在稍后对此进行更多研究并提供更新。

回答by Aleksandr Erokhin

@Rares Oltean's approach could also be implemented using preRenderView event listener. On your jsf page register listener:

@Rares Oltean 的方法也可以使用 preRenderView 事件侦听器来实现。在您的 jsf 页面注册侦听器:

<f:event listener="#{managedBean.preRenderViewListener}" type="preRenderView" />

and implement it in a ManagedBean:

并在 ManagedBean 中实现它:

public void preRenderViewListener() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    if (!facesContext.isPostback()) {
        return;
    }

    PartialViewContext partialViewContext = facesContext.getPartialViewContext();
    if (partialViewContext != null) {
        Collection<String> renderIds = partialViewContext.getRenderIds();

        for (String renderId : renderIds) {
            UIComponent component = facesContext.getViewRoot().findComponent(renderId);
            if (component instanceof DataTable) {
                DataTable table = (DataTable) component;
                if (!table.isLazy()) {
                    updateDataTable(table);
                }
            }
        }
    }
}

private void updateDataTable(DataTable table) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    if (facesContext == null || table == null) {
        return;
    }
    if (!table.getFilters().isEmpty()) {
        FilterFeature filterFeature = new FilterFeature();
        filterFeature.decode(facesContext, table);
    } else {
        table.setFilteredValue(null);
    }
    ValueExpression tableSortByVE = table.getValueExpression("sortBy");
    if (tableSortByVE != null) {
        String tableSortByExpression = tableSortByVE.getExpressionString();
        for (UIComponent child : table.getChildren()) {
            Column column = (Column) child;
            ValueExpression columnSortByVe = column.getValueExpression("sortBy");
            if (columnSortByVe != null) {
                String columnSortByExpression = columnSortByVe.getExpressionString();
                if (tableSortByExpression != null && tableSortByExpression.equals(columnSortByExpression)) {
                    SortFeature sortFeature = new SortFeature();
                    sortFeature.sort(facesContext, table, tableSortByVE, table.getVar(),
                            SortOrder.valueOf(table.getSortOrder().toUpperCase(Locale.ENGLISH)),
                            table.getSortFunction());
                    break;
                }
            }
        }
    }
}

回答by Seitaridis

I had the same problem as you. I was able to fix the issue using a LazyDataModel. Because of the PrimeFaces issue with the row index, I needed to add the extra UtilitiesLazyDataModel(see row index comments):

我和你有同样的问题。我能够使用LazyDataModel解决这个问题。由于行索引PrimeFaces 问题,我需要添加额外的 UtilitiesLazyDataModel(请参阅行索引注释):

public abstract class UtilitiesLazyDataModel <T> extends LazyDataModel<T>{

    public UtilitiesLazyDataModel() {
    }    

    @Override
    public void setRowIndex(int rowIndex) {
        /*
         * The following is in ancestor (LazyDataModel):
         * this.rowIndex = rowIndex == -1 ? rowIndex : (rowIndex % pageSize);
         */
        if (rowIndex == -1 || getPageSize() == 0) {
            super.setRowIndex(-1);
        } else {
            super.setRowIndex(rowIndex % getPageSize());
        }      
    }
}

Then use your LazyDataModelclass with this:

然后使用您的LazyDataModel课程:

public class MyDataModel extends UtilitiesLazyDataModel<MyObjectClass>{

//override getRowData and getRowKey

}

回答by montblack

I implement a Comparator with my toString() method

我用我的 toString() 方法实现了一个 Comparator

municipios = municipioFacade.findAll();
Collections.sort(municipios, new DefaultComparator());
UIComponent table = FacesContext.getCurrentInstance().getViewRoot().findComponent(":municipios:municipios-tabla");
    table.setValueExpression("sortBy", null);

comparator

比较器

public class DefaultComparator implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        return o1.toString().compareToIgnoreCase(o2.toString());
    }
}

回答by Endre

The solution is to have a conversational bean. In this case the p:dataTable refreshes the table and its entries without affecting the sort order. In the case when you have a p:commandButton on each line the table refresh works correctly too.

解决方案是使用会话 bean。在这种情况下, p:dataTable 会在不影响排序顺序的情况下刷新表及其条目。在每一行都有 ap:commandButton 的情况下,表格刷新也能正常工作。

The conversational bean:

对话豆:

@Named
@Stateful
@ConversationScoped
public class ItemBean {

    @Inject
    private Conversation conversation;

    private List<Item> items;

    public List<Item> getItems() {
        return this.items;
    }

    public void retrieve() {
        // if it's an Ajax call then avoid retrieving items
        if (FacesContext.getCurrentInstance().isPostback()) {
            return;
        }
        // start a conversation
        if (this.conversation.isTransient()) {
            this.conversation.begin();
        }
        this.items = retrieveItems();
    }
}

The associated page:

相关页面:

<f:metadata>
    <f:event type="preRenderView" listener="#{itemBean.retrieve}" />
</f:metadata>

<h:form id="form">
    <p:dataTable id="table" var="_item" value="#{testBean.items}">
    ... <!-- you can have the p:commandButton in a column too, to refresh the respective column only
        <p:commandButton value="Refresh" action="#{itemBean.update(_item)}" 
        -->
    </p:dataTable> 
    <p:commandButton value="Refresh" action="#{itemBean.update}" update=":form:table"/>
</h:form>