p:dataTable selections are lost after paginating a LazyDataModel

datatable, jsf-2, lazy-loading, pagination, primefaces

Solution

This is because when `SelectionFeature` is decoded a new list is created.

And if `table.getRowData(rowKeys[i])` (related to your `LazyDataModel` implementation) returns null your old selectıons in the previous page are gone.may try to solve it by changing your LazyDataModel implementation I didn't try these but take a look at this and this

Had the same problem and I think this solution is easier if you have a lot of different tables implementing LazyDataModel.

This is what I did: check if it is lazy first then add currently selected rows to the selectionList.

For primefaces 4.0

1)Override DataTableRenderer

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

And

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)Override SelectionFeature's decode

Updated: edited to allow deselecting

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);
            }
        }
    }
}

Might not be the best solution but should work, let me know if there is a better way. Hope this helps someone.

Problem

My problem is that after I've selected a few items on the 1st page, if I paginate to another page and come back, my initial selections are not shown. I've tried to implement the `SelectableDataModel` as well as using the `rowKey` attribute but the problem persists. This is my test 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 } ``` This is my test page: ``` <?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> ``` I'd be very grateful if you could show me what I've done wrong here. UPDATE: After I added more `System.out.println("TEST")` to the above code, I observed the following things: - On the console, every time I paginate, `TEST 1: getRowData` is always printed before `TEST 2: load`. As a consequence, I believe the method `#LazyDataModel.getWrappedData()` may return data from the old page. At first, I thought this method's goal was to retrieve the selected rows to highlight on the table. However, if this method is called before `load`, there's no way it can do the job right? - After I selected the 1st 2 items on the 1st page, on the console, I saw `TEST 3: chosenItems's size: 2`. If I paginate to the 2nd page and then back to the 1st page, the selections are lost as mentioned. However, if I continued to select another item, on the console, I saw `TEST 3: chosenItems's size: 3`. Obviously, the `chosenItems` list still kept my old selections but they're not rendered on the table.

Original source