Wednesday, April 1, 2009

Creating dynamic listener method binding

If you are JSF developer, you are most probably familiar with the following scenario:











The my bean instance should be created by the MyBean class which should include the following three methods:

public class MyBean{

public String action(){
// do something and return a string for the JSF navigation facility

return "navigateToPage2";
}

public void actionListener(ActionEvent event){

UIComponent component = event.getComponent();
// do something with or with out the component.
// the same view in JSF will be reloaded after
// this method returns.
}

public void valueChangeListener(ValueChangeEvent event){
UIComponent component = event.getComponent();
// do something with or with out the component.
// the same view in JSF will be reloaded after
// this method returns.
}
}


Sometimes, there are cases, where you have to do the binding for your self instead letting JSF bind your components with your listener methods via XML.

It these case, you should do, what JSF will do for you. From the JSF Context, you can get a handle to the Expression Language Factory or short referred as EL.

The expression language works similarly as the Reflection API of Sun Java SE. The main difference (at least for me) is that you can use the EL expression to bind your components to certain bean instances and methods.

To get a grip of the EL handle, you can use the following approach:


FacesContext ctx = FacesContext.getCurrentInstance();
Application app = ctx.getApplication();
ExpressionFactory el = app.getExpressionFactory();


Instead writing this code, you can use the inline call of the EL expression as follows:

ExpressionFactory el = FacesContext.getCurrentInstance().
getApplication().
getExpressionFactory();


In end effect, it is all the same for Java. It only saves you few lines of code.

Now that we have the grip of the handle for the EL, we can use it to create dynamic binding of component's and bean methods.

I will reproduce the same code from above with the command button and the text input. One should keep in mind that one can do this basically with every single JSF component.

public HtmlCommandButton createMyButton(){
HtmlCommandButton button = FacesContext.getCurrentInstance().getApplication().
createComponent(HtmlCommandButton.COMPONENT_TYPE );

//create the EL binding for the action method with return type String
MethodExpression action= FacesContext.getCurrentInstance().
getApplication().getExpressionFactory().
createMethodExpression(FacesContext.getCurrentInstance().getELContext(),
#{myBean.action}, String.class, new Class[]{});

//create the EL binding for the action listener method with return type void
// and argument of ActionEvent type
MethodExpression actionListener= FacesContext.getCurrentInstance().
getApplication().getExpressionFactory().
createMethodExpression(FacesContext.getCurrentInstance().getELContext(),
#{myBean.action}, null, new Class[]{ActionEvent.class});

//bind them to the button component.
button.setActionExpression(action);
// add the listener to the action
button.addActionListener(new MethodExpressionActionListener(actionListener));

return button;

}


What is important to notice, is that when creating the action method EL with return type String the createMethodExpression method of the EL factory takes four arguments:
  1. The faces context
  2. The String which represents the EL expression which should be used for binding
  3. The return type of String class
  4. The argument's of the method, which in this case are none to be passed.
The same goes for the creation of the action listener EL expression, where as the return type is set to null and the only argument passed is of type ActionEvent class.

The last thing to do is to save this method expressions expression in the map of the JSF component for later use. This goes only for the action method with return type of String. To add the EL expression for listener, you have to wrap the Method expression with the MethodExpressionActionListener class. In doing so, you add this expression to the component via UIComponent.addActionListener(ActionEvent); method.

The next thing to do is to create the text input component with it's value change listener binding.


public HtmlInputText createMyInput(){
HtmlInputText input = FacesContext.getCurrentInstance().getApplication().
createComponent(HtmlInputText.COMPONENT_TYPE );
// create the value change listener
MethodExpression valueChange=
FacesContext.getCurrentInstance().getApplication().getExpressionFactory().
createMethodExpression(FacesContext.getCurrentInstance().getELContext(),
#{myBean.valueChangeListener}, null, new Class[]{ValueChangeEvent.class});

//add the listener to the component
input.addValueChangeListener(new MethodExpressionValueChangeListener(valueChange));

}



As you can see, this procedure is the same as if you create action event. The only difference is the argument of the method,which is of type ValueChangeEvent class.