Accessing attributes passed to extended PrimeFaces component - jsf

I'm trying to create a custom component to extend PrimeFaces.
I have a simple component called textInput under the test namespace that simply calls the PrimeFaces textInput component and prints out the value passed to an attribute named fieldClass and the names of any attributes passed
if I pass fieldClass as a string:
<test:textInput id="foo" fieldClass="field-foo" />
this is the result
fieldClass = field-foo
[com.sun.faces.facelets.MARK_ID, fieldClass]
If I pass fieldClass as an expression
<ui:param name="bar" value="field-foo"/>
<test:textInput id="foo" fieldClass="#{bar}" />
fieldClass vanishes
fieldClass = NONE
[com.sun.faces.facelets.MARK_ID]
How do I actually get hold of the attributes passed to the component?
Classes used by the custom component follows:
test.components.ExtendInputTextRenderer
package test.components;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import org.primefaces.component.inputtext.*;
#FacesRenderer(
componentFamily=ExtendInputText.COMPONENT_FAMILY,
rendererType=ExtendInputTextRenderer.RENDERER_TYPE
)
public class ExtendInputTextRenderer extends InputTextRenderer {
public static final String RENDERER_TYPE = "com.example.ExtendInputTextRenderer";
#Override
public void encodeEnd(FacesContext context, UIComponent component)
throws java.io.IOException {
ResponseWriter writer = context.getResponseWriter();
Map attrs = component.getAttributes();
String fieldClass = attrs.containsKey("fieldClass") ? (String) attrs.get("fieldClass").toString() : "NONE";
writer.write("fieldClass = " + fieldClass + "<br/>");
writer.write(attrs.keySet().toString() + "<br/>");
super.encodeEnd(context, component);
}
}
test.components.ExtendInputText
package test.components;
import javax.faces.component.FacesComponent;
import org.primefaces.component.inputtext.InputText;
#FacesComponent(ExtendInputText.COMPONENT_TYPE)
public class ExtendInputText extends InputText {
public static final String COMPONENT_FAMILY = "com.example";
public static final String COMPONENT_TYPE = "com.example.ExtendInputText";
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public String getRendererType() {
return ExtendInputTextRenderer.RENDERER_TYPE;
}
}

String fieldClass = attrs.containsKey("fieldClass") ? (String) attrs.get("fieldClass").toString() : "NONE";
Your mistake is that you're using containsKey() to check if the property has been specified.
Here's an extract from UIComponent#getAttributes() javadoc:
getAttributes
public abstract java.util.Map<java.lang.String,java.lang.Object> getAttributes()
Return a mutable Map representing the attributes (and properties, see below) associated wth this UIComponent, keyed by attribute name (which must be a String). The returned implementation must support all of the standard and optional Map methods, plus support the following additional requirements:
The Map implementation must implement the java.io.Serializable interface.
Any attempt to add a null key or value must throw a NullPointerException.
Any attempt to add a key that is not a String must throw a ClassCastException.
If the attribute name specified as a key matches a property of this UIComponent's implementation class, the following methods will have special behavior:
containsKey() - Return false.
get() - If the property is readable, call the getter method and return the returned value (wrapping primitive values in their corresponding wrapper classes); otherwise throw IllegalArgumentException.
put() - If the property is writeable, call the setter method to set the corresponding value (unwrapping primitive values in their corresponding wrapper classes). If the property is not writeable, or an attempt is made to set a property of primitive type to null, throw IllegalArgumentException.
remove() - Throw IllegalArgumentException.
Note that it thus always returns false for containsKey for component's properties. That's because dynamic properties are not stored in the attribute map, but instead in the component instance itself. They're only resolved when calling get().
You need to change the wrong line as follows:
String fieldClass = (String) attrs.get("fieldClass");
if (fieldClass == null) fieldClass = "NONE";

Related

Get managed bean and type bound to "value" attribute

