Tapestry for JSF Users

This is a brief guide for learning Tapestry, designed for those who already know JavaServer Faces (JSF).

Because both JSF and Tapestry are component oriented frameworks designed to serve mostly the same kinds of problems in similar ways, developers who already know JSF will find it very easy to learn Tapestry. In fact, Facelets, the default view technology in JSF 2.0, was created specifically to give JSF a Tapestry-like templating capability, so Facelets users should feel right at home.

Related Articles

Since almost all modern JSF applications use Facelets as their view technology, we assume the use of Facelets here when discussing JSF features.

JSF is a rich, mature web framework specification, and there are lots of smart people who use it productively. This guide isn't intended as a pro-versus-con comparison or as advocacy of any kind. Instead, it just attempts to make transitions between the two frameworks easier, regardless of the reason for doing so.

Side-by-side Comparison

JSF and Tapestry have a lot of superficial similarities, so the first steps in that transition are all about relating similar concepts, terms and components in your mind:

Concepts & Terminology

JSF

Tapestry

Java class associated with a page or component

"Backing Bean"

"Component Class"

Component attributes/parameters

"attributes"

"parameters"

Common Attributes/Parameters

JSF

Tapestry

HTML Attribute used for invisible instrumentation

jsfc="someComponentType"

t:type="someComponentType"

CSS "class" attribute name

styleClass

class

Alternating "zebra" striped rows

rowclasses="class1,class2"

class="${cycle:class1,class2}" using cycle binding prefix, or with CSS: .rowClass:nth-child(even) {background-color: #e8e8e8;}

Output and Messages

JSF

Tapestry

Escaped HTML from property

<h:outputText value="myBean.myValue"/>

${myValue}

Raw HTML from property

#{myBean.myValue}

<t:outputRaw value="myValue"/>

Error messages

<h:message> and <h:messages>

<t:error> and <t:errors> (for forms) or <t:alerts>

Image display

<h:graphicImage>

use standard <img> tag, but see Assets

Conditionals and Looping

JSF

Tapestry

Render-time loop

<ui:repeat>

<t:loop>

Compile-time loop

<c:forEach>

<t:loop>

Conditional

<c:if test="#{myBean.myValue}">

<t:if test="myValue">

Conditional

<ui:fragment rendered="#{myBean.someCondition}"/>...</ui:fragment>

<t:if test="someCondition">...</t:if>

Switch

<c:choose><c:when ... ></c:choose>

See Switching Cases

Server-side comment

<ui:remove>

<t:remove>

Links and Buttons

JSF

Tapestry

Navigational link

<h:link outcome="nextpage.xhtml"/>

<t:pagelink page="nextpage"/>

Event-triggering link, without form submission

not available

<t:actionLink> or <t:eventLink>

Form submission link

<h:commandLink>

<t:linkSubmit>

Form submission button

<h:commandButton>

<t:submit>

Link to Javascript file

<h:outputScript>

<script> or use @Import in component class

Link to CSS file

<h:outputStylesheet>

<style> or use @Import in component class

Grids, Tables and Trees

JSF

Tapestry

Tabular data in <table>

<h:datatable>

<t:grid>

Table used for layout

<h:panelGrid> with <h:panelGroup>

use standard <table> tag

Hierarchical tree

depends on component library

<t:tree>

Form Tags/Components

JSF

Tapestry

Form

<h:form>

<t:form>

Single-line text input field

<h:inputText>

<t:textField>

Password field

<h:inputSecret>

<t:passwordfield>

Select menu

<h:selectOneMenu>

<t:select>

Checkbox

<h:selectBooleanCheckbox>

<t:checkbox>

Checkbox list

<h:selectManyCheckbox>

<t:checklist>

Radio button list

<h:selectOneRadio>

<t:radioGroup> with <t:radio>

Multiple select menu

<h:selectManyListbox>

not available (but see Palette and Checklist)

Hidden field

<h:inputHidden>

<t:hidden>

textarea tag

<h:inputTextarea>

<t:textArea>

Label tag

<h:outputLabel for="...">

<t:label for="...">

Some important notes:

  • With Tapestry, you don't use the ${...} syntax with parameters of components. Just use a bare expression within the quotes. For example: <t:textfield value="myProperty"> instead of <t:textfield value="${myProperty}">, because in the latter case the expression is converted to a read-only string before the textfield component gets it.

Hello World Comparison

Faces templates and Tapestry templates are superficially quite similar.

JSF template (helloworld.xhtml)
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html">
  <h:body>
    <p><h:outputText value="#{helloWorldBean.greeting} /></p>
  </h:body>
</html>
Tapestry template (HelloWorld.tml)
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
  <body>
    <p>${greeting}</p>
  </body>
</html>

Though these are very similar, notice some differences:

