How To Create JSF Component

How To Create JSF Component

A JSF component is a pretty complicated beast. In the worst case, you have to:

  1. create a UIComponent class, consisting of a lot of attributes with special getter's and setters that interact with the StateHelper
  2. create a renderer-specific class that contains similar attributes for html-related parameters ( even worse, they require special code if you wish to use attributes access optimization in the Renderer ) and define JSF Behavior events for them
  3. create a Renderer class that generates html code using a spaghetti of startElement/writeAttribute/writeText/endElement calls
  4. describe these classes in the faces-config.xml
  5. create a Facelets taglib.xml to define component as VDL tag
  6. write a TagHandler class
  7. if your component fires FacesEvents for some listeners, you also need Listener interface and listener TagHandler with default ListenerWrapper

 

Similar work is required for the ClientBehavior, except for the renderer-specific implementation. Faces Converters and Validators are a bit simpler, because they don’t require separate renderers and cannot broadcast events.

 

RichFaces uses a special Component Development Kit ( CDK ) that takes care of most of these tasks and lets developers concentrate on component functionality.  In the best case, a developer has to merely create an abstract component class, containing only functional code, and a Renderer template that describes generated Html with Facelets-like syntax. Everything else is generated by the tool.

Define Java Package as Components Library

By default, all components in the same project are included in the same library. Developers can provide a package-level annotation to define the library for all components which belong to that package

package-info.java:

@TagLibrary(uri="http://richfaces.org/test",shortName="testLib")
package org.richfaces.cdk.test.component;
import org.richfaces.cdk.annotations.TagLibrary;
 

Abstract Component Class

Most JSF components contain only a few lines of functional code. With the CDK, the developer only has to create a java class with such functionality and let the boilerplate be generated.

Base Class

The base class can be abstract if it needs references to generated methods. That class should be annotated with a @JsfComponent annotation that describes the generation details:

@JsfComponent(type = "foo.Bar",
    family="foo.Bar",
    description=@Description(displayName="Bar Component",
    largeIcon="large.gif",
    smallIcon="spall.png"),
    generate="foo.component.UIBar",
    facets=@Facet(name="caption",
                 generate=true,
                 description=@Description("Caption Facet")),
    fires={@Event(FooEvent.class),
           @Event(value=BarEvent.class,listener=BarListener.class)},
   interfaces=Ext.class,         tag=@Tag(name="abbr",generate=true,handler="foo.facelets.barHandler"),
attributes={"core-props.xml",
            "events-props.xml",
            "i18n-props.xml"},
renderer=@JsfRenderer(type="foo.HtmlBarRenderer")),
)
public abstract class AbstractBarComponent extends UIComponentBase
 
For type-safe definition, optional annotations are defined as @JsfComponent attributes, so the java compiler can check their usage and allow for code competition in modern editors. All annotation attributes are optional and can be inferred by the CDK:
  • type() defines the JSF component type, used as a key when the framework creates the component instance. Its value can also be defined by a public constant COMPONENT_TYPE or inferred from the class name by Naming Conventions.
  • family() defines the component family, which is common for a group of similar components ( for example, UICommand, HtmlCommandButton and HtmlCommandLink all share “javax.faces.Command” family ).
  • description() defines an optional description of the component, used by JSF tools and the documentation generator. The full description comes from the class JavaDoc comment or can be defined by a value() attribute.
  • generate() defines the fully qualified name for concrete component class. There are two options for the default value of this attribute. If @JsfAnnotation is applied to the abstract class, the name of generated class will be inferred from Naming Conventions. Otherwise, if the annotated class is concrete, the CDK will not generate a concrete component class and instead use an annotated class as the component implementation.
  • facets()  defines the component facets. It’s an array of @Facet annotations. The Facet#generate() attribute tells the CDK to generate getter and setter for a facet.
  • fires() is array of @Event annotations which define the JSF events fired by the component. It defines a FacesEvent class for event, listener and source interface. For each event, CDK generates add/remove/get<Event>Listener methods defined by the source interface, creates an eventListener tag handler and creates a listener instance that can be bound to an EL-expression.
  • tag() is an array of @Tag annotations. Using multiply tags for a single component is necessary to define tags for different VDL ( View Description Language ), or to make aliases. A tag links a JSF component with its renderer. If no tag is defined for a component, but there is a Renderer associated with the component, a Facelets tag will be generated. The tag name, handler class and library will be inferred.
  • attributes() contains an array of strings, each contains the name of a faces-config.xml fragment with attribute definitions, that lets the CDK reuse attribute definitions in different components. CDK looks for these files in the project classpath META-INF/cdk/attributes folder, and provides a set of such files for most html elements and standard components.
  • renderer() associates the component with a Renderer implementation. There are two options to link the component with a renderer#: by the renderer type or by the template name. For type, CDK doesn’t check for the existence of the target renderer, because it would be defined in another module. For template name, CDK enforces both renderer and component to share the same renderer type and family.
  • interfaces() attribute contains an array of Java interface classes that should be implemented by the generated component, that provides another way to reuse attribute definitions in different components, similar to ‘attributes()’ option. That’s the preferred method for user attributes, because it allows for a type-safe check. The result is the same as including these interfaces in ‘implements’ keyword for component class, but it does not enforce the component class to be abstract, and can be used to define interfaces implemented by the renderer-specific components ( see late ).

 

