My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

duminică, 30 noiembrie 2014

OmniFaces Custom Components Approach

 ('component-family', 'component-type' and 'renderer-type')

In OmniFaces custom components we can see many useful programming techniques. Per example, in this post, we will take a look on how the custom components are declared and grouped. This is especially useful for developers who want to see how to manage multiple components in large projects. Following the right techniques is important right from the start!

Starting with JSF 2.0, custom components can be created via @FacesComponent annotation, but the *taglib.xml file is still needed. In order to avoid this file, JSF 2.2 has added three more attributes to this annotation. Now, we can create a custom component without a *taglib.xml file by exploiting these attributes:

createTag: This can be set to true or false. When it is set to true, JSF will
generate the tag for us (to be more specific, JSF will create, at runtime, a
Facelet tag handler that extends ComponentHandler). This element can be
used only in JSF 2.2.

tagName: This allows us to indicate the tag name. When createTag is set to
true, JSF will use this name for the generated tag. This element can only be
used in JSF 2.2.

namespace: This allows us to indicate the tag namespace. When createTag
is set to true, JSF will use this namespace for the generated tag. When
namespace is not specified, JSF will use the http://xmlns.jcp.org/jsf/component namespace.
This element can be used only in JSF 2.2.

value: This element comes from JSF 2.0 and indicates the component-type.
The component-type can be used as the argument of the Application.createComponent(java.lang.String) method for creating instances of the UIComponent class. JSF uses the component-type for creating components.
As of JSF 2.2, if the value element is missing or is null, JSF will obtain it by calling the getSimpleName() method on the class to which @FacesComponent is attached and lowercasing the first character.

So, starting with JSF 2.2, the OmniFaces components (e.g. DeferredScript) should look like this:

@FacesComponent(value=DeferredScript.COMPONENT_TYPE, createTag=true, tagName="deferredScript", namespace="http://omnifaces.org/ui")
public class DeferredScript extends ScriptFamily {
   public static final String COMPONENT_TYPE =
                 "org.omnifaces.component.script.DeferredScript";

But, if you check out the OmniFaces 2.0 components, you will see that, basically, each component uses the @FacesComponent, like this (e.g. the DeferredScript component):

@FacesComponent(DeferredScript.COMPONENT_TYPE)
public class DeferredScript extends ScriptFamily {
   public static final String COMPONENT_TYPE =
                 "org.omnifaces.component.script.DeferredScript";

Note The @FacesComponent(DeferredScript.COMPONENT_TYPE)
 is just a shortcut for @FacesComponent(value=DeferredScript.COMPONENT_TYPE). If more attributes are used, then the shortcut is useless, and the  value is mandatory!

There is a common practice to define the component-type as a static final string directly in component class and to name it COMPONENT_TYPE. Some developers tend to place the component-type string directly in annotation, which somehow restricts the programmatic access to this information, since, by default, there is no public UIComponent.getComponentType() method to override - the below example is not recommended, but it is not a big issue if you have a single component (a didactical example):

@FacesComponent("org.omnifaces.component.script.DeferredScript")
@FacesComponent(value="org.omnifaces.component.script.DeferredScript")

