Sealyu

--- 博客已迁移至: http://www.sealyu.com/blog

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  618 随笔 :: 87 文章 :: 225 评论 :: 0 Trackbacks

Creating Custom Components with JSF and Facelets

Creating custom components with JSF and Facelets is NOT the same as creating custom JSF components with JSP. If you search on the internet for custom JSF components you will get messed up, confused, and nothing is seemingly clear. Its actually relatively easy to create a custom component with Facelets relative to JSP. Allot less code is required and only a few configuration files need to be modified/added.

There are four main steps in creating a custom component with JSF and Facelets.

  1. Create a facelet-taglib for your custom tag named something like myTags.taglib.xml . Also make sure this file is copied over to the EAR file in the META-INF directory. This is usually done through your build.xml under the target to build your EAR.
    ..

    http://mystuff.com/mycomponent

    mycomponent

    com.mystuff.mycomponent

    com.mystuff.components.mycomponent

  2. Next let JSF and Facelets know of your new component by adding it to /WEB-INF/faces-config.xml.
    ...   

    com.mystuff.mycomponent

    com.mystuff.components.MyComponent

    ...

  3. Create the java code to render the component. To do this you need to remember a few things. 
    1. Extend from the UIComponentBase class or any class that is a child of it.
    2. @Override getFamily , which should return the component type ( the same name as in he facelet-taglib and faces-config ).
    3. @Override encodeBegin and/or encodeEnd for outputting the componenet’s markup. Its good practice to use the response writer’s methods to start elements, add attributes to them, and to end elements.
    4. Provide getters and setters for your attributes.
    5. Be Careful!!!
      If your component is used on a page with a form, and you throw exceptions for required values, then you must implement saveState and restoreState. If not the JSF lifecycle will try to recreate the component on the restoreView phase upon an invalid form entry. If not present the required exception is thrown since the value no longer exists.
  4.  

    public MyComponent(){

      super();

      setRendererType(null)

      }   



    @Override



    public String getFamily(){



      return COMPONENT_TYPE;



      }



    @Override

      public void encodeBegin(FacesContext context) throws IOException{



      super.encodeBegin(context);



      final ResponseWriter writer = context.getResponseWriter();



      writer.write(""n");


      writer.writeComment(" BEGIN MYCOMPONENT INCLUDE ");





      writer.write(""n");





      if(this.getRenderIncludes()){





      String jsURL = context.getExternalContext().getRequestContextPath() + JS_DIR + "/" + JS_FILE;



      writer.startElement("script", null);

      writer.writeAttribute("type", "text/javascript", null);

      writer.writeAttribute("src", jsURL, null);

      writer.endElement("script");

      writer.write(""n");

      String jsURL2 = context.getExternalContext().getRequestContextPath() + JS_DIR + "/" + JS_FILE_2;

      writer.startElement("script", null);

      writer.writeAttribute("type", "text/javascript", null);

      writer.writeAttribute("src", jsURL2, null);

      writer.endElement("script");

      writer.write(""n");

      }

      writer.startElement("script", null);

      writer.write(""n");

      writer.write("createMyComponentTag(");

      writer.write("'" + getPageId() + "', ");

      writer.write("'" + getTemplateId() + "', ");

      writer.write("null, ");

      writer.write("'" + getCategoryId() + "', ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null, ");

      writer.write("null);"n");

      writer.write(""n");

      writer.endElement("script");

      writer.write(""n");

      writer.writeComment(" END MYCOMPONENT INCLUDE ");

      }



    @Override

      public void encodeEnd(FacesContext context) throws IOException{

      super.encodeEnd(context);

      }



    public String getPageId(){

      if(null != this.pageId){

      return this.pageId;

      }

      ValueExpression ve = getValueExpression("pageId");

      if(ve == null){

      throw new javax.el.ELException("Value for mystuff mycomponent Tag: 'pageId' is required");

      }

      return (String) ve.getValue(getFacesContext().getELContext());

      }



    public void setPageId(String pageId){

      this.pageId = pageId;

      }

  5. Now using your custom component is as easy as adding the namespace and adding the tag to your Facelet. 

Other techniques for saving and restoring state, adding ajax functionality through phase listeners, and determining if you need to implement ActionSource, ValueHolder, or EditableValueHolder into your component can be searched on the internet. A good source of Facelets documentation is here https://facelets.dev.java.net/nonav/docs/dev/docbook.html .

Here is an example I added to show an easy custom component.

custom-pmc-ui1.zip

Its from the SEAM examples of the jboss-seam-2.0.2.SP1 release. Just add this to the examples folder of you SEAM directory.
Its path is “{SEAM_HOME}/examples/ui” . I’ve just added a package called “org.mcjury.components”, a class for the component “TableNameComponent.java”, a facelet called “tableName.xhtml”, an xml file called “META-INF/table-component.taglib.xml”, and lastly edited “faces.config.xml” to register the component.

The usage is followed by a couple of examples of my custom table component and a use of the entity-manager with a h:dataTable.

To deploy just run “ant” in the “examples/ui” folder. If you have seam set up right it should copy over the EAR to you jboss server deploy directory. The jboss home is set in your “build.properties” in the root of the SEAM directory. If its not there just add this
“jboss.home=/PATH/TO/JBOSS/jboss-4.2.2.GA”
to it and you should be all set. Than start up jboss with “run.sh”.



Writing Custom Components in JSF + Facelets PDF Print E-mail
Mar 20, 2007 at 03:27 PM

One of the misleading parts for me was that available articles treating "custom JSF component creation" around are targetting JSF components used within JSP pages. If you're using Facelets (a JSP independent view-handler for JSF - see http://facelets.dev.java.net) things are slightly different. At least it seems to be easier to create custom components with Facelets than doing it with JSP.

Hopefully to provide some help, I'll shortly describe the steps necessary for me to build my first
custom JSF component within a Facelets web-app:

1. Tell Facelets to load your custom taglibrary. Add something like this to your web.xml file:  

<context-param>
    <param-name>facelets.LIBRARIES</param-name>
    <param-value>/WEB-INF/facelets/my.taglib.xml</param-value>
</context-param>

The xml file above is treated as tag-library for the facelets framework similar as we know it from JSP (the tld files). You can add all your custom tags/components into this file. Basically it should look like this:

2. Create a facelets-taglib for your custom tags:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
    <namespace>http://org.zapto.hanzz/mytags</namespace>
    <tag>
        <tag-name>rdolink</tag-name>
        <component>
            <component-type>hanzz.RdoLinkComponent</component-type>
        </component>
    </tag>
    <tag>
        <tag-name>rdocoll</tag-name>
        <component>
            <component-type>hanzz.RdoCollComponent</component-type>
        </component>
    </tag>
</facelet-taglib>

3. Publish your custom components to JSF in faces-config.xml. Add something like this:

    <component>
        <component-type>hanzz.RdoLinkComponent</component-type>
        <component-class>org.zapto.hanzz.jsf.RdoLinkComponent</component-class>
    </component>
     <component>
        <component-type>hanzz.RdoCollComponent</component-type>
        <component-class>org.zapto.hanzz.jsf.RdoCollComponent</component-class>
    </component>
    
As you can see - the <component-type> element in faces-config.xml maps the facelet-tag's <component-type> element as visible above. This is the way how facelets can map a custom tag within a XML file to a JSF component and JSF can map the component type to a specific implementation - the Java Class.

4. Write the corresponding Java Class(es).
This is where it gets specific: Depending on the requirements of your custom component the class should be derived from a JSF base component. In my example I had to write a selectOneRadio that was able to render the radio-buttons label with a http-link to a descriptive text. Therefore my custom component was derived from UISelectOne:

public class RdoLinkComponent extends UISelectOne {
    /** Creates a new instance of RdoLinkComponent */
    public RdoLinkComponent() {
        super();
    }
    public void encodeBegin(FacesContext context) throws IOException {
        final ResponseWriter writer = context.getResponseWriter();
        final String clientId = getClientId(context);
        
        RdoCollComponent childColl = null;
        for (UIComponent comp : getChildren()) {
            if(comp instanceof RdoCollComponent) {
                childColl = (RdoCollComponent) comp;
            }
        }      
        if((childColl==null)||(childColl.getValue()==null)) {
            writer.writeText("Keine Produkte verfügbar.",null);
        } else {
            Collection<Product> prods = (Collection<Product>)childColl.getValue();
            for(Product prod:prods) {
                encodeRadio(writer,prod,clientId);  
            }
        }
    }
    
    private void encodeRadio(ResponseWriter writer, PlcProduct prod, String clientId) throws IOException {
        writer.startElement("input",null);
        writer.writeAttribute("type","radio",null);
        writer.writeAttribute("name", clientId, "clientId");
        writer.writeAttribute("value", prod.getKey(), "value");
        writer.endElement("input");
        writer.startElement("a",null);
        writer.writeAttribute("href",prod.getDescUrl(),null);
        writer.writeAttribute("target","_blank",null);
        writer.writeText(prod.getLabel(),null);        
        writer.endElement("a");
    }

    public void encodeEnd(FacesContext context) throws IOException {
    }

    public void decode(FacesContext context) {
        Map requestMap = context.getExternalContext().getRequestParameterMap();
        final String clientId = getClientId(context);

        String string_submit_val = ((String) requestMap.get(clientId));
        setSubmittedValue(string_submit_val);
        setValid(true);  
    }
    
    public void encodeChildren(FacesContext context) throws IOException {
    }
    protected void validateValue(FacesContext context, Object value) {
        setValid(true);
    }

The encodeXXX-methods are used to render the component into HTML, the decode method is used to read the components selected value of the submitted form. I won't explain that in more detail here - look at the methods in the example above, it should be straightforward to understand. For more information about encoding and decoding refer to the JSF docs.

You can see that I've used a child component to assign the radio-groups select items but the
rendering of the html- input tags is done right here in the parent class, so I created only a dummy implementation for the child RdoColl-Component:

public class RdoCollComponent extends UIOutput {
    /** Creates a new instance of RdoCollComponent */
    public RdoCollComponent() {
    }
    public void encodeBegin(FacesContext context) throws IOException {   
    }
    public void encodeEnd(FacesContext context) throws IOException {
    }
    public void encodeChildren(FacesContext context) throws IOException {
    }
    public void decode(FacesContext context) {
    }
}

The above RdoColl-Component is used to "hold" the collection of possible options (i.e. the SelectItems) for the RdoLink-Component above. Implementing all of the above empty methods ensures that the child component does not produce any HTML output. I'm not sure if this is the state-of-the-art way to do this, basically it works fine in this case. I could not use the standard f:selectItems here because more than only a label and a value attribute is needed -  the URL for the link to the description of the product.

5. Now we have written our custom components and publicized them to facelets/jsf, we simply can add it on the required .xhtml page like this:

- add the namespace as defined in your facelet taglib and a prefix:
 xmlns:hanzz="http://org.zapto.hanzz/mytags"
 
- use your tags:
 <hanzz:rdolink value="#{orderBean.selectedProduct}"   >
       <hanzz:rdocoll value="#{orderBean.availableProducts}"/>
 </hanzz:rdolink>

posted on 2009-03-20 10:47 seal 阅读(914) 评论(0)  编辑  收藏 所属分类: Seam

只有注册用户登录后才能发表评论。


网站导航: