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:
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?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.In webPage just add a event for when page switch:
<p:ajax event="page" listener="#{listingBean.updateSelected()}" />
In the listingBean, just save the selected:
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);
}
}
}
}
Just implement the property bound to selection property of DataTable (selection="#{pageBackingForm.selectedEntityList}"
) like this and it will work :
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();
}
While Bruno's solution works for keeping selections across paginations, it doesn't account for retaining selections on an individual page (i.e. when never changing pages).
This problem can be resolved more simply by using the rowSelectCheckbox and rowUnselectCheckbox ajax events, in addition to having a separate "saved" row list.
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>
Backing 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;
}
This way the list of saved rows is always kept up to date without needing a "page" ajax event.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With