Using ValueExpression for f:convertNumber pattern attribute - jsf

According to the TLD, convertNumber accepts ValueExpressions for its pattern attribute. But it doesn't seem to work (JSF 1.2 RI):
<h:outputText value="#{Test.numberValue}">
<f:convertNumber pattern="#{Test.numberPattern}" />
</h:outputText>
outputs
0.0210000000000000013045120539345589349977
(Test.numberValue evaluates to 0.021, Test.numberPattern to "0.00%")
If I use a String literal, everything works fine:
<h:outputText value="#{Test.numberValue}">
<f:convertNumber pattern="0.00%" />
</h:outputText>
outputs
2,10%
The h:outputText is part of a h:dataTable column, if that matters.

The h:outputText is part of a h:dataTable column, if that matters.
Found out that it actually matters, please see this question about convertDateTime in a datatable. According to that (thanks to BalusC, as always), this is my solution:
Custom converter:
public class DynamicNumberConverter extends NumberConverter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
setPattern((String) component.getAttributes().get("pattern"));
return super.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
setPattern((String) component.getAttributes().get("pattern"));
return super.getAsString(context, component, value);
}
}
Markup:
<h:outputText value="#{Test.numberValue}">
<f:converter converterId="DynamicNumberConverter" />
<f:attribute name="pattern" value="#{Test.numberPattern}"/>
</h:outputText>

Related

Using converter for custom objects in selectCheckboxMenu doesn't work [duplicate]

This question already has an answer here:
<f:selectItems> only shows toString() of the model as item label
(1 answer)
Closed 6 years ago.
I am trying to use an converter for custom objects, that are used in a primefaces' selectCheckboxMenu.
This is the JSF part:
<p:outputLabel value="#{msg.cars}: " for="cars" />
<p:selectCheckboxMenu id="cars"
value="#{controller.selected.cars}"
converter="carConverter" label="#{msg.cars}"
filter="true" filterMatchMode="startsWith"
panelStyle="width:200px">
<f:selectItems
value="#{controller.available.cars}" />
<f:converter converterId="carConverter" />
</p:selectCheckboxMenu>
And this is my converter:
#FacesConverter("carConverter")
public class CarConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String newValue) {
return null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object object) {
if (object == null) {
return "";
}
if (object instanceof Car) {
Car car = (Car) object;
String name = car.getName();
return name;
} else {
throw new ConverterException(new FacesMessage(object + " is not a valid car"));
}
}
}
getAsString() returns the correct String. But the selectCheckboxMenu still lists the objects and not the Strings.
Am I missing something?
If you need to show the car name in checkboxMenu label you have to use the selectItems' itemLabel attribute
<p:outputLabel value="#{msg.cars}: " for="cars" />
<p:selectCheckboxMenu id="cars"
value="#{controller.selected.cars}"
converter="carConverter"
filter="true" filterMatchMode="startsWith"
panelStyle="width:200px">
<f:selectItems value="#{controller.available.cars}" var="car" itemLabel="#{car.name}" itemValue="#{car}"/>
</p:selectCheckboxMenu>
BTW don't declare two converters (one via converter attribute and the other via f:converter), and override correctly the getAsObject method (it's needed during the Apply Request Values phase). Check the docs for the details

How can you access the #Size annotation of a field in JSF?

