I have primefaces datatable which has 4 to 5 input components. One of the input is p:autocomplete. There is a option to add new row through UI, i have added first row later i am trying to add 2nd row whatever value selected in the first row it remains same in the 2nd row. Along with that if i choose different value in the 2nd through autocomplete that value is reflecting for both the rows.
But i would like to retain the value which is selected for first row then i need to retain the 2nd row value differently.
Note: I have used p:selectonemenu inorder to show the values from DB, it has 10000+ records it is taking more time. Due to performance issue i had gone for p:autocomplete
Please see the code
XHTML code- LocationDetails.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.org/ui">
<h:form id="atcs">
<p:dataTable var="atcscirreq" id="atcscirreqtbl"
value="#{atcsCircuitIdAddressRequestBean.atcsAddressCircuitRequestList}"
binding="#{atcsCircuitIdAddressRequestBean.dataTable}" lazy="true" resizableColumns="true">
<p:column id="locn">
<p:autoComplete id="lcnLst" required="true"
requiredMessage="LocationName is required field"
converter="locationNameAutoCompleteConverter"
completeMethod="#{atcsCircuitIdAddressRequestBean.locationNames}"
var="locn" itemLabel="#{locn.locName}" itemValue="#{locn}"
value="#{atcsCircuitIdAddressRequestBean.locnInfo}"
emptyMessage="No Records Found" maxResults="10"
forceSelection="true">
</p:autoComplete>
</p:column>
</p:dataTable>
<p:commandButton value="Add Another Request" action="# {atcsCircuitIdAddressRequestBean.addNewRequestData}" update="atcscirreqtbl">
</h:form>
ATCSCircuitIdAddressRequestBean.java
package com.bean.request;
public class ATCSCircuitIdAddressRequestBean {
TblTrackLocationinformation locnInfo = new TblTrackLocationinformation();
List<TblTrackLocationinformation> filteredLocations = new ArrayList<TblTrackLocationinformation>();
private List<TblTrackLocationinformation> atcsAddressCircuitRequestList = new ArrayList<TblTrackLocationinformation>();
public List<TblTrackLocationinformation> locationNames(String name) {
List<TblTrackLocationinformation> allLocations = service.getAllLocations;
if (name.trim().equals(""))
return allLocations;
for (int i = 0; i < allLocations.size(); i++) {
TblTrackLocationinformation data = allLocations.get(i);
if (data.getLocName().toString()
.contains(name)
|| data.getLocName().toLowerCase()
.contains(name.toLowerCase())) {
filteredLocations.add(data);
}
}
return filteredLocations;
}
public TblTrackLocationinformation getLocnInfo() {
return locnInfo;
}
public void setLocnInfo(TblTrackLocationinformation locnInfo) {
this.locnInfo = locnInfo;
}
public void addNewRequestData() {
TblTrackLocationInformationdata = new TblTrackLocationInformation();
filteredLocations=new ArrayList<TblTrackLocationinformation>();
atcsAddressCircuitRequestList.add(data);
}
}
TblTrackLocationInformation.java
package com.bean.request;
public class TblTrackLocationInformation{
private String locName;
private Integer locationId;
public String getLocName() {
return locName;
}
public void setLocName(String locName) {
this.locName = locName;
}
public Integer getLocationId() {
return locationId;
}
public void setLocationId(Integer locationId) {
this.locationId = locationId;
}
}
LocationNameAutoCompleteConverter.java
package com.bean.request;
#FacesConverter("locationNameAutoCompleteConverter")
public class LocationNameAutoCompleteConverter implements Converter{
LocationInfoDAO locnDao = new LocationInfoDAOImpl();
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String value) {
return locnDao.getLocationInfoById(Integer.valueOf(value));
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object value) {
return String.valueOf(((TblTrackLocationinformation) value)
.getLocationId());
}
}
Specifications of the projects are JSF 2.1, Primefaces 5.0,Servlet 2.5
I have searched many links but i did not get answer. I am checking this for past 2 days.
Please help me out !
After long time, i came to know the solution. We need to know when and how to use List with object and List with String.
Have to use
public List<String> locationNames(String name)
instead of
public List<TblTrackLocationinformation> locationNames(String name)
Related
Hi I have built two consecutive dropdowns one being initialized in init() method and the other being populated on the basis on first one's selection.
Command Button is used to use both the selected values for processing, Strange that Setter for the selected values is being called twice(setting blank the second time..as I found out in debugging)..I need understand whyu I am getting blank values?
myPage.xhtml
<p:selectOneMenu id="dbList"
value="#{backingBean.selectedConnection}"
style="width:200px">
<p:ajax listener="#{backingBean.onConnectionChange()}"
update="aliasList" />
<f:selectItem itemLabel="Select" itemValue="#{backingBean.selectedConnection}"
noSelectionOption="false" />
<f:selectItems value="#{backingBean.connectionsTypeList}" />
</p:selectOneMenu>
<p:outputLabel for="aliasList" value="Connection Alias" />
<p:selectOneMenu id="aliasList"
value="#{backingBean.aliasSelected}" style="width:200px">
<f:selectItem itemLabel="Select" itemValue="#{backingBean.aliasSelected}"
noSelectionOption="false"/>
<f:selectItems value="#{backingBean.connectionsList}" />
</p:selectOneMenu>
<p:commandButton value="myButton"
actionListener="#{backingBean.myMethod()}"
id="myButton" />
backingBean.java
#ManagedBean
#ViewScoped
public class BackingBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private ArrayList<String> connectionsList;
private String aliasSelected;
private String selectedConnection;
private ArrayList<String> connectionsTypeList;
#PostConstruct
public void init() {
// Initialize the connection alias drop down here
this.connectionsTypeList = getConnectionTypeList();
this.connectionsList=new ArrayList<String>();
}
public void onConnectionChange() {
System.out.println("In Connection Change, populating aliases");
getConnectionDBList();
}
public String getAliasSelected() {
return aliasSelected;
}
//Dont know why this is called twice on button click and second time value set is blank
public void setAliasSelected(String aliasSelected) {
this.aliasSelected = aliasSelected;
}
public ArrayList<String> getConnectionsList() {
return connectionsList;
}
public void setConnectionsList(ArrayList<String> connectionsList) {
this.connectionsList = connectionsList;
}
public String getSelectedConnection() {
return selectedConnection;
}
public void setSelectedConnection(String selectedConnection) {
this.selectedConnection = selectedConnection;
}
public ArrayList<String> getConnectionsTypeList() {
return connectionsTypeList;
}
public void setConnectionsTypeList(ArrayList<String> connectionsTypeList) {
this.connectionsTypeList = connectionsTypeList;
}
public void myMethod() {
System.out.println("selected Alias is "+aliasSelected);
System.out.println("selected connection is "+selectedConnection);
}
}
New to primefaces,please help
Answer to this :"setting blank the second time..as I found out in debugging"
This is happening because I realized I was pointing the same property to two ui components..as in..two dropdowns in the same page were linked to the same property of the backing bean..Sillyh Mistake..hence the second call to the setter..with Blank values..as it was not initialized for the second UI Component
I have a List<String> and I successfully represent it in a datatable; now I am trying to create a composite component out of it, but it seems I keep having trouble understanding how StateHelper does work.
What I want to do is, if the value attribute passed by xhtml evaluates to null, to create a new List<String> automatically. Right now, the only possible action is clicking a button that adds a new item to the list.
My component
<cc:interface componentType="testComponent">
<cc:attribute name="value" required="true" type="java.util.List"/>
</cc:interface>
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
<p:dataTable id="data" value="#{cc.data}" var="_data">
<p:column headerText="Nombre / Relación">
<h:outputText value="#{_data}" />
</p:column>
</p:dataTable>
<p:commandButton value="Añadir" process="#this" update="data"
actionListener="#{cc.addData}" ajax="true"/>
</cc:implementation>
The component bean is
#FacesComponent("testComponent")
public class TestComponent extends UIOutput implements NamingContainer {
private static final String LISTA_DATOS = "LST_DATOS";
private static final Logger log = Logger.getLogger(TestComponent.class.getName());
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
public List<String> getData() {
#SuppressWarnings("unchecked")
List<String> data = (List<String>) this.getStateHelper().get(LISTA_DATOS);
return data;
}
public void setData(List<String> data) {
this.getStateHelper().put(LISTA_DATOS, data);
}
public void addData() {
List<String> data = (List<String>)this.getData();
data.add("HOLA");
this.setData(data);
}
public void init() {
log.info("En init()");
if (this.getStateHelper().get(LISTA_DATOS) == null) {
if (this.getValue() == null) {
this.getStateHelper().put(LISTA_DATOS, new ArrayList<String>());
} else {
this.getStateHelper().put(LISTA_DATOS, this.getValue());
}
}
}
The component is called like that
<h:form>
<imas:editorTest value="#{testBean.data1}"/>
</h:form>
<h:form>
<imas:editorTest value="#{testBean.data2}"/>
</h:form>
with testBean being:
private List<String> data1 = new ArrayList<>(Arrays.asList("ONE", "TWO", "SIXTYNINE"));
private List<String> data2 = null;
public List<String> getData1() {
return this.data1;
}
public void setData1(List<String> data1) {
this.data1 = data1;
}
public List<String> getData2() {
return this.data2;
}
public void setData2(List<String> data2) {
this.data2 = data2;
}
The issue I found is that, when passing data2 (the null list), clicking the button adds a new item but only the first two times; after that, no matter how many times I click the button, no new items are added to the list (no exception shown in the log). On the opposite, there is no problem adding as many items as I wish to the component initialized with data1.
One thing that I have observed and that leads me to thinking that I am misusing getStateHelper is that, when I click the button, the init() method is executed twice and, at that time, this.getStateHelper().get(LISTA_DATOS) is null, while I expected it to be not null due to having initialized it when the component was first rendered. I expected getStateHelper to carry such state between invocations, where am I wrong?.
Oh! I am using Wildfly 8.1 (no upgrades) with JDK 7.
Digging deeper, I found some evidence of a bug, so I reported it. I'll update the answer to see if it is really a bug or some big misconception from me.
My composite components share values stored in StateHelper when placed inside PrimeFaces DataTable. The most of examples about keeping component state that I've seen suggest to use getStateHelper().put()/eval() methods of UINamingContainer. I do use these methods but without luck. How to do that properly? (currently I use workaround described in the end of this post)
To illustrate the issue I've created click counter based on PrimeFaces commandLink component. In the example below two counters that are outside of dataTable work as expected. But all counters that appear inside dataTable share the same counter value (clicking on any one of them continues common value).
Update:
I've figured out that to allow sorting (for example) to work correctly inside datatable I need to bind my component to certain raw somehow. And "shared" state helper allows to do exactly that. So now I specify row key as an attribute and have updated methods to store state. There is no question if this way is correct.
Update for counterLink.xhtml:
<composite:interface componentType="CounterLink2Component">
<composite:attribute name="key" type="java.io.Serializable"/>
</composite:interface>
And CounterLinkComponent.java now is:
#FacesComponent("CounterLinkComponent")
public class CounterLinkComponent extends UINamingContainer {
private enum PropertyKeys {
COUNTER_VALUE
}
public void count() {
storeInstanceValue(PropertyKeys.COUNTER_VALUE.toString(), getCounterValue() + 1);
}
public Integer getCounterValue(){
return (Integer) evalInstanceValue(PropertyKeys.COUNTER_VALUE.toString(), 0);
}
private Serializable getKeyAttr() {
return (Serializable) getAttributes().get("key");
}
private void storeInstanceValue(String key, Object value) {
Serializable subkey = getKeyAttr();
if (subkey == null) {
getStateHelper().put(key, value);
} else {
getStateHelper().put(subkey, key, value);
}
}
private Object getInstanceValue(String key) {
Serializable subkey = getKeyAttr();
if (subkey == null) {
return getStateHelper().eval(key);
} else {
return ((Map) getStateHelper().eval(subkey, Collections.emptyMap())).get(key);
}
}
private Object evalInstanceValue(String key, Object _default) {
Object result = getInstanceValue(key);
return result != null ? result : _default;
}
}
Original example:
Primefaces 5.0, Glassfish 4.
counterLink.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui">
<composite:interface componentType="CounterLinkComponent">
</composite:interface>
<composite:implementation>
<p:commandLink action="#{cc.count()}" partialSubmit="true" update="#this">
<h:outputText value="#{cc.counterValue}"/>
</p:commandLink>
</composite:implementation>
</html>
CounterLinkComponent.java:
import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
import java.io.Serializable;
#FacesComponent("CounterLinkComponent")
public class CounterLinkComponent extends UINamingContainer {
private enum PropertyKeys {
COUNTER_VALUE
}
public void count() {
getStateHelper().put(PropertyKeys.COUNTER_VALUE, getCounterValue() + 1);
}
public Integer getCounterValue(){
return (Integer) getStateHelper().eval(PropertyKeys.COUNTER_VALUE, 0);
}
}
Usage example:
<h:form>
<p:panelGrid columns="1">
<cmp:counterLink/>
<cmp:counterLink/>
<p:dataTable var="item" value="#{counterLinkStoreBean.itemList}">
<p:column headerText="Name">
#{item.name}
</p:column>
<p:column headerText="Counter">
<cmp:counterLink/>
</p:column>
</p:dataTable>
</p:panelGrid>
</h:form>
Backing bean for this example (just creates several items):
#Named
#ViewScoped
public class CounterLinkStoreBean implements Serializable {
private List<Item> itemList;
#PostConstruct
private void init() {
itemList = new ArrayList<Item>();
itemList.add(new Item("Test 1"));
itemList.add(new Item("Test 2"));
itemList.add(new Item("Test 3"));
}
public List<Item> getItemList() {
return itemList;
}
public static class Item {
private final String name;
public Item(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
In my case I can use workaround storing values in a map with component clientId as a secondary key:
private void storeInstanceValue(Serializable key, Object value) {
getStateHelper().put(key, getClientId(), value);
}
private Object getInstanceValue(Serializable key) {
return ((Map)getStateHelper().eval(key, Collections.emptyMap())).get(getClientId());
}
Is there more natural solution?
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.
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);
}
}
}
}
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.
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.
I had the same problem with my data table. Although my case is a bit different because I am using selectBooleanCheckbox instead. I found a simple solution that works for me. It hit me when you said "old selection are not rendered in the table".
strap the checkbox with a a4j:support event
code:
<h:selectBooleanCheckbox value="#{batch.toPortfolio}">
<a4j:support event="onchange" />
</h:selectBooleanCheckbox>
I am trying to learn PF so I started with displaying datatable first and navigating to next page on rowClick passing parameters, but got stuck with the following error. I found similar problem for that question but no luck yet. I hope somebody will help me out.
I am getting following error:
DataModel must implement org.primefaces.model.SelectableDataModel when selection is enabled.
Caused by:
javax.faces.FacesException - DataModel must implement org.primefaces.model.SelectableDataModel when selection is enabled.
my Page:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Primefaces 3.1</title>
</h:head>
<h:body>
<h:form id="form">
<p:dataTable value="#{tableBean.cars}" var="var" rowkey="#{var.model}"
selection="#{tableBean.car}" selectionMode="single">
<p:column> <f:facet name="header">
<h:outputText styleClass="outputText" value="Model"></h:outputText>
</f:facet>
<h:outputText styleClass="outputText"
value="#{var.model}"></h:outputText>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText styleClass="outputText" value="Color"></h:outputText>
</f:facet>
<h:outputText styleClass="outputText"
value="#{var.randomColor}"></h:outputText>
</p:column></p:dataTable>
</h:form>
</h:body>
</html>
my bean Classes:
#ManagedBean
#ViewScoped
public class TableBean extends ListDataModel<Car> implements SelectableDataModel<Car>{
private List<Car> cars;
private Car car;
public List<Car> getCars() {
cars = new ArrayList<Car>();
Car car1 = new Car();
car1.setModel("BMW");
car1.setRandomColor("Black");
cars.add(car1);
Car car2 = new Car();
car2.setModel("Audi");
car2.setRandomColor("White");
cars.add(car2);
return cars;
}
public String onRowSelect(){
System.out.println("Row Click!!!");
return "otherpage";//Does this nav works???if not how???
}
public Car getCar() {
return car;
}
#Override
public Car getRowData(String pArg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public Object getRowKey(Car pArg0) {
// TODO Auto-generated method stub
return null;
}
}
OtherBean:
public class Car{
private String model;
private String randomColor;
public String getRandomColor() {
return randomColor;
}
public void setRandomColor(String pRandomColor) {
randomColor = pRandomColor;
}
public String getModel() {
return model;
}
public void setModel(String pModel) {
model = pModel;
}
}
The #{tableBean.cars} must implement SelectableDataModel if you don't specify the rowKey attribute on the <p:dataTable>.
You have however implemented it on the #{tableBean} instead. This is not right. You can find correct examples in the PrimeFaces showcase. Basically, your TableBean class must look like this according to the showcase code example:
#ManagedBean
#ViewScoped
public class TableBean implements Serializable {
private List<Car> cars;
private Car car;
private CarDataModel carsModel;
public TableBean() {
cars = new ArrayList<Car>();
// Populate cars here and thus NOT in the getter method!
carsModel = new CarDataModel(cars);
}
// ...
}
Where the CarDataModel look like this (again, just copied from PrimeFaces showcase code example):
public class CarDataModel extends ListDataModel<Car> implements SelectableDataModel<Car> {
public CarDataModel() {
}
public CarDataModel(List<Car> data) {
super(data);
}
#Override
public Car getRowData(String rowKey) {
List<Car> cars = (List<Car>) getWrappedData();
for(Car car : cars) {
if(car.getModel().equals(rowKey))
return car;
}
return null;
}
#Override
public Object getRowKey(Car car) {
return car.getModel();
}
}
Finally use #{tableBean.carsModel} instead of #{tableBean.cars} as value of <p:dataTable>. Exactly as in the showcase example.
<p:dataTable value="#{tableBean.carsModel}" var="car" ... />
Another, easier, way is to just specify the rowKey attribute on the <p:dataTable>.
<p:dataTable value="#{tableBean.cars}" var="car" rowKey="#{car.model}" ... />
This way you don't need the whole SelectableDataModel. You only need to ensure that it's never null and always unique across the rows. See also DataModel must implement org.primefaces.model.SelectableDataModel when selection is enabled, but I have already defined rowKey.
Ensure to set the rowKey parameter in the bean method that populates the "value=.." of the datatable. Mine returned this error because the rowKey was null.
It worked for my project using a composite key for rowkey e.g.: rowKey="#{course.getCompositeKey()
Since I didn't have a compositeKey variable I simply created one in my Course Class (in your case the Car Class). The 2 primary keys are strings so I just said this.compositeKey=this.courseNumber+this.product--you just use whatever 2 primary keys are in your Car class instead of courseNumber and product.
Youd on't have to implement the datamodal in your managedbean, just specify in the proprety of the datatable the "rowkey" like this: rowkey="{mbean.atribute1}"
-- atribute1 must be diplayed in the data table column.
Must