Steps for Creating and Using a Custom Render KitBefore going into detail on how to create and use a custom render kit, let's summarize the tasks involved. The following list outlines the steps required to create a custom render kit and to use its renderers to display components on the target client.
Understanding the lifecycle DemoThe lifecycle demo uses Scalable Vector Graphics (SVG) and the XML User Interface Language (XUL) along with JavaServer Faces technology to render an animated diagram of the JavaServer Faces life cycle. SVG is an XML-based language that is used to describe two-dimensional graphics, such as lines, rectangles, text, and images and to allow animation of these graphic elements. XUL is an XML-based language for creating dynamic user interfaces. As JavaServer Faces technology does, XUL supports a rich set of UI components. The lifecycle demo doesn't require the use of XUL; it is there just to demonstrate how easy it is for a JavaServer Faces application to utilize more than one render kit. In fact, when using releases of JavaServer Faces technology prior to 1.2, you had to implement a custom ViewHandler class (see Creating a RenderKit Class for more information on this class) to handle multiple render kits, in addition to doing everything outlined in this document. The lifecycle demo application uses three render kits: the standard HTML render kit, a custom SVG render kit, and a custom XUL render kit.They each render their respective markups. The following diagram shows the flow of the demo between the pages of different markup.
The markup for the first page is produced with the standard HTML render kit, which means it contains HTML markup. From there, you navigate to the SVG page, which displays a diagram of the JavaServer Faces life cycle phases. Pressing any of the buttons on the lower left corner of the diagram produces an animation that shows how a request flows through each of the phases. The SVG markup for the SVG page is produced from an SVG render kit. Clicking on any of the life cycle phase boxes causes an HTTP post to the JavaServer Faces controller, which in turn causes the next view to be returned. The response that is returned is the markup and a view identifier for an XUL page. This page provides more detailed information about the selected phase. This demo also shows how to deal with the fact that SVG and XUL do not support the notion of an HTTP post mechanism as does HTML. Instead, the developer must use JavaScript to register an event handler so that when a button is pressed, an event is generated. The JavaScript onclick event handler collects the form input data, builds a post data string, and sends it to the server. From SVG there are a couple of different ways to post data to a server from JavaScript. This demo relies on browsers that have built-in SVG support, such as in Deer Park Alpha 2, which makes available theXMLHttpRequest object as the posting mechanism. The demo uses this object to post the request to the JavaServer Faces controller. It also uses this object to handle the response from the JavaServer Faces controller back to the client. The problem with using the XMLHttpRequest object is that the response it generates will not include the view identifier of the view to send with the response. This demo includes an implementation of a ResponsePhaseListener instance that will access the view identifier from the FacesContext instance and add it to the response. This way, the JavaScript callback handling the response knows what view to display next. The following figure illustrates how this process works. See Handling Submits Generated from a non-HTML Page for more details on implementing theResponsePhaseListener class.
We recommend that you download the demo, take a look at the code,
and
run it. You will need to register at java.net to download the example,
but registration is free of charge. To download the example, go to https://javaserverfaces.dev.java.net/
,
go to the section that lists the nightly bundles, and follow the
instructions for downloading and unpacking the samples bundle.
The lifecycle demo is packaged as the jsf-renderkits.war. This demo
runs on Sun's Java System Application Server PE
9.0, code-named glassfish. You can download glassfish from https://glassfish.dev.java.net/.
Please read the README file for information on software requirements
and how to deploy
and run the demo. If you would like to see the source code of the
demo, you can unpack the WAR file using the command jar -xvf jsf-renderkits.war The rest of this document describes how to create and use a render kit using the SVG render kit included in the lifecycle demo. Creating the Component Classes or Determining Which Standard Components to UseA component class defines the state and behavior of a UI component. Before creating any new component classes, you should determine if some of the standard components provided by JavaServer Faces technology already meet your needs and can be rendered to your target client. In the case of the lifecycle demo, the standardUIOutput and UICommand components are sufficient for use as labels and buttons, respectively. The lifecycle demo need only provide custom renderers for these components so that they can be rendered in an SVG-enabled browser. On the other hand, JavaServer Faces technology does not provide any components that represent the shapes that SVG can render. Therefore, the lifecycle demo needs custom components to represent the shapes that it uses. If you decide that you do need a custom component, then you must decide whether it should extend a standard component or directly extend UIComponentBase, the base class for all the standard components. This base class, along with the standard component classes, are located in the javax.faces.component package and their names begin with UI. If your custom component serves the same purpose as a standard component, you should extend that standard component rather than directly extend UIComponentBase. For example, the UIOutput component is intended for displaying something. Therefore, the Shape component class used by the lifecycle demo extends UIOutput rather than UIComponentBase. The dynamic lifecycle example defines three component
classes: Shape , Line,
and Rectangle. The Line and Rectangle
classes both extend Shape,
which makes sense because lines and rectangles are shapes.
The job of the Line and Rectangle component classes is to identify the component type and the component family for the purpose of registering the components with the application and delegating the rendering of the components to the appropriate renderers. To delegate rendering, a component class must override the getFamily method of UIComponent to return the identifier of a component family. A component family is used to refer to a component or set of components that can be rendered by a set of renderers. Here is the getFamily method fromLine.java: public String getFamily() {
The Line class sets
the
static variable COMPONENT_FAMILY
to"Line". This
identifier
must match that defined by the configuration of the Line
component of the application configuration resource file, as described
in Registering the Render Kit, Renderers, and
Components
in the Configuration File.
The Line class also sets a static variable called COMPONENT_TYPE to "Line". This value must match that returned by thegetComponentType method of the tag handler that implements the component tag representing the Line component on the page. The implementation of the tag handler is described in Creating Custom Tags and Tag Handlers. The Rectangle class also implements getFamily and sets theCOMPONENT_TYPE variable to match that defined in the RectangleTag tag handler class. The Shape class does not define a component type or component family; it is merely the base class for Rectangle and Line. After creating the component classes, you can create renderers for them. Creating the Renderer Classes to Render Components on the Target ClientJavaServer Faces technology offers two techniques for performing rendering: direct implementation and delegated implementation. By using direct implemenation, a component writer includes all rendering code in the component class. Conversely,delegated implementation involves the component class delegating the task of rendering the component to a separate renderer. 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. This is why the custom components of the lifecycle demo all delegate their rendering to separate renderers. LineRenderer renders the Line component, andRectangleRenderer renders the Rectangle component. The lifecycle demo also provides renderers for the standard components that it uses so that it can render them to the SVG client. ButtonRenderer renders the UICommand component as a button. FormRenderer renders the UIForm component as an HTML form. TextRenderer renders the UIOutput component as a label. This section uses LineRenderer and ButtonRenderer to explain the basic requirements for writing a renderer class for custom and standard components. At the very least, a renderer class must perform the encoding of a response. This is the process of generating the markup for the target client. A renderer class might also need to do some decoding, which involves taking the component's local value from the request and converting it to a type acceptable to the component class--essentially the reverse of encoding. A renderer is required to perform decoding only if it needs to retrieve a component's local value or if it needs to queue an event onto the component. Let's first talk about how to perform encoding. Performing EncodingDuring the render response phase of the JavaServer Faces life cycle, the implementation processes the encoding methods of all components in the view and their associated renderers. The encoding methods convert the current local value of the component into the corresponding markup that represents it in the response.The UIComponentBase class defines a set of methods for rendering markup: encodeBegin, encodeChildren, and encodeEnd. If the component has child components, you might need to use more than one of these methods to render the component. For example, if the tag for one component is nested within a tag for another component, you need to render the parent component's start tag with the encodeBegin method and render its end tag with the encodeEnd method. The nested component's renderer takes over rendering the nested component after the parent component's start tag is rendered and before its end tag it rendered. If the parent component must perform the rendering for child components, it does this in its encodeChildren method. The lifecycle demo components don't have any child
components.
Therefore, rendering these components is a bit simpler. The
renderer
class can perform the rendering with itsencodeBegin
method, encodeEnd
method,
or both methods. In fact,LineRenderer
uses both methods: encodeBegin
to render most of the start
tag and encodeEnd to
render the end
tag.
Here are the encodeBegin
and encodeEnd methods
of LineRenderer:
public void encodeBegin(FacesContext context, UIComponent component)Notice that encodeBegin renders the beginning line tag. The encodeEnd method renders the ending line tag. The encoding methods accept a UIComponent argument and a FacesContext argument. The FacesContext instance contains all the information associated with the current request. The UIComponent argument is the component that needs to be rendered. The methods render the markup to the ResponseWriter instance, which writes out the markup to the current response. This basically involves passing the HTML tag names and attribute names to the ResponseWriter instance as strings, retrieving the values of the component attributes, and passing these values to the ResponseWriter instance. The startElement method takes a String (the name of the tag) and the component to which the tag corresponds (in this case, line). Passing this information to the ResponseWriter instance helps design-time tools know which portions of the generated markup are related to which components. After calling startElement, the encodeBegin method calls writeIdAttributeIfNecessary (defined inBaseRenderer), which tries to get the ID of the component in order to write it out. After rendering the ID, the method calls writeAttribute to render all the tag's attributes. The writeAttribute method takes the name of the attribute, its value, and the name of the corresponding property or attribute of the containing component. The last parameter can be null, and it won't be rendered. As explained in Creating
the
Renderer Classes, your renderer might also need to supply adecode
method. The next section describes the decode
method included in ButtonRenderer.
Performing DecodingDuring the apply
request values phase, the JavaServer Faces implementation
processes
the decode methods of all components in the tree. The decode
method extracts a component's local value from incoming request
parameters
and converts the value to a type that is acceptable to the component
class.
The process of
decoding is more bound to the concept of HTTP than it is to a specific rendering technology. A renderer must implement the decode method only if it must retrieve the local value or if it needs to queue events. The decode method of ButtonRenderer does both of these things. It first retrieves the client ID of the button component that it rendered previously and checks if it matches the client ID for the button the user has just clicked. If the IDs match, the decode method calls the component's queueEvent method to queue on ActionEvent onto the component. Here is thedecode method from ButtonRenderer:
public void decode(FacesContext context, UIComponent component) {
if (context == null || component == null) {
throw new NullPointerException("'context' and/or 'component is null");
}
if (log.isTraceEnabled()) {
log.trace("Begin decoding component " + component.getId());
}
String clientId = component.getClientId(context);
Map requestParameterMap = context.getExternalContext()
.getRequestParameterMap();
String value = (String) requestParameterMap.get(clientId);
if (value == null) {
if (requestParameterMap.get(clientId + ".x") == null &&
requestParameterMap.get(clientId + ".y") == null) {
return;
}
}
ActionEvent actionEvent = new ActionEvent(component);
component.queueEvent(actionEvent);
if (log.isDebugEnabled()) {
log.debug("This command resulted in form submission " +
" ActionEvent queued " + actionEvent);
}
if (log.isTraceEnabled()) {
log.trace("End decoding component " + component.getId());
}
return;
}
Note the renderer classes extend BaseRenderer,
which in turn extends Renderer. The BaseRenderer
class
contains definitions of the Renderer class methods so that
you
don't have to include them in your renderer class.
Now that you have your set of renderers, it's time to add them to your render kit. Before you do that, you need to create a RenderKit class. Creating a RenderKit ClassBefore going into how to create a RenderKit class, it's worthwhile explaining what exactly a render kit does and what is its role in the application life cycle. This will help you better understand the pieces of the RenderKit implementation that you need to create. As you've guessed by now, a render kit defines a set of renderers that have the ability to render a set of components to one particular kind of client. For example, this section shows you how to create a render kit for an SVG client. Much more than just defining a set of renderers, a RenderKit instance also takes part in rendering the response, re-building the component tree structure, and saving and restoring component state. It does this in partnership with the default ViewHandler implementation, which allows applications to control what happens in the restore view and render response phases of the life cycle. In a nutshell, the life cycle implementation uses ViewHandler to get information and necessary objects from the render kit so that it can determine whether ViewHandler should create or restore a view, save or restore state, or render the response, and can instruct ViewHandler to do one of these things. The primary objects that participate with ViewHandler in building the view, rendering it, and restoring state are summarized in the following figure.
One of the objects that ViewHandler obtains from the render kit is the ResponseStateManager object, which knows the rendering technology used by the render kit and can perform rendering-specific state-management duties. It might not be immediately obvious what the relationship is between rendering and state-management. The two are actually related because state is saved on the client by default. In order to save the state on the client, it must be rendered using the rendering technology specified by the render kit. Another object the ViewHandler instance obtains from the render kit is the ResponseWriter object. It implements methods for writing out markup to a specific client. Let's take a look at how these objects are used during the life cycle of a JavaServer Faces page. During the restore view phase of the life cycle, ViewHandler retrieves the ResponseStateManager object in order to test if the request is a postback or an initial request. The ResponseStateManager object is needed in this case because it is the only one that knows what rendering technology is being used and is therefore the only one that can look at a request, which is rendering-technology specifiec. If the request is an initial request, the life cycle implementation jumps to the render response phase. During the render response phase, theViewHandler instance uses the ResponseStateManager object to save the state of the tree for the benefit of subsequent requests. Additionally, the renderView method of ViewHandler is called. This method callsencodeAll on the UIViewRoot instance, which represents the root of the component tree. The encodeAll method traverses the tree of components, calling each component's encoding methods. These methods use the component family and renderer type specified in the application's configuration file to find the renderer associated with the component. Once the renderer is found, it calls the methods of the ResponseWriter object to render the view. If a request is a postback, therestoreView method of ViewHandler is called. This method uses theResponseStateManager object to re-build the component tree and restore state. After the tree is built and state is restored, theViewHandler instance is not needed until the render response phase occurs again. Meanwhile, the render kit and renderers are needed to decode the request values during the apply request values phase. To do this, the FacesContext instance calls the processDecodes method on the UIView instance. This method traverses the component tree, calling processDecodes on all the components in the tree. The components' processDecodes methods call the associated decoding methods of the renderers. The renderers then call the methods of the ResponseWriter object that write out the markup to the client. Subsequently, the rest of the life cycle phases are executed, including render response, during which the same process occurs as did during the render response phase that was executed for the initial request. When using JavaServer Faces technology, version 1.2, you do not have to create a custom ViewHandler to handle multiple render kits in an application, as you did when using prior versions. You need only specify the render kit to be used for each view using the view tag on the corresponding page, as explained in Using the Render Kit in the Page. Now that you understand the role of the render kit and itsResponseStateManager and ResponseWriter objects in the life cycle, you're ready to implement a RenderKit class, which must perform the following tasks:
The first thing to do is to add some private, static
variables that define the content type and character encoding that thisRenderKit
class supports:
private static String SVG_CONTENT_TYPE = "image/svg+xml"; The RenderKit
class will use these variables to test against the current content type
and encoding to ensure that they match the ones that this render kit
supports.
The JavaServer Faces implementation invokes theaddRenderer method of RenderKit at application startup time as it processes the application configuration file, in which the render kit and set of renderers are configured. You'll see in Configuring the Render Kit, Components, and Renderers how to configure the render kit, renderers, and components included in your application. The addRenderer method populates a map with the renderer, as shown here: public void addRenderer(String family, String rendererType,
The getRenderer
method returns a renderer that can render the components of the
specified
component family and has the specified renderer type. The
component
family is defined in the component class. The renderer type is
defined
by the tag handler implementing the tag that renders the
component.
public Renderer getRenderer(String family, String rendererType) {
The JavaServer Faces life cycle implementation
invokes
the getResponseStateManager
method to retrieve the ResponseStateManager
instance associated with the render kit. Here is thegetResponseStateManager
method from SVGRenderKit:
The following method returns a ResponseStateManager instance: public synchronized ResponseStateManager getResponseStateManager() {
The createResponseWriter
method returns aResponseWriter
instance that provides the content type (in this caseimage/svg+xml).
The ViewHandler
instance
associated with the current view invokes this method during the render
response phase to obtain a ResponseWriter
object so that the current view can be written to this object and
subsequently
rendered to the client. The developer of the render kit is also
responsible
for creating the customResponseWriter
class.
The createResponseWriter method takes a Writer object, a String that contains a list of content types, and the character encoding, all of which are used to render the response. The method first loads the supported content types defined by the RenderKit class into an array. The method then checks if the String of content types passed to the method is null. If it is, the method looks for a content type in the context representing the response and in the request header. If the method finds a content type, it looks for a matching content type in the array of supported types. If no match is found, the content type is set to SVG. The method then returns a new SVGRenderKit instance with the specified writer, and an acceptable content type and character encoding. Creating a ResponseWriter ClassThe ResponseWriter
class defines methods for rendering to the target client. A ResponseWriter
object is used by the renderView
method of ViewHandler
to render the view during the render response phase of the life cycle.
The default ResponseWriter that comes with JavaServer Faces technology extends the Writer class from the Java 2 Standard Edition to add special methods for producing elements and attributes for markup languages, such as XML and HTML. It also defines the content type for the render kit that creates it. Because SVG is derived from XML, SVGResponseWriter pretty much re-uses the code from the default response writer class. The only real difference is that SVGResponseWriter defines a content type of SVG, as shown by this line of code from SVGResponseWriter: private String contentType = "image/svg+xml"; If you are creating a custom render kit and the
client
for which it defines renderers is XML-based then you will be able to
simply
copy over the SVGResponseWriter
code to your ResponseWriter
class.
Creating a ResponseStateManager ClassAs explained in Creating a RenderKit Class, the ResponseStateManager class defines methods for re-building the component tree structure and restoring state during the restore view phase and for saving state during the render response phase. If you are creating your own render kit, you also need to provide an implementation of ResponseStateManager that is aware of the rendering technology that your render kit uses. Creating a ResponseStateManager
class involves providing implementations for the following methods:
More specifically, the writeState method does the following:
Creating a SerializedView ClassRecall from the previous section that the writeState method of ResponseStateManager writes out the component state to the client. To do this, it requires a SerializedView object that encapsulates the state and structure of the component tree. Therefore, if you are creating a custom render kit, you must provide a SerializedView class. The following code is the entire SerializedView class included in the lifecycle demo: public class SerializedView extends Object implements Serializable {
public Object getStructure() {
public Object getState() {
When you create your own render kit and use it in an application, you
can
copy this class as it is to your application.
Registering the Render Kit, Renderers, and Components in the Configuration FileAfter you've created the render kit, renderers, and components, you can register them with the application using the application's configuration file. This file is usually called faces-config.xml, as it is in the lifecycle demo. The following piece of the lifecycle demo's configuration file
show's
how to register the SVG render kit, the Line
component, theLineRenderer
renderer, and the ButtonRenderer
renderer, which renders a UICommand
component to an SVG client. The rest of the configuration
information
is ommitted. Please refer to the lifecycle demo's faces-config.xml
file to find out how the other components and renderers are configured.
...The component element is fairly self-explanatory, as is the render-kit element. The elements that might not be so obvious are thecomponent-family and renderer-type sub-elements of the renderer element. The component-family element identifies a set of components that the specified renderer can render. The value of this element must match that returned by the component class'sgetFamily method. The value of the renderer-type element must match that returned by the getRendererType method of the tag handler class, which we'll talk about next. Creating Custom Tags and Tag HandlersIn JavaServer Faces applications, the tag handler class associated with a component drives the rendering of that component. The first thing that the tag handler does is to retrieve the type of the component associated with the tag. Next, it sets the component's attributes to the values given in the page. Finally, it returns the type of the renderer to the JavaServer Faces implementation so that the component's encoding can be performed when the tag is processed. This section uses LineTag.java to explain how to implement a tag handler. It also explains how to define the tag in a TLD. To implement a tag handler, you need to create a class that extends UIComponentELTag and add the following to it:
The LineTag
tag handler has several properties that are called by the life cycle
implementation
to set the component's attribute values to those supplied as tag
attributes
in the page. The following property is used to access the value
of
the Line
component's onClick
attribute:
// PROPERTY: onclick Notice that the property (and all of the properties
in this tag handler) accepts a ValueExpression
instance. This is because the attributes of Line
component only accept value expressions.
To pass the values of the tag attributes to Line component, the tag handler implements the setProperties method. The following lines set the value on the onClick property: if (onclick != null) {
The tag handler also needs to retrieve the types of
the component and renderer that the tag represents on the page.
It
does this with the getComponentType
and getRendererType
methods:
public String getRendererType() {
The types that these methods return must match those
under which the component and renderer are registered with the
configuration
file as shown in Registering the Render
Kit, Components, and Renderers in the Configuration File.
Finally, it's recommended that all tag handlers implement a release method, which releases resources allocated during the execution of the tag handler. Therelease method of LineTag is as follows: public void release() {
Now that the tag handler, component, and renderer
are
defined, the next step is to define the actual tag in a tag library
descriptor
(TLD). The web container uses the TLD to validate the tag.
The set of tags that are part of the standard HTML render kit are
defined
in the html_basic TLD.
The custom tags used to render SVG components for the lifecycle demo are defined in the svg.tld file. The following code snippet is part of the line tag's definition. The definition must have the following sub-elements:
Every attribute
element must have a name
element defining the name of the attribute and a required
element defining if the attribute is required for every instance of the
tag.
All attribute elements except the one defining the id attribute must also include a deferred-value element that specifies what type of value that the attribute accepts. <tag> Using the Render Kit and Associated Tags in the PageAfter performing all the steps outlined so far of this document, you can now create pages using the custom render kit. In order to use the render kit, your pages must include the following:
<%@ page contentType="image/svg+xml"%>
Handling Submits Generated from a non-HTML PageThe SVG language does not support any notion of a form or submitting a form. Therefore, the lifecycle demo handles submits from the SVG page using Ajax techniques, namely an XMLHttpRequest object, which allows performing a submit without refreshing an entire page. When doing form submissions in this way, the request does not contain the view ID for the page that must be accessed next. For this reason, the lifecycle demo requires a ResponsePhaseListener implementation to get the view ID after the invoke application phase and before the render response phase. When theResponsePhaseListener object is notified that the invoke application phase has occurred, it retrieves the view ID from the FacesContext instance and adds it to the URL to be used to render the next page. It then adds this URL to the response header so that the next view can be rendered in the subsequent render response phase. The following code sample shows the lifecycle demo'sResponsePhaseListener.afterPhase method, which the JavaServer Faces life cycle implementation calls after the invoke application phase. This method has the task of appending the URL of the new view to the response. public void afterPhase(PhaseEvent event) {
For more detail on AJAX, please see the AJAX
entries of the blueprints solution catalog.
ConclusionJavaServer Faces technology provides a rich, flexible component and rendering model that makes it easy to build custom render kits to render components to various kinds of clients. You've also seen how easy it is to configure and use the render kit in a web application. Finally, you've learned how to use the new Ajax techniques to handle form submits for rendering technologies that have no facility for form submits. You now have the means to create and use your own render kit using JavaServer Faces technology. ResourcesFor more information on this topic, visit the following links: JavaServer Faces Technology AJAX with the Java 2 Enterprise Edition The Glassfish Project | ||||
Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.
|
| ||||||||||||