Validation error selectOneListbox. Entity #OneToOne relationship - jsf

I tried to make same list as in the below link:
http://www.primefaces.org/showcase/ui/input/listbox.xhtml
Unfortunately I get validation error:
Select Box: Validation Error: Value is not valid
I don't know where I've made mistake. I read a lot about this issue (in most cases it was solved by BalusC) however still can't find the issue.
If you can help I really appreciate.
StoreHouse.java
#Entity
public class StoreHouse implements Serializable {
#Id
#GeneratedValue
private Integer id;
#OneToOne
private Supply supply;
#Column(nullable = false)
private Integer amount;
//geters setters namedqueryies
Supply.java
#Entity
public class Supply implements Serializable {
#Id
#GeneratedValue
private Integer id;
#Column(unique = true, nullable = false, length = 32)
private String name;
#Column(length = 1024)
private String description;
#Column
private Double price;
#Enumerated(EnumType.STRING)
#NotNull
private SupplyType supplyType;
//geters setters namedqueryies
StoreHouseController.java
#ManagedBean
public class StoreHouseController implements Serializable {
#Inject
private StoreHouseBean storeHouseBean; // DAO for storeHouse
#ManagedProperty("#{supplyController}")
private SupplyController supplyController; //Manged bean for Supply
private StoreHouse storeHouse = new StoreHouse();
private List<Supply> allSupplies;
#PostConstruct
public void init() {
allSupplies = supplyController.findAll();
}
public void check() {
System.out.println("storeHousetheme" + storeHouse.toString()); // Function just to check if the supply was set
}
SupplyConverter.java
#FacesConverter("supplyConverter")
public class SupplyConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
try {
SupplyController supplyController = (SupplyController) context.getExternalContext().getApplicationMap().get("supplyController");
Supply supply = supplyController.findById(Integer.parseInt(value));
System.out.println("CONVERTER:" + supply.toString());
return supply;
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion error", "ERROR."));
}
} else {
return null;
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object object) {
if (object != null) {
return String.valueOf(((Supply) object).getId());
} else {
return null;
}
}
}
view.xhtml
<h:form>
<p:messages autoUpdate="true" />
<h:panelGrid columns="2">
<h:outputLabel value="Nazwa" />
<p:selectOneListbox id="supplies" value="#{storeHouseController.storeHouse.supply}" converter="supplyConverter" var="s" filter="true" label="Select Box">
<f:selectItems value="#{storeHouseController.allSupplies}" var="supply" itemLabel="#{supply.name}" itemValue="#{supply}"/>
<p:column>
<h:outputText value="#{s.name}" />
</p:column>
</p:selectOneListbox>
<p:commandButton action="#{storeHouseController.check}" type="submit" value="submit" />
</h:panelGrid>
If you need any other file/class please just add comment.

Your SelectItem-value is your object, so the toString()-method is called and you get something like "Supply#44de6bae".
Then, your controller tries to parse it to int and fails. Try...
<f:selectItems value="#{...}" var="..." itemLabel="..." itemValue="#{supply.id}"/>
If the error occures while loading the page, this might help as well as at least some components don't like "none-basic-type-or-enum" values. I hope this helped, Greez.

After few more days of searching and googling I've found the solution. I'm a bit angry that this is not posted anywhere on primefaces because I've lost many hours to fix the issue.
It came out that I did not overwrite equals method of Supply class which is required.
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Supply other = (Supply) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
if (!Objects.equals(this.name, other.name)) {
return false;
}
if (this.supplyType != other.supplyType) {
return false;
}
return true;
}
I believe that this topic will help someone in the future...

Related

Why selected items is always empty in the Bean for Primefaces selectCheckboxMenu

