Custom converter in JSF 2 with arguments - jsf

I'm trying to implement a custom truncate converter, which truncates a string at a given index and adds a continuation symbol. The converter works fine, only when i hard code the parameters, as they are not being passed to the backend. What am I doing wrong?
The parameters are properties of the converter class:
#FacesConverter(value = TruncateConverter.CONVERTER_ID)
public class TruncateConverter implements Converter, StateHolder
{
public static final String CONVERTER_ID = "bla.blablabla.Truncate";
private int truncateIndex;
private String contSymbol;
Here is how i'm using the converter (or trying to):
<h:outputText id="news-text-left" value="#{newsListBean.newsList_teaser.text}">
<f:converter converterId="bla.blablabla.Truncate" truncateIndex="150" contSymbol="..." />
</h:outputText>
I googled around for quite a bit and wasn't able to find a single example of a JSF2 converter with parameters... Thank you guys for your help, really appreciate it!

You may take a look at JSF2.0 sources. For example DateTimeConverter... JSF sources available here in svn reposotory: https://svn.java.net/svn/mojarra~svn/trunk
IMO creating such converter is not easy. It also required to create converter tag to register converter.
Other way to pass some data to converter is attibutes. So You can write
<h:outputText ...>
<f:converter converterId="bla.blablabla.Truncate" />
<f:attribute name="truncateIndex" value="150"/>
</h:outputText>
Than call to component.getAttributes().get("truncateIndex");
in converter code.

Based on #Maks solution: It's possible to combine the converter and the attribute in one tag:
<facelet-taglib version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
<namespace>http://mycompany.com/some-identifier</namespace>
<tag>
<tag-name>truncate</tag-name>
<converter>
<converter-id>bla.blablabla.Truncate</converter-id>
</converter>
<attribute>
<name>truncateIndex</name>
</attribute>
</tag>
</facelet-taglib>
Then you can use the converter like this:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:my="http://mycompany.com/some-identifier">
<my:truncate truncateIndex="150" />
</ui:composition>
You also don't need to grab the parameter from the component-attributes. A bean-property with the same name will be populated automatically:
#FacesConverter("bla.blablabla.Truncate")
public class Truncate implements Converter {
private String truncateIndex;
// getters + setters
...
}

http://jerryorr.blogspot.nl/2011/10/creating-jsf-12-custom-converter-with.html is a good guide to set up your first custom converter with parameters

This is how I did it (in a separated jar file, and using maven's default directories):
1) Create the converter's class (inside src/main/java)
2) Create a .taglib.xml class (inside src/main/resources/META-INF)
3) Create a faces-config.xml (inside src/main/resources/META-INF)
Example:
Step 1)
package com.ocabit.jsf.converter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import org.springframework.stereotype.Service;
import com.ocabit.utils.currency.CurrencyUtils;
/**
* Converter para valores BigDecimal.
*
* #author Carlos Eduardo Pauluk
*
*/
#FacesConverter("bigDecimalConverter")
#Service("bigDecimalConverter")
public class BigDecimalConverter implements Converter, Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public static final String CONVERTER_ID = "com.ocabit.jsf.converter.BigDecimalConverter";
/**
* Em caso de null, força a saída para 0.0;
*/
private boolean nullToZero = false;
/**
* Em caso de zero, força a saída para null;.
*/
private boolean zeroToNull = false;
/**
* Só retorna números positivos.
*/
private boolean onlyAbs = false;
/**
* Só retorna números negativos.
*/
private boolean onlyNeg = false;
/**
* Quantidade de casas decimais.
*/
private int decimals = 2;
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value) {
try {
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(getDecimals(), RoundingMode.HALF_DOWN);
if (bd.equals(BigDecimal.ZERO) && isZeroToNull()) {
return null;
}
if (isOnlyAbs()) {
bd = bd.abs();
}
if (isOnlyNeg()) {
bd = bd.abs();
bd = bd.negate();
}
return bd;
} catch (final Exception e) {
BigDecimal bd = null;
if (isNullToZero()) {
bd = CurrencyUtils.getBigDecimalCurrency("0.0");
bd = bd.setScale(getDecimals(), RoundingMode.HALF_DOWN);
}
return bd;
}
}
#Override
public String getAsString(final FacesContext context, final UIComponent component, final Object value) {
if (!(value instanceof BigDecimal)) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro ao converter o valor decimal.", ""));
}
final NumberFormat nf = NumberFormat.getInstance();
// sempre terá 2 casas decimais
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
return nf.format(((BigDecimal) value).doubleValue());
}
public boolean isNullToZero() {
return nullToZero;
}
public void setNullToZero(boolean nullToZero) {
this.nullToZero = nullToZero;
}
public boolean isZeroToNull() {
return zeroToNull;
}
public void setZeroToNull(boolean zeroToNull) {
this.zeroToNull = zeroToNull;
}
public boolean isOnlyAbs() {
return onlyAbs;
}
public void setOnlyAbs(boolean onlyAbs) {
this.onlyAbs = onlyAbs;
}
public boolean isOnlyNeg() {
return onlyNeg;
}
public void setOnlyNeg(boolean onlyNeg) {
this.onlyNeg = onlyNeg;
}
public int getDecimals() {
return decimals;
}
public void setDecimals(int decimals) {
this.decimals = decimals;
}
}
Step 2)
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd">
<facelet-taglib version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
<namespace>http://ocabit.com.br/facelets</namespace>
<tag>
<tag-name>convertBigDecimal</tag-name>
<converter>
<converter-id>com.ocabit.jsf.converter.BigDecimalConverter</converter-id>
</converter>
</tag>
</facelet-taglib>
Step 3)
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
version="2.2">
<converter>
<description></description>
<converter-id>com.ocabit.jsf.converter.BigDecimalConverter</converter-id>
<converter-class>com.ocabit.jsf.converter.BigDecimalConverter</converter-class>
<property>
<property-name>nullToZero</property-name>
<property-class>boolean</property-class>
<description>Ao invés de retornar 'null', retorna '0.0'</description>
</property>
<property>
<property-name>zeroToNull</property-name>
<property-class>boolean</property-class>
<description>Ao invés de retornar '0.0', retorna 'null'</description>
</property>
<property>
<property-name>onlyAbs</property-name>
<property-class>boolean</property-class>
<description>Somente retorna números positivos</description>
</property>
<property>
<property-name>onlyNeg</property-name>
<property-class>boolean</property-class>
<description>Somente retorna números negativos</description>
</property>
<property>
<property-name>decimals</property-name>
<property-class>int</property-class>
<description>Quantidade de casas decimais</description>
</property>
</converter>
</faces-config>
Voilà.

