Conversion error for JSF custom component attribute [duplicate] - jsf

I have created a custom ISO date time Converter:
public class IsoDateTimeConverter implements Converter, StateHolder {
private Class type;
private String pattern;
private boolean transientValue = false;
public void setType(Class type) {
this.type = type;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
if (StringCheck.isNullOrEmpty(value)) {
throw new ConverterException("value not specified");
}
try {
if (IsoDate.class.equals(type)) {
if (WebConst.ISO_DATE_NONE.equals(value)) {
return IsoDate.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoDate(value, TimeZone.getDefault().getID());
}
} else if (IsoTime.class.equals(type)) {
if (WebConst.ISO_TIME_NONE.equals(value)) {
return IsoTime.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoTime(value, TimeZone.getDefault().getID());
}
} else if (IsoTimestamp.class.equals(type)) {
if (WebConst.ISO_TIMESTAMP_NONE.equals(value)) {
return IsoTimestamp.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoTimestamp(value, TimeZone.getDefault().getID());
}
} else {
throw new ConverterException("value not convertible");
}
} catch (Exception e) {
throw new ConverterException(e.getMessage());
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
if (value == null) {
throw new ConverterException("value not specified");
}
if (IsoDate.class.equals(value)) {
IsoDate isoDate = (IsoDate) value;
if (isoDate.isDummy()) {
return WebConst.ISO_DATE_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoDate.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else if (IsoTime.class.equals(value)) {
IsoTime isoTime = (IsoTime) value;
if (isoTime.isDummy()) {
return WebConst.ISO_TIME_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoTime.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else if (IsoTimestamp.class.equals(value)) {
IsoTimestamp isoTimestamp = (IsoTimestamp) value;
if (isoTimestamp.isDummy()) {
return WebConst.ISO_TIMESTAMP_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoTimestamp.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else {
throw new ConverterException("value not convertible");
}
}
#Override
public Object saveState(FacesContext context) {
return new Object[]{type, pattern};
}
#Override
public void restoreState(FacesContext context, Object state) {
type = (Class) ((Object[]) state)[0];
pattern = (String) ((Object[]) state)[1];
}
#Override
public boolean isTransient() {
return transientValue;
}
#Override
public void setTransient(boolean transientValue) {
this.transientValue = transientValue;
}
}
And I use the Converter as <mh:IsoDateTimeConverter> in the following view:
<p:dataTable value="#{imports.list}" var="item">
<p:column>
<h:outputText value="#{item.balanceDate}" immediate="true">
<mh:IsoDateTimeConverter type="#{webConst.ISO_DATE_CLASS}" pattern="#{webConst.ISO_DATE_FORMAT}"/>
</h:outputText>
</p:column>
</p:dataTable>
The problem is, when I first open this view, all properties are set in my Converter class only once and then the datatable renders and converts the values based on initial properties.
I expected that the properties are set on a per-row basis. How can I achieve this?

To the point, you expected that the converter's properties are set every time a datatable row is rendered. This is indeed not true. JSF will create only one converter instance per component when the view is to be built, it will not create/reset the converter each time the row is rendered.
There are several ways to get it to work.
Pass the dynamic attributes as <f:attribute> of the component and let the Converter intercept on that. You can find an example here: JSF convertDateTime with timezone in datatable. This can then be used as
<h:outputText value="#{item.balanceDate}">
<f:converter converterId="isoDateTimeConverter" />
<f:attribute name="pattern" value="#{item.pattern}" />
</h:outputText>
Use an EL function instead of a Converter. You can find an example here: Facelets and JSTL (Converting a Date to a String for use in a field). This can then be used as
<h:outputText value="#{mh:convertIsoDate(item.balanceDate, item.pattern)}" />
Bind the converter and datatable's DataModel as a property of the same managed bean. This way you will be able to set the converter's properties based on the row data before returning it. Here's a basic kickoff example based on standard JSF components and standard DateTimeConverter (it should work equally good on PrimeFaces components and with your custom converter):
<h:dataTable value="#{bean.model}" var="item">
<h:column>
<h:outputText value="#{item.date}" converter="#{bean.converter}" />
</h:column>
</h:dataTable>
with
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<Item> items;
private DataModel<Item> model;
private DateTimeConverter converter;
#PostConstruct
public void init() {
items = Arrays.asList(
new Item(new Date(), "dd-MM-yyyy"),
new Item(new Date(), "yyyy-MM-dd"),
new Item(new Date(), "MM/dd/yyyy"));
model = new ListDataModel<Item>(items);
converter = new DateTimeConverter();
}
public DataModel<Item> getModel() {
return model;
}
public Converter getConverter() {
converter.setPattern(model.getRowData().getPattern());
return converter;
}
}
(the Item class is just a bean with two properties Date date and String pattern)
this results in
23-09-2011
2011-09-23
09/23/2011
Use OmniFaces <o:converter> instead. It supports render time evaluation of EL in the attributes. See also the <o:converter> showcase example.
<h:outputText value="#{item.balanceDate}">
<o:converter converterId="isoDateTimeConverter" pattern="#{item.pattern}" />
</h:outputText>

The above excellent (as always) answer from BalusC is comprehensive but didn't quite hit my exact requirement. In my case, I need to bind a Converter to each iteration in a ui:repeat. I need a different Converter depending on each item being repeated. The answer did point me in the right direction, though, so I thought it worth sharing my solution in case it helps anyone else.
I use a Converter that delegates all it's work to another Converter object specified in the attribute, as in the first of BalusC's answers. Note that this doesn't help at all if you wish to use converters with parameters, it's aimed at the situation where you would want to bind a Converter to a property of a repeating object.
Here's the delegating Converter. It's also a Validator, which works in exactly the same way.
// package and imports omitted for brevity
#FacesConverter(value="delegatingConverter")
#FacesValidator(value="delegatingValidator")
public class Delegator implements Converter, Validator {
// Constants ---------------------------------------------------------------
private static final String CONVERTER_ATTRIBUTE_NAME = "delegateConverter";
private static final String VALIDATOR_ATTRIBUTE_NAME = "delegateValidator";
// Business Methods --------------------------------------------------------
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) throws ConverterException {
return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) throws ConverterException {
return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
.getAsString(context, component, value);
}
#Override
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
retrieveDelegate(component, Validator.class, VALIDATOR_ATTRIBUTE_NAME)
.validate(context, component, value);
}
private <T> T retrieveDelegate(UIComponent component, Class<T> clazz,
String attributeName) {
Object delegate = component.getAttributes().get(attributeName);
if (delegate == null) {
throw new UnsupportedOperationException("No delegate was specified."
+ " To specify, use an f:attribute tag with: name=\""
+ attributeName + "\"");
}
if (!(clazz.isAssignableFrom(delegate.getClass()))) {
throw new UnsupportedOperationException("The specified delegate "
+ "was not a " + clazz.getSimpleName() + " object. " +
"Delegate was: " + delegate.getClass().getName());
}
return (T) delegate;
}
}
So now where I would wish to use this code within my ui:repeat, which won't work:
<h:outputText value="#{item.balanceDate}">
<f:converter binding="#{item.converter} />
<f:validator binding="#{item.validator} />
</h:outputText>
I can instead use this code, which works OK:
<h:outputText value="#{item.balanceDate}">
<f:converter converterId="delegatingConverter"/>
<f:validator validatorId="delegatingValidator"/>
<f:attribute name="delegateConverter" value="#{item.converter}"/>
<f:attribute name="delegateValidator" value="#{item.validator}"/>
</h:outputText>
Assuming that the repeating item has a public Converter getConverter() method and similar for the Validator.
This does have the advantage that the same Converters or Validators that are used elsewhere can be re-used without any changes.

Related

JSF : convertNumber MonetaryAmount not working in dataTable [duplicate]

I have created a custom ISO date time Converter:
public class IsoDateTimeConverter implements Converter, StateHolder {
private Class type;
private String pattern;
private boolean transientValue = false;
public void setType(Class type) {
this.type = type;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
if (StringCheck.isNullOrEmpty(value)) {
throw new ConverterException("value not specified");
}
try {
if (IsoDate.class.equals(type)) {
if (WebConst.ISO_DATE_NONE.equals(value)) {
return IsoDate.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoDate(value, TimeZone.getDefault().getID());
}
} else if (IsoTime.class.equals(type)) {
if (WebConst.ISO_TIME_NONE.equals(value)) {
return IsoTime.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoTime(value, TimeZone.getDefault().getID());
}
} else if (IsoTimestamp.class.equals(type)) {
if (WebConst.ISO_TIMESTAMP_NONE.equals(value)) {
return IsoTimestamp.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoTimestamp(value, TimeZone.getDefault().getID());
}
} else {
throw new ConverterException("value not convertible");
}
} catch (Exception e) {
throw new ConverterException(e.getMessage());
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
if (value == null) {
throw new ConverterException("value not specified");
}
if (IsoDate.class.equals(value)) {
IsoDate isoDate = (IsoDate) value;
if (isoDate.isDummy()) {
return WebConst.ISO_DATE_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoDate.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else if (IsoTime.class.equals(value)) {
IsoTime isoTime = (IsoTime) value;
if (isoTime.isDummy()) {
return WebConst.ISO_TIME_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoTime.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else if (IsoTimestamp.class.equals(value)) {
IsoTimestamp isoTimestamp = (IsoTimestamp) value;
if (isoTimestamp.isDummy()) {
return WebConst.ISO_TIMESTAMP_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoTimestamp.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else {
throw new ConverterException("value not convertible");
}
}
#Override
public Object saveState(FacesContext context) {
return new Object[]{type, pattern};
}
#Override
public void restoreState(FacesContext context, Object state) {
type = (Class) ((Object[]) state)[0];
pattern = (String) ((Object[]) state)[1];
}
#Override
public boolean isTransient() {
return transientValue;
}
#Override
public void setTransient(boolean transientValue) {
this.transientValue = transientValue;
}
}
And I use the Converter as <mh:IsoDateTimeConverter> in the following view:
<p:dataTable value="#{imports.list}" var="item">
<p:column>
<h:outputText value="#{item.balanceDate}" immediate="true">
<mh:IsoDateTimeConverter type="#{webConst.ISO_DATE_CLASS}" pattern="#{webConst.ISO_DATE_FORMAT}"/>
</h:outputText>
</p:column>
</p:dataTable>
The problem is, when I first open this view, all properties are set in my Converter class only once and then the datatable renders and converts the values based on initial properties.
I expected that the properties are set on a per-row basis. How can I achieve this?
To the point, you expected that the converter's properties are set every time a datatable row is rendered. This is indeed not true. JSF will create only one converter instance per component when the view is to be built, it will not create/reset the converter each time the row is rendered.
There are several ways to get it to work.
Pass the dynamic attributes as <f:attribute> of the component and let the Converter intercept on that. You can find an example here: JSF convertDateTime with timezone in datatable. This can then be used as
<h:outputText value="#{item.balanceDate}">
<f:converter converterId="isoDateTimeConverter" />
<f:attribute name="pattern" value="#{item.pattern}" />
</h:outputText>
Use an EL function instead of a Converter. You can find an example here: Facelets and JSTL (Converting a Date to a String for use in a field). This can then be used as
<h:outputText value="#{mh:convertIsoDate(item.balanceDate, item.pattern)}" />
Bind the converter and datatable's DataModel as a property of the same managed bean. This way you will be able to set the converter's properties based on the row data before returning it. Here's a basic kickoff example based on standard JSF components and standard DateTimeConverter (it should work equally good on PrimeFaces components and with your custom converter):
<h:dataTable value="#{bean.model}" var="item">
<h:column>
<h:outputText value="#{item.date}" converter="#{bean.converter}" />
</h:column>
</h:dataTable>
with
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<Item> items;
private DataModel<Item> model;
private DateTimeConverter converter;
#PostConstruct
public void init() {
items = Arrays.asList(
new Item(new Date(), "dd-MM-yyyy"),
new Item(new Date(), "yyyy-MM-dd"),
new Item(new Date(), "MM/dd/yyyy"));
model = new ListDataModel<Item>(items);
converter = new DateTimeConverter();
}
public DataModel<Item> getModel() {
return model;
}
public Converter getConverter() {
converter.setPattern(model.getRowData().getPattern());
return converter;
}
}
(the Item class is just a bean with two properties Date date and String pattern)
this results in
23-09-2011
2011-09-23
09/23/2011
Use OmniFaces <o:converter> instead. It supports render time evaluation of EL in the attributes. See also the <o:converter> showcase example.
<h:outputText value="#{item.balanceDate}">
<o:converter converterId="isoDateTimeConverter" pattern="#{item.pattern}" />
</h:outputText>
The above excellent (as always) answer from BalusC is comprehensive but didn't quite hit my exact requirement. In my case, I need to bind a Converter to each iteration in a ui:repeat. I need a different Converter depending on each item being repeated. The answer did point me in the right direction, though, so I thought it worth sharing my solution in case it helps anyone else.
I use a Converter that delegates all it's work to another Converter object specified in the attribute, as in the first of BalusC's answers. Note that this doesn't help at all if you wish to use converters with parameters, it's aimed at the situation where you would want to bind a Converter to a property of a repeating object.
Here's the delegating Converter. It's also a Validator, which works in exactly the same way.
// package and imports omitted for brevity
#FacesConverter(value="delegatingConverter")
#FacesValidator(value="delegatingValidator")
public class Delegator implements Converter, Validator {
// Constants ---------------------------------------------------------------
private static final String CONVERTER_ATTRIBUTE_NAME = "delegateConverter";
private static final String VALIDATOR_ATTRIBUTE_NAME = "delegateValidator";
// Business Methods --------------------------------------------------------
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) throws ConverterException {
return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) throws ConverterException {
return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
.getAsString(context, component, value);
}
#Override
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
retrieveDelegate(component, Validator.class, VALIDATOR_ATTRIBUTE_NAME)
.validate(context, component, value);
}
private <T> T retrieveDelegate(UIComponent component, Class<T> clazz,
String attributeName) {
Object delegate = component.getAttributes().get(attributeName);
if (delegate == null) {
throw new UnsupportedOperationException("No delegate was specified."
+ " To specify, use an f:attribute tag with: name=\""
+ attributeName + "\"");
}
if (!(clazz.isAssignableFrom(delegate.getClass()))) {
throw new UnsupportedOperationException("The specified delegate "
+ "was not a " + clazz.getSimpleName() + " object. " +
"Delegate was: " + delegate.getClass().getName());
}
return (T) delegate;
}
}
So now where I would wish to use this code within my ui:repeat, which won't work:
<h:outputText value="#{item.balanceDate}">
<f:converter binding="#{item.converter} />
<f:validator binding="#{item.validator} />
</h:outputText>
I can instead use this code, which works OK:
<h:outputText value="#{item.balanceDate}">
<f:converter converterId="delegatingConverter"/>
<f:validator validatorId="delegatingValidator"/>
<f:attribute name="delegateConverter" value="#{item.converter}"/>
<f:attribute name="delegateValidator" value="#{item.validator}"/>
</h:outputText>
Assuming that the repeating item has a public Converter getConverter() method and similar for the Validator.
This does have the advantage that the same Converters or Validators that are used elsewhere can be re-used without any changes.

Retrieve class values from selectCheckboxMenu

I have a <p:selectCheckboxMenu> and I want to get the selected values back in bean. But the value I receive
when I select an item from the menu it's a string, representing the type field from the CategorizationBean.
I just want when I select an item from the table, to get the whole CategorizationBean structure in the bean.
This is the snippet from the xhtml page:
<p:selectCheckboxMenu label="Categorization"
value="#alertMB.selectedCategories}"
converter="com.converter.CategoryConverter">
<f:selectItems value="#{alertMB.categoryDomainEntry}"
var="category"
itemLabel="#{category.type}"
itemValue="#{category}"/>
</p:selectCheckboxMenu>
Snippet from bean:
public List<CategorizationBean> getSelectedCategories() {
return selectedCategories;
}
public void setSelectedCategories(List<CategorizationBean> selectedCategories) {
this.selectedCategories = selectedCategories;
}
public class CategorizationBean implements Serializable{
private String type;
private long id;
I think that you have missed by using a list of beans, I use this example and it works:
<p:selectCheckboxMenu id="slctRdBtn"
value="#{yourBean.compLovDtgrid}"
converter="compLovDtgridConverter">
<f:selectItems
value="#{yourBean.listCompLovDtgrid}"
var="rdbtn" itemLabel="#{rdbtn.vjlrLibelleRep}"
itemValue="#{rdbtn}" />
</p:selectCheckboxMenu>
and for the converter:
#FacesConverter(forClass=CompLovDtgrid.class , value="compLovDtgridConverter")
public class CompLovDtgridConverter implements Converter{
#Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
return (value instanceof CompLovDtgrid) ? ((CompLovDtgrid) value).getVjlrCodeRep() : null;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,String value)
{
if(value == null)
return null;
YourBean data = context.getApplication().evaluateExpressionGet(context, "#{yourBean}", YourBean.class);
for(CompLovDtgrid compLovDtgrid : data.getListCompLovDtgrid())
{
if(compLovDtgrid.getVjlrCodeRep().equals(value))
return compLovDtgrid;
}
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to CompLovDtgrid", value)));
}
}
and for the list, I use:
public List<CompLovDtgrid> getListCompLovDtgrid()
{
return listCompLovDtgrid;
}
public void setListCompLovDtgrid(List<CompLovDtgrid> listCompLovDtgrid) {
this.listCompLovDtgrid = listCompLovDtgrid;
}

selectonemenu jsf on objects with converter

Here is my SelectOneMenu
<h:selectOneMenu value="#{bean.myObject}" >
<f:ajax render="componentToRender" listener="#{bean.onSelect}"/>
<f:converter converterId="myObjectConverter" />
<f:selectItem itemLabel="None" itemValue="#{null}" />
<f:selectItems value="#{bean.objects}" var="object" itemValue="#{object}" itemLabel="#{object.name}" />
</h:selectOneMenu>
And my converter
#FacesConverter("myObjectConverter")
public class MyObjectConverter implements Converter{
private List<MyObject> objects;
public MyObjectConverter(){
this.objects = MyController.getAllMyObjects();
}
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(!StringUtils.isInteger(value)) {
return null;
}
return this.getMyObject(value);
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if(value == null) {
return null;
}
return String.valueOf(((MyObject) value).getId()).toString();
}
public MyObject getMyObject(String id) {
Iterator<MyObject > iterator = this.objects.iterator();
while(iterator.hasNext()) {
MyObject object = iterator.next();
if(object.getId() == Integer.valueOf(id).intValue()) {
return object;
}
}
return null;
}
}
The problem is that my ajax listener is never called and my component never rendered.
Is there something wrong with my converter or selectOneMenu? I follow an example and I can't figure the mistake out.
BTW : my simple method in the bean
public void onSelect() {
System.out.println(this.myObject);
if(this.myObject != null) {
System.out.println(this.myObject.getName());
}
}
I already had a problem like this and I changed my selected value from object to id. But here I want to make it work with objects because I know it's possible.
Thanks
I have the solution. I had to override the "equals" method in MyObject class!
Thanks.
EDIT: the code
#Override
public boolean equals(Object obj) {
if(this.id == ((MyObject) obj).id) {
return true;
}else {
return false;
}
}

rich:pickList & selectManyListbox - JSF Converter Validation Error: Value not valid [duplicate]

This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 7 years ago.
I replaced rich:pickList to selectManyListbox for testing, when submit values to server it still displays error: "Validation Error: Value is not valid". I also set breakpoint to debug StaffConverter (getAsobject), but system never invokes it. Pls let me know some reasons why my converter never invoked and suggest me how to fix this. thanks
xhtml file:
<h:selectManyListbox value="#{reportController.selectedStaffList}" converter="staffConverter">
<f:selectItems value="#{reportController.staffList}"
var="item" itemValue="#{item}" itemLabel="#{item.name}" />
</h:selectManyListbox>
<rich:pickList value="#{reportController.selectedStaffList}" converter="staffConverter" sourceCaption="Available flight" targetCaption="Selected flight" listWidth="195px" listHeight="100px" orderable="true">
<f:selectItems value="#{reportController.staffList}" var="item" itemValue="#{item}" itemLabel="#{item.name}" />
</rich:pickList>
My Converter:
#FacesConverter(forClass = Staff.class)
public static class StaffConverter implements Converter {
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
StaffController controller = StaffController.getInstance();
return controller.facade.find(Staff.class, getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Staff) {
Staff o = (Staff) object;
return getStringKey(o.getStaffCode());
} else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName()
+ "; expected type: " + StaffController.class.getName());
}
}
}
I implemented equals method in Staff:
#Override
public boolean equals(Object object) {
if (!(object instanceof Staff)) {
return false;
}
Staff other = (Staff) object;
return (this.staffCode == other.staffCode);
}
Just to have it posted somewhere for people - like me - who like searching for solutions for development issues on google or stackoverflow...
I had this issue several times, depending on which kind of pojos I was using in the converters... Finally I think I found an elegant solution.
In my case I use JPA entity classes directly as I wanted to save the DTO layer. Well, with some entities the rich:pickList worked, with others not... I also tracked it down to the equals method. In the example below for instance it did not work for the userGroupConverter bean.
My solution is simply to inline overwrite the equals method, so the one from the entity (I often use lombok) is untouched and does not need to be changed, at all! So in my converter below I only compare the name field in equals:
xhtml:
<rich:pickList id="pickListUserGroupSelection"
value="#{usersBean.selectedUserGroups}" switchByDblClick="true"
sourceCaption="Available user groups" targetCaption="Groups assigned to user"
listWidth="365px" listHeight="100px" orderable="false"
converter="#{userGroupConverter}"
disabled="#{!rich:isUserInRole('USERS_MAINTAIN')}">
<f:validateRequired disabled="true" />
<rich:validator disabled="true" />
<f:selectItems value="#{usersBean.userGroups}" var="userGroup"
itemValue="#{userGroup}"
itemLabel="#{userGroup.name}" />
<f:selectItems value="#{usersBean.selectedUserGroups}" var="userGroup"
itemValue="#{userGroup}"
itemLabel="#{userGroup.name}" />
</rich:pickList>
<rich:message for="pickListUserGroupSelection" />
Converter:
package ...;
import ...
/**
* JSF UserGroup converter.<br>
* Description:<br>
* JSF UserGroup converter for rich:pickList elements.<br>
* <br>
* Copyright: Copyright (c) 2014<br>
*/
#Named
#Slf4j
#RequestScoped // must be request scoped, as it can change every time!
public class UserGroupConverter implements Converter, Serializable {
/**
*
*/
private static final long serialVersionUID = 9057357226886146751L;
#Getter
Map<String, UserGroup> groupMap;
#Inject
MessageUtil messageUtil;
#Inject
UserGroupDao userGroupDao;
#PostConstruct
public void postConstruct() {
groupMap = new HashMap<>();
List<UserGroup> userGroups;
try {
userGroups = userGroupDao.findAll(new String[] {UserGroup.FIELD_USER_ROLE_NAMES});
if(userGroups != null) {
for (UserGroup userGroup : userGroups) {
// 20150713: for some reason the UserGroup entity's equals method is not sufficient here and causes a JSF validation error
// "Validation Error: Value is not valid". I tried this overridden equals method and now it works fine :-)
#SuppressWarnings("serial")
UserGroup newGroup = new UserGroup() {
#Override
public boolean equals(Object obj){
if (!(obj instanceof UserGroup)){
return false;
}
return (getName() != null)
? getName().equals(((UserGroup) obj).getName())
: (obj == this);
}
};
newGroup.setName(userGroup.getName());
groupMap.put(newGroup.getName(), newGroup);
}
}
} catch (DaoException e) {
log.error(e.getMessage(), e);
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Error initializing user group converter!", null);
fc.addMessage(null, message);
}
}
/*
* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
*/
#Override
public UserGroup getAsObject(FacesContext context, UIComponent component,
String value) {
UserGroup ug = null;
try {
ug = getGroupMap().get(value);
} catch (Exception e) {
log.error(e.getMessage(), e);
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Error converting user group!", null);
fc.addMessage(null, message);
}
return ug;
}
/*
* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
*/
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
String name = ((UserGroup) value).getName();
return name;
}
}
Brgds and have fun!
Thanks Brian and BalusC for your help. I fixed my problem by adding named converter inside selectManyListbox & rich:picklist so they run well. But normally, I only use #FacesConverter(forClass = Staff.class), and don't need to add named converter in jsf so they still run well except for selectManyListbox & rich:picklist

How to set converter properties for each row/item of h:dataTable/ui:repeat?

I have created a custom ISO date time Converter:
public class IsoDateTimeConverter implements Converter, StateHolder {
private Class type;
private String pattern;
private boolean transientValue = false;
public void setType(Class type) {
this.type = type;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
if (StringCheck.isNullOrEmpty(value)) {
throw new ConverterException("value not specified");
}
try {
if (IsoDate.class.equals(type)) {
if (WebConst.ISO_DATE_NONE.equals(value)) {
return IsoDate.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoDate(value, TimeZone.getDefault().getID());
}
} else if (IsoTime.class.equals(type)) {
if (WebConst.ISO_TIME_NONE.equals(value)) {
return IsoTime.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoTime(value, TimeZone.getDefault().getID());
}
} else if (IsoTimestamp.class.equals(type)) {
if (WebConst.ISO_TIMESTAMP_NONE.equals(value)) {
return IsoTimestamp.DUMMY;
} else {
//TODO User spezifische TimeZone auslesen
return new IsoTimestamp(value, TimeZone.getDefault().getID());
}
} else {
throw new ConverterException("value not convertible");
}
} catch (Exception e) {
throw new ConverterException(e.getMessage());
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
if (value == null) {
throw new ConverterException("value not specified");
}
if (IsoDate.class.equals(value)) {
IsoDate isoDate = (IsoDate) value;
if (isoDate.isDummy()) {
return WebConst.ISO_DATE_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoDate.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else if (IsoTime.class.equals(value)) {
IsoTime isoTime = (IsoTime) value;
if (isoTime.isDummy()) {
return WebConst.ISO_TIME_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoTime.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else if (IsoTimestamp.class.equals(value)) {
IsoTimestamp isoTimestamp = (IsoTimestamp) value;
if (isoTimestamp.isDummy()) {
return WebConst.ISO_TIMESTAMP_NONE;
} else {
//TODO User spezifische TimeZone auslesen
return isoTimestamp.toString(pattern, TimeZone.getDefault().getID(), false);
}
} else {
throw new ConverterException("value not convertible");
}
}
#Override
public Object saveState(FacesContext context) {
return new Object[]{type, pattern};
}
#Override
public void restoreState(FacesContext context, Object state) {
type = (Class) ((Object[]) state)[0];
pattern = (String) ((Object[]) state)[1];
}
#Override
public boolean isTransient() {
return transientValue;
}
#Override
public void setTransient(boolean transientValue) {
this.transientValue = transientValue;
}
}
And I use the Converter as <mh:IsoDateTimeConverter> in the following view:
<p:dataTable value="#{imports.list}" var="item">
<p:column>
<h:outputText value="#{item.balanceDate}" immediate="true">
<mh:IsoDateTimeConverter type="#{webConst.ISO_DATE_CLASS}" pattern="#{webConst.ISO_DATE_FORMAT}"/>
</h:outputText>
</p:column>
</p:dataTable>
The problem is, when I first open this view, all properties are set in my Converter class only once and then the datatable renders and converts the values based on initial properties.
I expected that the properties are set on a per-row basis. How can I achieve this?
To the point, you expected that the converter's properties are set every time a datatable row is rendered. This is indeed not true. JSF will create only one converter instance per component when the view is to be built, it will not create/reset the converter each time the row is rendered.
There are several ways to get it to work.
Pass the dynamic attributes as <f:attribute> of the component and let the Converter intercept on that. You can find an example here: JSF convertDateTime with timezone in datatable. This can then be used as
<h:outputText value="#{item.balanceDate}">
<f:converter converterId="isoDateTimeConverter" />
<f:attribute name="pattern" value="#{item.pattern}" />
</h:outputText>
Use an EL function instead of a Converter. You can find an example here: Facelets and JSTL (Converting a Date to a String for use in a field). This can then be used as
<h:outputText value="#{mh:convertIsoDate(item.balanceDate, item.pattern)}" />
Bind the converter and datatable's DataModel as a property of the same managed bean. This way you will be able to set the converter's properties based on the row data before returning it. Here's a basic kickoff example based on standard JSF components and standard DateTimeConverter (it should work equally good on PrimeFaces components and with your custom converter):
<h:dataTable value="#{bean.model}" var="item">
<h:column>
<h:outputText value="#{item.date}" converter="#{bean.converter}" />
</h:column>
</h:dataTable>
with
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<Item> items;
private DataModel<Item> model;
private DateTimeConverter converter;
#PostConstruct
public void init() {
items = Arrays.asList(
new Item(new Date(), "dd-MM-yyyy"),
new Item(new Date(), "yyyy-MM-dd"),
new Item(new Date(), "MM/dd/yyyy"));
model = new ListDataModel<Item>(items);
converter = new DateTimeConverter();
}
public DataModel<Item> getModel() {
return model;
}
public Converter getConverter() {
converter.setPattern(model.getRowData().getPattern());
return converter;
}
}
(the Item class is just a bean with two properties Date date and String pattern)
this results in
23-09-2011
2011-09-23
09/23/2011
Use OmniFaces <o:converter> instead. It supports render time evaluation of EL in the attributes. See also the <o:converter> showcase example.
<h:outputText value="#{item.balanceDate}">
<o:converter converterId="isoDateTimeConverter" pattern="#{item.pattern}" />
</h:outputText>
The above excellent (as always) answer from BalusC is comprehensive but didn't quite hit my exact requirement. In my case, I need to bind a Converter to each iteration in a ui:repeat. I need a different Converter depending on each item being repeated. The answer did point me in the right direction, though, so I thought it worth sharing my solution in case it helps anyone else.
I use a Converter that delegates all it's work to another Converter object specified in the attribute, as in the first of BalusC's answers. Note that this doesn't help at all if you wish to use converters with parameters, it's aimed at the situation where you would want to bind a Converter to a property of a repeating object.
Here's the delegating Converter. It's also a Validator, which works in exactly the same way.
// package and imports omitted for brevity
#FacesConverter(value="delegatingConverter")
#FacesValidator(value="delegatingValidator")
public class Delegator implements Converter, Validator {
// Constants ---------------------------------------------------------------
private static final String CONVERTER_ATTRIBUTE_NAME = "delegateConverter";
private static final String VALIDATOR_ATTRIBUTE_NAME = "delegateValidator";
// Business Methods --------------------------------------------------------
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) throws ConverterException {
return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) throws ConverterException {
return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
.getAsString(context, component, value);
}
#Override
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
retrieveDelegate(component, Validator.class, VALIDATOR_ATTRIBUTE_NAME)
.validate(context, component, value);
}
private <T> T retrieveDelegate(UIComponent component, Class<T> clazz,
String attributeName) {
Object delegate = component.getAttributes().get(attributeName);
if (delegate == null) {
throw new UnsupportedOperationException("No delegate was specified."
+ " To specify, use an f:attribute tag with: name=\""
+ attributeName + "\"");
}
if (!(clazz.isAssignableFrom(delegate.getClass()))) {
throw new UnsupportedOperationException("The specified delegate "
+ "was not a " + clazz.getSimpleName() + " object. " +
"Delegate was: " + delegate.getClass().getName());
}
return (T) delegate;
}
}
So now where I would wish to use this code within my ui:repeat, which won't work:
<h:outputText value="#{item.balanceDate}">
<f:converter binding="#{item.converter} />
<f:validator binding="#{item.validator} />
</h:outputText>
I can instead use this code, which works OK:
<h:outputText value="#{item.balanceDate}">
<f:converter converterId="delegatingConverter"/>
<f:validator validatorId="delegatingValidator"/>
<f:attribute name="delegateConverter" value="#{item.converter}"/>
<f:attribute name="delegateValidator" value="#{item.validator}"/>
</h:outputText>
Assuming that the repeating item has a public Converter getConverter() method and similar for the Validator.
This does have the advantage that the same Converters or Validators that are used elsewhere can be re-used without any changes.

Resources