Introduction
In order to understand what a render kit is we have to be aware of some
major notions that are very important in this context. For a better
understanding please check out the below picture and identify the notions
described here, and the relationships between them. The subject of this picture
is the OmniFaces DeferredScript
component, but don't worry, you don't have to understand that component. The
role and functionality of this component is not our goal. We use it because it
is a professional approach for writing custom components and sustains the topic
of this article by exposing best practices of accomplishing such tasks.
A JSF component is annotated with @FacesComponent and it is characterized by
three coordinates (we won't take here into account the declarative approach):
component-family -This is a piece of information that
groups more components under the same family/category (e.g. ScriptFamily - a
family of components that deals with scripts). Typically, a family of
components are logically related, but there is no written rule. Nevertheless, a
family name can be represented by the package name that holds the classes of
the related components (e.g. org.omnifaces.component.script).
It is a common practice that the classes of the components that are related to
be placed in the same package, so the package name can be considered a family.
But, again, there is no rule to sustain this practice. In order to expose into
public is family a component will override the getFamily() method. 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);
}
...
component-type - This is a piece of information that uniquely
identifies a component and 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. Typically a component type will be the fully qualified
named of the component class (e.g. org.omnifaces.component.script.DeferredScript). 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. Components of different types can be grouped in families.
renderer-type - This is a piece of information that
uniquely identifies a renderer (e.g. org.omnifaces.DeferredScript). A component will used its setRendererType()
method to point to the render-type
that should render this component. Components can call this method as setRendererType(null)
to point out that they will render themselves. But, by delegating the rendering
to a separate renderer, the component makes itself more versatile because
multiple renderers would be able to render it to different clients.
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.
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!
Overview
of Renderer
What is the main goal of a renderer
?
Is responsible to decode the values from the incoming request and to
encode the values to be displayed by transforming the component tree into the
HTML markup that will be displayed to the client machine. Shortly, to transform
a JSF component in markup (e.g. HTML, XML, etc).
When you commonly need a custom
renderer ?
When you need to render a custom component (brand new or extension of a
built-in one), because none of the built-ins do what you want to achieve.
When you want to alter the look/functionality of an built-in component.
What should I know before start
writing a custom renderer ?
Mainly you need to know that a renderer extends directly/indirectly the
Renderer class.
Starting with JSF 2.2, you can extend RendererWrapper, which is a simple
implementation of Renderer.
JSF 2.2 comes with many wrappers, which are simple implementations of what they
are wrapping, and they help developer to override only the necessary methods,
and to provide specialized behavior to an existing wrapped instance (e.g. RendererWrapper can be
used for providing specialized behavior to an existing Renderer instance). The wrapped
instance is available via the getWrapped()
method.
The main three methods of a Renderer are encodeBegin(), encodeChildren() and encodeEnd(). By default, JSF calls them in
this order, and the first one usually renders the beginning of the markup -
"open" tags (e.g. <head>,
<input>, <form>, etc),
the second one renders the children
(this is configurable via getRendersChildren()
flag), and the last one is ending the markup - "close" tags (e.g. </head>, </input>, </form>).
In order to link a component with a renderer, you should know how to
work with the UIComponent.setRenderType()
method and with the component-family,
component-type
and renderer-type
artifacts as level of annotations or with the <render-kit> and <renderer> tags in faces-config.xml.
How do I usually write a
Renderer skeleton ?
•when you write a brand new component (extending UIComponentBase), you
will extend the Renderer
class directly and override most of its methods. Usually in these cases you
will link the custom component with the renderer via annotations and setRendererType()
method.
More examples:
Create and render a brand new component (source
code).
When you write a custom component which extends a built-in component,
you usually extend the renderer of the built-in component also - directly (most
probably) or indirectly. Usually in these cases you will link the custom
component with the renderer via the setRenderType() method. Of course, you can also use the
renderer of the built-in component without any modifications (source
code).
When you just want to alter a built-in component at rendering level,
you usually extend the built-in renderer and you instruct JSF to use your
renderer instead of the default one via faces-config.xml, <renderer> tag. E.g. use a custom
Renderer for the
JSF UIOutput
component (source
code). In order to run this application you have to keep in mind that we
are extending a Mojarra renderer (com.sun.faces.renderkit.html_basic.TextRenderer), so you
need to:
- manually install the JSF 2.2 JAR so your IDE will find the TextRenderer.
- in pom.xml,
the entry for JSF 2.2 should have its scope set to provided, as it shouldn't be copied into the deployment.
Overview
of RenderKit
What is the main goal of a
RenderKit ?
While the Renderer
class converts the internal representation of UI components into markup (e.g.
HTML), RenderKit
represents a collection of Renderer
instances capable to render JSF UI component's instances for a specific client
(for example, a specific device). Each time JSF needs to render a UI component,
it will call the RenderKit.getRenderer()
method which is capable of returning an instance of the corresponding renderer
based on two arguments that uniquely identifies it: the component-family and
the renderer-type. Moreover, RenderKit
registers renderers via RenderKit.addRenderer()
method based on the component-family,
renderer-type
and Renderer instance.
When we write a correct renderer (respecting the JSF specification) JSF will
automatically find it and register it for us.
When you commonly need a custom
RenderKit ?
You may use a custom RenderKit
to instruct JSF to delegate renderers in a specific approach. Per example, a
custom RenderKit
can choose the right renderer depending on device (PC, tablet, iPhone, etc).
Or, you may have a custom Renderer
that extends the RendererWrapper,
and use a custom RenderKit
to pass an instance of an existing Renderer to the custom Renderer.
What should I know before start
writing a custom RenderKit ?
Mainly, you need to know that a custom RenderKit extends directly/indirectly the RenderKit class.
Starting with JSF 2.0, you can extend RenderKitWrapper, which is a simple
implementation of RenderKit.
Via RenderKitWrapper,
you can provide a specialized behavior to an existing RenderKit instance. The wrapped
instance is available via the getWrapped()
method.
The main two methods of a RenderKit are addRenderer() and getRenderer(). By overriding these methods,
you can take control over the Renderers
registration and delegation. Of course, there are many other useful methods
listed in documentation.
How do I usually write a
RenderKit skeleton ?
Usually, you will extend the RenderKitWrapper class, override the necessary methods,
and configure it in the faces-config.xml
via <render-kit>
tag.
So, as you can see in figure below, the RenderKit sits between components and
renderers and act as a conductor:
Now, 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 defined 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>
Ok, so now let's have several examples of custom RenderKits:
Log how JSF renderers are added/delegated by JSF (source
code).
Instruct JSF to render all components of a family via a common custom
renderer (we simply apply a common CSS style to all components from javax.faces.Input
family) (source
code).
Instruct JSF to render UIOutput
components via a custom renderer that was registered for other type of
components, but pass to it an instance of the original renderer (source
code).
Overview
of a RendererKitFactory
What is the main goal of a
RenderKitFactory ?
It manages (register/provide) instances of available RenderKits.
When you commonly need a custom
RenderKitFactory ?
You may use a custom RenderKitFactory
to instruct JSF to delegate RenderKits
in a specific approach. By default, JSF has a single RenderKit, identified by an ID, HTML_BASIC_RENDER_KIT.
But, if want to write another RenderKit
then you can programmatically choose between them using a custom RenderKitFactory.
What should I know before start
writing a custom RenderKitFactory ?
Mainly, you need to know that a render kit factory extends
directly/indirectly the RendererKitFactory
class.
The main two methods of a RenderKitFactory are addRenderKit() and getRenderKit() . By overriding these
methods, you can take control over the RenderKits registration and delegation. The render kits
IDs can be obtained via getRenderKitIds()
method. If this factory has been decorated, the implementation doing the
decorating may override the getWrapped()
method to provide access to the implementation being wrapped.
In order to link a component with a renderer, you should know how to
work with the UIComponent.setRenderType()
method and with the component-family,
component-type
and renderer-type
artifacts as level of annotations or with the <render-kit> and <renderer> tag in faces-config.xml.
How do I usually write a
RenderKitFactory skeleton ?
Usually, you will extend the RenderKitFactory class, override the necessary methods,
and configure it in the faces-config.xml
via <render-kit-factory>
tag.
Niciun comentariu :
Trimiteți un comentariu