Let's suppose I have following structure:
1) Managed Bean:
#ViewScoped
#ManagedBean
public class TestBean {
private Test test;
//getters/setters
}
2) Test class:
public class Test {
private String attribute;
//gets/sets
}
3) XHTML
<p:inputText id="test" value="#{testBean.test.atribute}" />
Now, I know there is a way to find and get component instance:
UIComponent c = view.findComponent(s);
From UIComponent, how do I get the type bound to component?
What I need is to get full qualified class name from what is set as "value" attribute in component. Something like: package.Test.attribute.
UIComponent offers getValueExpression("attributeName")
sample :
UIViewRoot viewRoot = Faces.getViewRoot();
UIComponent component= viewRoot.findComponent("x");
ValueExpression value = component.getValueExpression("value");
Class<?> expectedType = value.getType(Faces.getELContext());
NB:Faces here is from Omnifaces, which is a "Collection of utility methods for the JSF API that are mainly shortcuts for obtaining stuff from the thread local FacesContext. "
excepts from getType() javadoc
public abstract Class getType(ELContext context) Evaluates the
expression relative to the provided context, and returns the most
general type that is acceptable for an object to be passed as the
value parameter in a future call to the setValue(javax.el.ELContext. java.lang.Object) method. This is not always the same as
getValue().getClass(). For example, in the case of an expression that
references an array element, the getType method will return the
element type of the array, which might be a superclass of the type of
the actual element that is currently in the specified array element.
For MethodExpression read this.

java.math.BigDecimal in p:selectOneMenu

Converter :
#FacesConverter("bigDecimalConverter")
public class BigDecimalConverter implements Converter {
private static final int SCALE = 2;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
BigDecimal newValue;
if (value instanceof Long) {
newValue = BigDecimal.valueOf((Long) value);
} else if (value instanceof Double) {
newValue = BigDecimal.valueOf((Double) value);
} else if (!(value instanceof BigDecimal)) {
throw new ConverterException("Message");
} else {
newValue = (BigDecimal) value;
}
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
formatter.setGroupingUsed(false);
formatter.setMinimumFractionDigits(SCALE);
formatter.setMaximumFractionDigits(SCALE);
return formatter.format(newValue);
}
}
List :
<p:selectOneMenu id="list" value="#{bean.value}">
<f:selectItems var="row" value="#{bean.list}" itemLabel="#{row}" itemValue="#{row}"/>
<f:converter converterId="bigDecimalConverter"/>
</p:selectOneMenu>
<p:message id="msg" for="list"/>
<p:commandButton value="Submit" update="list msg" actionListener="#{bean.action}"/>
The managed bean backed by the above <p:selectOneMenu> :
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<BigDecimal> list; // Getter only.
private BigDecimal value; // Getter & setter.
private static final long serialVersionUID = 1L;
public Bean() {}
#PostConstruct
private void init() {
list = new ArrayList<BigDecimal>(){{
add(BigDecimal.valueOf(10));
add(BigDecimal.valueOf(20.11));
add(BigDecimal.valueOf(30));
add(BigDecimal.valueOf(40));
add(BigDecimal.valueOf(50));
}};
}
public void action() {
System.out.println("action() called : " + value);
}
}
A validation message, "Validation Error: Value is not valid" appears upon submission of the form. The getAsObject() method throws no exception upon form submission.
If a value with a scale like 20.11 is selected in the list, then the validation passes. It appears that the equals() method in the java.math.BigDecimal class goes fishy which for example, considers two BigDecimal objects equal, only if both of them are equal in value and scale thus 10.0 != 10.00 which requires compareTo() for them to be equal.
Any suggestion?
You lose information when converting to String. The default JSF BigDecimalConverter does this right, it uses BigDecimal#toString in its getAsString. The BigDecimal#toString's javadoc says:
There is a one-to-one mapping between the distinguishable BigDecimal values and the result of this conversion. That is, every distinguishable BigDecimal value (unscaled value and scale) has a unique string representation as a result of using toString. If that string representation is converted back to a BigDecimal using the BigDecimal(String) constructor, then the original value will be recovered.
This is exactly what you need. Don't treat converters like they must produce user readable-writable results when converting to String. They mustn't and often don't. They produce a String representation of an object, or an object reference. That's it. The selectItems' itemLabel defines a user readable representation in this case. I'm assuming that you don't want user writable values here, that you really have a fixed list of values for user to choose from.
If you really mean that this data must always have a scale of 2, and you need a user writable value, then that would be better checked in a validator, and user input could be helped out with p:inputMask.
Finally, let's set aside the fact, that your converter is not the best. It says "data must have a scale of 2". Then your should provide conforming data in your selectItems. More generally, server defined values must conform to relevant converters and validators. E.g. you could have problems in the same vein, when using the DateTimeConverter with the pattern "dd.MM.yyyy", but setting the default value to be new Date() without getting rid of the time part.
See also (more general notions about converters): https://stackoverflow.com/a/30529976/1341535

