Update a value pased as a parameter in a composite component - jsf-2

I use this questions to create a composite component with the behaviour of a time selector.
This is my composite xhtml
<ui:composition xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:rich="http://richfaces.org/rich">
<cc:interface componentType="customTimeBean">
<cc:attribute name="date" type="java.util.Date" required="true" />
</cc:interface>
<cc:implementation>
<rich:inputNumberSpinner value="#{cc.hours}" minValue="0"
maxValue="23" />
<h:outputLabel value=":" />
<rich:inputNumberSpinner value="#{cc.minutes}" minValue="0"
maxValue="59" />
</cc:implementation>
</ui:composition>
This is my Faces Component
#FacesComponent(value = "customTimeBean")
public class CustomTimeBean extends UINamingContainer {
private Date getDate() {
Date d = (Date) getAttributes().get("date");
if (d == null) {
//throw new RuntimeException("Date no debe ser nulo");
d = new date();
}
return d;
}
public void setMinutes(int value) {
getDate().setMinutes(value);
}
public void setHours(int value) {
getDate().setHours(value);
}
public int getMinutes() {
return getDate().getMinutes();
}
public int getHours() {
return getDate().getHours();
}
public void setSeconds(int value) {
getDate().setHours(value);
}
public int getSeconds() {
return getDate().getSeconds();
}
}
And the usage
<sigh:time date="#{bean.date}" hasSeconds="false"/>
My test case:
<h:outputLabel value="#{controller.date}" id="date" />
<sigh:time date="#{controller.date}" />
<a4j:commandButton render="date" />
The "controller":
#ManagedBean
public class Controller {
Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
This works well, but, this doesn't work when I pass a null Date as attribute, how can I update the value in the Bean (controller.date)?
In my test case, when I press the a4j:commandButton and date in the controller is null, the outputlabel dont show nothing (date is null), when the date is not null, the date is update everytime I click the Button.
Sorry for my bad english.
Thanks!

Just replace
throw new RuntimeException("Date no debe ser nulo");
by
d = new Date();
Unrelated to the concrete problem, the Date methods which you're using there are deprecated. See also the javadoc. The main reason is that it doesn't deal with timezones/DST properly. You should internally in the composite be using java.util.Calendar instead.

Related

primefaces p:autocomplete selected value repeating in next row with datatable

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)

How to avoid sharing of values stored in StateHelper of composite components placed inside PrimeFaces datatable?

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?

p:dataTable selections are lost after paginating a LazyDataModel

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>

How to update the page regularly in jsf2.0?

My JSF 2.0 page is contains some data, which is dynamic hence needs automatic refresh in some predefined time interval(say every 10 seconds).
I am using PrimeFaces 3.5 as a powerful component suite. Below is the managed bean-
#ManagedBean
#ViewScoped
public Monitor implements Serializable {
//max,min,avg,stdDev,reason are caluclated based on some dynamic data
private int max;
private int min;
private double avg;
private double stdDev;
private String reason;
public int getMax() {
//Here before returning I am calling some methods to get the max from dynamic data
return max;
}
public int getMin() {
//Here before returning I am calling some methods to get the min from dynamic data
return min;
}
public double getAvg() {
//Here before returning I am calling some methods to get the avg from dynamic data
return avg;
}
public String getReason() {
//Here before returning I am calling some methods to get the reason from dynamic data
return reason;
}
public double getStdDev() {
//Here before returning I am calling some methods to get the stdDev from dynamic data
return stdDev;
}
public void setMax(int max) {
this.max = max;
}
public void setMin(int min) {
this.min = min;
}
public void setAvg(double avg) {
this.avg = avg;
}
public void setStdDev(double stdDev) {
this.stdDev = stdDev;
}
public void setReason(String reason) {
this.reason = reason;
}
}
I haven't finalize the layout till now however i would be like this-
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Insert title here</title>
</h:head>
<h:body>
<p:panel>
<h:outputLabel value="#{monitor.min}" />
<h:outputLabel value="#{monitor.max}" />
<h:outputLabel value="#{monitor.avg}" />
<h:outputLabel value="#{monitor.stdDev}" />
<h:outputLabel value="#{monitor.reason}" />
</p:panel>
</h:body>
</html>
How can I update the page regularly in JSF 2.0?
You can use Poll to auto refresh your page, Poll can start stop via javascript:
for example, poll will stop when count(bean variable) >=10:
You can use stop like #Ravi post, my example demo you can start stop(via: pol.stop() and pol.start()).
Facelets:
<h:form id="form">
<p:panel id="pntest">
// content here
</p:panel>
<h:outputText id="txt_count" value="#{tabview.count}" />
<p:poll interval="1" listener="#{tabview.increment}" update="txt_count" widgetVar="pol" oncomplete="test(xhr, status, args);"/>
<script type="text/javascript">
//<![CDATA[
function test(xhr, status, args){
if(args.sotest >= 10){
pol.stop();
}else{
location.reload(); // refresh page
}
}
//]]>
</script>
</h:form>
Bean:
private int count = 0;
public void increment() {
count++;
RequestContext reqCtx = RequestContext.getCurrentInstance();
reqCtx.addCallbackParam("sotest", count);
//1. update by using PrimeFaces specific API, use [RequestContext#update()][3].
RequestContext.getCurrentInstance().update("form:pntest");
//2. update by using standard JSF API, add the client ID to [PartialViewContext#getRenderIds()][4].
FacesContext.getCurrentInstance().getPartialViewContext().getRenderIds().add("form:pntest");
}
public int getCount() {
return this.count;
}
public void setCount(int count) {
this.count = count;
}

