Values converted by custom converter not displayed - jsf

I am using a PrimeFaces' pickList with a custom converter.
JSF:
<p:pickList converter="costsConverter" value="#{offerController.costsAsDualListModel}" var="cost" itemLabel="#{cost}" itemValue="#{cost}" />
offerController.costsAsDualListModel looks like this:
public DualListModel<Cost> getCostsAsDualListModel() {
DualListModel<Cost> costsDualList;
List<Cost> costsSource = new ArrayList<Cost>();
List<Cost> costsTarget = new ArrayList<Cost>();
for (Cost c : costs) {
costsSource.add(c);
}
costsDualList = new DualListModel<Cost>(costsSource, costsTarget);
return costsDualList;
}
And my custom converter looks like this:
public String getAsString(FacesContext context, UIComponent component, Object object) {
if (object == null) {
return "";
}
Integer no = ((Cost) object).getNo();
String valueOf = String.valueOf(no);
return valueOf;
}
getAsString() is called and valueOf is correct but inside my picklist I still see the objects and not the return value fo getAsString().
I tried to use <f:converter converterId="costsConverter"/> within the picklist element. Same issue. Also I registered the converter in faces-config.xml:
<converter>
<converter-id>costsConverter</converter-id>
<converter-class>com.my.converter.CostsConverter</converter-class>
</converter>
What could be the problem?

You have a wrong understanding of values in components like picklists, selectonemenus, etc. These values are never displayed there but the labels are. And since converters are for values, not labels, you'll never see the converted value but the labels and everything behaves as it should. Just use itemLabel="#{cost.no}" and everything should be fine (display wise).
See e.g. how it is used in these two Q/A that also use a converter
How to write a custom converter for <p:pickList>
Primefaces Picklist Converter

Related

PrimeFaces selectOneMenu with custom editable inputField

Silly question, but that's my situation. I am having editable PrimeFaces selectOneMenu where inputField has following restriction:
lower and upper limit of number typed in
predefined text allowed
when decimal number is being typed allow only 2 decimal numbers
All is good except the last one with decimal number restriction. What it means is that I can't type there 1.111 but only 1.11. Change event keyUp for selectOneMenu is sadly added to the tag select but not to input.
Any ideas how to solve?
This calls for a custom validator. Create one which checks for the predefined values, if no match is found, check the number format. Basic example:
#FacesValidator("myValidator")
public class MyValidator implements Validator
{
private List<String> predefinedValues = Arrays.asList("my", "list");
#Override
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException
{
String valueStr = (String) value;
// Check if value is predefined
if (predefinedValues.contains(valueStr)) {
return;
}
// If not predefined, check number format
if (! valueStr.matches("^\\d+(\\.\\d\\d?)?$")) {
throw new ValidatorException(new FacesMessage("Value is invalid!"));
}
// Check number limits...
}
}
The validator can be used in your XHTML as:
<p:selectOneMenu editable="true" ...>
...
<f:validator validatorId="myValidator" />
</p:selectOneMenu>
As an alternative you could use jQuery to find the input field and bind a keypress listener to it. See for example: Jquery: filter input on keypress. However, I would keep the validator in place. Users could paste text for example.
See also:
How to perform validation in JSF, how to create a custom validator in JSF
Regular expression for floating point numbers

Converter does not seem to be used when component has a renderer

As known, we can easily write our custom conveter and register it to the component as follows:
<f:converter converterId="cId" />
or
<f:converter binding="#{mybean}" />
depneds on our needs. But now consider the source code of UIInput that's responsible for the exact conversion (comments ommited):
protected Object getConvertedValue(FacesContext context,
Object newSubmittedValue) throws ConverterException {
Renderer renderer = getRenderer(context);
Object newValue;
if (renderer != null) {
newValue = renderer.getConvertedValue(context, this,
newSubmittedValue); // <----------- 1
} else if (newSubmittedValue instanceof String) {
Converter converter = getConverterWithType(context);
if (converter != null) {
newValue = converter.getAsObject(context, this,
(String) newSubmittedValue);// <----------- 2
} else {
newValue = newSubmittedValue;
}
} else {
newValue = newSubmittedValue;
}
return newValue;
}
So, it's clear that we make a choise about what converter we should use. Moreover, as far as I understand, if a component has a separated Renderer we can't apply our custom converter specified as a tag in a facelet (because of the if(renderer != null){...} else if(newSubmittedValue instanceof String){...}). For instance <h:inputText /> the component class is UIInput, and also it has a custom com.sun.faces.renderkit.html_basic.TextRenderer, therfore getRenderer(context) returns something that's not null. So, we should skip the convertion specified by our converter tag even if we define it as
<h:inputText value="#{myBean.prop}"/>
<f:converter inding="#{bean}" />
</h:inputText>
The inputText has a custom renderer, so the converter shouldn't do anything specified here:
Converter converter = getConverterWithType(context);
if (converter != null) {
newValue = converter.getAsObject(context, this,
(String) newSubmittedValue);// <----------- 2
} else {
newValue = newSubmittedValue;
}
Couldn't you explain it? How does the conversion actually work and what's the difference between Renderer's converter and our custom converter
Actually, the renderer does the same. See also javadoc of Renderer#getConvertedValue().
Attempt to convert previously stored state information into an object of the type required for this component (optionally using the registered Converter for this component, if there is one). If conversion is successful, the new value should be returned from this method; if not, a ConverterException should be thrown.
In case of <h:inputText> in Mojarra, source code can be found in HtmlBasicInputRenderer.
This design allows the renderer to take full control over the conversion step and if necessary manipulate this step. Note that it's possible to declaratively (via faces-config.xml) override the renderer of a (standard) component without changing the component itself in the view. Also note that the term "custom renderer" in your question is not entirely right. It's just the standard renderer. We talk about a "custom renderer" only when the web application provides its own, overriding the standard one.

F:selectItems showing the class not a value

I'm new to facelets and I have generated a project using netbeans but I struggling with the tag.
I have
<h:selectOneMenu id="country" value="#{organisation.organisation.country}" title="Country" >
<f:selectItems value="#{country.countryItemsAvailableSelectOne}"/>
</h:selectOneMenu>
In the select I get classpath.Country[iso=GB] which I can see is an object but I really want to see the the country.prinableName value.
I've looked at this for half a day and have drawn a blank
Thanks for any help
Since you're talking about Facelets, I'll assume JSF 2.x.
To start, HTML is one and all String. JSF generates HTML. By default, non-String Java objects are by toString() method converted to their String representation while JSF generates the HTML. To properly convert between those Java objects and String, you need a Converter.
I assume that your Country object has already the equals() method properly implemented, otherwise validation will later fail with "Validation error: Value not valid" because the selected object doesn't return true on testing the equals() for any of the available items.
I'll also make a little change in the naming since #{country} is a confusing managed bean name because it does apparently not represent an instance of the Country class. I'll call it Data which should hold application wide data.
#ManagedBean
#ApplicaitionScoped
public class Data {
private static final List<Country> COUNTRIES = populateItSomehow();
public List<Country> getCountries() {
return COUNTRIES;
}
// ...
}
I'll assume that the Country class has two properties code and name. I'll assume that the managed bean which receives the selected country has a private Country country property. In your <f:selectItems>, you need to loop over #{data.countries} and specify the country object as item value and the country name as item label.
<h:selectOneMenu value="#{bean.country}">
<f:selectItems value="#{data.countries}" var="country" itemValue="#{country}" itemLabel="#{country.name}" />
</h:selectOneMenu>
Now, you need to create a Converter for the Country class. We'll convert based on the country code which is unique for every country (right?). In the getAsString() you implement code which converts the Java object to its unique String representation which is to be used in HTML. In getAsObject() you implement code which converts the unique HTML String representation back to the Java object.
#FacesConverter(forClass=Country.class)
public class CountryConverter implements Converter {
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return (value instanceof Country) ? ((Country) value).getCode() : null;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null) {
return null;
}
Data data = context.getApplication().evaluateExpressionGet(context, "#{data}", Data.class);
for (Country country : data.getCountries()) {
if (country.getCode().equals(value)) {
return country;
}
}
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Country", value)));
}
}
The #FacesConverter will register it automatically in JSF and JSF will automatically use it whenever it encounters a value expression of the Country type. Ultimately, you end up with country code as item value and country name as item label. JSF will convert the submitted country code back to a fullworthy Country object upon form submission.
In JSF 1.x the principle is not much different. In this blog you can find two basic kickoff examples: Objects in h:selectOneMenu.
What happened to you, selectOneMenu call the toString() method for all given objects.
You've to use selectitems or a simple converter to manage that. A very simple example:
price.xhtml:
<h:selectOneMenu id="priceMenu" value="#{priceBean.selectedPrice}">
<f:selectItems value="#{priceBean.prices}" />
</h:selectOneMenu>
PriceBean.java:
..
private String selectedPrice;
..
public String getSelectedPrice() {
return selectedPrice;
}
public void setSelectedPrice(String newPrice) {
selectedPrice = newPrice;
}
..
public List<SelectItem> getPrices() {
List<SelectItem> retVal = new ArrayList<SelectItem>();
retVal.add(new SelectItem("2"));
retVal.add(new SelectItem("4"));
retVal.add(new SelectItem("6"));
return retVal;
}
Further informations about the SelectItem. If you want to use a specially object directly, for example an object called Price, you have to use a converter. Here an example is shown and here.
If you add editable="true" in your
<h:selectOneMenu value="#{bean.country}">
Then you'll get an unexpected String value (not from getAsString()) in the converter method:
public Object getAsObject(FacesContext context, UIComponent component, String value) { }

JSF 2 : selection grouping with SelectItemGroup + POJO

I have tried working with the grouped selections with something like this :
<h:selectOneMenu value="#{selectionLabBean.oneSelectMenuGroup}"
id="SelectOneMenuGroup" >
<f:selectItems value="#{selectionLabBean.heroGroupList}" />
</h:selectOneMenu>
<p:message for="SelectOneMenuGroup" />
where the heroGroupList is something like this :
SelectItem[] heroArr = new SelectItem[] {
new SelectItem("Paladin"),
...
};
heroListWithGrouping.add(
new SelectItemGroup("Human",
"A collection of human race Heroes",
false,
heroArr
)
);
.....
And i'm left wondering if i can do this kind of grouping with POJOs instead of SelectItem objects ?
If i couldnt achieve this, i think i have to somehow convert my domain objects or my query results into arrays of SelectItem to make it work.
Any ideas ?
That's indeed not possible when you want to use SelectItemGroup. You need to convert from collection of POJO's to List<SelectItem> in a double for-loop during bean's (post)construction.
#PostConstruct
public void init() {
List<HeroRace> heroRaces = getItSomehowFromDatabase();
this.heroGroupList = new ArrayList<SelectItem>();
for (HeroRace heroRace : heroRaces) {
SelectItemGroup group = new SelectItemGroup(heroRace.getName()); // Human, etc
List<SelectItem> heroes = new ArrayList<SelectItem>();
for (Hero hero : heroRace.getHeroes()) {
heroes.add(new SelectItem(hero.getName()); // Paladin, etc
}
group.setSelectItems(heroes.toArray(new SelectItem[heroes.size()]));
this.heroGroupList.add(group);
}
}
You could also use Hero as item value
heroes.add(new SelectItem(hero, hero.getName()); // Paladin, etc
so that you can bind #{selectionLabBean.oneSelectMenuGroup} to a Hero type instead of String. But then you need to supply a Converter. That part is already answered by Amorfis.
Yes, you can return List or array of POJOs instead of SelectItems. You'll need converter for this to work, but it's not a big deal. So, converter first:
#FacesConverter(forClass=Hero.class)
public class HeroConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return new Hero(value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return ((Hero)value).getName();
}
}
Now if you return list of Heroes to <f:selectItems>, you have options in HTML where label is Hero.toString(), and value is returned from HeroConverter.getAsString().
One more thing. If you submit some value for this selection, JSF converts it to object and checks (by equals() method) if this object was in list of objects for selection. So in case above, you'll need to override equals() in Hero to check if names are equal. Another solution is not to create new instance in getAsObject, but to keep somewhere list of available Heroes and return this list to <f:selectionItems> and return object from this list in getAsObject().

Refresh JSF validator attributes on rerender

I have an issue with the attributes values of a validator component.
Apparently the validator is created when I first visit a page.
Please see my code below:
<h:inputText value="#{qsetting.value}" rendered="#{qsetting.dataType=='Double'}">
<mw:validateRange min="#{qsetting.minValue}" max="#{qsetting.maxValue}" />
</h:inputText>
The inputText component is rerendered through ajax but apparently, including the value that is displayed.
Unfortunately, the qsetting.minValue and qsetting.maxValue are not refreshed, causing my validator to not work correctly.
Is there a possibility to refresh the validator, to make sure it re-retrieves its attributes or to just create a new instance of the validator?
The validator class itself is currently implementing "Validator, Serializable".
Also, I'm using jsf1.2 with facelets...
Thanks,
Steven
I've hit this problem in a non-ajax environment a few times over the years, and hit it again today. The addition of Ajax doesn't really change anything since a validator attribute is never evaluated again once the page is initially built, ajax or otherwise.
The only solution I've come up with is to set the validator attribute to a validator expression, then evaluate that expression inside the validate method.
One other issue I hit (also with JSF 1.2 and Facelets) is that not all EL variables worked. I had to use a static managed bean as the root of my expression to access the value. A facelet ui:param value as a root would not work. I haven't tested to see what else may not correctly evaluate. This could be due to another bug in the design of JSF itself. See http://myfaces.apache.org/core12/myfaces-api/apidocs/javax/faces/context/FacesContext.html#getELContext%28%29.
For example, instead of:
max="#{qsetting.maxValue}"
use
maxExpression="qsetting.maxValue"
Then
public String getMax(FacesContext context) {
Application app = context.getApplication();
ExpressionFactory exprFactory = app.getExpressionFactory();
ValueExpression ve = exprFactory.createValueExpression(context.getELContext(),
"#{" + getMaxExpression() + "}",
String.class);
Object result = ve.getValue(context.getELContext());
return (String)result;
}
public String getMaxExpression() {
return this.maxExpression;
}
public void setMaxExpression(String maxExpression) {
this.maxExpression = maxExpression;
}
//// StateHolder
public boolean isTransient() {
return isTransient;
}
public void setTransient(boolean newTransientValue) {
isTransient = newTransientValue;
}
public Object saveState(FacesContext context) {
Object[] state = new Object[1];
state[0] = maxExpression;
return state;
}
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[]) state;
maxExpression = (String) values[0];
}
UPDATE 2012-09-19:
After investigating how MyFaces Commons solves this problem, the better solution is to change the rules Facelets uses to evaluate validator and converter attribute expressions.
It basically comes down to adding a new validator or converter MetaRule which, when applied, checks to see if the attribute value is non-literal. If it is non-literal, call a special method on your validator or converter which passes in the value expression rather than the current value.
http://svn.apache.org/viewvc/myfaces/commons/trunk/myfaces-commons-validators/src/main/java/org/apache/myfaces/commons/validator/_ValidatorRule.java?view=markup
The validator at that point needs to store the value expression as state and evaluate it when needed. MyFaces commons provides all of the complicated infrastructure to make this happen generically, but you could dump all of that and write a simple custom rule and directly manage the ValueExpression yourself, similar to what I originally posted.

Resources