 If you check JSF components (or simply the documentation), you will notice that all JSF components defines the COMPONENT_TYPE static field, except UIComponent and UIComponentBase, which are abstract (generic) components. Next to this field, we have the component-family field, named COMPONENT_FAMILY.  Per example, the UIInput component has the following COMPONENT_TYPE and COMPONENT_FAMILY:

/**
 * <p>The standard component type for this component.</p>
*/
public static final String COMPONENT_TYPE = "javax.faces.Input";

/**
 * <p>The standard component family for this component.</p>
*/
public static final String COMPONENT_FAMILY = "javax.faces.Input";

If you extend UIInput you will inherit its type and family, but if you extend UIComponentBase, then you need to explicitly provide the COMPONENT_TYPE and COMPONENT_FAMILY. You just saw above how OmniFaces provides the COMPONENT_TYPE for one of its components. Before we focus on COMPONENY_FAMILY, let's notice that, OmniFaces uses the JSF 2.2 @FacesComponent at minimum power. The @FacesComponent annotation and the  component-type are mandatory. If a custom component doesn't use @FacesComponent and doesn't define its component-type, then you will see an error like this:

Expression Error: Named Object: ... not found.

The alternative to @FacesComponent can be added in faces-config.xml (this declarative approach has higher priority than @FacesComponent) - in Mojarra, the faces-config.xml is named jsf-ri-runtime.xml and it is available in com.sun.faces folder (JSF doesn't uses @FacesComponent ):

<component>
 <component-class>component-class</component-class>
 <component-type>component-type</component-type>
</component>

Per example, in Mojarra, for UIViewParameter, we have:

<component xmlns:xi="http://www.w3.org/2001/XInclude">
 <component-class>javax.faces.component.UIViewParameter</component-class>
 <component-type>javax.faces.ViewParameter</component-type>
 ...
</component>

The @FacesComponent annotation registers the components with the JSF implementation, while the component-type is used to create the component by Application.createComponent() methods.
Since JSF 2.2, the component-type can be omitted in @FacesComponent, because JSF will determine it like this (ComponentConfigHandler class):

...
String value = ((FacesComponent) annotation).value();
if (null == value || 0 == value.length()) {
    value = target.getSimpleName();
    value = Character.toLowerCase(value.charAt(0)) + value.substring(1);
}
...

Per example, if the OmniFaces DeferredScript had been declared like this:

@FacesComponent
public class DeferredScript extends ScriptFamily {

then you could programmatically have:

FacesContext.getCurrentInstance().getApplication().createComponent("deferredScript");

But, in order to inform Facelets, in the *taglib.xml, you need this - when you do this, you instruct Facelets to create a component of the given component-type:

<tag>
  ... 
  <component>
   <component-type>deferredScript</component-type>
  </component>
  ...
</tag>

When you have many custom components, as OmniFaces contains, this is not recommended. If more than one component with this derived name is found, the behavior is undefined. Usually, the component-type is of type (org.omnifaces.component.output.GraphicImage, javax.faces.Input, javax.faces.ViewRoot, etc). Maybe, in the future, OmniFaces will provide an alternative to getSimpleName().

In order to avoid the *taglib.xml, you need to use the createTag, tagName and namespace attributes also. But, notice that these attributes are not used by OmniFaces. Well, there are multiple explanations for this. Per example, let's take the namespace attribute; since OmniFaces contains many custom components, it should add this attribute for each of them. But, if someday it decides to change that namespace  (http://omnifaces.org/ui) then it has to perform the modification in all components, which is not desirable. Further, let's think that most tags has attributes, and some of them may be required. We can't mark the required attributes in component source code. Required attributes are defined in *taglib.xml (e.g. Omnifaces use this style):

<tag>
  ... 
 <attribute>
  <description>
   <![CDATA[
     The "resource name" part of the resource identifier.
   ]]>
  </description>
  <name>name</name>
  <required>true</required>
  <type>java.lang.String</type>
 </attribute>
  ...
</tag>

JSF reference implementation, Mojarra is using*taglib.xml also. In Mojarra, the *taglib.xml files are located under com.sun.faces.metadata.taglib.

Is true that this approach will not cause JSF to perform a check at runtime, and is strongly linked to the editor type. NetBeans IDE will cause an error, while Eclipse doesn't. By the other hand, you can exploit the ComponentHandler.getRequiredAttribute() method, which will perform a check at runtime and will throw a TagException if the attribute was not found, but this means to declare a ComponentHandler for each component and to configure it in *taglib.xml via <handler-class> tag.
Moreover, without *taglib.xml, IDEs (e.g. NetBeans) cannot provide a solid documentation and auto-completion for these tags. So using createTag and tagName in @FacesComponent will not provides us the above advantages. In addition, as we said earlier, a custom component may need a custom component handler, which cannot be defined in @FacesComponent, and only in *taglib.xml via <handler-class> tag. So, if you are in case of creating a bulk of components, like OmniFaces, then you better rely on *taglib.xml, and not entirely on @FacesComponent. OmniFaces components are defined in omnifaces-ui.taglib.xml file (e.g. in OmniFaces 2.0 - https://github.com/omnifaces/omnifaces/blob/2.0/src/main/resources/META-INF/omnifaces-ui.taglib.xml). But, for testing purposes, or in house components used in different small applications, is not bad to try the @FacesComponent at full capacity and avoid using the *taglib.xml files.

Another important aspect is how to group components ? We know that each component is rendered after its component-family and renderer-type passes through the RenderKit.getRenderer() method:

public abstract Renderer getRenderer(java.lang.String family,
                                     java.lang.String rendererType)

This method match the correct renderer , like this:

private ConcurrentHashMap<String, HashMap<String, Renderer>> rendererFamilies =
  new ConcurrentHashMap<String, HashMap<String, Renderer>>();
...
HashMap<String,Renderer> renderers = rendererFamilies.get(family);
return ((renderers != null) ? renderers.get(rendererType) : null);

So, in order to obtain its renderer, each component must reveal its family (COMPONENT_FAMILY) and renderer-type to this method (with a simple custom RenderKit you can check out the JSF/OmniFaces components families and renderer types). Programmatically, a family, component-family, is obtained via UIComponent.getFamily(), and the renderer-type via UIComponent.getRendererType():

public abstract java.lang.String getFamily()
public abstract java.lang.String getRendererType()

Now, JSF search through available renderers that was added via RenderKit.addRenderer().  JSF has inspected faces-config.xml file for:

<render-kit>
    <renderer>
        <component-family>component-family</component-family>
        <renderer-type>renderer-type</renderer-type>
        <renderer-class>RendererClass</renderer-class>
    </renderer>
</render-kit>

 and all classes annotated with @FacesRenderer:

@FacesRenderer(componentFamily=ComponentClass.COMPONENT_FAMILY, rendererType= RendererClass.RENDERER_TYPE)
public class RendererClass extends Renderer {
public static final String RENDERER_TYPE = "renderer-type";

Optionally, Facelets can be also informed by the render type in *taglib.xml. When you do that, you instruct Facelets to create a component of the given component-type. The component class is annotated with @FacesComponent or has been definied in faces-config.xml. In addition Facelets will set to the given renderer type.

<tag>
  ... 
 <component>
   <component-type>component-type</component-type>
   <renderer-type>renderer-type</renderer-type>
 </component>
  ...
</tag>

Note There is no <component-family> tag! That is known from the getFamily() method.
Note Is important to know that you cannot override the default JSF renderers using the @FacesRenderer annotation. The default renderer is applied, except if you are using the declarative approach.
Note A Renderer is not selected based on the component-type and renderer-type! Is selected based on component-family and renderer-type, which allows a renderer to be used for multiple components in the same family. The component-type is used for creating components in view root!

Now, when you need to create multiple components, is a good practice to group their common characteristics under an abstract class . Per example, the OmniFaces components: DeferredScript, Highlight and OnloadScript share the abstract component, ScriptFamily:

package org.omnifaces.component.output;

import javax.faces.component.UIComponentBase;

public abstract class OutputFamily extends UIComponentBase {

/** The standard component family. */
public static final String COMPONENT_FAMILY = "org.omnifaces.component.output";

 @Override
 public String getFamily() {
  return COMPONENT_FAMILY;
 }

 @Override
 public boolean getRendersChildren() {
  return true;
 }
}

Next, each of DeferredScript, Highlight and OnloadScript components extends the ScriptFamily and inherits the same family and the fact that they will render their children. So, when you have multiple components that share common artifacts, is recommended to follow this technique, and instead of repeating code in each component, you may declare an abstract component as above. This will allow you to change common artifacts with just a snap of a finger!


In the below figure, you can see how the component-type, component-family and renderer-type are related in OmniFaces, DeferredScript component (I know that it seems complicated, but if you take a deep breath and focus a little, this figure can clear up many important aspects):


Sometimes, a component render itself (it doesn't have an explicit Renderer) and extends another existent component. An interesting case is represented by the OmniFaces, CommandScript component, which extends the UICommand component, but it belongs to the Script family, not to the javax.faces.Command family. Since it extends the UICommand, it cannot extends the ScriptFamily also, so it needs to override the UICommand.getFamily() method:

@Override
public String getFamily() {
 return ScriptFamily.COMPONENT_FAMILY;
}

Moreover, the CommandScript doesn't have an external Renderer, so it has to override the setRendererType() as below:

setRendererType(null);

The techniques revealed here are simple, but very useful when you think to start to a large project. Is nice to see that OmniFaces provides us the guarantee that we can use these techniques in our projects, without having any doubts!

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors