p:在对LazyDataModel进行分页后,dataTable选择会丢失

时间:2013-08-31 10:55:52

标签: jsf-2 primefaces datatable pagination lazy-loading

我的问题是,在我在第一页上选择了几个项目后,如果我分页到另一个页面并返回,我的初始选择不会显示。我已尝试实施SelectableDataModel以及使用rowKey属性,但问题仍然存在。

这是我的测试bean:

@ManagedBean
@ViewScoped
public class MrBean {
    private List<Item> chosenItems;
    private LazyDataModel lazyModel;

    @PostConstruct
    public void prepareTest() {
        this.lazyModel = new LazyItemDataModel();
    }

    public void countItems() {
        System.out.println("TEST 3: chosenItems's size: " + chosenItems.size());
    }

    private class LazyItemDataModel extends LazyDataModel<Item> implements SelectableDataModel<Item> {
        @Override
        public Item getRowData(String rowKey) {
            System.out.println("TEST 1: getRowData");
            Iterator<Item> iter = ((List<Item>) this.getWrappedData()).iterator();
            while (iter.hasNext()) {
                Item item = iter.next();
                if (item.getId().equals(rowKey)) {
                    return item;
                }
            }

            return null;
        }

        @Override
        public Object getRowKey(Item item) {
            return item.getId();
        }

        @Override
        public List<Item> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters) {
            System.out.println("TEST 2: load");
            // Code to retrieve items from database
        }
    }

    // Getters and Setters
}

这是我的测试页:

<?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://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <title>Test page</title>
    </h:head>
    <h:body>
        <h:form>
            <p:dataTable id="itemTable" var="item" value="#{mrBean.items}" rows="5" 
                         paginator="true" selection="#{mrBean.chosenItems}" lazy="true" >

                <p:ajax event="rowSelectCheckbox" listener="mrBean.countItems" />                    

                <p:column selectionMode="multiple" />

                <p:column headerText="ID">
                    <h:outputText value="#{item.id}" /> 
                </p:column>

                <p:column headerText="Name">
                    <h:outputText value="#{item.name}" /> 
                </p:column>

            </p:dataTable>
        </h:form>
    </h:body>
</html>

如果你能告诉我这里我做错了什么,我将非常感激。

更新:我在上面的代码中添加了更多System.out.println("TEST")后,我发现了以下内容:

  1. 在控制台上,每次分页时,TEST 1: getRowData始终会在TEST 2: load之前打印。因此,我认为方法#LazyDataModel.getWrappedData()可能会返回旧页面中的数据。起初,我认为这个方法的目标是检索选定的行以突出显示在表上。但是,如果在load之前调用此方法,那么它无法正常工作吗?
  2. 在我选择了第一页上的前两项后,在控制台上,我看到了TEST 3: chosenItems's size: 2。如果我分页到第二页然后回到第一页,则选择将丢失,如上所述。但是,如果我继续在控制台上选择另一个项目,我会看到TEST 3: chosenItems's size: 3。显然,chosenItems列表仍然保留了我的旧选项,但它们没有呈现在桌面上。

5 个答案:

答案 0 :(得分:2)

在webPage中只需为页面切换添加一个事件:

<p:ajax event="page" listener="#{listingBean.updateSelected()}" />

在listingBean中,只需保存选中的内容:

private List<Entity> selectedInstances;
private List<Entity> selectedInstancesSaved;

public List<Entity> getSelectedInstances()
{
    return selectedInstancesSaved;
}

public void setSelectedInstances(List<Entity> selectedInstances)
{
    this.selectedInstances = selectedInstances;
}

public void updateSelected()
{
    if (selectedInstances != null && !selectedInstances.isEmpty()) {
        for (Entity inst : lazyModel.getDatasource()) {
            if (selectedInstances.contains(inst)) {
                selectedInstancesSaved.add( inst);
            } else {
                selectedInstancesSaved.remove( inst);
            }
        }
    }
}

答案 1 :(得分:2)

虽然Bruno的解决方案适用于保留跨分页的选择,但它并不考虑保留单个页面上的选择(即从不更改页面时)。

除了拥有单独的“已保存”行列表外,还可以使用 rowSelectCheckbox rowUnselectCheckbox ajax事件更简单地解决此问题。

<强> JSF:

 <p:dataTable selection="#{myBean.selectedRows}" ... >
   <p:ajax event="rowSelectCheckbox" process="@this" listener="#{myBean.onSelectRow}" />
   <p:ajax event="rowUnselectCheckbox" process="@this" listener="#{myBean.onUnselectRow}" />
   <p:column selectionMode="multiple" ... />
    ...
 </p:dataTable>

支持Bean:

    private List<MyRowClass> selectedRows;
    private List<MyRowClass> selectedRowsSaved;

    ...

    public void onSelectRow(SelectEvent event){
        MyRowClass row = (MyRowClass) event.getObject();
        selectedRowsSaved.add(row);
    }

    public void onUnselectRow(UnselectEvent event){
        MyRowClass row = (MyRowClass) event.getObject();
        selectedRowsSaved.remove(row);
    }

    public List<MyRowClass> getSelectedRows(){
        return selectedRowsSaved;
    }

    public void setSelectedRows(List<MyRowClass> selectedRows){
        this.selectedRows = selectedRows;
    }

这样,保存的行列表始终保持最新,而不需要“页面”ajax事件。

答案 2 :(得分:1)

这是因为当解码SelectionFeature时会创建一个新列表。

如果table.getRowData(rowKeys[i])(与您的LazyDataModel实现相关)返回null,则您在上一页中的旧选择已经消失。可能会尝试通过更改您的LazyDataModel实现来解决它我没有尝试这些但是请查看thisthis

遇到同样的问题,如果您有很多不同的表实现LazyDataModel,我认为这个解决方案会更容易。

这就是我所做的:首先检查它是否是懒惰的,然后将当前选定的行添加到selectionList。

对于primefaces 4.0

1)覆盖DataTableRenderer

在faces-config.xml

<render-kit>
   <renderer>
       <component-family>org.primefaces.component</component-family>
       <renderer-type>org.primefaces.component.DataTableRenderer</renderer-type>
       <renderer-class>com.package.LazyDataTableRenderer</renderer-class>
   </renderer>
</render-kit>

并且

public class LazyDataTableRenderer extends DataTableRenderer {
static Map<DataTableFeatureKey,DataTableFeature> FEATURES;

    static {
        FEATURES = new HashMap<DataTableFeatureKey,DataTableFeature>();
        FEATURES.put(DataTableFeatureKey.DRAGGABLE_COLUMNS, new DraggableColumnsFeature());
        FEATURES.put(DataTableFeatureKey.FILTER, new FilterFeature());
        FEATURES.put(DataTableFeatureKey.PAGE, new PageFeature());
        FEATURES.put(DataTableFeatureKey.SORT, new SortFeature());
        FEATURES.put(DataTableFeatureKey.RESIZABLE_COLUMNS, new ResizableColumnsFeature());
        FEATURES.put(DataTableFeatureKey.SELECT, new LazySelectionFeature());
        FEATURES.put(DataTableFeatureKey.ROW_EDIT, new RowEditFeature());
        FEATURES.put(DataTableFeatureKey.CELL_EDIT, new CellEditFeature());
        FEATURES.put(DataTableFeatureKey.ROW_EXPAND, new RowExpandFeature());
        FEATURES.put(DataTableFeatureKey.SCROLL, new ScrollFeature());
    }

    @Override
    public void decode(FacesContext context, UIComponent component) {
        DataTable table = (DataTable) component;

        for(Iterator<DataTableFeature> it = FEATURES.values().iterator(); it.hasNext();) {
            DataTableFeature feature = it.next();

            if(feature.shouldDecode(context, table)) {
                feature.decode(context, table);
            }
        }

        decodeBehaviors(context, component);        
    }
}

2)覆盖SelectionFeature的解码

已更新:已修改以允许取消选择

public class LazySelectionFeature extends org.primefaces.component.datatable.feature.SelectionFeature{

