Unlike most of my blog posts, where I try to describe the easiest possible way to do things, in this posting, I'll instead go over a Java-based custom JSF component that responds to the Ajax tag. The reason being that there simply aren't any examples out there of how to do this, and at least two people have expressed interest in finding exactly out how this is done. I'd advise anyone considering doing this to make really sure that you can't do the same thing in a Composite Component (you usually can), but sometimes, a Java-based custom JSF component is going to be required.

We're going to cover the following topics here, and it's going to be a little more code than usual, but I suspect that this will end up saving some folks a bunch of time, so lets plow forward. I'll cover:

  • Ajax listeners
  • Facelet components
  • Integrating the two
First, the Ajax Listener

An ajax listener, connected to your ajax event with the listener attribute, is a method that will be called every time the ajax request is made. For example, let's look at the following page section:

1 Echo test: 2
3 Echo count: 4
5 6 7 8
9 Event count: We've got three bean properties - hello (which is the string entered by the inputText), count (which is a count of the characters in hello, and eventCount (which is a count of the number of ajax requests). We also have a method on the bean, update (line 6), which will be called every time the ajax call is submitted.

The behavior of this page is pretty simple - every time you press a character in the inputText, the complete value of the input is echoed to the outputText "out" (line 1) - the length of "out" is written to "count" (line 3), and the "eventCount" outputText (line 9) has it's value incremented by one.

So - what code is in the bean? Here's the relevant bits:



1 public void setHello(String hello) { 2 this.hello = hello; 3 } 4 public int getCount() { 5 return count; 6 } 7 public int getEventCount() { 8 return eventCount; 9 } 10 public void update(AjaxBehaviorEvent event) { 11 count = hello.length(); 12 eventCount++; 13 }

Not so bad - the only thing new here is that AjaxBehaviorEvent class - and we're not even using it. The update method will simply set up the values to be correct, and we let the Ajax render to the rest. So - listeners are easy.



Facelets Components

Now, we'll want to create a custom tag in Java. To do that, we'll need to make a few configuration file entries, and write a little java code. But first, let's see it used in the page:

In the XHTML header, we'll say:

1 Setting up the "cu" prefix (line 5) to point to "custom-taglib" (the whole URL is significant). Then later on in the page, we'll use it like so:

We then need to add an entry in web.xml:

1 2 javax.faces.FACELETS_LIBRARIES 3 /WEB-INF/custom-taglib.xml 4 This points to our next config file, which is the filename on line 3. Here's its contents, in full:

1 5 http://javaserverfaces.dev.java.net/demo/custom-taglib 6 7 custom 8 9 mycustom 10 11 12 Note that the namespace element on line 5 matches the URL we used for the namespace in the html element of the using page. We said this taglibrary will have one tag "custom" (line 7), which maps to the FacesComponent "mycustom". Where does it find the definition of "mycustom"? In the Java file defining the component, using the new @FacesComponent attribute. Here's the full Java code, leaving out the imports:

1 @FacesComponent(value = "mycustom") 2 public class MyCustom extends UIComponentBase { 3 4 @Override 5 public String getFamily() { 6 return "custom"; 7 } 8 9 @Override 10 public void encodeEnd(FacesContext context) throws IOException { 11 12 ResponseWriter responseWriter = context.getResponseWriter(); 13 responseWriter.startElement("div", null); 14 responseWriter.writeAttribute("id",getClientId(context),"id"); 15 responseWriter.writeAttribute("name", getClientId(context),"clientId"); 16 responseWriter.write("Howdy!"); 17 responseWriter.endElement("div"); 18 } 19 } In fact, the Java code itself is simple enough that I don't really think it requires any explanation. Putting the cu:custom tag in your page will now render Howdy!, surrounded by a div with the same id and name as you gave the component. All that's left is to add the Ajax. That... is a bit more complicated, but now that we've handled everything else, it's really just incremental.

Using f:ajax with your custom tag

To use the f:ajax tag, we'd like to, for instance, do something like this:

1 2 3 Meaning, we'd like to just decorate the tag, and let it do something "smart". In this case, we'll default to "onclick" (since we're dealing with a div, after all, we could also default to "onmouseover", for instance). It'd also be nice if we could still call the ajax listener. That'll require a bit more code. Here's the full Java component, with the additional ajax code. I'll go over it at the end:

1 @FacesComponent(value = "mycustom") 2 public class MyCustom extends UIComponentBase implements ClientBehaviorHolder { 3 4 @Override 5 public String getFamily() { 6 return "custom"; 7 } 8 9 @Override 10 public void encodeEnd(FacesContext context) throws IOException { 11 12 ClientBehaviorContext behaviorContext = 13 ClientBehaviorContext.createClientBehaviorContext( context, 14 this, "click", getClientId(context), null); 15 16 ResponseWriter responseWriter = context.getResponseWriter(); 17 responseWriter.startElement("div", null); 18 responseWriter.writeAttribute("id",getClientId(context),"id"); 19 responseWriter.writeAttribute("name", getClientId(context),"clientId"); 20 Map behaviors = getClientBehaviors(); 21 if (behaviors.containsKey("click") ) { 22 String click = behaviors.get("click").get(0).getScript(behaviorContext); 23 responseWriter.writeAttribute("onclick", click, null); 24 } 25 responseWriter.write("Click me!"); 26 responseWriter.endElement("div"); 27 } 28 29 30 @Override 31 public void decode(FacesContext context) { 32 Map behaviors = getClientBehaviors(); 33 if (behaviors.isEmpty()) { 34 return; 35 } 36 37 ExternalContext external = context.getExternalContext(); 38 Map params = external.getRequestParameterMap(); 39 String behaviorEvent = params.get("javax.faces.behavior.event"); 40 41 if (behaviorEvent != null) { 42 List behaviorsForEvent = behaviors.get(behaviorEvent); 43 44 if (behaviors.size() > 0) { 45 String behaviorSource = params.get("javax.faces.source"); 46 String clientId = getClientId(context); 47 if (behaviorSource != null && behaviorSource.equals(clientId)) { 48 for (ClientBehavior behavior: behaviorsForEvent) { 49 behavior.decode(context, this); 50 } 51 } 52 } 53 } 54 } 55 56 @Override 57 public Collection getEventNames() { 58 return Arrays.asList("click"); 59 } 60 61 @Override 62 public String getDefaultEventName() { 63 return "click"; 64 } 65 } At 65 lines, this is probably the longest code example I've ever posted, but most of this is either really easy, or stuff you've seen in the previous section. First, we define what Ajax events we'll accept ("click") and what one is the default ("click" again), on lines 56-64. These are part of the ClientBehaviorHolder interface (line 2). We also had to add a little code to the encodeEnd method, so that we correctly output the DOM event script as part of the div (lines 12-14, 20-24). And lastly, we needed to add a decode method, since our component is no longer output only - the ajax event handling code is always part of the decode process (lines 31-50). This is the part where we actually make sure that that listener is being called.

Did I mention that you can do pretty much the same thing in a composite component? That'll be the subject of a future blog.

Well, I warned you this was a little more complex - hopefully it's all fairly clear. If it isn't - ask in the comments.





More...