I am writing here of components written in the same way as those shipped with JSF are, in Java. There’s plenty out there on how to do the much simpler process of writing so-called composite components strictly in HTML and XML. Composite components have their limitations, however, so they are not complete substitutes for doing things the “hard way.” (Which really isn’t all that hard once one figures it out.)
This is something that there is virtually no decent documentation on anywhere, so I figured I’d write it here.
By “decent documentation,” I mean any series of instructions that is both reasonably complete and correct. By “anywhere” I mean literally anywhere, so far as I have been able to tell. Even in supposedly up-to-date and comprehensive books (which for Java Server Facelets are very rare) end up being less than comprehensive and have significant inaccuracies; there is no way that following the instructions therein will result in successfully implementing even the simplest component. It’s the bane of open-source software: coding is fun, but documenting things isn’t so much fun. So lots of coding gets done, not much documentation, and one ends up with huge complex software systems and only the spottiest of documentation.
But I digress. Onward.
First, there are a minimum of four files you must modify or create in order to create a custom component:
*taglib.xml
taglib.xml
. It’s customary to use hyphens or dots to set off the
suffix, e.g. gunk.taglib.xml
or
gunk-taglib.xml
.faces-config.xml
web.xml
*.java
The web.xml
and faces-config.xml
files live in the
WEB-INF
directory. It is traditional (but not required) to put
*taglib.xml
there as well.
<span>
element.
(A sample *.java
file may be found here.)
This must be a subclass of javax.faces.component.UIComponent
;
typically this will be a subclass of javax.faces.component.UIInput
(your component will
render HTML that accepts input from the end user’s browser within a form) or
more commonly javax.faces.component.UIOutput
(your component does not accept any user
input).
Output-only tags are by far the most common, so that’s what I’m covering here for now. Not surprisingly, the core of defining a tag is telling JSF what HTML to render in its output when it encounters your tag in the input file. The class library documentation does not explicitly make it clear in any one place, but there are five key methods you need to be concerned with:
encodeBegin
encodeChildren
getRendersChildren
true
. If not, define it to return false
.
Note that this implies you should always define this to return true
if you’re defining an encodeChildren
method. Note also that a parent
class of UIOutput
defines a standard encodeChildren
method, so it is meaningful to have this method return true
even
if you are not supplying your own encodeChildren
method.encodeEnd
<prefix:tag attribute="value" />
),
then this method will be called by JSF immediately after encodeBegin
returns.Note that it is not always necessary to (re)define all five methods. For
example, if your tag is not going to ever have enclosed content, or separate
beginning or ending tags, then you need not implement
encodeChildren
or encodeEnd
; you can put all your
rendering into encodeBegin
and be done with it.
The easiest (and safest) way to generate HTML in the rendering methods is to
use the javax.faces.context.ResponseWriter
object returned by the
getResponseWriter()
method of the passed
javax.faces.context.FacesContext
object. (It is also possible to
use the stream returned by getResponseStream()
, but this is not so
safe or easy as using a ResponseWriter
, because the latter takes
pains to ensure that the generated HTML is valid.)
Note that you do not need to explicitly do anything to handle any attributes
for your tag. JSF will automatically collect these for you and make them
available in a Map<String,Object>
returned by the
getAttributes()
method, which is already defined for you by a
parent class of UIOutput
.
The bad news about attributes is that there doesn’t seem to be any graceful
way to make them mandatory or otherwise enforce restrictions about them. The
<attribute>
tag should allow one to do this in
the taglib file, but it doesn’t seem to be honored very well. At least,
it failed to trigger any errors when I attempted to use this feature to make an
attribute mandatory (and then deliberately omitted it in a Facelets file) in
Apache Myfaces.
Note that your tag will automatically support some attributes common to all
Facelets tags, such as the rendered
attribute, which controls if
it renders as output at all, because parent classes of UIOutput
already support such attributes.
(A sample *taglib.xml
file may be found here.)
This file defines one or more Facelets tags by describing the name space for
one or more tags
and the component type for each tag. (See next section for how the
component type is associated with a class.) Note that the arbitrary
prefix represented by an
asterisk in this name need not match the file name represented by the asterisk
in *.java
. Although the example file describes only a single tag,
it is possible for a taglib file to describe more than
one tag.
Although this file can reside in any web application directory, it is customary
to place taglib files in the WEB-INF
directory.
(A sample faces-config.xml
file may be found here.)
The <component>
entries of this file associate the
component types mentioned in the taglib file with Java classes, which must
appear in a jar file under WEB-INF/lib
or a class file under
WEB-INF/classes
.
(A sample web.xml
file may be found here.)
The facelets.LIBRARIES
context parameter in this file defines a
semicolon-delimited list of taglib files. If your taglib file(s) are not mentioned
here, JSF will be unaware of your tags and they will not render properly.
The name of this context parameter is not well-standardized and may be
different; facelets.LIBRARIES
appears to be the most common value
(at least, it’s what both Mojarra and Apache Myfaces use). If that does not
work, try javax.faces.FACELETS_LIBRARIES
.
There’s at least one more file in the picture, of course, and that’s the
Facelets file(s) which contain your tag. If you’ve done everything else in this
guide correctly, at this point you’ve managed to create a tag which JSF recognizes
just like any other Facelets tag. In other words, to use your tag, all you need to do
is include an xmlns
attribute in the <html>
tag to associate a name with the URL name space of your tag, and then use your tag,
prefixed with that name.
A sample which uses the tag I’ve defined may be found here. Since the tags in that will actually render in your browser (and moreover, if this blog is migrated to a Facelets-aware web server, the Facelets tag will render to HTML), here is a version as a plain-text file.
At long last (it's taken over a year since I first thought of sharing some of what can be heard on the shortwave bands via the Internet):
Monthly Index for 2011 |
Index of Years