    @Override
    public void decode(FacesContext context, DataTable table) {
        String clientId = table.getClientId(context);
        Map<String,String> params = context.getExternalContext().getRequestParameterMap();

        String selection = params.get(clientId + "_selection");

        if(table.isSingleSelectionMode())
            decodeSingleSelection(table, selection);
        else
            decodeMultipleSelection(context, table, selection);
    }

    void decodeSingleSelection(DataTable table, String selection) {
            if(ComponentUtils.isValueBlank(selection))
                table.setSelection(null);
            else
                table.setSelection(table.getRowData(selection));
        }

    void decodeMultipleSelection(FacesContext context, DataTable table, String selection) {
        Class<?> clazz = table.getValueExpression("selection").getType(context.getELContext());
        boolean isArray = clazz.isArray();

        if(!isArray && !List.class.isAssignableFrom(clazz)) {
            throw new FacesException("Multiple selection reference must be an Array or a List for datatable " + table.getClientId());
        }

        if(ComponentUtils.isValueBlank(selection)) {
            if(isArray) {
                table.setSelection(Array.newInstance(clazz.getComponentType(), 0));
            }
            else {
                table.setSelection(new ArrayList<Object>());
            }
        }
        else {
            String[] rowKeys = selection.split(",");
            List<Object> selectionList = new ArrayList<Object>();

            boolean lazy=table.isLazy();
            if (lazy) {

            List<String> currentRowKeys = new ArrayList<String>(Arrays.asList(rowKeys));
            if (table.getSelection() != null) {
                List<Object> alreadySelected = (List<Object>) table.getSelection();

                for (Object object : alreadySelected) {//For deselecting
                    Object rowKeyFromModel = table.getRowKeyFromModel(object);
                    if (currentRowKeys.contains(rowKeyFromModel)) {
                        selectionList.add(object);
                        currentRowKeys.remove(rowKeyFromModel);
                    } 
                }                    
            }
            for (String key : currentRowKeys) {//For selecting  
                Object rowData = table.getRowData(key);
                if (rowData != null && !selectionList.contains(rowData)) {
                       selectionList.add(rowData);
                }
            }

        }else{

                for(int i = 0; i < rowKeys.length; i++) {
                    Object rowData = table.getRowData(rowKeys[i]);

                    if(rowData != null)
                        selectionList.add(rowData);
                }

            }

            if(isArray) {
                Object selectionArray = Array.newInstance(clazz.getComponentType(), selectionList.size());
                table.setSelection(selectionList.toArray((Object[]) selectionArray));
            }
            else {
                table.setSelection(selectionList);
            }
        }
    }
}

可能不是最好的解决方案,但应该有效,请告诉我是否有更好的方法。希望这有助于某人。

答案 3 :(得分:1)

只需像这样实现绑定到DataTable(selection="#{pageBackingForm.selectedEntityList}")的selection属性的属性,它就可以工作:

    private Map<Integer, List<Entity>> selectedEntityListMap = new Hashtable<>();

    public List<Entity> getSelectedEntityList() {
        return selectedEntityListMap.get(getCurrentEntitySelectionPage());
    }

    public void setSelectedEntityList(List<Entity> selectedEntityList) {
        if (selectedEntityList == null) {
            selectedEntityListMap.remove(getCurrentEntitySelectionPage());
            return;
        }

        selectedEntityListMap.put(getCurrentEntitySelectionPage(), selectedEntityList);
    }

    public Integer getCurrentEntitySelectionPage() {
        DataTable dataTable = (DataTable) FacesContext.getCurrentInstance().getViewRoot().findComponent("formId:dataTableId");
        return dataTable.getPage();
    }

答案 4 :(得分:-1)

我的数据表遇到了同样的问题。虽然我的情况有点不同,因为我使用的是 selectBooleanCheckbox 。我找到了一个适合我的简单解决方案。当你说“旧的选择未在表格中呈现”时,它会打击我。

  • 使用 a4j:支持活动
  • 打开复选框

代码:

<h:selectBooleanCheckbox value="#{batch.toPortfolio}">
    <a4j:support event="onchange" />
</h:selectBooleanCheckbox>