Skip to main content

JSF 2, Custom Java Components, and Ajax Behaviors

Posted by driscoll on October 9, 2009 at 4:30 PM PDT

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: <h:outputText id="out" value="#{custom.hello}"/>
   2 <br/>
   3 Echo count: <h:outputText id="count" value="#{custom.count}"/>
   4 <br/>
   5 <h:inputText id="in" value="#{custom.hello}" autocomplete="off">
   6     <f:ajax event="keyup" render="out count eventcount" listener="#{custom.update}"/>
   7 </h:inputText>
   8 <br/>
   9 Event count: <h:outputText id="eventcount" value="#{custom.eventCount}"/>

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 <html xmlns="http://www.w3.org/1999/xhtml"
   2       xmlns:ui="http://java.sun.com/jsf/facelets"
   3       xmlns:h="http://java.sun.com/jsf/html"
   4       xmlns:f="http://java.sun.com/jsf/core"
   5       xmlns:cu="http://javaserverfaces.dev.java.net/demo/custom-taglib">

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:

<cu:custom id="customId">

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

   1 <context-param>
   2    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
   3    <param-value>/WEB-INF/custom-taglib.xml</param-value>
   4 </context-param>

This points to our next config file, which is the filename on line 3. Here's its contents, in full:

   1 <facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee"
   2               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   3               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
   4               version="2.0">
   5   <namespace>http://javaserverfaces.dev.java.net/demo/custom-taglib</namespace>
   6   <tag>
   7     <tag-name>custom</tag-name>
   8       <component>
   9         <component-type>mycustom</component-type>
  10       </component>
  11   </tag>
  12 </facelet-taglib>

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 <cu:custom id="customId">
   2     <f:ajax render="eventcount" listener="#{custom.updateEventCount}"/>
   3 </cu:custom>

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<String,List<ClientBehavior>> 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<String, List<ClientBehavior>> behaviors = getClientBehaviors();
  33         if (behaviors.isEmpty()) {
  34             return;
  35         }
  36
  37         ExternalContext external = context.getExternalContext();
  38         Map<String, String> params = external.getRequestParameterMap();
  39         String behaviorEvent = params.get("javax.faces.behavior.event");
  40
  41         if (behaviorEvent != null) {
  42             List<ClientBehavior> 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<String> 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.

 

Related Topics >>

Comments

JSF 2, Custom Java

Hello Jim,
I am developing a workflow that download and upload files from the FileSystem to an Oracle database. Based on your blog and the and BalusC's blog at
http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servl...
I am developing a needed custom tag HtmlInputFile. Here it is,
<code>
import javax.faces.component.FacesComponent;
import javax.faces.component.html.HtmlInputText;

/**
* Faces component for <code>input type="file"</code> field.
*
* @author BalusC
* @link http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servl...
*/
@FacesComponent(value = "HtmlInputFile")
public class HtmlInputFile extends HtmlInputText {

// Getters ------------------------------------------------------------------------------------

@Override
public String getRendererType() {
return "javax.faces.File";
}
}

</code>
This class extends HtmlInputText so the idea is that it heritage all the properties and attributes.
Now the tag library,
<code>
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
version="2.0">
<namespace>http://balusc.net/jsf/html</namespace>
<tag>
<tag-name>inputFile</tag-name>
<component>
<component-type>HtmlInputFile</component-type>
</component>
</tag>
</facelet-taglib>
</code>
and the web.xml,
<code>

<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/balusc.taglib.xml</param-value>
</context-param>
</code>

Finally the xhtml,
<hh:inputFile id="file" value="#{tomaRequisitosManagedBean.file}" />
Well, the xhtml file does not recognize the atributte 'value' and I do not know why.
Any idea about it?
Thanks in advance,
Jose

Need to use web.xml?

Hi! I've noticed the trend in JavaEE 6 is away from having to modify web.xml, with things like web.xml fragments. Along the same lines, I think that custom tag libraries can be found by having the web server just look at the META-INF (?) folder of jar files in WEB-INF/lib. I was wondering if there is an equivalent mechanism to just add the custom-taglib.xml file to some well-known location?

Very nice - thanks for

Very nice - thanks for explaining this. It would be interesting to see how to proceed if we want to add additional functionality to the JS called when the event occurs and when the AJAX callback is executed. I guess we have to implement our own ClientBehavior?

Maybe, it depends.

If you're limiting yourself to a Java-based custom component, then yes, a custom ClientBehavior would be one way. But that requires another tag on the using page. If you instead just want that behavior no matter what the user says on the using page, check out the commandLink renderer for an example of a different way to do it, within the custom component itself.