Tuesday, 11 May 2010

Converting custom JSF 1 components to JSF 2

These are just some observations/tips I made when converting my JSF 1 components to JSF 2.

Simpler .taglib.xml

The taglib definition changes from mycomponent.tld to mycomponent.taglib.xml. The xml inside the new file is similar but with some changes (i.e. <name> becomes <tag-name>). The major simplification is that attributes no longer need to be defined. Just the tag-name and component-type.
<tag>
 <name>menuItem</name>
 <tag-class>net.devgrok.component.MenuItemTag</tag-class>
 <body-content>JSP</body-content>
 <attribute>
  <name>value</name>
  <required>false</required>
  <deferred-value>
   <type>java.lang.String</type>
  </deferred-value>
 </attribute>
</tag>
Becomes:
<tag>
 <tag-name>menuItem</tag-name>
 <component>
  <component-type>net.devgrok.MenuItem</component-type>
 </component>
</tag>
And the newly created xml file is referenced as a context-param in web.xml (it could just be located under WEB-INF):
<context-param>
   <description>list of facelet libraries</description>
   <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
   <param-value>/WEB-INF/classes/META-INF/custom.taglib.xml</param-value>
</context-param>

Component Class

The component class is annotated with: @FacesComponent(value = "value from mycomponent.taglib.xml"). Then for each property create a setter (I also create a getter) that sets the value as evaluated at the apply request values phase. I use an enum as the key to store attributes against.
@FacesComponent(value = "net.devgrok.MenuItem")
public class MenuItem extends UIComponentBase {
 private enum PropertyKeys {
  value;
  String toString;

  PropertyKeys(String toString)  {
   this.toString = toString;
  }

  PropertyKeys()  {
  }
 }
 
 public void setValue(Object value) {
  getStateHelper().put(PropertyKeys.value, value);
 }
 
 public Object getValue()
 {
  return getStateHelper().get(PropertyKeys.value);
 }

No need for tag handler

As facelets is fully integrated into JSF 2 and it is not driven by JSPs, the need to create a class for every component that extends com.sun.facelets.tag.TagHandler or javax.faces.webapp.UIComponentELTag has been removed. For cases where you need to perform special actions after the values have been populated, create a simple class extending javax.faces.view.facelets.ComponentHandler. There are some instructions here: JSF2 Facelet Tag Handlers.

Some problems

Had some trouble with tag attributes, which I worked around by using the above getter instead of using (String) this.getAttributes().get("name");