I am using selectCheckboxMenu from Primefaces in a JSF project, the problem is that the "selectedDepartments" object in the Bean is always empty.
Here is the code:
Xhtml Page
<p:selectCheckboxMenu id="menu" label="name" style="width: 15rem" converter="#{departmentConverter}" value="#{StudentMB.selectedDepartments}"
multiple="true" filter="true" filterMatchMode="startsWith" panelStyle="width: 15rem" scrollHeight="250" >
<c:selectItems value="#{StudentMB.departmentList}" var="department" itemLabel="#{department.name}" itemValue="#{department}"/>
<p:ajax event="change" process="#this" update=":form2:dt-Students" global="false"/>
</p:selectCheckboxMenu>
The converter for selectCheckboxMenu:
#Named
#FacesConverter(value = "departmentConverter")
public class departmentConverter implements Converter {
#Inject
private departmentDAO departmentDAO;
#Override
public department getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
try {
department c= departmentDAO.getdepartmentById(Integer.parseInt(value));
return c;
}
catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid country."));
}
}
else {
return null;
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value != null) {
department c=(department)value;
return String.valueOf(c.getId());
}
else {
return null;
}
}
}
The code of the Managed Bean
public class StudentMB implements Serializable{
private LazyDataModel<Student> lazyModel = null;
private List<department> departmentList;
private List<department> selectedDepartments;
#Inject
private departmentDAO departmentDAO;
#PostConstruct
public void init() {
this.selectedDepartments= new ArrayList<>();
this.departmentList= new ArrayList<>();
this.departmentList = this.departmentDAO.listDepartments();
lazyModel = new StudentLazyList(StudentDAO){
#Override
public List<Student> load(int first, int pageSize, Map<String, SortMeta> sortBy, Map<String, FilterMeta> filterBy) {
return load2(first, pageSize, sortBy, filterBy,selectedDepartments);
}
};
}
}
Thanks for help.
The solution is found for those who encounter the same problem. it was relative to the converter, indeed I don't know why we have to use a converter when it comes to a Pojo class because we already mentioned the name of class in the value property of selectItems.
Nevermind , here is the code that works
The selectCheckboxMenu component
<h:form id="form1">
<p:selectCheckboxMenu id="menu" label="department" converter="departmentConverter" value="#{studentMB.selectedDepartments}"
multiple="true" filter="true" filterMatchMode="startsWith" style="width: 15rem" panelStyle="width: 15rem" scrollHeight="250" >
<c:selectItems value="#{studentMB.departmentList}" var="department" itemLabel="#{department.name}" itemValue="#{department}"/>
<p:ajax event="change" update=":form2:dt-students"/>
</p:selectCheckboxMenu>
</h:form>
The converter class
#Named
#FacesConverter(value = "departmentConverter")
public class DepartmentConverter implements Converter {
#Override
public Department getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null)
return null;
StudentMB data = context.getApplication().evaluateExpressionGet(context, "#{studentMB}", StudentMB.class);
int actualId=Integer.parseInt(value);
for(Department dep : data.getDepartmentList())
{
if(categ.getId()==actualId)
return dep;
}
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid department."));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value != null) {
Department c=(Department)value;
return String.valueOf(c.getId());
}
else {
return null;
}
}
}
Hope this help

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

Primefaces p:orderList java backing list does not update