My problem is:
Some of my entity fields need to have an exact length for compatibility reasons. That length is defined via #Size(min=10, max=10) or similar on the field. Although the fields are typed as String, they actually contain numbers. Most of the fields have values with leading zeroes, for example: 0000148233.
Now I don't want to force the user to enter those leading zeroes in the input fields. It should be possible to just enter 148233.
My first approach was writing a composite component that uses a simple FacesConverter to add leading zeroes on the input, based on an attribute length:
<composite:interface>
<composite:attribute name="value" required="true" />
<composite:attribute name="length" required="true"
type="java.lang.Integer" />
</composite:interface>
<composite:implementation>
<h:inputText value="#{cc.attrs.value}">
<f:converter converterId="leadingZeroesConverter" />
<f:attribute name="length" value="#{cc.attrs.length}" />
</h:inputText>
</composite:implementation>
I read the length attribute in the Converter:
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
int length = (Integer) component.getAttributes().get("length");
return Strings.zeroPrefixFillUp(value, length);
}
That does work quite well, but I actually don't want to define the length in JSF.
What I would like to do is access the annotation somehow, either in JSF or in the converter. This way I could avoid maintaining that attribute in two places.
In the converter I have the FacesContext and the UIComponent (which is an InputText, obviously). Is there any way to get the field's name (and it's class), so I can access that annotation?
PS: Just to let you know, I stripped all the error handling from the Converter for clarity reasons.
Following #Kukeltje's link kind of brought me on the right track:
I now have two composite attributes, namely bean and field. Via cc.attrs.bean[cc.attrs.field] I set the inputText's value. In my converter I evaluate the expressions #{cc.attrs.bean} and #{cc.attrs.field}, which return the bean and the name of the field. Using reflection I can now access the #Size annotation.
The composite component
<composite:interface>
<composite:attribute name="bean" required="true" />
<composite:attribute name="field" required="true" type="java.lang.String" />
</composite:interface>
<composite:implementation>
<h:inputText value="#{cc.attrs.bean[cc.attrs.field]}">
<f:converter converterId="leadingZerosConverter" />
</h:inputText>
</composite:implementation>
The converter
#FacesConverter(value = "leadingZerosConverter")
public class LeadingZerosConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Object bean = evaluateExpression("#{cc.attrs.bean}", context, Object.class);
String fieldName = evaluateExpression("#{cc.attrs.field}", context, String.class);
if (bean == null || fieldName == null) {
throw new IllegalArgumentException("bean and field must not be null");
}
try {
Size annotation = bean.getClass().getDeclaredField(fieldName).getAnnotation(Size.class);
return Strings.zeroPrefixFillUp(value, annotation.min());
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalArgumentException(e);
}
}
private <T> T evaluateExpression(String expression, FacesContext context, Class<T> clazz) {
ExpressionFactory factory = context.getApplication().getExpressionFactory();
ValueExpression exp = factory.createValueExpression(context.getELContext(), expression, clazz);
return clazz.cast(exp.getValue(context.getELContext()));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return ((String) value).replaceFirst("^0+(?!$)", "");
}
}

JSF does not convert InputText value to string variable of backing bean without custom converter

I try learn JSF and faced with problem.
I did use Servlet 2.5, JDK 1.6 and JSF 2.0.6 (com.sun.faces version from pom file).
I have a simple JSF page that has a <h:inputText/> tag for interaction with user
I expect what user fill this h:inputText then click on h:commandButton and on server side i will get backing bean with updated value.
But in my case lifecycle of JSF breaks on process validations, move to render
response and show to user "Parser error!" message
I.e. for simple h:inputText without any validator and converter i receive error message from server side about parsing of h:inputText value.
After some time i figured out what i can create my own converter which will not modify object, just pass String through himself.
I did add my realization of converter to <h:inputText/> and this work.
Question:
In all examples in books and other tutorials nobody used custom converter for <h:inputText/> if inputText is representation of String value of backing bean.
Why all of this tutorials and examples not working for me without custom converter? Where my mistake?
Source codes:
index.xhtml without converter, not worked for me:
<h:form id="UserForm">
<h:outputText value="Insert your first name:" />
<h:inputText id="userNameID" required="true" value="#{userBean.firstName}">
<f:validateLength minimum="5" maximum="25" />
</h:inputText>
<h:message showSummary="true" showDetail="false" for="userNameID" />
<h:commandButton id="submit" action="/view/validator/response?faces-redirect=true"
value="Submit" />
</h:form>
UserBean.java:
#ManagedBean(name = "userBean")
#SessionScoped
public class UserBean implements Serializable {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
MyConverter.java - dummy converter
#FacesConverter(value = "myConverter")
public class MyConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value.toString();
}
}
index.xhtml with converter, worked as expected:
<h:form id="UserForm">
<h:outputText value="Insert your first name:" />
<h:inputText id="userNameID" required="true" value="#{userBean.firstName}" converter="myConverter">
<f:validateLength minimum="5" maximum="25" />
</h:inputText>
<h:message showSummary="true" showDetail="false" for="userNameID" />
<h:commandButton id="submit" action="/view/validator/response?faces-redirect=true"
value="Submit" />
</h:form>
The cause of the problem is not visible in the code posted so far, but the key symptom "it fails with a message coming from a so far unidentified converter while it succeeds with an explicit converter" suggests that you've elsewhere in the same project a #FacesConverter(forClass=String.class) which would run automatically on every single String property which doesn't have another converter explicitly specified.

getAsObject not called

