How to register a custom renderer in JSF? - jsf

We have numerical values in our database, representing a two-value-state. Of course this would perfectly match a boolean, but oracle has no such datatype. The NUMBER(1,0) type from the database is matched to a java.lang.Short type in Java (sometimes they used a NUMBER(*,0) to represent booleans, which are matched to java.math.BigDecimal).
Since it is somehow obvious, I want to offer ice:selectBooleanCheckbox in the view as a value representation and UIComponent to the user. (I use IceFaces as JSF implementation)
Since some people who specified JSF think it is obvious to always match the value of a ice:selectBooleanCheckbox or in JSF h:selectBooleanCheckbox to a boolean in the model, so the renderer of the component never calls any converter, even if you specify one:
Issue disscused at java.net
Therefore I tried the following:
I created a converter to specify it in the UIComponent:
public class BooleanBigDecimalConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String str) {
if (StringUtils.isEmptyString(str)) {
return new BigDecimal(0);
}
if (str.equals("true")) {
return new BigDecimal(1);
} else {
return new BigDecimal(0);
}
}
public String getAsString(FacesContext context, UIComponent component, Object obj) {
if (obj != null) {
String str = obj.toString();
if (str.equalsIgnoreCase("1")
|| str.equalsIgnoreCase("yes")
|| str.equalsIgnoreCase("true")
|| str.equalsIgnoreCase("on")) {
return "true";
} else {
return "false";
}
}
return "false";
}
}
The converter works fine for the render phase (the getAsString-method is called correctly), but the getAsObject-method (Ignore that it's not correct at the moment, because it's not called anyway, so it will be fixed if it's called!) is never called, because in the renderer of the UIComponent a converter is not foreseen, like you can see here (snip from com.icesoft.faces.renderkit.dom_html_basic.CheckboxRenderer):
public Object getConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object submittedValue) throws ConverterException
{
if(!(submittedValue instanceof String))
throw new ConverterException("Expecting submittedValue to be String");
else
return Boolean.valueOf((String)submittedValue);
}
So this results in an IllegalArgumentException, since in the UpdateModelValues phase it is tried to apply a Boolean to a numerical value (please ignore the BigDecimal/Short confusion... it is just a numerical type in any case!).
So I tried to overwrite the renderer with a new one like this:
import com.icesoft.faces.component.ext.renderkit.CheckboxRenderer;
public class CustomHtmlSelectBooleanCheckbox extends CheckboxRenderer {
public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
Converter converter = ((ValueHolder) component).getConverter();
return converter.getAsObject(context, component, (String) submittedValue);
}
}
and registered it like this in faces-config.xml:
<render-kit>
<renderer>
<component-family>com.icesoft.faces.HtmlSelectBooleanCheckbox</component-family>
<renderer-type>com.icesoft.faces.Checkbox</renderer-type>
<renderer-class>com.myapp.web.util.CustomHtmlSelectBooleanCheckbox</renderer-class>
</renderer>
</render-kit>
I guess this should be correct, but the overridden method "getConvertedValue" is never called, nor is the getAsObject()-method, so I guess I made a mistake in registering the custom renderer, but I can't find any more documentation or hints how to do this properly and especially how to find the correct component-family (I looked up the one I use in icefaces.taglib.xml) and the correct renderer-type.
I don't want to edit the complete model because of this. Any hints, how this can be resolved?

We could fix the problem and correctly register our custom renderer.
The problem was to find the correct properties for the intended renderer. Our tries were wrong, since I found out how to get the appropriate information. It's a bit of work and searching, but it finally did the trick.
Just start your container in debug mode and add a breakpoint on class level into the derived class the custom renderer is based on (in my case com.icesoft.faces.renderkit.dom_html_basic.CheckboxRenderer).
During container start-up this breakpoint will be reached and in the stacktrace you'll find a call of the method FacesConfigurator.configureRenderKits().
This object contains an ArrayList of registered renderers. I searched the list for the renderer I'd have liked to overwrite and could find the informations I need to register my custom renderer. In my case this is the correct entry in faces-config.xml:
<render-kit>
<description>The ICEsoft Renderers.</description>
<render-kit-id>ICEfacesRenderKit</render-kit-id>
<render-kit-class>com.icesoft.faces.renderkit.D2DRenderKit</render-kit-class>
<renderer>
<component-family>javax.faces.SelectBoolean</component-family>
<renderer-type>com.icesoft.faces.Checkbox</renderer-type>
<renderer-class>com.myapp.web.util.CustomHtmlSelectBooleanCheckbox</renderer-class>
</renderer>
</render-kit>
Now the getAsObject()-method in the converter is called by the custom renderer. Be sure to override the method correctly, in case you don't want a converter on every SelectBooleanCheckbox object:
public Object getConvertedValue(FacesContext context,
UIComponent component, Object submittedValue)
throws ConverterException {
Converter converter = ((ValueHolder) component).getConverter();
if (converter == null) {
if(!(submittedValue instanceof String))
throw new ConverterException("Expecting submittedValue to be String");
else
return Boolean.valueOf((String)submittedValue);
}
return converter.getAsObject(context, component,
(String) submittedValue);
}
Otherwise you'll get a NullPointerException.
PS: There surely is a smarter way to achieve this information, but I am not smart enough. ;-)