How do I use a MethodExpression with parameters for a custom JSF component? [duplicate]

I'm trying to develop a custom component that will need to call a method from the backingbean to get some data from the bb (this will be called in the decode phase after a certain Ajax call) with one parameter (it will come in the ajax call).
The problem I'm having is that I define the attribute as a MethodExpression (in the taglibrary and the component), I get the Ajax post, decode the parameter and when I try to get the Method binding from the component I get the following error:
javax.el.PropertyNotFoundException: /easyFaces.xhtml #19,151
dataSource="#{theBean.loadDataFromSource}": The class
'ar.com.easytech.faces.test.homeBean' does not have the property
'loadDataFromBean'.
Here is the relevant code.. (and please let me know if this is not the correct way to do this..)
taglib:
<attribute>
<display-name>Data Source</display-name>
<name>dataSource</name>
<required>true</required>
<type>javax.el.MethodExpression</type>
<method-signature>java.util.List theDataSource(java.lang.String)</method-signature>
</attribute>
Component definition:
public class Autocomplete extends HtmlInputText implements ClientBehaviorHolder
...
public MethodExpression getDataSource() {
return (MethodExpression) getStateHelper().eval(PropertyKeys.dataSource);
}
public void setDataSource(MethodExpression dataSource) {
getStateHelper().put(PropertyKeys.dataSource, dataSource);
}
and finally the rendered method that generates the error:
private List<Object> getData(FacesContext context, Autocomplete autocomplete, String data) {
Object dataObject = null;
MethodExpression dataSource = autocomplete.getDataSource();
if (dataSource != null) {
try {
dataObject = dataSource.invoke(context.getELContext(), new Object[] {data});
return convertToList(dataObject);
} catch (MethodNotFoundException e) {
logger.log(Level.INFO,"Method not found: {0}", dataSource.getExpressionString() );
}
}
return null;
}
Here is the method from the BB
public List<String> autcompleteFromSource(String param) {
List<String> tmpData = new ArrayList<String>();
tmpData.add("XXA_TABLE_A");
tmpData.add("XXA_TABLE_B");
tmpData.add("XXA_TABLE_C");
return tmpData;
}
And the .xhtml with the component
<et:autocomplete id="autoc" minLength="3" delay="500" value="#{easyfacesBean.selectedValue}" dataSource="#{easyfacesBean.autcompleteFromSource}" />
The thing is if I define a method getAutocompleteFromSource() it recognised the method and the error changes to can't convert list to MethodExpression, so evidently it is simply interpreting the autocompleteFromSource as a simple property and not a method definition, is this even the correct way to call method from BB? (giving that it's not an actual action nor validation )
I found the solution for this, as it turns out you also need to define a "Handler"to define the Method Signature, so I created the handler and added to the taglib and everything started to work fine..just for reference.. here is the handler..
Regards
public class AutocompleteHandler extends ComponentHandler {
public AutocompleteHandler(ComponentConfig config) {
super(config);
}
protected MetaRuleset createMetaRuleset(Class type) {
MetaRuleset metaRuleset = super.createMetaRuleset(type);
metaRuleset.addRule(new MethodRule("dataSource", List.class, new Class[] { String.class }));
return metaRuleset;
}
}