Related

Adding an attribute to primefaces by extending commandButton component

Primefaces 8
Java 11
I would like to add a controller attribute to a commandButton which takes in page bean instance as value . I can then use this page bean in command button renderer to disable the button by calling method on page bean.
Component Bean class
#FacesComponent(CustomCommandButton.COMPONENT_TYPE)
public class CustomCommandButton extends CommandButton
{
private static final Logger logger = Logger.getInstance(CustomCommandButton.class);
public static final String COMPONENT_FAMILY = "com.xxx.faces.component";
public static final String COMPONENT_TYPE = "com.xxx.faces.component.CustomCommandButton";
private enum PropertyKeys {
controller
}
private IpmsBean controller;
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public String getRendererType() {
return CustomCommandButtonRenderer.RENDERER_TYPE;
}
public CustomCommandButton()
{
super();
}
public void setController(IpmsBean bean)
{
getStateHelper().put(PropertyKeys.controller, bean);
this.controller=bean;
}
public IpmsBean getController()
{
return (IpmsBean) getStateHelper().eval(PropertyKeys.controller, null);
}
}
Renderer class which doesn't have whole lot just yet:
#FacesRenderer(
componentFamily=CustomCommandButton.COMPONENT_FAMILY,
rendererType=CustomCommandButtonRenderer.RENDERER_TYPE
)
public class CustomCommandButtonRenderer extends CommandButtonRenderer {
public static final String RENDERER_TYPE = "com.xxx.faces.component.CustomCommandButtonRenderer";
#Override
public void encodeEnd(FacesContext context, UIComponent component)
throws java.io.IOException {
//plan is to disable button after asking pagebean
//if (component.getController().shouldDisable())
// component.setDisabled("true");
//
super.encodeEnd(context, component);
}
}
taglibs-faces.xml
<?xml version="1.0"?>
<facelet-taglib version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
id="lu">
<namespace>http://com.xxx.faces/ui</namespace>
<tag>
<tag-name>customCommandButton</tag-name>
<component>
<component-type>com.xxx.faces.component.CustomCommandButton</component-type>
<renderer-type>com.xxx.faces.component.CustomCommandButtonRenderer</renderer-type>
</component>
<attribute>
<description>
<![CDATA[Any instance of IpmsBean.]]>
</description>
<name>controller</name>
<required>false</required>
<type>com.xxx.xxxx.client.jsf.bean.IpmsBean</type>
</attribute>
...copied rest of commandbutton's attributes here...
</tag>
</facelet-taglib>
xhtml page:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
template="/resources/layout/masterLayout.xhtml"
xmlns:c="http://xmlns.jcp.org/jsf/core"
xmlns:incl="http://java.sun.com/jsf/composite/includes"
xmlns:lu="http://com.xxx.faces/ui">
<lu:customCommandButton
value="Custom Button"
controller="#{regionPolicyBean}"
oncomplete="hello()"/>
....
</ui:composition>
"Custom Button" shows up just fine. But setController(bean) method is not valled. setOnComplete() method is called. Not sure what am I missing here.
I did refer to this answer Adding Custom Attributes to Primefaces Autocomplete Component in JSF but still not sure why setter is not being called.
Not a direct answer, but I don't see why you do this the hard way. You can simply do:
<p:commandButton ...>
<f:attribute name="controller" value="#{...}"/>
</p:commandButton>
and get controller from the component's attributes map.