You don't say whether you're using Hibernate but I assume that you must be for this to be an issue. Have you tried treating the numeric as a boolean in your mapping?
See this thread from the Hibernate forums

Related

Removing trailing spaces in <af:inputText> [duplicate]

In my JSF 2.2 application, when I create JSF form, the problem is that I can put spaces in the beginning of <h:inputText> when I insert it from my form and it will return these spaces with my value that I inserted in it. So I should handle each value with mystring.tirm() to void the spaces. Can I use any something else to return this value without spaces? I know about converter and JavaScript, so can you give me another option to use? I don't want to use any converter and JavaScript.
I don't want to use any converter and JavaScript
There's no magic here. You have to write code to do the job. I gather that your concern is more that you don't want to repeat the same conversion job over every single input field.
In that case, just register a converter specifically on String.class via the forClass attribute of the #FacesConverter like below.
#FacesConverter(forClass=String.class)
public class TrimConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
String trimmed = (submittedValue != null) ? submittedValue.trim() : null;
return (trimmed == null || trimmed.isEmpty()) ? null : trimmed;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
return (modelValue != null) ? modelValue.toString() : "";
}
}
This way you don't need to declare the converter in every single input field. It will get applied transparently on every String model value which doesn't already have an explicit converter registered.
The JavaScript way is not recommended as it runs entirely at the client side and endusers can easily disable and manipulate JavaScript. The JSF converter runs at server side and its outcome is not manipulatable by the enduser.
I thing the real problem is in somewhere else. I faced with same thing in my forms (using postgresql and JSF 2.1)
When I created a field type "char(20)". In this case i got unnecessary spaces in h:inputText.
Then I changed the field type to "character varying(20)". In this case there were no spaces in h:inputText.
Here is the explanation for that; Postgresql doc

How to prevent inputtextbox not to show default value zero for Integer? [duplicate]

I know there are a number of posts about converting empty string to null in JSF2. The usual prescription is to add the following to web.xml.
<context-param>
<description>Does not appear to work</description>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
This just does not seem to work - at all. I then created a custom string converter to test if that would work. I explicitly added it as a converter to my inputText (otherwise it does not fire when blank).
When INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL is set to true the converter receives null and the setter for the input text still receives "".
When INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL is set to false (or commented out) the converter receives "" and the setter for the input text receives "" (even after the converter returns null).
#FacesConverter(forClass=java.lang.String.class, value="emptyStringToNull")
public class StringConverter implements Converter, Serializable {
private static final long serialVersionUID = -1121162636180944948L;
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.trim().isEmpty()) {
return null;
}
return value;
}
public String getAsString(FacesContext context, UIComponent component, Object object) {
if (object == null)
return null;
return object.toString();
}
}
I've event tried (to no avail) to explicitly set the component submitted value in getAsObject:
if (component instanceof EditableValueHolder)
((EditableValueHolder) component).setSubmittedValue(null);
I'm using JBoss6 (a snapshot of 6.1 really) and JSF 2.1.1.
This is not Mojarra specific. This is Tomcat specific (JBoss uses Tomcat as servletcontainer). Add the following VM argument to startup options.
-Dorg.apache.el.parser.COERCE_TO_ZERO=false
To my experience, this one should actually only apply on Number properties (int, long, etc), however since a certain late Tomcat 6.0.x version (at least after 6.0.20) it seems to be broken for strings as well and it is relying on the above VM argument.
On GlassFish 3.x for example it works perfectly fine out the box.

Trim String in JSF h:outputText value

Is there any way to string JSF h:outPutTextValue ? my string is A-B-A03 ,i just want to display last 3 characters .does openfaces have any avialable function to do this ?
Thanks
You could use a Converter for this job. JSF has several builtin converters, but no one suits this very specific functional requirement, so you'd need to create a custom one.
It's relatively easy, just implement the Converter interface according its contract:
public class MyConverter implements Converter {
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
// Write code here which converts the model value to display value.
}
#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 won't be used in h:outputText, but in UIInput components only.
}
}
Provided that you're using JSF 2.0 (your question history confirms this), you can use the #FacesConverter annotation to register the converter. You can use the (default) value attribute to assign it a converter ID:
#FacesConverter("somethingConverter")
(where "something" should represent the specific name of the model value you're trying to convert, e.g. "zipcode" or whatever it is)
so that you can reference it as follows:
<h:outputText value="#{bean.something}" converter="somethingConverter" />
For your particular functional requirement the converter implementation can look like this (assuming that you actually want to split on - and return only the last part, which makes so much more sense than "display last 3 characters"):
#FacesConverter("somethingConverter")
public class SomethingConverter implements Converter {
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
if (!(modelValue instanceof String)) {
return modelValue; // Or throw ConverterException, your choice.
}
String[] parts = ((String) modelValue).split("\\-");
return parts[parts.length - 1];
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) throws ConverterException {
throw new UnsupportedOperationException("Not implemented");
}
}
You can try and use the fn:substring functions from JSTL:
${fn:substring('A-B-A03', 4, 7)}
If your string comes from a bean you can add an extra getter to return the trimmed version:
private String myString = "A-B-A03";
public String getMyStringTrimmed()
{
// You could also use java.lang.String.substring with some ifs here
return org.apache.commons.lang.StringUtils.substring(myString, -3);
}
Now you can use the getter in your JSF page:
<h:outputText value="#{myBean.myStringTrimmed}"/>