<f:attribute value="#{some EL expression}"> not found via getAttributes().containsKey()

I cannot retrieve attributes that contain EL inside my ViewHandler.
As a minimal example, here's my ViewHandler. It just looks for an attribute "attributeName" and prints out its value.
public class MiniViewHandler extends ViewHandlerWrapper
{
private ViewHandler defaultViewHandler = null;
public MiniViewHandler()
{
}
public MiniViewHandler(ViewHandler defaultViewHandler)
{
super();
this.defaultViewHandler = defaultViewHandler;
}
#Override
public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException
{
viewToRender.visitTree(VisitContext.createVisitContext(context),
new VisitCallback()
{
#Override
public VisitResult visit(VisitContext context, UIComponent target)
{
if (target.getAttributes().containsKey("attributeName"))
{
System.out.println("Found it: " + target.getAttributes().get("attributeName"));
}
return VisitResult.ACCEPT;
}
}
);
defaultViewHandler.renderView(context, viewToRender);
}
#Override
public ViewHandler getWrapped()
{
return defaultViewHandler;
}
}
This is registered in the faces-config.xml. The xhtml that I'm hitting:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:body>
<f:view>
<f:attribute name="attributeName" value="#{2 + 5}"/>
</f:view>
<f:view>
<f:attribute name="attributeName" value="2 + 5"/>
</f:view>
</h:body>
</html>
The logged output shows that only the non-EL attribute was picked up.
INFO [stdout] (http-/127.0.0.1:8080-1) Found it: 2 + 5
The mistake is here:
if (target.getAttributes().containsKey("attributeName"))
You're using containsKey() to check if the property has been specified. This however won't work if the attribute is a ValueExpression. Here's an extract from UIComponent#getAttributes() javadoc with emphasis:
getAttributes
public abstract java.util.Map<java.lang.String,java.lang.Object> getAttributes()
Return a mutable Map representing the attributes (and properties, see below) associated wth this UIComponent, keyed by attribute name (which must be a String). The returned implementation must support all of the standard and optional Map methods, plus support the following additional requirements:
The Map implementation must implement the java.io.Serializable interface.
Any attempt to add a null key or value must throw a NullPointerException.
Any attempt to add a key that is not a String must throw a ClassCastException.
If the attribute name specified as a key matches a property of this UIComponent's implementation class, the following methods will have special behavior:
containsKey() - Return false.
get() - If the property is readable, call the getter method and return the returned value (wrapping primitive values in their corresponding wrapper classes); otherwise throw IllegalArgumentException.
put() - If the property is writeable, call the setter method to set the corresponding value (unwrapping primitive values in their corresponding wrapper classes). If the property is not writeable, or an attempt is made to set a property of primitive type to null, throw IllegalArgumentException.
remove() - Throw IllegalArgumentException.
Thus, it always returns false for containsKey for component's properties (read: component's ValueExpressions). That's because dynamic properties are not stored in the attribute map, but instead in the component instance itself via UIComponent#setValueExpression() calls. They're only resolved when calling get().
You basically need to change the wrong line as follows, this also works for "static" attributes:
Object value = target.getAttributes().get("attributeName");
if (value != null) {
System.out.println("Found it: " + value);
}
If you would like to check if the attribute is actually being set, even though it would evaluate null like value="#{bean.returnsNull}", then you'd instead like to check if UIComponent#getValueExpression() doesn't return null.
if (target.getValueExpression("attributeName") != null) {
System.out.println("Found it: " + target.getAttributes().get("attributeName"));
}
This does in turn however not work for "static" attributes. You could just combine the checks if necessary, depending on the concrete functional requirements.

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}"/>

Resources