Sunday, November 02, 2008

"Simple" JSF 2.0 Component vs. Tapestry

I just saw an amusing example of JSF 2.0 on Jim Driscoll's blog. He's creating a simple output-only component to display some text in yellow, using an inline CSS style. To make things 'simpler' (my quotes) he's using JSF.

Here's his JSF page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ez="http://java.sun.com/jsf/composite/simpleout">
<h:head>
    <title>Yellow Text Example</title>
</h:head>
<h:body>
        <h1>Editable Text Example</h1>
        <h:form id="form1">
                <ez:out value="Test Value"/>
            <p><h:commandButton value="reload"/></p>
            <h:messages/>
        </h:form>
</h:body>
</html>

Not too bad ... the relevant part is <ez:out value="Test Value">, that's the reference to his Out component inside his simpleout library. JSF puts a lot of interpretation into the namespace URLs.

On the other hand, the implementation of this simple component seems a bit out of hand:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:composite="http://java.sun.com/jsf/composite">
<head>
<title>This will not be present in rendered output</title>
</head>
<body>

<composite:interface name="yellowOut"
                     displayName="Very Basic Output Component"
                     preferred="true"
                     expert="false"
                     shortDescription="A basic example">
    <composite:attribute name="value" required="false"/>
</composite:interface>

<composite:implementation>
    <h:outputText value="#{compositeComponent.attrs.value}" style="background-color: yellow"/>
</composite:implementation>
</body>
</html>

You can kind of vaguely see, inside all that clutter, what the component is doing. I'm personally troubled by name="yellowOut" ... is the component's name "out" or "yellowOut"? Also, compared to the Tapestry version coming up, you can see that a few nods to visual builder tools are included.

So, what does the Tapestry equivalent look like?

Well, our page template would look something like*:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
  <title>Yellow Text Example (Tapestry)</title>
</head>
<body>
  <h1>Yellow Text Example</h1>
  <t:form t:id="form1">
    <t:out value="Test Value"/>
   <p><input type="submit" value="reload"/></p>
  </t:form>
</body>
</html>

The Tapestry namespace identifies elements and attributes that are not truly HTML, but controlled by Tapestry. This includes built-in Tapestry components such as Form as well as user-defined components such as Out.

The Out component itself is in two parts: a Java class to define the parameters (and, in a real world example, provide other logic and properties) and a matching template to perform output**. First the class:

package org.example.myapp.components;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.annotations.*;

public class Out
{
  @Property
  @Parameter(defaultPrefix = BindingConstants.LITERAL)
  private String value;
}

The @Parameter annotation on the value field marks the field as a parameter. The defaultPrefix attribute is configured to indicate that, by default, the binding expression (the value attribute inside the page template) should be interpreted as a literal string (as opposed to the normal default, a property expression).

The @Property annotation directs Tapestry to provide a getter and setter method for the field, so that it can be accessed from the template.

<span style="background-color: yellow">${value}</span>

Again, this is a trivial example. We didn't even have to define the Tapestry namespace for this template. The ${value} is an expansion which outputs the value property of the Out component instance, which itself is the literal string ("Test Value") bound to the Out component's value parameter.

Let's add a little improvement: If the value is null or empty, don't even render the <span> tag. This is accomplished in one of two ways.

First, we can change the template slightly, to turn the <span> tag into an If component that renders a span tag:

<span t:type="if" t:test="value" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" style="background-color: yellow;">${value}</span>

The If component evaluates its test parameter and if true, renders its tag (the <span> in this case), its non-Tapestry attributes (style) and its body (${value}).

Alternately, we can leave the template alone and abort the component's render in the Java class:

  boolean beforeRenderTemplate()
  {
    return ! (value == null || value.equals(""));  
  }

This is a Tapestry render phase method, a kind of optional callback method that can control how the component is rendered. We return true to render normally, and false to skip the template rendering. Tapestry invokes this method at the right time based on a naming convention, though an annotation can be used if that is more to your taste.

This is a good example of how Tapestry really cuts to the chase relative to other frameworks. Both the usage of the component and its definition are simple, concise and readable. Further, Tapestry components can scale up nicely, adding sub-components in the template, and more properties and logic in the Java class. Finally, we can also see a hint of how Tapestry relieves you of a lot of low-level drudgery, such as using the @Property annotation rather than writing those methods by hand (Tapestry has a wide ranging set of such meta-programming tools built in).

* I'm not sure why the original version used a form, but I've dutifully recreated that here.

** For a component this simple I'd normally not bother with a template and do it all in code.

1 comment:

  1. From the 'name' attribute description in the JSF 2.0 Facelets Tag library docs:

    "The name of this composite component. Advisory only. The real name is taken from the filename. The value of this attribute will be set as the value for this property on the composite component bean descriptor."

    As you shown, Tapestry components are easy to read and understand while JSF 2.0 needs a little practice. But sometimes feels good to write simple components using only a xhtml file.

    ReplyDelete

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费