Mojarra findComponent return null for valid id when inside composite component

I have a custom component that look as follow
<custom:container>
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
so when encodeBegin first call, it will show hit the tag <custom:container>, and it will try to save this component cliend id,
private String containerClientId;
public void encodeBegin(FacesContext context, UIComponent component){
if (component instanceof ManyCheckboxContainer) {
containerClientId = component.getClientId(context);
return;
}
}
so encodeEnd get called when I hit <custom:checkbox index="0"/>, like this
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
...
if (component instanceof Checkbox) {
renderCheckbox(context, (Checkbox) component);
}
...
}
protected void renderCheckbox(FacesContext facesContext, InforRadio radio) throws IOException {
...
UIComponent uiComponent = radio.findComponent(containerClientId);
if(uiComponent == null){
//throw error
}
...
}
If I DO NOT have this custom component inside composite component then everything work great, but once I put it in a composite component, radio.findComponent(containerClientId); return null. Is this a bug in mojarra? I test this under 2.1.10 and 2.1.11, same behavior.
EDIT
So I take that back, this behavior happen when my custom component is inside two nested NamingContainer, so something like this
<h:form id="myForm">
<f:subView id="myView">
<custom:container id="myCustom">
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
</f:subView>
</h:form>
so in this case the client id (that return by component.getClientId(context)) for <custom:container> is myForm:myView:myCustom, but inside Mojarra, the findComponent method has this
public UIComponent findComponent(String expr) {
...
else if (!(base instanceof NamingContainer)) {
// Relative expressions start at the closest NamingContainer or root
while (base.getParent() != null) {
if (base instanceof NamingContainer) {
break;
}
base = base.getParent();
}
...
}
so it looks for the next ancestor NamingContainer, which in my case is the f:subView not the h:form. It then parse the client id, loop through it, each time passing piece of the id to the UIComponent findComponent(UIComponent base, String id, boolean checkId). So the first time in, this method take form3 as id and current UIComponent is f:subView, it search for all its facets and children to see if any component match form3, of course, none will match since form3 is the parent of f:subView in my structure. So null is return. Is this Mojarra bugs or am I doing some wrong. I thought that the client id is relative from the next NamingContainer ancestor instead of all the way to the root of the NamingContainer? Am I wrong on that?
After reading the docs it seems to me that Mojarra got it right.
According to the docs you should place a seperator (a colon) in front of your search expression if you want to do an "absolute" search from the root of your tree.
Otherwise it will do a relative search from the component itself if it is a NamingContainer or the first parent that is a NamingContainer.
BTW: The docs I linked to appear to be the same as the official docs distributed with the specs. Official specs are here.

After validation jsf fails to reset values

I have a jsf composite component implemented from two p:calendar components.
The idea is when the first calendar is selected, the value of the second calendar need to be reset. There is a problem when the validation takes place, and the reset of the second calendar is not performed.
After reading posts I decided to use EditableValueHolder in my validator.
I have custom validator: in which I added the following code:
#Override
public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException {
//....
resetValues(fc);
}
public void resetValues(FacesContext fc) {
PartialViewContext partialViewContext = fc.getPartialViewContext();
Collection<String> renderIds = partialViewContext.getRenderIds();
UIComponent input;
UIViewRoot viewRoot = fc.getViewRoot();
for (String renderId : renderIds) {
input = viewRoot.findComponent(renderId);
if (input.isRendered() && input instanceof EditableValueHolder) {
EditableValueHolder editableValueHolder = (EditableValueHolder) input;
editableValueHolder.setSubmittedValue(null);
editableValueHolder.setValue(null);
editableValueHolder.setValid(true);
editableValueHolder.setLocalValueSet(false);
}
}
}
After debug I can see that each code line is passed, but nothing is happening on jsf side.
This is not the right moment to reset the values. They will be overridden anyway for the current component after the validate() method leaves and also for the second calendar once it get validated. You need to perform the reset somewhere after the update model values phase, preferably before the invoke action phase, so that you've chance to change the model value in an action(listener) method. You could use an ActionListener or a PhaseListener for this.
By the way, the JSF utility library OmniFaces has a reuseable solution for this in flavor of ResetInputAjaxActionListener.

Resources