Вы находитесь на странице: 1из 9

Creating an AJAX enabled JSF (1.1 and 1.2 version) components.

Article created on the groundwork of Dr. Winston Prakash's article Creating AJAX enabled JSF component part 1. When I red Dr. Winston Prakash's article Creating AJAX enabled JSF component part 1 I had been verry impressed. In mine opinion it was the best manual reffered to creating JSF components I whenever saw. There was all I wanted. Dr. Prakash's article introduced me in exciting world of creating JSF components:). Some time ago however I was aware of my working knowledge must be put in a good order. Then I decide to create new, but based on Dr. Prakash's article, manual. I hope, it will be usefull for all of You. As well I hope, an author of the article I based on, perceives my initiative with kindness and interest. Mariusz Choda

Following are the steps for part 1 of creating an AJAX enabled custom JSF component. Initial Steps to create the skeleton project

Start Netbeans IDE 5.5. Create a new Web Application and call it saimage using File -> New Project... menu item. In the Name and Location panel of the dialog, select any J2EE compliant Application Server such as tomcat 5.5 or glassfish. Add the required jsf jars 1.1 to tomtat common/lib directory. In glassfish jar for support of jsf 1.2 comes bundled with the server In the Frameworks panel select Java Server faces. This adds necessary JSF support to the project. First create a Tag Library Descriptor file. Click on the New File button in the toolbar or New -> File/Folder from the menu item of the Project Folder. In the Choose File Type panel of the dialog select Web -> Tag Library Descriptor. In the Name and Location panel give the TLD name as Image Prefix as sa The URI as http://pl.sapere.sa Next create the Tag Handler Class Click on the New File button in the toolbar and in the dialog choose the file type Web -> Tag Handler. In the Name and Location panel provide the following Class Name - saImageTag The w eb container uses the TLD to v alidate the tag. Package - pl.sapere.sa.image Eg. the set of tags that are part of the HTML render kit are defined in the html_basic.tld. In the TLD Information panel provide A t a minimum, each tag must hav e a name and a tag Tag Name - saImageTag class attribute. TLD File - WEB-INF/tlds/saImage.tld In the same panel add the following attributes (this action adds the attributes to saImageTag and saImage TLD): style - type String and mark this as required, if you plan to import this component in to Netbean VWP width - type String height - type String url - type String and mark as required. The URL(s) for the image slide is specified via this attribute. immediate type String alt type String styleClass type String Double click on saImageTag.java from the project and open it in the editor, and add manulally two additional attributes: actionListener: JSF 1.1 JSF 1.2

private String actionListener;

private javax.el.MethodExpression actionListener = null;

public void setActionListener(String actionListener) { public void this.actionListener = actionListener; } setActionListener(javax.el.MethodExpression actionListener) { this.actionListener = actionListener; }

action:
JSF 1.1 JSF 1.2

private String action

public void setAction(String action) { this.action = action; }

private javax.el.MethodExpression action= null; public void setAction(javax.el.MethodExpression action) { this.action = action; }

Next modify two other attributes (only for JSF 1.2, for JSF 1.1 do not modify): style change type of style from String to javax.el.ValueExpression: private javax.el.ValueExpression style = null; public void setStyle(javax.el.ValueExpression style) { this.style = style; } immediate: private javax.el.ValueExpression immediate = null; public void setImmediate(javax.el.ValueExpression immediate) { this.immediate = immediate; } Open and edit saImage.tld and add: actionListener: JSF 1.1 <attribute> <name>actionListener</name> <rtexprvalue>true</rtexprvalue> <required>false</required> <type>String</type> </attribute> JSF 1.2 <attribute> <name>actionListener</name> <required>false</required> <deferred-method> <method-signature>void actionListener(javax.faces.event.ActionEvent) </method-signature> <deferred-method> </attribute>

action:
JSF 1.1 <attribute> <name>action</name> <rtexprvalue>true</rtexprvalue> <required>false</required> <type>String</type> </attribute> JSF 1.2 <attribute> <name>action</name> <required>false</required> <deferred-method> <method-signature>java.lang.String action() </method-signature> <deferred-method> </attribute>

Next modify definitions of style and immediate attributes: style: JSF 1.1 JSF 1.2

do not modify

<attribute> <name>style</name> <required>false</required> <deferred-value> <type> java.lang.String </type> </deferred-value> </attribute>

immediate: JSF 1.1 JSF 1.2

do not modify

<attribute> <name>immediate</name> <required>false</required> <deferred-value> <type> boolean </type> </deferred-value> </attribute>

