8

I'm trying to add JSF <h:commandButtons> dynamically to my webpage, and so far I have them displaying, but I cannot set the action with parameters, like I could in a static page:
action="#{bean.function(parameter)}". (this is of course using EL-2.2)
Looking around I find that I have to create a MethodExpression, but this is obscure to me and I haven't been able to find much information on this. If someone could shine a light through the fog and explain how this can be done, it would be greatly appreciated.

EDIT: so now I have this

public void displayNode( String childName ){
//lots of messy code instantiating JSF components

if( activeEmployee.getParent() != null ){
        HtmlCommandButton parent = new HtmlCommandButton();
        HtmlOutputText parentLabel = new HtmlOutputText();

        parentLabel.setId("label" + count++);  //I really hate having to use count
        parentLabel.setValue( "Parent: " );

        parent.setId("Parent" + count++); 
        String parentName = activeEmployee.getParent().getName();
        parent.setValue( parentName );
        MethodExpression expression = createMethodExpression("#{tree.displayNode('" + parentName + "')}",
                                                                null, String.class);
        parent.setActionExpression( expression );

        newDiv.getChildren().add( parentLabel );
        newDiv.getChildren().add( parent );
    }
Jake Long
  • 706
  • 3
  • 7
  • 17

1 Answers1

19

Use ExpressionFactory#createMethodExpression().

public abstract MethodExpression createMethodExpression(
                                      ELContext context,
                                      java.lang.String expression,
                                      java.lang.Class<?> expectedReturnType,
                                      java.lang.Class<?>[] expectedParamTypes)

Here's a convenience method:

public static MethodExpression createMethodExpression(String expression, Class<?> returnType, Class<?>... parameterTypes) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    return facesContext.getApplication().getExpressionFactory().createMethodExpression(
        facesContext.getELContext(), expression, returnType, parameterTypes);
}

The following action method examples:

public void submit1()
public String submit2()
public void submit3(String argument)
public String submit4(String argument)
public void submit5(String argument1, Long argument2)
public String submit6(Long argument1, String argument2)

can then be created as follows:

createMethodExpression("#{bean.submit1}", null);
createMethodExpression("#{bean.submit2}", String.class);
createMethodExpression("#{bean.submit3('foo')}", null, String.class);
createMethodExpression("#{bean.submit4('foo')}", String.class, String.class);
createMethodExpression("#{bean.submit5('foo', 0)}", null, String.class, Long.class);
createMethodExpression("#{bean.submit6(0, 'foo')}", String.class, Long.class, String.class);

Note that the EL expression is exactly the same as you would use in the normal view file.


Update here's an SSCCE which works fine for me with Mojarra 2.1.12 on Tomcat 7.0.27.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
>
    <h:head>
        <title>SO question 12098611</title>
    </h:head>
    <h:body>
        <h:form binding="#{bean.form}">
            <h:commandButton value="add" action="#{bean.add}" />
        </h:form>
    </h:body>
</html>
@ManagedBean
@RequestScoped
public class Bean {

    private UIForm form;

    public void add() {
        String id = "button" + form.getChildCount();
        UICommand button = new HtmlCommandButton();
        button.setId(id);
        button.setValue(id);
        button.setActionExpression(createMethodExpression(String.format("#{bean.submit('%s')}", id), null, String.class));
        form.getChildren().add(button);
    }

    public void submit(String arg) {
        System.out.println("submit: " + arg);
    }

    public UIForm getForm() {
        return form;
    }

    public void setForm(UIForm form) {
        this.form = form;
    }

    public static MethodExpression createMethodExpression(String expression, Class<?> returnType, Class<?>... parameterTypes) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        return facesContext.getApplication().getExpressionFactory().createMethodExpression(
            facesContext.getELContext(), expression, returnType, parameterTypes);
    }

}

Unrelated to the concrete problem, all of above is a poor practice. See also How does the 'binding' attribute work in JSF? When and how should it be used?

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    One more time, you're the man. I was looking for this info on internet because I've made it but using JSF 1.2. – Luiggi Mendoza Aug 23 '12 at 20:03
  • I guess I prematurely assumed I needed a `MethodExpression` because I've seen it done before using `button.setActionExpression(...)`. What's the difference between using `setActionExpression()` and `setAction()`, besides the obvious arguments required? – Jake Long Aug 23 '12 at 20:06
  • 1
    @Jake: `setAction()` is the [deprecated](http://docs.oracle.com/javaee/6/api/javax/faces/component/UICommand.html#setAction(javax.faces.el.MethodBinding)) leftover of JSF 1.x using "deferred EL", which used its own EL implementation with `ValueBinding`, `MethodBinding` and so on (which was later unified with JSP EL). Do **not** use any deprecated API in JSF 2.x at all. They are subject for removal. See also http://stackoverflow.com/questions/4812755/difference-between-jsp-el-jsf-el-and-unified-el for a bit of history on EL. – BalusC Aug 23 '12 at 20:08
  • hmm the button isn't invoking the action, I'll throw up some code – Jake Long Aug 23 '12 at 20:21
  • Code looks fine. Are you implying that an argumentless method get properly invoked? – BalusC Aug 23 '12 at 20:34
  • No, the function isn't being called at all. There are no errors from attempting to call `displayNode` without arguments, simply nothing happens after clicking the created button – Jake Long Aug 23 '12 at 20:37
  • Then the button is apparently missing after restoring the view. How exactly are you holding `newDiv` in the bean? It should be a request scoped bean and it should be just a property with a normal getter/setter and nothing more (also no lazy loading; JSF will set it by itself). All action methods depending on the manipulated component tree should return `void` or `null` (so that the same view is retained for subsequent request). – BalusC Aug 23 '12 at 20:45
  • I'm view scoped bean because I build the tree up within the `tree` class, then use these functions to display the nodes. `newDiv` is simply an `HtmlPanelGroup` element, and at the end of the function it is added to an `HtmlPanelGroup topDiv` (soon to change, needs to be a table) that I have bound to a `div` on the JSF page. – Jake Long Aug 23 '12 at 20:56
  • @Jake: a view scoped bean can also, but `UIComponent` isn't serializable, so state saving will fail. If you absolutely need view scoped data, rather split the `UIComponent` property (and the actions depending on it) into a new request scoped bean and inject the view scoped bean as a managed property if you need some data from it. – BalusC Aug 23 '12 at 20:59
  • Sorry, which `UIComponent` are you referring to? – Jake Long Aug 23 '12 at 21:02
  • Any subclass of `UIComponent`, so also the `newDiv`. – BalusC Aug 23 '12 at 21:03
  • ok I think I understand, I'll have to try it out tomorrow (5:00 wahoo!) – Jake Long Aug 23 '12 at 21:03
  • Another disadvantage of using `binding` on a view scoped bean is that a brand new instance will be created on every request. So it's fairly pointless to make it a view scoped one. – BalusC Aug 23 '12 at 21:05
  • I accepted it because you answered the question I asked (fantastic and thoroughly), but I'll most likely be asking related questions soon XP – Jake Long Aug 24 '12 at 19:45
  • @Balus submit method never is invoked for me on glassfish 4,JSF 2.2,Spring WebFlow and Primefaces. I copied your code as you've written. I've tried add ajax behavior, actionlistener and action on my project but the result is the same...button rendered and useless when I push. – Rafael Ruiz Tabares Dec 02 '14 at 12:01
  • If I change @BalusC code to SessionScope then method submit is invoked, but requestScope or viewScope not work. – Rafael Ruiz Tabares Dec 03 '14 at 13:27