Renderer Specific Components

In JSF, the single component can have more then one renderer, to allow different client-side representations of the same functionality. For example, base command UICommand component can be rendered as an html button ( <input> element ) or link ( <a> element ). Because these representations can have different attributes and ClientBehavior events, JSR best practices recommend having a renderer-specific component that contains bean attributes for specific a implementation. The CDK follows that structure and lets the developer define renderer-specific components in the base class using a @JsfComponent#components() attribute.

This attribute contains an array of @RendererSpecificComponents annotations which have the same set of attributes as @JsfComponent, except the family() that should be shared by all components. These annotations let one define third level of generated classes, specific to the particular renderer.

Define Attributes

JSF component attributes are not plain bean getters and setters, they can contain code to evaluate EL-expressions, and to persist a value in the View state. Also, some attributes can be associated with ClientBehavior events. CDK generates all the code to make attributes compatible with the JSF spec.

To define a component attribute, the developer can annotate a class field or getter method with @Attribute annotation. The method can be abstract, that allows it to use that annotation in the interfaces or abstract classes:

    @Attribute(
        aliases={@Alias("getAction")},
        defaultValue="null",
        description=@Description(),
        events={@EventName(value="click",defaultEvent=true)},
        hidden=false,
        literal=false,
        passThrough=false,
        readOnly=true,
        required=false,
        generate=true,
        suggestedValue="#{foo}",
        signature = @Signature(parameters = {String.class},
                    returnType = Boolean.class)) 
    public abstract MethodExpression getMethodExpression(); 

Same as for the @JsfComponent, all annotation attributes are optional.

  • aliases() lets one define different names for the single attribute. In the example above, CDK will generate public MethodExpression getAction(){ return getMethodExpression();}
  • defaultValue() should contain a valid Java expression evaluated to the default attribute value.
  • description() contains the attribute description for IDEs and the documentation, the same annotation used elsewhere in CDK. By default, CDK uses the method JavaDoc comment to generate the description.
  • events() contains an array of ClientBehavior event definitions. The value of the EventName annotation is the name itself, and the  defaultEvent defined marks the event as the component default.
  • hidden() attribute tells the CDK to remove the attribute from tag.
  • literal() disables EL- expressions for the attribute.
  • passThrough() defines an attribute to be rendered as an html attribute without conversion. Attributes with ClientBehavior events are always threated as passed through.
  • readOnly() disables generation for the setter method.
  • requires() enforces the tag handler to check that the developer provided an explicit value for that attribute.
  • geterate() enforces the CDK to generate getter and setter methods for the attribute, even if a concrete method already exists. If omitted, the CDK checks for an ‘abstract’ modifier for getter and setter methods and generates an implementation for abstract methods only.
  • suggestedValue() can be used in tools for code competition.
  • signature() only used for attributes with a MethodExpression type to define signature of the target method.

 

Define Facets

Component facets can be defined in the two ways: in the facets() attribute of component annotations ( described above ) or by the getter method @Facet annotation:

    @Facet(generate=true,
          description=@Description())
    public abstract UIComponent getHeader();

The ‘name()’ attribute is optional in this case, the CDK uses the bean attribute name instead. Same as for the attribute, it uses the JavaDoc comment to generate the description and generate the implementation for abstract methods.