Custom converter with attributes/injection not working with anymore after upgrade to JSF 2.3

I am trying to upgrade/use a custom converter that worked with JSF 2.2 (on Wildfly 13) to work on JSF 2.3 (with Mojarra 2.3.9.SP02 running on Wildfly 17.0.1)
The converter is used via its own tag defined in a tag library.
Everything's fine as long as no tag attributes are used. The attributes are just not set in the converter. The setters never get called.
But if I remove the managed = true from the converter the attributes are set but then the injection no longer works.
The converter is used like this:
<h:inputText id="text" value="#{welcome.text}">
<cdijsf:converterWithAttr id="myConverter" attr="myAttrValue" />
</h:inputText>
Tag library:
<facelet-taglib version="2.3"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_3.xsd">
<namespace>http://cdijsf.transdata.net/jsf</namespace>
<tag>
<tag-name>converterWithAttr</tag-name>
<converter>
<converter-id>cdijsf.ConverterWithAttr</converter-id>
</converter>
<attribute>
<name>attr</name>
<type>java.lang.String</type>
</attribute>
</tag>
</facelet-taglib>
And this is the converter code:
#Dependent
#FacesConverter(value = "cdijsf.ConverterWithAttr", managed = true)
public class ConverterWithAttr implements Converter<String> {
#Inject
private BeanManager beanManager;
private String attr;
public ConverterWithAttr() {
}
#PostConstruct
private void init() {
// If 'managed = true' beanManager is injected at this point.
// If 'managed = false' beanManager is null at this point
}
#Override
public String getAsObject(FacesContext context, UIComponent component, String value) {
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, String value) {
return value;
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
// If 'managed = true' setAttr is never called
// If 'managed = false' setAttr is called
this.attr = attr;
}
}
faces-config.xml:
<faces-config version="2.3"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
</faces-config>
And I also explicitly declare JSF 2.3 like this:
#FacesConfig(version = Version.JSF_2_3)
#ApplicationScoped
public class JsfConfiguration {
}
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0"
bean-discovery-mode="annotated">
</beans>

cdi bean injection for initialization code

I have a bean configured which have some initialization logic. I have annotated this bean using #ApplicationScoped annotation. But somehow, cdi is not picking this bean.
beans.xml content:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="annotated">
</beans>
Bean file:
#ApplicationScoped
public class Initializer{
#Inject #ConfigProperty(name = "app.name")
private String appName;
#Inject #ConfigProperty(name = "app.token")
private String appToken;
#Inject #ConfigProperty(name = "app.version")
private String appVersion;
#PostConstruct
public void init() {
System.out.println("flow should come here....); //but this line does not execute.
}
}
Code to read config file:
#Exclude
public class ConfigurationFile implements PropertyFileConfig {
#Override
public String getPropertyFileName() {
String env = Util.getEnv();
switch (env) {
case "dev":
case "uat":
case "prod":
return "config/" + env + "/default.properties";
default:
return "config/default.properties";
}
}
#Override
public boolean isOptional() {
return false;
}
}
I am using:
cdiL: for dependency injection,
apache-deltaspike: for reading config file.
wildfly-swarm: server
I have got the solution to this problem.
Issue is solved by changing the method declaration as follows:
public void init(#Observes #Initialized(ApplicationScoped.class) Object init) {
//................code logic here................
}

JSF:Resourcebundle Problem with Internationalization

I implemented internationalization like in that tutorial!
When I change the language in my app. It works. But only until the next request happens. Then language settings are reset to my standard language -.-
What am I missing here:
LanguageBean.java
#ManagedBean(name="language")
#SessionScoped
public class LanguageBean implements Serializable{
private static final long serialVersionUID = 1L;
private String localeCode;
private static Map<String,Object> countries;
static{
countries = new LinkedHashMap<String,Object>();
countries.put("Deutsch", Locale.GERMAN); //label, value
countries.put("English", Locale.ENGLISH);
}
public Map<String, Object> getCountriesInMap() {
return countries;
}
public String getLocaleCode() {
return localeCode;
}
public void setLocaleCode(String localeCode) {
this.localeCode = localeCode;
}
//value change event listener
public void countryLocaleCodeChanged(ValueChangeEvent e){
String newLocaleValue = e.getNewValue().toString();
//loop country map to compare the locale code
for (Map.Entry<String, Object> entry : countries.entrySet()) {
if(entry.getValue().toString().equals(newLocaleValue)){
FacesContext.getCurrentInstance()
.getViewRoot().setLocale((Locale)entry.getValue());
}
}
}
}
my facelets template:
<h:selectOneMenu value="#{language.localeCode}" onchange="submit()"
valueChangeListener="#{language.countryLocaleCodeChanged}">
<f:selectItems value="#{language.countriesInMap}" />
</h:selectOneMenu>
faces-config:
<application>
<locale-config>
<default-locale>de</default-locale>
</locale-config>
<resource-bundle>
<base-name>org.dhbw.stg.wwi2008c.mopro.ui.text</base-name>
<var>msg</var>
</resource-bundle>
</application>
Add the following line to setLocaleCode().
FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale(localeCode));
See also this tutorial which I wrote.