Next step is to create the Component class Click on the New File button again in the toolbar and in the dialog choose the file type Java Classes -> Java Class. In the Name and Location panel provide the following Class Name - saImageComponent Package pl.sapere.sa.image Add the following properties to the ImageComponent.java using the menu item "Add -> Property" in the Bean Patterns Node of the class in the project. style - type String

width - type String height - type String url - type String immediate type boolean alt type String styleClass type String action type String actionListener type String Edit saImageComponent.java file and modify action and actionListener properties::

JSF 1.1

JSF 1.2

private MethodBinding action; public MethodBinding getAction() { return this.action; } public void setAction(MethodBinding mb) { this.action = mb; } private MethodBinding actionListener; public MethodBinding getActionListener() { return this.action; } public void setActionListener(MethodBinding mb) { this.action = mb; }

private MethodExpression action; public MethodExpression getAction() { return this.action; } public void setAction(MethodExpression mb) { this.action = mb; } private MethodExpression actionListener; public MethodExpression getActionListener() { return this.action; } public void setActionListener(MethodExpression mb) { this.action = mb; }

Next step is to create the Renderer Class. Click on the New File button again in the toolbar and in the dialog choose the file type Java Classes -> Java Class. In the Name and Location panel provide the following Class Name - saImageRenderer Package pl.sapere.sa.image

Component, Renderer and Tag Combinations In our example we create a custom renderer, component and tag. In many cases a programmer can reuse some piece of existing jsf code. The table sumarizes what you must or can associate with a custom component, custom renderer or custom tag.

Custom Item Custom component Custom renderer Custom JSF tag

Must Have Custom tag

Can Have Custom renderer or standard renderer Custom tag Custom component or standard component Some server-side object, Custom component or like a component, a standard component custom renderer or associated with a custom validator custom renderer

When y ou create a custom component, y ou w ill usually create a custom renderer to go w ith it, and a custom tag to associate the component w ith the renderer and to reference the component from the page. You might, how ev er, use a custom renderer w ith a standard component (for example to render new jav a script code w ith the custom renderer. You might, also use a custom tag w ithout a custom renderer.

So far we have not written any code. We have asked the IDE to generate the necessary skeleton code for us. Now, let us modify these code to create the real component.

Step 1: Modify the Tag Handler Class (saImageTag.java) In JSF applications, the tag handler class associated with a component drives the render response phase of the JSF lifecycle:

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 attributer to the values given in the page Finally, it returns the type of the renderer (if there is one) to the JSF implementation so that the component's encoding can be performed when the tag is processed

Open and edit saImageTag class file. You would notice, IDE has generated the class extending SimpleTagSupport. public class ImageSlideTag extends SimpleTagSupport{ But we want the class to extend UIComponentELTag (JSF 1.2) or UIComponentTag (JSF 1.1). So edit the code and make the class to extend UIComponentTag (or UIComponentElTag) instead of SimpleTagSupport. public class ImageSlideTag extends UIComponentTag{
saImageTag extends UIC omponentTag, w hich supports jsp.tagex.Tag functionality as w ell as JSF -specific funcionality . UIC omponentTag is the base class for all JSF tags that correspond to a component. Tags that need to process thier tag bodies should instead subclass UIC omponentBody Tag.

Note: IDE also generated the method doTag(), which is needed by SimpleTagSupport. This is no longer needed as this is already implemented in the base UIComponentTag (UIComponentELTag) class. So remove this method and its body completely. Remove the red squiggly error line by click on the light bulb and ask IDE to generate import javax.faces.webapp.UIComponentTag; (import javax.faces.webapp.UIComponentELTag; for JSF 1.2) Click again on the light bulb and ask the IDE to generate all the abstract methods. This action adds the methods getComponentType() & getRendererType(). Modify these two methods as follows. public String getComponentType() { return "pl.sapere.sa.image.ImageComponent"; } public String getRendererType() { return "pl.sapere.sa.image.ImageRenderer"; } The above is necessary to tell the JSFRuntime about the component type and component renderer type. Later we will add these information to the faces-config file. Next let us override the setProperties() method of UIComponentELTag in our Tag Handler class. Add the following to saImageTag.java protected void setProperties(UIComponent component) { super.setProperties(component); saImageComponent img = ((saImageComponent)component); if (url != null) { img.setUrl(url); } if (width != null) { img.setWidth(width); }
The second thing saImageTag does is to set component's attribute v alues to those supplied as tag attributes in the page. The handler gets the attribute v alues from the page v ia Jav aBeans properties that correspond to the attributes, eg: priv ate jav a.lang.String sty le; public v oid setSty le(jav a.lang.String v alue) { this.sty le = v alue; } To pass the v alue of the tag attributes to saImageC omponent, the handler implements the setProperties method.

A s explained abov e, the first thing saImageTag does is to retriev e the ty pe of the component. The v alue returned must match the v alue for the component w ith the <component-ty pe> element of the app's configuration resource file faces-config.xml. F inally the tag handler prov ides a renderer ty pe, if there is one associated w ith the component. If component does not hav e a renderer, getRendererTy pe should return null.

if (height != null) { In addition, an attribute must be of ty pe String. img.setHeight(height); F or each attribute that accepts a JSF EL expression, the setProperties method must get either a MethodBinding or V alue Binding for it from the A pplication instance. A V alueBinding object is used to ev aluate v alue-binding } expressions that refer to backing bean properties. A MethodBinding object is used to ev aluate method-binding ... expressions that refer to backing bean methods. //JSF 1.1 code sample: ... if (style != null) { if (isValueReference(style)) { ValueBinding vb = getFacesContext().getApplication().createValueBinding(style); img.setValueBinding("style", vb); } A s w e prev ious set, the ty pe of sty le is: jav ax.el.V alueExpression (it is a new ty pe introduced in else JSF v . 1.2 implementation) img.setStyle(style); } //JSF 1.2 code sample: if (style != null) { if (!style.isLiteralText()) { img.setValueExpression("style", style); } else { img.getAttributes().put( "style", style.getExpressionString()); } } When the JSP engine encounters the tag, it sets the values specified in the attributes to the corresponding setter method in the Tag Handler class and then it invokes the doTag() method. However, doTag() method of UIComponentELTag calls the setProperties() method thus hiding many of ugly details of tag handling, but giving the sub class ( saImageTag)an opportunity to set the attribute values as component properties. These component properties will be used by the component renderer. Note: What can we do, when we have an UICommand component and an action or an actionListener attribute?

Note: If y ou do make y our tag attributes accept v alue-binding expressions then the component propert must also be enabled for v alue-bindings expressions.

JSF 1.1 //declarations & setters private String actionListener = null;

JSF 1.2

//declarations & setters private javax.el.MethodExpression action= null;

public void setActionListener(String actionListener) { this.actionListener = actionListener; } private String action = null; public void setAction(String action) { this.action = action; }

public void setAction(javax.el.MethodExpression action) { this.action = action; }

private javax.el.MethodExpression actionListener = null; public void setActionListener(javax.el.MethodExpression actionListener) { this.actionListener = actionListener; }

//inner the setProperties method JSF 1.1 version ... if(actionListener != null) { if(isValueReference(actionListener)) { Class args[] = {ActionEvent.class}; MethodBinding mb = FacesContext.getCurrentInstance(). getApplication(). createMethodBinding(actionListener, args); img.setActionListener(mb); } else { Object params[] = {actionListener}; throw new javax.faces.FacesException(); } } if (action != null) { if (isValueReference(action)) { MethodBinding vb = FacesContext. getCurrentInstance().getApplication(). createMethodBinding(action, null); img.setAction(vb); } else { img.setAction(new ConstantMethodBinding(action)); } } ... //inner the setProperties method JSF 1.2 version ... if (actionListener != null) { img.addActionListener( new MethodExpressionActionListener(actionListener)); } if (action != null) { img.setActionExpression(action); } ... Step 2: Modify the Component Class (saImageComponent.java) A component class defines the state and behavior of a UI component. The bahavior includedes the following:

The v alue of the actionListener attribute must be a method-binding expression that points to a method on a backing bean that takes an A ctionEv ent object as its argument. The setProperties method must create a MethodBinding for the attribute, passing in the signature that this method must hav e, and it sets the MethodBinding object as the v alue of the actionListener attribute.

The action attribute can take a literal String or a method-binding expression that points to a backing bean method that takes no parameters and returns a literal String. To handle the case of the literal string, the setProperties method creates a special constant method binding around the literal String in order to satisfy the requirement that the argument to the action attribute of the component be a MethodBinding instance. To handle the method-binding expression, setProperties creates the MethodBinding object as it does for the actionListener attribute. You must also import: import jav ax.faces.ev ent.A ctionEv ent;

Decoding (converting the request parameter to the component's local value) Encoding (converting the local value into the corresponding markup) Saving the state of the component. To the state information belongs: component's type, identifier and local value. Updating the bean value with the local value Processing validation on the local value Queueing events
The rendering tasks can be performed by a separate renderer. To delegate rendering to the renderer class y ou must do this explicity . F or more information see Step 3: ...

First, you must decide from a which class extends your custom component. All the classes representing the JSF components should extend directly from UIComponentBase or any of a classes derived form this base component. Whether yoy decide to have your component extend UIComponentBase or other class, you might also want your component to implement one or more of these interfaces:

ActionSource: indicates that the component can fire an ActionEvent

EditableValueHolder: extends ValueHolder and specifies additional features for editable components, such as validation and emitting value-change events NamingContainer: mandates that each component rooted at this component have an unique ID StateHolder: denotes, that a component has state that must be saved between requests ValueHolder: Indicates that the component maintains a local value as well as the option of accessing data in the model tier

We have created a bean class with set of properties. Let us modify this bean class to make it a JSF component class. To do this, we need to extend this class with one of pre-defined component type (Ex. UIInput, UIOutput). Let us extend this class with UIOutput. Edit the class and extend it with UIOutput. public class ImageSlideComponent extends UIOutput{ and add the line (or call FixImports): import javax.faces.component.UIOutput; We can tell the JSF runtime about the family to which this component belongs by adding the following to the saImageComponent.java public String getFamily() { return "pl.sapere.sa.image.ImageFamily"; }
If y ou delegate rendering to the separate renderer, the component needs to ov erride the getF amily method of UIC omponent to return the identifier of a component family . This property is used to refer to a component or set of components that can be rendered by a renderer or set of renderers. C omponent family v alue must match that defined in faces-config.xml. The custom component automatically implements behav ioral interfaces of the base component. If y our component extends: UIC ommand it implements A ctionSource and StateHolder, UIO utput it implements StateHolder and V alueHolder, UIInput it implements EditableV alueHolder, StateHolder and V alueHolder UIC omponenBase it implements StateHolder. If y ou w ant y our custom component to exhibit the bahav ior of one of these interfaces, it must either explicity implement the interface or extend a standard component class that implements the interface.

Our custom component delegates rendering to the separate renderer, so we shouldn't create in a component class any encoding or decoding methods. The custom component class implements also a component attributes and it's auto generated getters and setters: ... private String style; public String getStyle() { return this.style; } public void setStyle(String style) { this.style = style; }

... To get the value of a component attribute that accepts a value-binding expression pointing to a backing bean property, the component class must get the ValueBinding instance associated with the attribute. //JSF 1.1 public boolean isImmediate() { ValueBinding vb = getValueBinding(immediate); if( vb != null ) { Boolean value = (Boolean) vb.getValue (getFacesContext()); if(value == null){ return (this.immediate); } return (value.booleanValue()); } else { return (this.immediate); } }
Note: If a component extends UIC ommand, it already has this w ork partially done. UIC ommand class does the w ork of getting the V alueBinding instance associated w ith each of the attributes that it supports.

The properties corresponding to the component attribute that accepts a method-binding expression pointing to a backing bean method must accept and return a MethodBinding object. public MethodBinding getAction() { return (this.action); } public void setAction( MethodBinding action) { this.action = action; } Because component classes implement StateHolder, they must implement the saveState and restoreState methods to help JSF implementation save and restore the state of components across multiple requests. The saveState(FacesContext) method is called during the render response phase, during which the state of the response is saved for processing on subsequent requests: public Object saveState ( FacesContext context ) { Object values[] = new Object [4]; values [0] = super.saveState ( context ); values [1] = style; values [2] = styleClass; values [3] = Boolean.valueOf(immediate); return ( values ); } A component that implements StateHolder must also provide an implementation for restoreState ( FacesContext, Object ) method. This method is called during the restore view phase, during which the JSF implementation checks whether there is any state that was saved

during last render response phase and needs to be restored in preparation for the next postback: public void restoreState ( FacesContext context, Object state ) { Object values [] = ( Object [] ) state; super.restoreState ( context, values [0] ); style = (String) values [1]; styleClass = (String) values [2]; immediate = ((Boolean) values [3]).booleanValue(); }
When y ou implement these methods, be sure to specify in the deploy ment descriptor w here y ou w ant the state to be sav ed: either client or serv er. If state is sav ed on the client, the state of the entire v iew is rendered to a hidden field on the page. To set this behav ior y ou must set jav ax.faces.STA TE_SA V ING_METHO D context parameter to either client or serv er.

Step 3: Modify the Component renderer (saImageRenderer.java) In this step we will modify the renderer class we have created previously. Double click and ImageSlideRenderer.java from the project and open it in the editor, if it is not already opened. Edit the class and extend it with Renderer. public class saImageRenderer extends Renderer{ Click on the light bulb and ask IDE to generate import javax.faces.render.Renderer; Now it is the time to write the renderer. In renderer class, we write the actual HTML that would be send to the browser as part of rendering this component. The base class Renderer gives us couple overridable methods such as encodeBegin(), encodeChildren() and encodeEnd(). Since our component has no children, let us place all the rendering code in the overridden encodeBegin() method as follows.

public void encodeBegin(FacesContext context, UIComponent component) throws IOException { saImageComponent img = (saImageComponent)component; ResponseWriter writer = context.getResponseWriter(); writer.startElement("table", img); String id = (String)img.getClientId(context); writer.writeAttribute("id", id, null); // get image elements height and width String width = img.getWidth(); String height = img.getHeight(); // Add code to implement "style" attribute for image String style = img.getStyle(); style = (style!=null) ? style + ";" : ""; if (width != null){ style += "width:" + width + ";"; } if (height != null){ style += "height:" + height + ";"; } writer.writeAttribute("style", style, null); // Render the <tr> Element writer.startElement("tr", img); // Render the <td> Element for image writer.startElement("td", img); // Render the <table> Element writer.startElement("img", img); ServletContext servletContext = (ServletContext) context.getExternalContext().getContext(); String contextPath = servletContext.getContextPath(); writer.writeAttribute("src", contextPath + img.getUrl(), "url"); writer.writeAttribute("width", img.getWidth(), "width"); writer.writeAttribute("height", img.getHeight(), "height");

If y ou are creating a custom component, y ou need to ensure, among other things, that y our component class performs these operations: Decoding conv erting the incoming request parameters to the local v alue of the component Encoding conv erting the current v alue of the component into the corresponding markup that represents in in the response The JSF specification supports tw o programming models for handling encoding and decoding: Direct implementation the component class itself implements the decoding and encoding Delegated implementation the component class delegates the implementation of encoding and decoding to the separate renderer F or rendering a components w ith children y ou can use encodeBegin, encodeC hildren and encodeEnd methods.

When y ou need to queue ev ents or to retriev e the component's v alue from the request parameters to, for example, update a bean's v alues y ou must also implement the decode method: public v oid decode (F acesC ontext context, UIC omponent component) { if((context == null) || (component == null )) { throw new NullPointerException(); } saImageC omponent sa = (saImageC omponent) component; String key = sa.getId( ); String v alue = (String)context.getExternalC ontext(). getRequestParameterMap().get(key ); }

// Render the </table> Element writer.endElement("td"); // Render the </td> Element for image writer.endElement("td"); // Render the </tr> Element writer.endElement("tr"); // Render the </table> Element for image writer.endElement("table");

Step 4: Modify the faces-config file We are almost ready with the component. Now we need to tell the JSF run time about our component. This is done through one of the the faces-config file. Remember, when we created the web application, we selected JSF framework to be used with the project. This action has saved us some trouble. It has given hin to the IDE to created a faces-config file for us. Also, to add the necessary code to map the url pattern (/faces/*) to the FacesServlet in the web.xml.Also a convenient JSP called "welcomeJSF.jsp" is also created with necessary @taglib to use standard JSF components. Let us add the Image component to the faces-config file. Double click and open the faces-config.xml under the Configuration Files node in the project and the add the following to it. <component> <component-type> pl.sapere.sa.image.ImageComponent </component-type> <component-class> pl.sapere.sa.image.saImageComponent </component-class> </component> <render-kit> <renderer> <description> Renderer for the image component. </description> <component-family> pl.sapere.sa.image.ImageFamily </component-family> <renderer-type> pl.sapere.sa.image.ImageRenderer </renderer-type> <renderer-class> pl.sapere.sa.image.saImageRenderer </renderer-class> </renderer> </render-kit> If you notice, we have specified the same component-type and renderer-type we specified in the Tag Handler class (ImageSlideTag.java). Step 5: Testing the custom built component Congratulations!. We have built a custom JSF component from scratch. Now it is time to test our newly built component. First, let us add an image to the project using the following steps.

Create a folder called "resources" under Web Pages in the project. Right click Web Pages and select New -> Folder. Copy any image in to the newly created folder

We can add our component to the welcomeJSF.jsp and test it to display the image we added to the resources folder.

Add our tag library definition to this JSP. Double click and open the JSP and add the tag lib as follows

<%@taglib prefix="is" uri="http://pl.sapere.sa"%>

Add the component inside the <h:view> tag as follows

<f:view> <h1> <h:outputText value="JavaServer Faces" /> </h1> <sa:Image url="/resources/hibiscus.jpg" style="border: medium solid #000080;" width="200" height="150"/>

</f:view>

Execute the web application by clicking on the gree arrow button in the tool bar.

Bingo!. Our Image component will display the image we specified in the JSP page, once you click the JavaServer Faces Welcome Page in the first page d. It should look like the following

Вам также может понравиться