In the first part of the article, we showed how to let a JSF bean drive a GWT module through an exposed JavaScript API. Now we'll see how to make a GWT module raise events in a way such that a call to the server is made letting it respond accordingly.
Defining the events to be raised
We'll start by defining which events will be raised by our GWT module. Let's put them all together in a single interface, which as a result of hours of thinking and thinking I decided to name IHelloWorldEventHandlers.
public interface IHelloWorldEventHandlers { void requestHelloWorldAlert(String name); }
Well, there will be only one event. Suppose we have a textbox containing the user name and a button which when clicked will raise the event.
Remember that one of our goals is to let JSF handle the request to the server, so the event will be implemented as a call to a JavaScript function which we'll suppose to have already been defined somewhere. The concrete class which we'll be called in the button's onClick looks like the one below:
public class HelloWorldEventHandlers implements IHelloWorldEventHandlers { public native void requestHelloWorldAlert(String name) /*-{ $wnd.requestHelloWorldAlert(name); }-*/; }
Some things to point out about this. You've probably have noticed that the method is defined with the "native" modifier. Think of this just as a way of wrapping a piece of JavaScript code with a Java method definition.
The second thing to point out is the $wnd reference. This is just a name for the global JavaScript scope in the browser.
The last, but the most important is that we are following a convention in the way we write the method body. The method in JavaScript is called exactly the same as the Java event handler, and it also has the same arity. This way we'll be able to do some useful stuff by reflection later.
Dynamically Generating JavaScript handlers for the events
Now we have to create the JavaScript functions which will be called. The first idea that comes to mind is to write a <a4j:jsFunction> for each event. Well, that would work, but we can do better than that. There's a common pattern between all the functions that we'll need to write: we'll want an action to be executed on a backing bean and we'll have to create an <a4j:actionParam> for each parameter in the function.
We can then use reflection and the RichFaces and JSF object models to dynamically generate the functions for us. We decided to implement this through a new custom component. Again, this article does not intend to show how to write custom JSF components, but we'll describe the basic concepts involved in the solution.
Our component we'll need three attributes: the name of a backing bean, the name of a Map object property of the same bean and the name of an element to reRender.
We'll override the setProperties method of our custom component CustomComponentTag class to add the dynamic generation of the jsFunction components through the following code:
FacesContext currentFacesCtx = FacesContext.getCurrentInstance(); Application application = currentFacesCtx.getApplication(); String reRenderedComponentId = (String) component.getAttributes() .get("reRenderedComponent"); String beanId = (String) component.getAttributes().get( "bean"); String functionParamsMapId = (String) component.getAttributes().get( "functionParams"); for (Method method : IHelloWorldEventHandlers.class.getMethods()) { HtmlAjaxFunction gwtEventHandler = new HtmlAjaxFunction(); //Set the name of the a4jFunction to be the same as the method //name in IHelloWorldEventHandlers gwtEventHandler.setName(method.getName()); gwtEventHandler.setReRender(reRenderedComponentId); gwtEventHandler.setAction(application.createMethodBinding("#{" + beanId + "." + method.getName() + "}", null)); int i = 1; for (Class> paramType : method.getParameterTypes()) { String paramName = "Param" + i; HtmlActionParameter htmlActionParam = new HtmlActionParameter(); htmlActionParam.setName(paramName); htmlActionParam.setAssignToBinding(createValueBinding("#{" + beanId + "." + functionParamsMapName + "['" + method.getName() + "." + paramName + "']}")); gwtEventHandler.getChildren().add(htmlActionParam); htmlActionParam.setParent(gwtEventHandler); //Important: we have to add this line in order to //pass the parameter. //It is not enough to add the actionParam as //a child of the a4j:jsFunction. gwtEventHandler.addActionListener(htmlActionParam); i++; } component.getChildren().add(gwtEventHandler); }
Note that we only need to know the IHelloWorldEventHandlers to generate the a4jFunction components. If we refactor the handlers changing their arity or name, this stuff will go on working. The only caveat is that we are forced to write those weird native methods in the implementation of IHelloWorldEventhandlers, but we think it is possible to work around that problem using GWT Generators also.
Another thing to point out is that we are using the Map to pass parameters, where the method name concatenated with a generated param name is used as key.
Martín