JSF - actionListener tag calls method which doesn't take an ActionEvent parameter - jsf

I keep reading posts which say that ActionListener methods must have the following signiture:
public void calledByActionListener(ActionEvent e) {
}
Invoked like so:
<p:commandButton value="Example" id="example" process="#this" ajax="false"
actionListener="#{exampleBean.calledByActionListener()}">
However I have a no-arg method like this which works:
public void calledByActionListener() {
}
Did something change?

Yes, that's the new EL 2.2 feature of invoking methods with custom arguments. Basically, you're explicitly invoking an argumentless method. This construct is legit.
Note that this is not related to JSF2. EL 2.2 just happens to be part of Java EE 6 as well like JSF2. So it look like a new JSF2 feature. But it actually isn't. As evidence, JSF2 is backwards compatible with Java EE 5 which thus implies EL 2.1, but this construct doesn't work over there.
When not explicitly specifying any custom arguments in the method expression, JSF will as per the specification assume a default argument of ActionEvent in the actual method.

Related

JSF 2.2: Passing bean attribute as ValueExpression to composite

I want to pass a non-managed (non-String) object as an attribute on a dynamically added composite component, and have it survive the session.
The JSF 2.2 ViewDeclarationLanguage#createComponent handles dynamic non-string attribute values to composite components differently than the older Mojarra dependent code (Application#createComponent). I can't find the approach that works completely with the JSF 2.2 technique, but it's probably me.
[I'm trying to remove Mojarra dependencies by converting to MyFaces (and also working around some other Mojarra issues). I'm using JSF 2.2, CDI Weld 2.2.16, Tomcat v8.0]
I'm instantiating different composite components like these programmatically (notice the bean attribute):
<cc:interface>
<cc:attribute name="bean" required="true" type="com.aadhoc.cvc.spikes.extensionsapproach.ExtensionBeanInterface"/>
</cc:interface>
<cc:implementation>
<h:panelGrid columns="2">
<h:outputText value="Title:"/>
<h:inputText value="#{cc.attrs.bean.title}"/>
</h:panelGrid>
</cc:implementation>
In the older Mojarra dependent approach, I instantiate the non-managed bean object, and add it directly to the composite component as an attribute and it works great (I'm using #BalusC's great but Mojarra dependent sample code from OmniFaces Component#includeCompositeComponent):
ExtensionBeanInterface bean = Class.forName(className).newInstance();
attributes = new HashMap<String, Object>();
attributes.put("bean", bean); // Using bean object itself
[..]
UIComponent composite = application.createComponent(context, resource);
composite.getAttributes().putAll(attributes);
[..]
In JSF 2.2, I've found that I must pass a String ValueExpression instead of my bean object directly. I'm currently using this technique, and can't get it quite right:
FacesContext context = FacesContext.getCurrentInstance();
ELContext elContext = context.getELContext();
ValueExpression beanValExp = context.getApplication().getExpressionFactory()
.createValueExpression(elContext, "#{customBeanVE}", ExtensionBeanInterface.class);
beanValExp.setValue(elContext, bean);
String beanValExpStr = beanValExp.getExpressionString();
attributes = new HashMap<String, Object>();
attributes.put("bean", beanValExpStr); // Using VE instead of bean object
UIComponent composite = context.getApplication().getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
.createComponent(context, taglibURI, tagName, attributes);
[..]
This works great on the first "add composite", but on any following form submit, I get:
/resources/com/aadhoc/cvc/spikes/extensionsapproach/components/House.xhtml
#16,49 value="#{cc.attrs.bean.title}": Target Unreachable, 'bean'
returned null
I've verified that the composite's required and type attributes are working fine, and that the #{cc.attrs.bean.title} is initially showing the bean's title. I verified with a static use of the composite component that refreshes work fine.
What's the deal, and how can I handoff the bean object so that it survives with the composite across the session?
I had this working great in Mojarra. I could put the bean object in the attribute value, and all was wonderful. Trying MyFaces, I needed to change/update my approach, and I now needed to use EL strings instead of direct object references.
Since all was working with just putting bean object into attributes Map, I wanted a magical yet elegant place to put bean objects and have them survive. I could have put them into the "global" session Map (like this: How to save an object into JSF Session), but it wasn't clean enough. I then put them into my one main session state bean (modelerBean), and it was right. This is how I saved the bean, and how I pointed to it with an EL string. No need to create ValueExpression or MethodExpression objects or register special mappings. This JSF 2.2 compatible approach worked for me in both Mojarra and MyFaces.
public void onAdd(ActionEvent ev) {
[..]
ChosenBean chosenBean = new ChosenBean();
chosenBean.setComponentId(id);
chosenBean.setBean(bean);
modelerBean.addChosen(chosenBean);
[..]
String el = "#{modelerBean.getChosen('"+id+"').bean}"
attributes.put(MODELER_EXTENSION_BEAN_ATTRIBUTE_NAME, el);
[..]
I decided on this after reading #BalusC's post: How do I get and set an object in session scope in JSF?
Note, my experience with #BalusC's two "add composites dynamically" approaches (How to programmatically or dynamically create a composite component in JSF 2) is that you should definitely use the JSF 2.2 approach if you can. The old Mojarra approach does work if you aren't in JSF 2.2. Once I modified my code to have the JSF 2.2 approach work, the old Mojarra approach would break in strange ways.

Why am I able to bind <f:actionListener> to an arbitrary method if it's not supported by JSF?

I'm using Glassfish 3.1.2.2 and JSF Mojarra 2.1.6.
I have the following Facelets page:
<h:form>
<h:commandLink value="link">
<f:actionListener binding="#{backingBean.someMethod(1)}"/>
</h:commandLink>
</h:form>
And the following backing-bean:
#RequestScoped
#ManagedBean
public class BackingBean {
public void someMethod(int i) {
System.out.println("It was called: " + i);
}
}
When I click the link, "Info: It was called: 1" appears in the console.
The documentation for binding reads:
Library: http://xmlns.jcp.org/jsf/core, http://java.sun.com/jsf/core (Jsf Core)
Tag: actionListener
binding
Value binding expression that evaluates to an object that implements javax.faces.event.ActionListener. [emphasis mine]
Also, the accepted answer to this question states that it's not possible for an f:actionListener to call an arbitrary method.
Why is the backing bean method called if this isn't supported?
This is the consequence of the new EL 2.2 feature of calling a method in a value expression via the #{bean.method()} syntax instead of only referencing a property via the #{bean.property} syntax (which should indeed be of the exact type ActionListener). It wouldn't have worked in EL 2.1 or older and it would also not work when you remove the arguments and the parentheses. That document was written when EL 2.2 didn't exist (it's actually not modified as compared to JSF 1.2 version from May 2006; EL 2.2 was introduced December 2009). I however do agree that it needs an update on that part as it's confusing to starters.
The answer you found made its points based on the document, but the answerer however didn't seem to realize based on the question that while binding="#{testController.nodeListener}" failed, the binding="#{testController.nodeListener(event)}" actually worked. This only doesn't give you the opportunity to pass the ActionEvent. The answer was better if it suggested to just use binding="#{testController.nodeListener()}" instead and grab the event information in other way, such as by calling UIComponent#getCurrentComponent() or even by passing #{component} as argument. Only if you really need to have a hand of it, of course.
<h:commandLink value="link">
<f:actionListener binding="#{bean.someMethod(component)}"/>
</h:commandLink>
public void someMethod(UIComponent component) {
System.out.println("It was called on: " + component); // HtmlCommandLink
}
See also:
Invoke direct methods or methods with arguments / variables / parameters in EL
Difference between JSP EL, JSF EL and Unified EL
Differences between action and actionListener

How to implement <f:setPropertyActionListener> programmatically

Basically i'm trying to set a dynamic columns for a <p:datatable>.
The content of one of my columns is a p:commandLink which used to show a dialog for text editing, i have this working like a charm in the XHTML but I need to translate it to Java for dynamic user customization and preferences.
here is what is my XHTML version:
<p:commandLink id="MRepShowButton" update=":form1:display" onclick="EditorDialog.show();" title="Editer le compte rendu">
<f:setPropertyActionListener value="#{exam}" target="#{examenListBean.selectedExamen}" />
</p:commandLink>
and this is my Java version(not working):
CommandLink rapstatelink = (CommandLink)application.createComponent(CommandLink.COMPONENT_TYPE);
rapstatelink.setId("MRepShowButton");
rapstatelink.setUpdate(":form1:display");
rapstatelink.setOnclick("EditorDialog.show();");
rapstatelink.setTitle("Editer le rapport du patient");
ValueExpression target = ef.createValueExpression(elc, "#{exam}", Object.class);
ValueExpression value = ef.createValueExpression(elc, "#{examenListBean.selectedExamen}", Object.class);
//rapstatelink.setActionListener(new SetPropertyActionListenerHandler(**i don't know wht to do here **));
column.getChildren().add(rapstatelink);
table.getChildren().add(column);
You need UICommand#addActionListener(), not UICommand#setActionListener(). The setActionListener() is a deprecated method from JSF 1.x which effectively does a <p:commandLink actionListener="..."> with a ValueBinding.
As to creating the <f:setPropertyActionListener> programmatically, there's unfortunately no JSF implementation independent way for that. Choose either of the following options:
Use the JSF implementation specific class, in case of Mojarra that's the com.sun.faces.taglib.jsf_core.SetPropertyActionListenerImpl:
link.addActionListener(new SetPropertyActionListenerImpl(target, value));
In case of MyFaces that's the org.apache.myfaces.event.SetPropertyActionListener:
link.addActionListener(new SetPropertyActionListener(target, value));
Keep in mind that using JSF implementation specific classes com.sun.faces.* or org.apache.myfaces.* in your own code is a poor practice.
Create a custom ActionListener implementation which does the job. Basically, just copypaste the class' source code from either Mojarra or MyFaces source code into your package. As compared to 1) this has the advantage that your web application does not break when deployed to a Java EE container which ships with the other JSF implementation bundled.
Make use of EL 2.2 feature of the ability to pass method arguments in EL expressions. Then you can just do the job in action or actionListener attribute:
link.setActionExpression(ef.createMethodExpression(elc,
"#{examenListBean.setSelectedExamen(exam)}", Void.class, Exam.class));
(the Exam.class should represent the type of #{exam})
This does effectively the same as
<p:commandLink ... action="#{examenListBean.setSelectedExamen(exam)}" />
Or if you really need to set an action listener:
link.addActionListener(new MethodExpressionActionListener(ef.createMethodExpression(elc,
"#{examenListBean.setSelectedExamen(exam)}", Void.class, Exam.class)));
This does effectively the same as
<p:commandLink ... actionListener="#{examenListBean.setSelectedExamen(exam)}" />

Calling bean methods with arguments from JSF pages

Is it possible to call bean methods & directly pass parameters to them from the view instead of requiring to first set the bean properties and then call methods without arguments using the commandButton or similar ?
I have a list of items with each item having a list of actions. To reduce the state, I am using a single primefaces remoteCommand, in place of several commandButton(s). On getting a action trigger from the view, I would call the remoteCommand from javascript but since the remoteCommand is one but used for multiple items thus I need to pass the id of the item as well. I am wondering if there is a way to pass the id of the item to the bean method directly as an argument instead of first setting it as a bean property ? Is there any way to do so ?
Actually I am looking at a better way to deal with multiple commandButtons on a page when there's a long list of items on the page.
Suggestions ? Thanks.
Using JSF 2.1.6 Mojarra with Primefaces 3.0RC1
Passing method arguments is supported since EL 2.2 which is part of Servlet 3.0. So if your webapp runs on a Servlet 3.0 compatible container (Tomcat 7, Glassfish 3, etc) with a web.xml declared conform Servlet 3.0 spec (which is likely true as you're using JSF 2.1 which in turn implicitly requires Servlet 3.0), then you will be able to pass method arguments to bean action methods in the following form:
<h:commandButton value="Submit" action="#{bean.submit(item.id)}" />
with
public void submit(Long id) {
// ...
}
You can even pass fullworthy objects along like as:
<h:commandButton value="Submit" action="#{bean.submit(item)}" />
with
public void submit(Item item) {
// ...
}
If you were targeting a Servlet 2.5 container, then you could achieve the same by replacing the EL implementation by for example JBoss EL which supports the same construct. See also Invoke direct methods or methods with arguments / variables / parameters in EL.
Yes, it is.
<h:commandButton action="#{bean.method(object)}" />
See this http://www.mkyong.com/jsf2/4-ways-to-pass-parameter-from-jsf-page-to-backing-bean/
You can call ManagedBean methods with arguments like this.
<h:commandButton actionListener="#{stateBean.delete(row.stateID)}"
value="Delete" id="btnDeleteS">
<f:ajax event="action" execute="#form" render="#form"/>
</h:commandButton>
The corresponding ManagedBean would be like this.
#ManagedBean
#RequestScoped
public class StateBean
{
#EJB
private RemoteInterface obj=null;
public void delete(String stateID)
{
//Code stuff here.
}
}
You can also directly set the value of ManagedBean properties using <f:setPropertyActionListener></f:setPropertyActionListener> like this.
<h:commandButton value="Delete" id="btnDeleteS">
<f:setPropertyActionListener target="#{stateBean.someProperty}"
value="#{someValue}"/>
<f:ajax event="action" execute="#form" render="#form"/>
</h:commandButton>

Passing parameters to a method in h:outputtext tag

I would like to display a text in jsf screen by passing an attribute to a method implemented in backing bean. I have to pass a dynamic value as an attribute. I tried the below but it seems to be an incorrect syntax -
<h:outputText value="#{getValue(#{item.product}).component.address}" />
Apart from the syntax error (you can never nest EL expressions like as #{#{}}), the following is valid in EL 2.2 which is in turn part of Servlet 3.0 / Java EE 6:
<h:outputText value="#{bean.getValue(item.product).component.address}" />
So if you have a Servlet 3.0 compatible target runtime (Tomcat 7, Glassfish 3, JBoss 6, etc) with a Servlet 3.0 compatible web.xml, then you can invoke non-getter methods with arguments like this.
However, based on your question history you're using JSF 1.2 and the chance is big that you're also targeting an older container where the above wouldn't work. If it is a Servlet 2.5 container, then you could use JBoss EL to get this EL syntax to work.
See also:
Invoking methods with parameters by EL in JSF 1.2
JSF 1.2 w/ EL 2.1 (the usual pairing) doesn't support calling methods with parameters on beans using EL expressions.
There exists one hack, abusing the fact that JSF EL expressions work on maps:
value="#{myBean['product']}"
This will assume myBean is implementing Map interface and will call get method with 'product' as parameter. I don't know if the key can be dynamically computed but it's worth a try.
Syntax would be:
value="#{myBean[item.product].component.address}"
Assuming here that myBean implements Map interface and returns the appropriate object and item.product is returns a string.
I am not sure this will work, but it's your only option beside implementing a getter that looks up item.product value in its implementation.

Resources