  • The #{...} syntax in JSF does not encode the underlying string, so you have to use the <h:outputText> tag if your data may contain HTML reserved characters such as <, >, or &. In contrast, the ${...} syntax in Tapestry does encode the underlying string.
  • In JSF, backing beans are not necessarily related one-to-one with page templates. Often several templates use the same backing bean, and one template may reference multiple backing beans. In Tapestry, they are always related one-to-one, and therefore you don't have to specify which component class your ${...} expressions are referencing.
JSF Backing Bean (HelloWorldBean.java)
@ManagedBean
@RequestScoped
public class HelloWorldBean {
    public String getGreeting() {
        return "Hello, World!";
    }
}
Tapestry page class (HelloWorld.java)
public class HelloWorld {
    public String getGreeting() {
        return "Hello, World!";
    }
}

Expressions in templates

JSF uses the Unified Expression Language with the #{...} or ${...} syntax for accessing Backing Bean properties. For its part, Tapestry uses the ${...} syntax with a similar but intentionally limited expression language called Property Expressions. Both allow easy access to properties via the usual JavaBean conventions, but with Tapestry you don't have to specify which class the expression starts at (because it always starts at the component class corresponding to the template). Some comparisons:

JSF Syntax

Tapestry Syntax

Property (calls getEmployeeName() or setEmployeeName())

#{employeeBean.employeeName}

${employeeName}

Boolean property (calls isHourly() or setHourly())

#{employeeBean.hourly}

${hourly}

Property chain

#{employeeBean.address.street}

${address.street}

Null-safe property chain

#{employeeBean.address.street}

${address?.street}

5th element in a List

#{employeeBean.employees[5].name}

${employees.get(5).name}

Negation

#{! employeeBean.hourly}

${! hourly}

Arithmetic & relational operators

+-*/% div mod

not available

Relational operators

== != ne < lt > gt <= le >= ge

not available

Ternary operator

#{myBean.foo < 0 ? 'bar' : 'baz'}

not available

Method calling

#{myBean.employees.size()}

${employees.size()}

Iterated Range

not avaialble

${1..10}

Iterated Range (calculated)

not avaialble

${1..groupList.size()}

List

not available

${ [ user.name, user.email, user.phone ] }

Map

not available

${ { 'id':'4039','type':'hourly' } }

Features shown as not available above are absent by design, because (in both Tapestry and JSF) it is considered best to keep complex logic in the component class rather than in the template.

Event Handling & Page Navigation

Event handling

In JSF, you specify the event via the action parameter (for example, <h:commandButton value="Submit" action="employeeBean.saveChanges">). For Tapestry, event handler methods are found by method naming conventions (onSomeEvent() or by method annotations (@Event), based on a combination of the "t:id" attribute and event name, and the action name used depends on the component. For example, the "<t:actionlink>" component in Tapestry emits an "action" event when clicked, and you handle that event in your "onAction()" method.

Validation

Tapestry applications can use JSR 303 Bean Validation annotations that JSF users should be familiar with:

public class Employee {
    @Validate("required,minlength=2,maxlength=100")
    private String lastName;
    @NotNull @Email private String email;

Post-Redirect-Get Navigation

By default, most JSF URLs are "one page behind". That is, when you click on an <h:commandLink> link or submit a form, the request goes back to the originating page, and the server returns the contents of the next page – but the URL in the browser shows the previous page's URL. To fix this in JSF you add the "?faces-redirect=true" to the URL you return from event handlers, which causes JSF to send a redirect to the browser to navigate to the next page.

By contrast, Tapestry implements this Post-Redirect-Get pattern by default. The URL will always reflect the page you're seeing, not the page you just came from.

Note that by default Tapestry does not save property values across the Post-Redirect-Get cycle. This means that you have to consider how (and whether) to persist property values from one page to the next. The usual solution is to either make the values part of the page's Activation Context (which means the values will be appended to the URL) or @Persist the properties the values in the session.

Custom and Composite Components

With JSF, creating custom components is an advanced topic. In fact, many JSF developers have never created a custom component. In JSF 1.x, creating each custom component requires a lot of work: creating 3 Java classes (component, component renderer and component tag), registering the component in an XML file, and registering the tag in the .tld file. In JSF 2.x composite components can be created without too much work (if your needs can be met by combining existing components and you don't need any custom Java), but you still have to use cumbersome <composite:interface> and <composite:implementation> tags in your component templates, and you have to list the composite components in the xml namespace declaration at the top of the pages where you are using them.

Creating true custom components in JSF 2.0 still requires several steps: create a component class (generally having the @FacesComponent annotation and extending UIComponentBase), create a renderer class (generally extending Renderer), add a <renderer> section to the facesconfig file, and create a *-taglib.xml file in the WEB_INF folder that defines the namespace, tag and component type of the custom component.

In contrast, with Tapestry, creating custom components is a beginner topic: it is expected to be a daily activity for developers, because it is so easy. In fact, the steps are the same as creating a page. All you have to do is create a (potentially empty) Java class in a "components" sub-package, and create a template file containing (X)HTML markup in the corresponding "components" sub-folder within your package hierarchy under /src/main/resources. You use a custom component just like you use any built-in Tapestry component: <t:mycomponent>.

Because they're so easy to create, Tapestry applications tend to have a lot of custom components and much less repetition of HTML than most JSF applications.

Other References