Spinning Your Code with XSLT and JSF in Cocoon, Page 2
First Rendering Method: Stylesheet Only
The simplest approach is to take the XML representation of our JSF UI tree and apply to it an XSL template (via a standard Cocoon "transformer" component designed for the purpose). We can use one stylesheet for each "page" in our finished application, selecting exactly the components we require from the XML representation and calling the XSL template we want to handle that component. By using the "extension" part of XSL, we can write a single stylesheet to handle every type of component and rendering what we wish—for example, we might want to be able to display a UISelectOne as a drop-down list box. We would write our stylesheet to format a UISelectOne's XML representation into an HTML "select" element once, and put it in our "master" stylesheet. This master stylesheet then is imported in each of our page-specific stylesheets, so nothing needs to be repeated.
The master stylesheet would contain a definition like this:
<xsl:template name="UISelectOne"> <xsl:param name="bgcolor" select="white"/> <xsl:param name="fontcolor" select="black"/> <td> <xsl:attribute name="bgcolor"> <xsl:value-of select="$bgcolor"/> </xsl:attribute> <font> <xsl:attribute name="color"> <xsl:value-of select="$fontcolor"/> </xsl:attribute> <select> <xsl:attribute name="name"> <xsl:value-of select="local-name()"/> </xsl:attribute> <xsl:for-each select="valid-values/valid-value"> <xsl:variable name="defval" select="../../default-value"/> <xsl:variable name="curval" select="@value"/> <option> <xsl:attribute name="value"> <xsl:value-of select="@value"/> </xsl:attribute> <xsl:if test="$defval=$curval"> <xsl:attribute name="selected"/> </xsl:if> <xsl:value-of select="@label"/> </option> </xsl:for-each> </select> </font> </td> </xsl:template>
A portion of our page-specific stylesheet might look something like the following:
<xsl:template match="/response"> <html> <body> <xsl:apply-templates select="*[@name='name']"/> <xsl:apply-templates select="*[@name='occupation']"/> <body> </html> </xsl:template> <xsl:template match="UISelectOne"> <xsl:call-template name="UISelectOne"> <xsl:with-param name="bgcolor" select="white"/> <xsl:with-param name="fontcolor" select="black"/> </xsl:call-template> </xsl:template>
In the example above, we assume that the XML of the response from JSF begins with a "response" element, and contains nested UIInput, UISelect, and other elements named for their respective components. In the body of the "/response" template, we are effectively selecting the specific rendering of each element, first the element called "name", then the one called "occupation". If "occupation" were actually a UISelectOne element, the "UISelectOne" template would then match, and call the named template from the master stylesheet. (In practice, we might use namespaces, e.g. the "namespace:element" notation, to keep names from potentially conflicting.)
Second Rendering Method: Template and Stylesheet
For page developers who find XSL daunting, however, this can be a problematic approach. Cocoon provides an easy way to combine multiple XML sources into a single XML stream within a pipeline (called "aggregating"). If we write a "template" file for each page, instead of an XSL stylesheet, we can make life easier for our page designer without sacrificing any of the power of XSL. The "template" can consist mostly of well-formed HTML (or XHTML, specifically), which is an XML dialect in any case. Where the dynamic components are to appear, we can use specific XML elements to denote the element we want—ideally, using a special "namespace" to ensure it is clear we're talking about a Faces component.
Our template might look something like this:
<html> <body> <form action="someAction"> <table border="1" align="center"> <tr> <td>Name:</td> <td><UIInput width="60" bgcolor="white" fontcolor="green"/></td> </tr> <tr> <td>Occupation:</td> <td><UISelectOne bgcolor="white" fontcolor="green"/></td> </tr> </table> </form> </body> </html>
In this example, the bulk of our template is just regular (but well-formed) HTML, with special elements indicating where the substitution of the Faces components should go.
Here's how it might work:
- The user makes a request for a page—say, for the sake of an example, a registration form.
- Cocoon matches the request to our pipeline, and executes the Action. The action interacts with JSF as we've described above, and produces an XML stream containing the response.
- The pipeline now merges the XML response with our template file, which indicates the order and layout of our registration form.
- Now, we pass this merged XML through a final "master" XSL stylesheet again, which combines the instructions in the template with the response data from the JSF response XML. This stylesheet can remain the same for every request, meaning the XSL only needs to be dealt with once.