JSF Custom Image Component Problem to display multiple Image

Created a Tag Library Descriptor File:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>image</short-name>
<uri>/WEB-INF/tlds/image</uri>
<tag>
<name>BsilImage</name>
<tag-class>com.custom.image.ImageTagHandler</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>url</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>style</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>type</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
ImageTagHandler
public class ImageTagHandler extends UIComponentELTag {
private String url;
private String style;
private String type;
private static final String IMAGE_COMP_TYPE = "IMAGE_COMPONENT";
private static final String IMAGE_RENDERER_TYPE = "IMAGE_RENDERER";
public void setUrl(String url) {
this.url = url;
}
public void setStyle(String style) {
this.style = style;
}
public void setType(String type) {
this.type = type;
}
public String getComponentType() {
return IMAGE_COMP_TYPE;
}
public String getRendererType() {
return IMAGE_RENDERER_TYPE;
}
#Override
protected void setProperties(UIComponent component) {
super.setProperties(component);
ImageComponent imgComponent =
((ImageComponent) component);
if (type != null) {
imgComponent.setType(ImageTranscoder.getImageTypeOf(type));
}
if (url != null) {
imgComponent.setUrl(ImageTranscoder.getImagePath(url));
}
if (style != null) {
imgComponent.setStyle(style);
}
}
}
ImageComponent
public class ImageComponent extends UIOutput {
private String url;
private String style;
private String type;
private static final String IMAGE_COMP_FAMILY = "IMAGE_FAMILY";
#Override
public String getFamily() {
return IMAGE_COMP_FAMILY;
}
/**
* Get the value of type
*
* #return the value of type
*/
public String getType() {
return type;
}
/**
* Set the value of type
*
* #param type new value of type
*/
public void setType(String type) {
this.type = type;
}
/**
* Get the value of style
*
* #return the value of style
*/
public String getStyle() {
return style;
}
/**
* Set the value of style
*
* #param style new value of style
*/
public void setStyle(String style) {
this.style = style;
}
/**
* Get the value of url
*
* #return the value of url
*/
public String getUrl() {
return url;
}
/**
* Set the value of url
*
* #param url new value of url
*/
public void setUrl(String url) {
this.url = url;
}
}
ImageRenderer
public class ImageRenderer extends Renderer {
#Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
ImageComponent imgComponent = (ImageComponent)component;
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div",component);
writer.startElement("img", imgComponent);
writer.writeAttribute("src",imgComponent.getUrl(), "url");
writer.writeAttribute("class", imgComponent.getStyle(), "style");
writer.endElement("div");
}
}
CODE TO ACCESS TAG LIB IS
<h:dataTable var="landing" binding="${LandingBean.tourPackages}" >
<h:column>
<tr:panelGroupLayout layout="vertical" styleClass="landingListing">
<bsil:BsilImage style="listingImage" url="${landing.photoThumbnail}" type="banner"/>
</tr:panelGroupLayout>
</h:column>
</h:dataTable>
The bean contain multiple photos which has to be shown in number of column when the page is displayed. The problem which I am facing is its cant show me multiple images gives an error saying
According to TLD or attribute
directive in tag file, attribute
binding does not accept any
expressions
If the bean contain single Image It is displayed the code for that is
<tr:panelHeader id="panelHeader" styleClass="banner" text="">
<bsil:BsilImage url="${LandingBean.bannerImage}"
style="bannerImage" type="banner"></bsil:BsilImage>
</tr:panelHeader>
I want to know how to display multiple images using custom component.
According to the error message you quoted, it seems that the issue is in the <h:dataTable ...> tag, it seems that its attribute called "binding" apparently doesn't accept expressions for some reason.
E.g.
<tag>
<name>dataTable</name>
<attribute>
<name>binding</name>
<!-- add this: -->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
If you own the code of the dataTable tag, add <rtexprvalue>true</rtexprvalue> next to the "binding" attribute of that tag (like you did in your image tag). If you don't, refer to it's documentation and find out why it doesn't allow expressions.

Resources