I am currently implementing a orderable list using PrimeFaces' component, embedded inside a . I was able to get the list to appear properly with my items. However, when I saved the list and submitted it back to the server, the rearranged items did not get reflected in the backing bean for some reason. Since the Primefaces showcase was able to see the changes, what am I doing wrong?
XHTML Snippet:
<h:form id="confirmDialogForm">
<p:confirmDialog id="arrangeProjDialog" widgetVar="arrangeDlg" width="600"
header="Meeting Order"
appendToBody="true" message="Drag and drop to rearrange meeting order">
<p:orderList id="arrangeProjDialogList"
value="#{adminMeetingListBean.orderProjList}"
converter="#{adminMeetingListBean.rowConverter}"
var="po"
controlsLocation="left"
styleClass="wideList"
itemLabel="#{po.projectTitle}"
itemValue="#{po}"
>
<f:facet name="caption">Proposals</f:facet>
</p:orderList>
<p:commandButton value="Save" ajax="true" process="arrangeProjDialogList #this"
actionListener="#{adminMeetingListBean.updateProposalMeetingOrder}" onclick="arrangeDlg.hide();">
</p:commandButton>
<p:button value="Cancel" onclick="arrangeDlg.hide(); return false;" />
</p:confirmDialog>
</h:form>
Backing Bean:
public void updateProposalMeetingOrder() {
if (selectedMeeting != null) {
orderProjTitles.get(0);
meetingService.updateMeetingProjSequence(orderProjList, selectedMeeting.getMeetingId());
}
}
The List is a list of POJO "ProposalOrderRow" objects. This has the definition:
public class ProposalOrderRow implements Serializable {
private static final long serialVersionUID = -5012155654584965160L;
private int dispSeq;
private int appId;
private int assignmentId;
private String refNo;
private String projectTitle;
public int getDispSeq() {
return dispSeq;
}
public void setDispSeq(int dispSeq) {
this.dispSeq = dispSeq;
}
public int getAppId() {
return appId;
}
public void setAppId(int appId) {
this.appId = appId;
}
public String getRefNo() {
return refNo;
}
public void setRefNo(String refNo) {
this.refNo = refNo;
}
public String getProjectTitle() {
return projectTitle;
}
public void setProjectTitle(String projectTitle) {
this.projectTitle = projectTitle;
}
public int getAssignmentId() {
return assignmentId;
}
public void setAssignmentId(int assignmentId) {
this.assignmentId = assignmentId;
}
}
Converter:
#FacesConverter("proposalOrderRowConverter")
public class ProposalOrderRowConverter implements Converter {
private List<ProposalOrderRow> orderRows;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String newValue) {
if (newValue.isEmpty()) {
return null;
}
for (ProposalOrderRow item : orderRows) {
String refNo = item.getRefNo();
if (refNo.equals(newValue)) {
return item;
}
}
return null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
ProposalOrderRow row = (ProposalOrderRow) value;
String output = row.getRefNo();
return output;
}
public List<ProposalOrderRow> getOrderRows() {
return orderRows;
}
public void setOrderRows(List<ProposalOrderRow> orderRows) {
this.orderRows = orderRows;
}
}
This problem is caused by appendToBody="true" in the confirm dialog. Setting it to false solved the problem.
See link here: link

JSF Converter Validation Error: Value not valid

