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.
- 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.
- Next let JSF and Facelets know of your new component by adding it to /WEB-INF/faces-config.xml.
- Create the java code to render the component. To do this you need to remember a few things.
- Extend from the UIComponentBase class or any class that is a child of it.
- @Override getFamily , which should return the component type ( the same name as in he facelet-taglib and faces-config ).
- @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.
- Provide getters and setters for your attributes.
- 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.
-
- 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 |
|
|
|
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>
|