How to prevent multiple composite components reset themselves on a JSF page?

I put this problem in a simple example, a composite component that calculates the sum of 2 inputs and prints the result in an outputText
Main JSF page:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ez="http://java.sun.com/jsf/composite/ezcomp/">
<h:head></h:head>
<h:body>
<ez:Calculator />
<br/>
<br/>
<ez:Calculator />
<br/>
<br/>
<ez:Calculator />
</h:body>
</html>
Composite component XHTML:
<!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:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>This content will not be displayed</title>
</h:head>
<h:body>
<composite:interface componentType="calculator">
</composite:interface>
<composite:implementation>
<h:form>
<h:inputText id="first" value="#{cc.firstNumber}" />
<h:commandButton value="+" action="#{cc.sum}"/>
<h:inputText id="second" value="#{cc.secondNumber}" />
</h:form>
<h:outputText id="result" value="#{cc.result}" />
</composite:implementation>
</h:body>
</html>
Composite component backing bean:
package ez;
import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
#FacesComponent("calculator")
public class Calculator extends UINamingContainer {
private Long firstNumber;
private Long secondNumber;
private Long result;
public Calculator() {
}
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
public void setFirstNumber(String firstNumber) {
this.firstNumber = Long.parseLong(firstNumber);
}
public String getFirstNumber() {
if(firstNumber == null) {
return null;
}
return firstNumber.toString();
}
public void setSecondNumber(String secondNumber) {
this.secondNumber = Long.parseLong(secondNumber);
}
public String getSecondNumber() {
if(secondNumber == null) {
return null;
}
return secondNumber.toString();
}
public String getResult() {
if(result == null) {
return null;
}
return result.toString();
}
public void setResult(String result) {
this.result = Long.parseLong(result);
}
public void sum() {
this.result = this.firstNumber + this.secondNumber;
}
}
So, I have 3 Composite Components that all should do the same thing, but when I press a SUM button, after the server processes the request, the result is printed out on the page, but the other 2 components are cleared of their values.
How can I prevent this? How can I force it to retain those values?
UIComponent instances are recreated on every request, hereby losing all instance variables everytime. They basically act like request scoped managed beans, while you intend to have them in the view scope. You need to take view state saving into account on a per-attribute basis. This is normally by default already done for all attributes of #{cc.attrs}. So, if you can, just make use of it:
<cc:interface componentType="calculator">
<cc:attribute name="firstNumber" type="java.lang.Long" />
<cc:attribute name="secondNumber" type="java.lang.Long" />
</cc:interface>
<cc:implementation>
<h:form>
<h:inputText id="first" value="#{cc.attrs.firstNumber}" />
<h:commandButton value="+" action="#{cc.sum}"/>
<h:inputText id="second" value="#{cc.attrs.secondNumber}" />
</h:form>
<h:outputText id="result" value="#{cc.attrs.result}" />
</cc:implementation>
with just this (nullchecks omitted; I recommend to make use of required="true" on the inputs)
#FacesComponent("calculator")
public class Calculator extends UINamingContainer {
public void sum() {
Long firstNumber = (Long) getAttributes().get("firstNumber");
Long secondNumber = (Long) getAttributes().get("secondNumber");
getAttributes().put("result", firstNumber + secondNumber);
}
}
Otherwise, you'd have to take state saving into account yourself by delegating all attribute getters/setters to UIComponent#getStateHelper(). Based on the very same Facelets code as you have, the entire backing component would look like this:
#FacesComponent("calculator")
public class Calculator extends UINamingContainer {
public void sum() {
setResult(getFirstNumber() + getSecondNumber());
}
public void setFirstNumber(Long firstNumber) {
getStateHelper().put("firstNumber", firstNumber);
}
public Long getFirstNumber() {
return (Long) getStateHelper().eval("firstNumber");
}
public void setSecondNumber(Long secondNumber) {
getStateHelper().put("secondNumber", secondNumber);
}
public Long getSecondNumber() {
return (Long) getStateHelper().eval("secondNumber");
}
public void setResult(Long result) {
getStateHelper().put("result", result);
}
public Long getResult() {
return (Long) getStateHelper().eval("result");
}
}
See, no local variables anymore. Note that I also removed the need for those ugly manual String-Long conversions by just declaring the right getter return type and setter argument type. JSF/EL will do the conversion automagically based on default converters or custom Converters. As there's already a default one for Long, you don't need to provide a custom Converter.
Unrelated to the concrete problem, you can safely remove the getFamily() method. The UINamingContainer already provides exactly this. If you were implementing NamingContainer interface instead, then you'd indeed need to provide it yourself, but this is thus not the case here. The above backing component examples have it already removed.

Resources