I have a rich:pickList, when submit values it still displays error: "Validation Error: Value is not valid". I also set breakpoint to debug (getAsobject), but after system invocation I had nothing.
BalusC said that I would implement the equals() method in my entities, or I have the entities in the web service, and I fill the right side of the picklist with data from this web service.
xHTML file
<h:form>
<rich:panel>
<h:panelGrid columns="2" styleClass="criteresSaisie"
rowClasses="critereLigne" columnClasses="titreColonne,">
<h:outputLabel for="libelleComplement" value=" "
size="20" />
<rich:pickList id="libelleComplement" sourceCaption="Compléments"
targetCaption="Compléments sélectionnés"
value="#{listeCmpltDispoModel.listeCmpltSelect}" size="15"
addText=">" addAllText=">>" removeText="<"
removeAllText="<<" listWidth="270px" listHeight="110px"
orderable="true">
<f:selectItems value="#{listeCmpltDispoModel.listeCmpltDispo}"
var="liste" itemLabel="#{liste.libelleComplement}"
itemValue="#{liste}" />
<f:converter converterId="cmpltsTitresConcerter" />
</rich:pickList>
</h:panelGrid>
<h:panelGroup>
<div align="right">
<h:panelGrid columns="8">
<h:commandButton value="Valider"
action="#{saisieCmpltsTitreCtrl.valider}" />
</h:panelGrid>
</div>
</h:panelGroup>
</rich:panel>
</h:form>
Converter
#FacesConverter(value="cmpltsTitresConcerter")
public class CmpltsTitresConcerter implements Converter
{
public Object getAsObject(FacesContext context, UIComponent component, String value)
{
ComplementsDispoSortieDTO cmpltSelect= new ComplementsDispoSortieDTO();
if(value != null)
{
cmpltSelect.setCdComplement(Long.parseLong(value));
//cmpltSelect.setLibelleComplement("aaa");
}
return cmpltSelect;
}
public String getAsString(FacesContext arg0, UIComponent arg1, Object obj)
{
String result = null;
if(obj != null)
{
result = String.valueOf(((ComplementsDispoSortieDTO) obj).getCdComplement());
}
return result;
}
}
Model
#ManagedBean(name = "listeCmpltDispoModel")
#SessionScoped
public class ListeCmpltDispoModel implements Serializable {
private static final long serialVersionUID = 1L;
private Long cdComplement;
private String libelleComplement;
private int nbCompl;
private List<ComplementsDispoSortieDTO> listeCmpltDispo ;
private List<ComplementsDispoSortieDTO> listeCmpltSelect ;
public ListeCmpltDispoModel() {
}
public Long getCodeComplement() {
return cdComplement;
}
public void setCodeComplement(Long cdComplement) {
this.cdComplement = cdComplement;
}
public String getLibelleComplement1() {
return libelleComplement;
}
public void setLibelleComplement1(String libelleCoplement) {
this.libelleComplement = libelleCoplement;
}
public Long getCdComplement() {
return cdComplement;
}
public void setCdComplement(Long cdComplement) {
this.cdComplement = cdComplement;
}
public String getLibelleComplement() {
return libelleComplement;
}
public void setLibelleComplement(String libelleComplement) {
this.libelleComplement = libelleComplement;
}
public List<ComplementsDispoSortieDTO> getListeCmpltDispo() {
return listeCmpltDispo;
}
public void setListeCmpltDispo(List<ComplementsDispoSortieDTO> listeCmpltDispo) {
this.listeCmpltDispo = listeCmpltDispo;
}
public int getNbCompl() {
return nbCompl;
}
public void setNbCompl(int nbCompl) {
this.nbCompl = nbCompl;
}
public List<ComplementsDispoSortieDTO> getListeCmpltSelect() {
return listeCmpltSelect;
}
public void setListeCmpltSelect(List<ComplementsDispoSortieDTO> listeCmpltSelect) {
this.listeCmpltSelect = listeCmpltSelect;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((cdComplement == null) ? 0 : cdComplement.hashCode());
result = prime
* result
+ ((libelleComplement == null) ? 0 : libelleComplement
.hashCode());
result = prime * result
+ ((listeCmpltDispo == null) ? 0 : listeCmpltDispo.hashCode());
result = prime
* result
+ ((listeCmpltSelect == null) ? 0 : listeCmpltSelect.hashCode());
result = prime * result + nbCompl;
return result;
}
#Override
public boolean equals(Object obj){
if(!(obj instanceof ComplementsDispoSortieDTO)){
return false;
}
return ((ComplementsDispoSortieDTO)obj).getCdComplement()==this.cdComplement;
}
}
The equals() method of your entity is not right. It's not only in the wrong class (the backing bean class instead of the model class), but it's also using == on an Object (the == on an object only tests the reference, not the internal value; as a starter, you should have experienced this mistake on String values already).
The == on a Long would only return true is the Long instance was created by Long#valueOf() or Long#parseLong() and the value is between -128 and 127. Anything else, including new Long(value) and those with values beyond the given "flyweight" range, return false.
As with every other Java object (such as your current one), you need equals() instead. Put it in the right class and implement it as follows:
public class ComplementsDispoSortieDTO {
private Long cdComplement;
// ...
#Override
public boolean equals(Object obj){
if (!(obj instanceof ComplementsDispoSortieDTO)){
return false;
}
return (cdComplement != null)
? cdComplement.equals(((ComplementsDispoSortieDTO) obj).cdComplement)
: (obj == this);
}
}
Note that I also added the missing reflexive obj == this. See also the javadoc for list of requirements.
See also:
Validation Error: Value is not valid
Right way to implement equals contract
I know which could be the solution.
You should store the list and selected element in properties and maintain them in the scope as long as the component is used.

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