What I am trying to do is that I am taking timeMillis property that stores time in millisecond(that I got my using System.currentTimeMillis()) and convert it to equivalent days,hours,mins and seconds after substracting it from the current time. The main problem is that whenever the converter timeConverter
is called only getAsString function is invoked , getAsObject is not invoked.
Here is the part of my xhtml file which causing the converter to not run properly.
<c:forEach var="p" items="#{statusBean.statusList}">
<h:form>
<div class="status">
<h:commandLink action="#{friendBean.gotoFriendProfile(p.email)}">
<img src="../images/profilePicture/#{p.picture}" style="height: 29px; width: 29px; "/>
<h:outputText value="#{p.statusBy}:"/>
</h:commandLink>
<h:outputText value="#{p.statusmsg}"/>
<h:outputText value="#{p.timeMillis}">
<f:converter converterId="timeConverter"/>
</h:outputText>
<br/>
<c:forEach var="q" items="#{statusBean.commentList(p.statusId)}">
<div class="barcomment">
<br/>
<h:commandLink action="#{friendBean.gotoFriendProfile(q.email)}">
<img src="../images/profilePicture/#{q.picture}" style="height: 29px; width: 29px; "/>
<h:outputText value="#{q.commentBy}:"/>
</h:commandLink>
<h:outputText value=" #{q.comment}"/>
</div>
</c:forEach>
<br/>
<div class="comment">
<p:inputText value="#{statusBean.comment.comment}" styleClass="box" />
<p:commandLink value="Views" action="#{statusBean.update(p.statusId)}" ajax="false" styleClass="link"/>
</div>
Here is the timeConverter class that I have written.
package com.converter;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
public class TimeConverter implements Converter {
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {
System.out.println("inside getAsObject");
long time=Integer.parseInt(arg2);
long currentTime=System.currentTimeMillis();
long eclapseTime=time-currentTime;
long secs=eclapseTime/1000;
long days=secs/(60*60*24);
long hours=(secs%(60*60*24))/60*60;
long mins=(secs%(60*60*24)%(60*60))/60;
long secs2=(secs%(60*60*24)%(60*60)%(60));
StringBuffer sb = new StringBuffer();
sb.append(days).append("days").append(hours).append("hours").append(mins).append("mins").append(secs2).append("secs");
String object1 = sb.toString();
return object1;
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
System.out.println("inside getAsString");
String value1 = value.toString();
return value1;
}
}
Why exactly is that a problem?
You're only using the converter here in an UIOutput component:
<h:outputText value="#{p.timeMillis}">
<f:converter converterId="timeConverter"/>
</h:outputText>
The getAsString() is been called to convert the Object model value to a String which can be embedded in the generated HTML output (you know, you can't put Java objects plainly in a HTML string).
However, you're nowhere using it in an UIInput component like <h:inputText>, so there is no means of a submitted String value which needs to be converted to the desired Object in the model, so the getAsObject() will obviously never be called.
Everything is working as designed. It look like that your concrete problem is that you should actually perform the job which you did in getAsObject() in the getAsString() instead.
I think that it would help if you give the methods a bit more sensible argument names:
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
    // Write code here which converts the model value to display value.
// This method will be used when generating HTML output.
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) throws ConverterException {
    // Write code here which converts the submitted value to model value.
    // This method will be used when processing submitted input values.
}

JSF convertDateTime with timezone in datatable

Trying to output a list of items in a datatable, like this:
<t:dataTable value="#{mybean.list}" var="item">
<h:column>
<h:outputText value="#{item.time}">
<f:convertDateTime pattern="yyyy-MM-dd HH:mm:ssZ" timeZone="#{item.timeZone}" />
</h:outputText>
</h:column>
</t:dataTable>
It always formats the time in GMT. It works as expected if I use a string constant or a bean which isn't the datatable variable (like '#{mybean.timeZone}').
Unfortunately, that's the nature of <f:xxx> tags. When the view is to be built, a single instance of the tag is been built where the converter is instantiated. All of its attribtues are been read and set only once. At the moment the view is been built, the #{item} resolves to null (it's only available during rendering of the view), so the timeZone attribute will be null and then default to UTC. When the view is to be rendered, the very same converter instance is been reused for each row of the table.
There are several ways to solve this. I can think of a custom converter or an EL function. I think a custom converter is after all the best as it can then also be reused in input components. The following kickoff example should work out for you (nullchecks and on omitted for brevity):
#FacesConverter("extendedDateTimeConverter")
public class ExtendedDateTimeConverter extends DateTimeConverter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
setPattern((String) component.getAttributes().get("pattern"));
setTimeZone(TimeZone.getTimeZone((String) component.getAttributes().get("timeZone")));
return super.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
setPattern((String) component.getAttributes().get("pattern"));
setTimeZone(TimeZone.getTimeZone((String) component.getAttributes().get("timeZone")));
return super.getAsString(context, component, value);
}
}
which can be used as
<h:outputText value="#{item.time}">
<f:converter converterId="extendedDateTimeConverter" />
<f:attribute name="pattern" value="yyyy-MM-dd HH:mm:ssZ" />
<f:attribute name="timeZone" value="#{item.timeZone}" />
</h:outputText>
This way the timezone is resolved everytime the converter is invoked instead of during its construction.
Update: the OmniFaces <o:converter> solves exactly this problem without the need for a custom converter.
<h:outputText value="#{item.time}">
<o:converter converterId="javax.faces.DateTime" pattern="yyyy-MM-dd HH:mm:ssZ" timeZone="#{item.timeZone}" />
</h:outputText>

Resources