Sie sind auf Seite 1von 48

Tapestry Tutorial

by Howard Lewis Ship

Updated for Tapestry 3.0


by Kevin C. Dorff
Tapestry Tutorial
by Howard Lewis Ship
Copyright © 2000, 2001, 2002, 2003 The Apache Software Foundation

by Kevin C. Dorff
Copyright © 2003 Kevin C. Dorff

ii
Special Thanks (from Kevin C. Dorff)
• Howard M. Lewis Ship for Tapestry.

• Geoff Longman for Spindle

http://spindle.sourceforge.net

• Vladimir Drndarski for helping me edit this Tutorial.

• Harish Krishnaswamy for the Illustrating Tapestry tutorial which helped me get going so I
could get enough up to speed to work on this tutorial.

http://myworkspace.sourceforge.net

iii
Table of Contents
Special Thanks (from Kevin C. Dorff) .............................................................................................. iii
Table of Contents ............................................................................................................................iv
Table of Figures................................................................................................................................v
Chapter 1: Introduction .................................................................................................................... 1
Chapter 2: Setting up the Tutorial.................................................................................................... 3
Chapter 3: Hello World .................................................................................................................... 8
Application Engine ....................................................................................................................... 8
Web Deployment Descriptor........................................................................................................ 8
Application Specification.............................................................................................................. 9
Home Page Specification .......................................................................................................... 10
Run the Application ................................................................................................................... 10
Chapter 4: Dynamic Content ......................................................................................................... 12
Chapter 5: Hangman ..................................................................................................................... 16
The Visit Object ......................................................................................................................... 18
The Home Page......................................................................................................................... 19
The Guess Page........................................................................................................................ 22
Chapter 6: Stylesheet and Image Assets (Hangman2) ................................................................. 25
Chapter 7: Creating Reusable Components ................................................................................. 28
Chapter 8: The Tapestry Inspector................................................................................................ 34
Navigation.................................................................................................................................. 34
Specification View...................................................................................................................... 35
Template View ........................................................................................................................... 36
Properties View.......................................................................................................................... 36
Engine View............................................................................................................................... 37
Chapter 9: Tapestry Workbench.................................................................................................... 38
Chapter 10: Localization................................................................................................................ 40
Localization of HTML Templates ............................................................................................... 41
Localization of Assets ................................................................................................................ 42
Other Options for Localization ................................................................................................... 42
Chapter 11: Further Study ............................................................................................................. 43

iv
Table of Figures
Figure 2 - 1: Eclipse and the tutorial projects .................................................................................. 5
Figure 2 - 2: Potential Build Problems............................................................................................. 6
Figure 2 - 3: The tutorial application running................................................................................... 7
Figure 3 - 1: Deployment descriptor, web.xml................................................................................. 9
Figure 3 - 2: helloworld.application.................................................................................................. 9
Figure 3 - 3: Home.page................................................................................................................ 10
Figure 3 - 4: Home.html................................................................................................................. 10
Figure 3 - 5: Hello World application running ................................................................................ 11
Figure 4 - 1: Dynamic Application.................................................................................................. 12
Figure 4 - 2: simple.application...................................................................................................... 12
Figure 4 - 3: Home.html................................................................................................................. 13
Figure 4 - 4: Home.page................................................................................................................ 14
Figure 4 - 5: Home.java ................................................................................................................. 15
Figure 4 - 6: HTML generated for the Home page for the simple application ............................... 15
Figure 5 - 1: Hangman Home Page............................................................................................... 16
Figure 5 - 2: Hangman Guess Page.............................................................................................. 17
Figure 5 - 3: Figure 5.3: Hangman Failed Page ............................................................................ 17
Figure 5 - 4: Hangman Success Page .......................................................................................... 18
Figure 5 - 5: hangman.application ................................................................................................. 18
Figure 5 - 6: Home.java ................................................................................................................. 20
Figure 5 - 7: Home.html (excerpt).................................................................................................. 21
Figure 5 - 8: Home.page................................................................................................................ 22
Figure 5 - 9: Guess.html (excerpt)................................................................................................. 23
Figure 5 - 10: Guess.page (excerpt).............................................................................................. 23
Figure 5 - 11: Guess.java (excerpt) ............................................................................................... 24
Figure 6 - 1: Success.page............................................................................................................ 25
Figure 6 - 2: Success.html............................................................................................................. 26
Figure 6 - 3: Excerpt of Guess.html............................................................................................... 26
Figure 6 - 4: Excerpt of Guess.page.............................................................................................. 27
Figure 6 - 5: Excerpt of Guess.java ............................................................................................... 27
Figure 7 - 6: Border Home Page ................................................................................................... 28
Figure 7 - 7: Border Credo Page ................................................................................................... 29
Figure 7 - 8: Home.html................................................................................................................. 29
Figure 7 - 9: Border.html................................................................................................................ 30
Figure 7 - 10: Border.jwc ............................................................................................................... 31
Figure 7 - 11: Show Inspector Button............................................................................................ 32
Figure 7 - 12: Home.page.............................................................................................................. 32
Figure 7 - 13: BorderEngine.java................................................................................................... 33
Figure 7 - 14: border.application.................................................................................................... 33
Figure 8 - 1: Tapestry Inspector .................................................................................................... 34
Figure 8 - 2: Specification View ..................................................................................................... 35
Figure 8 - 3: Template View .......................................................................................................... 36
Figure 8 - 4: Properties View ......................................................................................................... 36
Figure 8 - 5: Engine View .............................................................................................................. 37
Figure 9 - 1: Workbench ................................................................................................................ 38
Figure 9 - 2: Workbench (Showing Requests) .............................................................................. 39
Figure 10 - 1: L10N Page (English) ............................................................................................... 40
Figure 10 - 2: Locale Changed (German) ..................................................................................... 41
Figure 10 - 3: L10N Page (German).............................................................................................. 41

v
Chapter 1: Introduction
A note from Kevin C. Dorff, the tutorial updater – November 2, 2003:

This tutorial is an attempt to bring the previous Tapestry 2.2 tutorial, written by Howard Lewis
Ship (HLS), into the Tapestry 3.0 realm and to include source code that will work with Tapestry
3.0 beta 3 (and hopefully with 3.0 when it is available). The majority of the text in this document
will remain unchanged from the original tutorial, but, I will make changes where changes,
additions, etc. where warranted. It should be noted that I, myself, a relative Tapestry newbie – I
have been using Tapestry for about six weeks at the time of this writing, so, there may be better
ways to do things, but, I felt the updated tutorial was desperately needed by people who currently
in the same boat I was in six weeks ago (and waiting until I was a guru would benefit no one).
Instead of just copying and pasting the original text, I have completely re-typed it, re-implemented
every example, and re-captured every screen shot. I have done this to avoid missing some 2.2 to
3.0 difference that the reader might find confusing. If I have missed something, you know of a
better (more straightforward) way of doing something or explaining something please don’t
hesitate to contact me so I can update this document ASAP.

Kevin C. Dorff, kevin@dorffweb.com

Tapestry is a new application framework for developing web applications. It uses a component
object model to represent the pages of a web application. This is similar in spirit to using the Java
Swing component object model to build GUIs.

Just like using a GUI toolkit, there's some preparation and some basic ideas that must be cleared
up before going to more ambitious things. Nobody writes a word processor off the top of their
head as their first GUI project; nobody should attempt a full-featured e-commerce site as their first
attempt using Tapestry.

The goal of Tapestry is to eliminate most of the coding in a web application. Under Tapestry,
nearly all code is directly related to application functionality, with very little "plumbing". If you have
previously developed a web application using PHP, Microsoft Active Server Pages, JavaServer
Pages or Java Servlets, you may take for granted all the plumbing: writing servlets, assembling
URLs, parsing URLs, managing objects inside the HttpSession, etc.

Tapestry takes care of nearly all of that, for free. It allows for the development of rich, highly
interactive applications.

This tutorial will start with basic concepts, such as the "Hello World" application, and will gradually
build up to more sophisticated examples.

This tutorial was build and tested with the Tomcat 4.1 servlet engine,
http://jakarta.apache.org/tomcat, a freely available servlet engine1. I believe it should work with
Tomcat 4.0 without issue.2

1
HLS used Jetty in the Tapestry 2.2 version of the Tutorial, but I have never used Jetty. I believe
the only thing that is stopping you from using Jetty (or any other servlet engine for that matter)
instead of Tomcat is that I have written ant scripts (deploy.xml for every project) that deploy
directly to Tomcat. Edit these scripts so they instead deploy to Jetty and you should be set.
2
In many Tapestry applications, the web.xml file includes <filter> and <filter-mapping>
tags. These do not appear to work with Tomcat 4.0, so I have not included them in these
applications. This is the only incompatibility I have found between Tapestry 3.0 and Tomcat 4.0
thus far.

1
This tutorial was built using Eclipse 2.1.1, http://www.eclipse.org, and Spindle 3.0.29,
http://spindle.sourceforge.net. It is strongly recommended that you setup a similar environment
(you will be happy you did). At the time of this writing, Spindle 3.x.x did not support Eclipse 3.x,
only 2.1 or 2.1.x.

The format of this tutorial is to describe (visually and with text) several small applications within
the tutorial and to describe how they are constructed, using code excerpts. The reader is best
served by having an IDE open so that they can look at the complete code in detail, as well as run
the applications.

2
Chapter 2: Setting up the Tutorial
Even if you skim the rest of this tutorial, please read this chapter carefully.

If you have no interest in using Eclipse and already have Tapestry running, but just want
to look at the tutorial source code and see it execute, a WAR file for each project is
included under

<project>\DeployWars\local-dev\<project>.war

Tapestry requires JDK 1.3 or later.

This document expects that you will have extracted the full Tapestry (binary) distribution to your
3
C: drive . This will have created a directory C:\Tapestry-x.x and, beneath it, several more
directories4.

This document expects you have Eclipse 2.1.x installed to c:\eclipse.

You will need to copy the Tapestry and related jar files to the shared/lib (or equivalent) area of
your servlet engine. For Tomcat 4.1 this means copying

C:\Tapestry-x.x\lib\*.jar
C:\Tapestry-x.x\lib\runtime\*.jar
C:\Tapestry-x.x\lib\ext\*.jar
To
<Tomcat>\shared\lib

<Tomcat>\shared\lib should now contain about 13 jar files and no directories (it should not
contain the directories runtime or ext, the jars from those directories should be placed directly in
<Tomcat>\shared\lib).

If you are using another servlet engine, the location to which you copy the files will be different. It
is likely you will have problems running the tutorial applications with Tomcat 4.0, please use 4.1.

This tutorial references the Workbench application. This is an application that is distributed with
the Tapestry binary distribution. It is assumed you will copy the workbench.war to your servlet
engine’s webapps directory. For Tomcat this means copying

C:\Tapestry-x.x\lib\examples\workbench.war
To
<Tomcat>\webapps

The complete source for the Workbench application is included with the Tapestry source
distribution. This code can be quite useful when you are developing your own Tapestry
applications.

This document expects that you have Spindle 3.0.29 (or whatever the latest version is) installed
within Eclipse.
3
If you are using Solaris or another non-Windows operating system, you're expected to be savvy
enough to translate to a sensibly constructed file system.
4
The x.x is the release number. At the time of this writing, the current version was 3.0-beta3, but
this is constantly changing. Simply adjust the actual pathname to reflect the release of Tapestry
you downloaded.

3
Next, obtain the source for this tutorial. It is distributed separately as a ZIP file named

TapestryTutorialWorkspace-x.x.zip

This file can be found on the same web site where you found this tutorial (if you cannot find it,
contact kevin@dorffweb.com).

Extract this tutorial ZIP file to

c:\save\eclipse

The result should be a directory named c:\save\eclipse\TapestryTutorialWorkspace which


will become a new Eclipse workspace. To make this a workspace, open “My Computer” (icon
found on your desktop), navigate to C:\Eclipse. Right click on Eclipse.exe and select Create
Shortcut. Rename that shortcut to “Tapestry Tutorial”. Right click on the new shortcut and select
Properties and change the Target to launch eclipse, specifying a new workspace directory
(where you expanded the tutorial ZIP file):

c:\eclipse\eclipse.exe
-data c:\save\eclipse\TapestryTutorialWorkspace

If you choose, you can move this new shortcut to your desktop (just drag it there or cut and
paste).

Launch Eclipse with your new shortcut, make sure Eclipse is in the Resource Perspective and
you should see 6 projects… !TapestryLibs (some bundled tapestry jar files), border, hangman,
helloworld, portal, simple, and tutorial. You can generally just ignore !TapestryLibs.

4
Figure 2 - 1: Eclipse and the tutorial projects

Each of these tutorial projects began their life using Spindle via

File | New | Project…

Tapestry | Tapestry Web Project

Note: By default, early versions of Spindle versions 3.0.x placed the Home.html template file in
the context directory. Versions 3.0.11 and later will place this file in the WEB-INF directory, so I
have placed all template (html) files in the WEB-INF directory (if this means nothing to you, don’t
worry about it at this point).

The first of two differences between these projects and a “standard” Spindle project is that a
standard Spindle project includes a Library named Tapestry Framework (plugin default).
For increased portability from my system to yours of these tutorial applications, I removed this
library and refer to a few Tapestry and related jars individually and distribute those jars with the
tutorial source code distribution.

The only other addition to the configuration of each project is an additional “External Tools
Builder” to deploy the project to Tomcat. Within each project you will find this external tool builder,
an ant script, with the filename deploy.xml.

You will need to edit each of the five deploy.xml files to replace the location in which you have
Tomcat installed and the username and password for a Tomcat manager user. At the top of each
of the deploy.xml files is a note about setting up manager users for Tomcat.

5
Once you have edited each of the deploy.xml files, you should be able to build and install each
of the pieces of the tutorial.

Note: After editing the project configuration and trying to rebuild the project, you may get an error
that the "Resource is out of synch with the file system:..." this is easily fixed by right clicking on
the project, selecting "Refresh" and then rebuilding the project as previously instructed. If you
received the below dialog, you would want to refresh the “simple” project.

Figure 2 - 2: Potential Build Problems

Let’s start with the project named tutorial. To build and install this project into Tomcat (make
sure Tomcat is running), click on the project name (tutorial) in the Eclipse Navigator pane,
then select, from the main menu

Project | Rebuild Project

You may notice the output of the execution of the deploy.xml ant script (in the Eclipse Console
pane) that it reports a FAIL when it try to remove the previous instance of the application from
Tomcat… this is normal. The FAIL indicates that the project didn’t previously exist in Tomcat. On
subsequent deployments you should not get this failure notice.

You can repeat this process for deploying all of the tutorial applications (border, hangman,
helloworld, and simple).

If, in the future, you make changes to a specific application and want to install the new version
into Tomcat, simple click on the project name in the Navigator pane (in the Resource
Perspective) and select the menu item

Project | Rebuild Project

You should now be able to navigate to the URL

http://localhost:8080/tutorial

6
where you will see the Tutorial application is running. There is no need to stop and restart
Tomcat, the deploy.xml ant script handles this for the specific application that is being deployed.
Figure 2 - 3: The tutorial application running

7
Chapter 3: Hello World
In this first example, we’ll create a very simple “Hello World” kind of application. It won’t have any
real functionality but it will demonstrate the simplest possible variation of a number of key aspects
of the framework.

We’ll define our application, define the lone page of our application, and configure everything to
launch it.

The code for this section of the tutorial can be found in the helloworld project, i.e.,

c:\save\eclipse\TapestryTutorialWorkspace\helloworld

Application Engine
As each new client connects to the application, an instance of the application engine is created
for them. The application engine is used to track that client’s activity within the application.

The application engine is an instance of (or a subclass of) the Tapestry class named

org.apache.tapestry.engine.BaseEngine

In these first few examples, we have no additional behavior to add to the provided base class, so
we simply use BaseEngine class directly.

Web Deployment Descriptor


The application servlet is a “bridge” between the servlet container and the application engine. Its
job is simply to create (on the first request) or locate (on subsequent requests) the application
engine.

All Tapestry applications use the same servlet class, however, its configuration is different. Part of
the configuration is to identify the location of the application specification which is like the master
index of all pages in the application.

The following figure shows the web deployment descriptor (web.xml) for one of the applications in
this tutorial, Hello World

8
Figure 3 - 1: Deployment descriptor, web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name>helloworld</display-name>
<servlet>
<servlet-name>helloworld</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>helloworld</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

These first few lines are a header for XML files. You will see very similar headers on all
.xml, .page, .application, and .jwc files (Tapestry is choc full of these files as
Tapestry strongly leverages XML). The header will vary slightly based on which of those
types the file is – each file type has a specific header that is required.
Most Tapestry applications will have a <servlet-class> of
org.apache.tapestry.ApplicationServlet. If you need to do something like override
the servlet init() or destroy() methods, you would extend this class and put the name
of your own class here.

Application Specification
The application specification is used to describe the application to the Tapestry framework. It
provides the application with a name, an engine class, and the name of your applications “Home”
page5.

In a deployed Tapestry application, the application specification lives in the application’s WEB-
INF directory.
Figure 3 - 2: helloworld.application
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<application name="helloworld"
engine-class="org.apache.tapestry.engine.BaseEngine">
<description>Hello World Application</description>
<page name="Home" specification-path="Home.page"/>
</application>

The helloworld.application file is very simple; we give the application name, use the
standard engine, and define a single page, named “Home”. In Tapestry, pages and components

5
In previous versions of Tapestry it was required that you list all of your application pages in the
.application file. In Tapestry 3.0 it is only necessary to list the “Home” page.

9
are specified with the path to their specification file (a file that ends with “.page” for page
specifications or “.jwc” for component specifications).

The page named “Home” has a special meaning to Tapestry: when you first launch a Tapestry
application, it loads and displays the “Home” page. All Tapestry applications are required to have
such a page.

Home Page Specification


The page specification defines the Tapestry component responsible for the page. In this first
example, our component is very simple.
Figure 3 - 3: Home.page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<page-specification class="org.apache.tapestry.html.BasePage">
<description>Hello World Home Page</description>
</page-specification>

This simply says that Home is a kind of page. We use the supplied Tapestry class BasePage
since we aren’t adding any behavior to the page.
Figure 3 - 4: Home.html
<html>
<head>
<title>Hello World</title>
</head>
<body>
Welcome to your first <b>Tapestry Application</b>.
</body>
</html>

Run the Application


You should already be running Tomcat and have a browser window running the tutorials page.
Select the first option, Hello World. You will be presented with the first (and only) page generated
by Tapestry for this application.

10
Figure 3 - 5: Hello World application running

Not much of an application… there’s no interactivity or dynamic content. It might as well be a


static web page, but it’s a start. Remember, there was no JavaServer page (JSP) here, and no
HTML directly visible to the web server. We used the Tapestry framework to assemble an
application consisting of a single component.

In the following chapters, we’ll see how to add dynamic content and then true interactivity.

11
Chapter 4: Dynamic Content
In this chapter, we’ll create a new web application that will demonstrate the dynamic components
of Tapestry. We'll do two simple things. First we'll have Tapestry invoke a java method that
displays the time. Second, we'll have Tapestry create a link to our own page so that the time can
be refreshed. Figure 4-1 shows how this example will look when we’re finished:
Figure 4 - 1: Dynamic Application

Clicking the word “here” will update the page showing the new date and time. Not incredibly
interactive, but it’s a start.

The code for this section of the tutorial is in the simple project, in the package tutorial.simple.

The application specification is almost identical to the Hello World example:


Figure 4 - 2: simple.application
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<application name="simple"
engine-class="org.apache.tapestry.engine.BaseEngine" >
<description>Simple Application</description>
<page name="Home" specification-path="Home.page"/>
</application>

Things only begin to get more interesting when we look at the HTML template for the home page:

12
Figure 4 - 3: Home.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Simple</title>
</head>
<body>
<p>This application demonstrates some dynamic
behavior using Tapestry components.
<p>The current date and time is:
<b><span jwcid="insertDate">This Text Will Be Replaced</span></b>
<p>Click <a jwcid="refresh">here</a> to refresh.
</body>
</html>

This looks like ordinary HTML, except for the jwcid attribute. “jwc” is short for “Java Web
Components”; these attributes identify the tag as a placeholder for a dynamic Tapestry
component.

We have two components. The first inserts the current date and time into the HTML response.
The second component creates a hyperlink that refreshes the page when clicked.

One of the goals of Tapestry is that the HTML should have the minimum amount of special
markup. This is demonstrated here; the dynamic component tags blend into the standard HTML
of the template. We also don’t confuse the HTML by explaining exactly what an “insertDate” or
“refresh” is; that comes out of the specification (described shortly). The ids used here are
meaningful only to the developer6, the particular type and configuration of each component is
defined in the component specification.

Tapestry doesn’t really care what HTML tag you use, as log as you balance the tag correctly. Any
tags that Tapestry is required to process must either be in the format

<span jwcid="…" />


or
<span jwcid="…" > … </span>

Again, it is not necessary that you use the <span> tag, only that tag closes itself or has a closing,
matching tag. You will also notice that Tapestry is often a stickler for the nesting of other tags. If
you have HTML code such as <tr><td></tr></td> don’t be surprised if Tapestry gives an error
when you try to display the page.

In fact, Tapestry ignores the tag entirely: the refresh component above, which use a set of <a>
tags, could just as easily have been done with a <span> tag, or any other tag for that matter.
Tapestry is only interested in the structure of the HTML template. The fact that you can use
meaningful tags is a convenience; it also allows a Tapestry HTML template to be previewed with
a WYSIWYG HTML editor, such as HomeSite or Dreamweaver or even opened directly with a
web Browser such as Internet Explorer. Additionally, Tapestry edits out the content of the tags
(the text between the open and close tags, if any exists) for components that don’t wrap around
other content, such as the insertDate component in this example. The insertDate <span> tags
contains the text This Text Will Be Replaced, but, when the page is rendered by Tapestry
that text will be completely replaced by the dynamically created text which represents the current
date. This allows a “preview” value to be kept in the template.

Very significant is the fact that Tapestry components can wrap around other elements of the
template. The refresh component wraps around the word “here”. What this means is that the
6
Of course, good and consistent naming is important.

13
refresh component will get a chance to emit HTML (an <a> hyperlink tag), then emit the HTML it
wraps (the word “here”), then get a chance to emit more HTML (the </a> closing tag).

What’s more important is that the component can not only wrap static HTML from the template
(as shown in this example) but may wrap around other Tapestry components and those
components may themselves wrap text and components, to whatever depth is required.

And, as we’ll see in later chapters, a Tapestry component itself may have a template and more
components inside of it. In a real application, the single page of HTML produced by the
framework may be the product of dozens of components, effectively “woven” from dozens of
HTML templates.

Again, the HTML templates don’t define what the components are, they are simply a mix of static
HTML that will be passed directly back to the web browser, with a few placeholders (the tags with
the jwcid attribute) where dynamic content will be plugged in.

The page’s component specification defines what types of components are used and how data
moves between the application, page, and any components.
Figure 4 - 4: Home.page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<page-specification
class="tutorial.simple.Home">
<description>Simple Home Page</description>
<component id="insertDate" type="Insert">
<binding name="value" expression="currentDate"/>
</component>
<component id="refresh" type="PageLink">
<static-binding name="page">Home</static-binding>
</component>
</page-specification>

Here’s what all that means: The Home page is implemented with a custom class,
tutorial.simple.Home. It contains two components, insertDate and refresh.

The two components used within this page are provided by the Tapestry framework.

The insertDate component is of the type Insert. Insert components have a value parameter
used to specify what should be inserted into the HTML produced by the page. The insertDate
component has its value parameter bound to a JavaBeans property of its container (the page),
the currentDate property.

The refresh component is of the type PageLink, meaning it creates a link to some other page in
the application. PageLink components have a parameter, named page, which defines the name
of the page to navigate to (when using the PageLink component, the specified page must be a
page within the current application).

In this case, we only have one page in our application (named “Home”), so we can use a
<static-binding> for the page parameter. A <static-binding> provides a value for the
component parameter statically, the same value every time. The value is defined right in the
specification.

14
This just leaves the implementation (Java code) of the Home page component:
Figure 4 - 5: Home.java
package tutorial.simple;

import java.util.Date;
import org.apache.tapestry.html.BasePage;

public class Home extends BasePage {


public Date getCurrentDate() {
return new Date();
}
}

Home implements a read-only JavaBeans property, currentDate. This is the same currentDate
that the insertDate component needs. When asked for the current date, the Home object returns
a new instance of the java.util.Date object. The insertDate component converts objects into
strings by invoking toString() on the object.

Now all the bits and pieces are working together.

Run the application and then use the View Source command to examine the HTML generated by
the Tapestry framework.
Figure 4 - 6: HTML generated for the Home page for the simple application
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Simple</title>
</head>
<body>
<p>This application demonstrates some dynamic
behavior using Tapestry components.
<p>The current date and time is:
<b>Wed Oct 29 16:18:57 MST 2003</b>
<p>Click <a href="/simple/app?service=page/Home">here</a> to refresh.
</body>
</html>

This should look very familiar, in that it is mostly the same as the HTML template for the page.
Tapestry not only inserted simple text (the current date and time, obtained from a
java.util.Date object), but the refresh component inserted the <a> and </a> tags, and
created an appropriate URL for the href attribute.

You may notice that Tapestry URLs can get somewhat strange… a standard Tapestry application
does not make URLs for human consumption. You won’t be typing in URLs to place yourself into
the middle of a standard Tapestry application. Friendly URLs are not a topic for this tutorial. The
example Vlib application (in the Tapestry distribution) provides examples of friendlier,
bookmarkable URLs.

15
Chapter 5: Hangman
So far, these examples have been a little thin. Let’s do a meatier example that uses a few more
interesting components. Let’s play hangman!

Our hangman application consists of four pages. The first page is, of course, the Home page. It
allows the user to start new game at one of three difficulty levels, which simply regulates the
number of guesses the user is allowed.
Figure 5 - 1: Hangman Home Page

16
The workhorse of this little web application is the Guess page, shown in figure 5-2, where the
partially filled out word is displayed, and the user makes guesses from a shrinking list of possible
letters.
Figure 5 - 2: Hangman Guess Page

If you give up, or make too many mistakes, you end up on the Failed page.
Figure 5 - 3: Figure 5.3: Hangman Failed Page

17
But if you guess all the letters, you are sent to the Success page:
Figure 5 - 4: Hangman Success Page

The Visit Object


The center of this application is an object that represents the game, an object of class
HangmanGame. This object is used to track the word being guessed, the letters that have been
used, the number of misses, and the letters that have been correctly guessed.

In this application, the HangmanGame object will a property of the application’s visit object.
What’s the visit object? The visit object is a holder of all information about a single client’s visit to
your web application. It contains data and methods that are needed by the pages and
components of your application.

The visit object is owned and created by the engine object. It is serialized and de-serialized with
the engine.

The application specification includes a little extra segment to specify the class of the applications
visit object.

Figure 5 - 5: hangman.application
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<application name="hangman" engine-


class="org.apache.tapestry.engine.BaseEngine" >
<description>Hangman Application</description>
<property name="org.apache.tapestry.visit-class">
tutorial.hangman.Visit
</property>
<page name="Home" specification-path="Home.page"/>
</application>

18
This property specifies that the engine should instantiate an instance of
tutorial.hangman.Visit when a visit object is first required. This is the default way in
which the visit object is specified, though if the visit object doesn’t have an empty
constructor method, the engine method createVisit() must be implemented instead.

So, returning from that distraction, the game object is a property of the visit object, which is
accessible from any page (via the page’s visit property).

The Home Page


The Home page’s job is to collect the difficulty and initiate a game:

19
Figure 5 - 6: Home.java
package tutorial.hangman;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;

public class Home extends BasePage {

public static final int EASY = 10;


public static final int MEDIUM = 5;
public static final int HARD = 3;

private int misses = 0;


private String error = "";

public void detach() {


misses = 0;
error = "";
super.detach();
}

public int getMisses() {


return misses;
}

public void setMisses(int value) {


misses = value;
}

public String getError() {


return error;
}

public boolean getHasError() {


if ((error == null) || (error.length() == 0)) {
return (false);
}
return (true);
}

public void formSubmit(IRequestCycle cycle) {


if (misses == 0) {
error = "Please select a game difficulty.";
return;
}
Visit visit = (Visit)getVisit();
visit.startGame(misses);
cycle.activate("Guess");
}
}

The detach method is automatically called after the page is displayed. The job of this
method is to reset class level variables back to "default" values. At the time a page is
being created, the associated Java class for the page is used to help display the page to
the user. After the page is displayed, the associated Java class it not destroyed, but
rather, it is placed in a “pool” for later use (for displaying the page at a later time).
Because the Java class is not destroyed, but instead pooled, it is generally desirable to
return any class-level variables to “default” values. The next time the page is requested
by any user, the page could be returned from the pool.

20
By just returning from the listener method, we are implicitly stating that the same page
needs to be displayed again.
In Tapestry, cycle.activate(pagename) is the standard way of changing to another
page. Here, we are moving control to the Guess page.

We don't provide a default difficulty level for the user. If the user fails to select a difficulty level
before pressing "Play!", the listener for the page's form, Home.formSubmit(), will set an error
message then redisplay the Home page.

Otherwise, we get the visit object and ask it to start a new game with the selected number of
misses. We then jump to the Guess page to start accepting guesses from the user.

The interesting part of the Home page HTML template is in the form:
Figure 5 - 7: Home.html (excerpt)
<form jwcid="form">
<span jwcid="group">
<span jwcid="ifError">
<font size=+2 color=red><span jwcid="insertError"/></font>
</span>
<table>
<tr>
<td><input jwcid="inputEasy"/></td>
<td>Easy game; you are allowed ten misses.</td>
</tr>
<tr>
<td><input jwcid="inputMedium"/></td>
<td>Medium game; you are allowed five misses.</td>
</tr>
<tr>
<td><input jwcid="inputHard"/></td>
<td>Hard game; you are only allowed three misses.</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Play!"></td>
</tr>
</table>
</span>
</form>

In HTML, PHP, Perl, JSPs, Servlets, etc., HTML forms can post to any page within the
application or website. It should be noted that Tapestry forms always post back to the
same Tapestry page or component that created the form. Since the Home page created
the form, the Home page always receive the post of the form. Because of this, it is
necessary that either the form, the submit button, or both define a listener (a method
which will be called to handle the form submission). If you define a listener on both the
submit button and on the form, the listener for the button will be called, then the listener
for the form. We can see in Home.page that a listener for the form has been specified.

Here, the interesting components are group, inputEasy, inputMedium, and inputHard. group is
of the type RadioGroup, a wrapper that must go around the Radio components (the other three).
The RadioGroup determines what property of the page is to be read and updated (its bound to
the misses property). Each Radio button is associated with a particular value to be assigned to
the property, when the radio button is selected by the user.

This comes together in the Home page specification

21
Figure 5 - 8: Home.page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<page-specification class="tutorial.hangman.Home">
<description>Hangman Home Page</description>
<component id="form" type="Form">
<binding name="listener" expression="listeners.formSubmit" />
</component>
<component id="ifError" type="Conditional">
<binding name="condition" expression="hasError" />
</component>
<component id="insertError" type="Insert">
<binding name="value" expression="error" />
</component>
<component id="group" type="RadioGroup">
<binding name="selected" expression="misses" />
</component>
<component id="inputEasy" type="Radio">
<binding name="value" expression="@tutorial.hangman.Home@EASY" />
</component>
<component id="inputMedium" type="Radio">
<binding name="value" expression="@tutorial.hangman.Home@MEDIUM" />
</component>
<component id="inputHard" type="Radio">
<binding name="value" expression="@tutorial.hangman.Home@HARD" />
</component>
</page-specification>

This is the listener for the form. Looking in Home.java, we will find a method named
formSubmit(…). This method will be called when the form is submitted.
A <binding> is used to read a value from a class. Here, instead of a standard
JavaBeans property, we are retrieving a static value from the class. You will notice a new
syntax that is related to using static properties or methods. The syntax is
@class@property.

So the end result is: when the user clicks the radio button for a Hard game, the static constant
HARD is assigned to the page’s misses property.

The Guess Page


This page is where users make letter guesses. The page has four sections:

• A display of the word being guessed, with underscores replacing un-guessed letters.
• A status area, showing the number of bad guesses and an optional error message when
the player guesses a wrong letter.
• A list of letters that may still be guessed. Letters disappear after they are used.
• An option to give up and see the word, terminating the game.

22
Let’s start with the HTML template this time:
Figure 5 - 9: Guess.html (excerpt)
<h1>Make a Guess</h1>
<font size=+3>
<span jwcid="insertGuess"/>
</font>
<p>
You have made <span jwcid="insertMissed"/> bad guesses,
out of a maximum of <span jwcid="insertMaxMisses"/>.
<span jwcid="ifError">
<p>
<font size=+3 color=red><span jwcid="insertError"/></font>
</span>
<p>
Guess:
<font size=+1>
<span jwcid="e">
<a jwcid="guess"><span jwcid="insertLetter"/></a>
</span>
</font>
<p>
<a jwcid="giveUp">Give up?</a>

Most of these components should be fairly obvious by now; we will focus on the components that
allow the user to guess a letter. This could have been implemented in a number of ways… using
more radio buttons, a drop down list or a text field the user could type into. In this example, we
chose to simply create a series of links, one for each letter the user may still guess.

Let’s look at the page specification.


Figure 5 - 10: Guess.page (excerpt)
<component id="insertGuess" type="Insert">
<binding name="value" expression="page.visit.game.guess"/>
</component>
<component id="insertMissed" type="Insert">
<binding name="value" expression="page.visit.game.missed"/>
</component>
<component id="insertMaxMisses" type="Insert">
<binding name="value" expression="page.visit.game.maxMisses"/>
</component>
<component id="ifError" type="Conditional">
<binding name="condition" expression="hasError"/>
</component>
<component id="insertError" type="Insert">
<binding name="value" expression="error"/>
</component>
<component id="e" type="Foreach">
<binding name="source" expression="page.visit.game.unused"/>
</component>
<component id="guess" type="DirectLink">
<binding name="listener" expression="listeners.makeGuess"/>
<binding name="parameters" expression="components.e.value"/>
</component>
<component id="insertLetter" type="Insert">
<binding name="value" expression="components.e.value"/>
</component>
<component id="giveUp" type="PageLink">
<static-binding name="page">Failed</static-binding>
</component>

23
Component e is simply a Foreach, the source is the unused property of the game.

Component insertLetter inserts the current letter from the list of unused letters. It gets this
current letter directly from the e component. On successive iterations, the Foreach component’s
value property is the value in the iteration.

Component guess is of type DirectLink, which creates a hyperlink on the page and notifies its
listener when the user clicks on the link. Just knowing that the component was clicked isn’t very
helpful, though; the application needs to know which letter was actually clicked. Passing that kind
of information along is accomplished by setting the parameters attribute of the DirectLink
component. The parameters attribute is an object, or array of objects, that will be encoded into
the URL for the hyperlink. When the component’s listener is executed, it can obtain the object
from the IRequestCycle7.

The parameters are often used to encode primary keys of objects, names of columns, or other
information specific to the application.

In this case, the parameters consist of a single value, the letter that was guessed.

All of this comes together in the Java code for the Guess page.
Figure 5 - 11: Guess.java (excerpt)
public void makeGuess(IRequestCycle cycle) {
Object[] parameters = cycle.getServiceParameters();
String letter = (String) (parameters[0]);
HangmanGame game = ((Visit)getVisit()).getGame();
try {
game.guess(letter);
} catch (HangmanGameException ex) {
error = ex.getMessage();
if (game.getFailed())
cycle.activate("Failed");
return;
}
// A good guess.
if (game.getDone()) {
cycle.activate("Success");
}
}

The component specification showed how data was encoded into the URL as parameters; here
we see how the makeGuess() listener method has access to the parameters and uses them. The
listener method extracts the letter and informs the game object, which throws an exception if the
letter is not in the word being guessed.

The method HangmanGame.getFailed() returns true when all the missed guesses are used up,
at which point we go to the Failed page to tell the user what the word was.

On the other hand, if an exception isn’t thrown, the guess was good. getDone() returns a true if
all the letters have been guessed, in which case we go to the Success page.

If all the letters weren’t guessed, we stay on the Guess page, which will display the word with the
guessed letters filled in, and with fewer options in the list of possible guesses.

7
Tapestry takes care of converting objects into strings when constructing the URL, the converts
those strings back into objects when the link is clicked. Your listener method will be able to get
copies of the original parameters.

24
Chapter 6: Stylesheet and Image Assets (Hangman2)
As a brief aside, we will make some changes to the hangman application. As to not confuse the
code, we have duplicated the Chapter 5 hangman application to a new application called
hangman2. The changes to the application are minor. We have added a set of graphics in the
context/images directory, we have added a stylesheet in the context/css directory, we have
made some minor changes to the .html and .page files to reference the stylesheet and images,
and have added one method to the Guess.java to display the correct game image based on the
number of incorrect guesses so far.

Tapestry has a concept of an “asset”. A Tapestry asset, generally specified in a .page file, can be
a file such as a stylesheet or an image that the application depends on to execute. If a .page
species an asset but the asset does not exist, the Tapestry framework will generate an error and
not display the page.

Looking at the Success.page file, we can see how we specify the assets that this page uses.
Figure 6 - 1: Success.page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<page-specification class="tutorial.hangman.Success">
<context-asset name="stylesheet" path="/css/hangman.css"/>
<context-asset name="win" path="/images/win.gif"/>
<component id="winImage" type="Image">
<binding name="image" expression="assets.win"/>
</component>
<component id="insertCorrectWord" type="Insert">
<binding name="value" expression="page.visit.game.correctWord"/>
</component>
<component id="home" type="PageLink">
<static-binding name="page">
Home
</static-binding>
</component>
</page-specification>

Creates a Tapestry asset named stylesheet that is associated with the file
context/css/hangman.css.
Creates an Tapestry asset named win that is associated with the file
context/images/win.gif.
This is a component that will be referred to from the Success.html template. The name of
the component is winImage, the type is Image. We can see that the image that will be
displayed is an asset that is named win.

Looking at the Success.html template, we can see how the stylesheet is called. It is also worth
noting that we are no longer specifying the title bar for the browser via the HTML <title> tag, but
we are letting Tapestry set the browser title for us.

25
Figure 6 - 2: Success.html
<html>
<span jwcid="@Shell" stylesheet="ognl:assets.stylesheet"
title="Hangman Success"/>
<body>
<p class="headerText">You Win!</p>
<p><img jwcid="winImage"/></p>
<p class="guessLettersPreText">The word was:
<span class="hiddenWord" jwcid="insertCorrectWord"/>.
<p><a jwcid="home">Start Again</a>
</body>
</html>

The @Shell component lets us specify attributes of stylesheet and title. We are
specifying that the stylesheet for the page should be the asset named stylesheet (as
defined in the .page file) and the browser title should be the static test Hangman Success.
The winImage component comes from the .page file and we have already seen this will
render the image associated with the asset named win, so context/images/win.gif
will be displayed to the user.

Up to now, we have always specified the name of the Tapestry components in the .html file and
the type, attributes, etc. of those components in the .page file. Here, we have provided a sample
of how components can be created completely in the .html file. When we specified in the .html file

<span
jwcid="@Shell"
stylesheet="ognl:assets.stylesheet"
title="Hangman Success"/>

this is equivalent to having a .html file of

<span jwcid="pageShell"/>

with associated entries in the .page file of

<component id="pageShell" type="Shell">


<binding name="stylesheet" expression="assets.stylesheet"/>
<static-binding name="value">Hangman Success</static-binding>
</component>

You will see both used in Tapestry applications.

The Failed and Home pages are very similar to the Success, so we will not cover those here.

Now that we have demonstrated how to use stylesheets and how to display static images using
assets, we will demonstrate a more dynamic use of graphical assets. On the Guess page, it would
be neat to display a graphic that changes, depending on the difficulty of the game and the current
number of incorrect graphics. To do this, we have created 11 additional graphics named 0.gif to
10.gif (again, these live in the <context>/images directory). To display the correct image on the
Guess page, we will first look at an excerpt the Guess.html file.
Figure 6 - 3: Excerpt of Guess.html
<p><img jwcid="guessImage"/></p>

Next we will look at how this component is defined in an excerpt of the Guess.page file.

26
Figure 6 - 4: Excerpt of Guess.page
<context-asset name="wrong_0" path="/images/0.gif"/>
<context-asset name="wrong_1" path="/images/1.gif"/>
<context-asset name="wrong_2" path="/images/2.gif"/>
<context-asset name="wrong_3" path="/images/3.gif"/>
<context-asset name="wrong_4" path="/images/4.gif"/>
<context-asset name="wrong_5" path="/images/5.gif"/>
<context-asset name="wrong_6" path="/images/6.gif"/>
<context-asset name="wrong_7" path="/images/7.gif"/>
<context-asset name="wrong_8" path="/images/8.gif"/>
<context-asset name="wrong_9" path="/images/9.gif"/>
<context-asset name="wrong_10" path="/images/10.gif"/>
<component id="guessImage" type="Image">
<binding name="image" expression="guessImageAsset"/>
</component>

This is very similar to the static image that is displayed via Compared to the static images from
Success.page with one notable difference, instead of directly specifying an asset, we are
specifying that a JavaBeans getter method (in the class Guess.Java) will provide the image
asset. The method that will be called must have the signature

public IAsset getGuessImageAsset()

We can see how that method is defined by looking at an excerpt of the Guess.java file.
Figure 6 - 5: Excerpt of Guess.java
public IAsset getGuessImageAsset() {
HangmanGame game = ((Visit)getVisit()).getGame();
int iMissed = game.getMissed();
int iMaxMisses = game.getMaxMisses();
int iFactor = 1;
if(iMaxMisses==3) {
iFactor = 3;
} else if(iMaxMisses==5) {
iFactor = 2;
}
String name = "wrong_" + iMissed*iFactor;
return (getAsset(name));
}

Obtain the current player’s HangManGame object from the visit object so we can see how
many letters the player has incorrectly guessed so far and how many they can miss
before the game ends.
Depending on the number of max misses, define a factor that can be used to determine
the correct asset name to display (so the correct image is displayed).
Determine the name of the asset to be displayed and obtain the actual asset (via the
Page’s getAsset(…) method). Return the asset.

The Guess page can now display a specific image depending on the number of incorrect
guesses.

27
Chapter 7: Creating Reusable Components
A quick special notation about the border project… The BorderEngine.java class in the border
project extends the org.apache.tapestry.engine.BaseEngine class. To do this, you need to
have the class javax.servlet.http.HttpSessionBindingListener in your classpath. If you
are using Tomcat, this means making sure servlet.jar is in the classpath. In order to make
Eclipse happy enough to build the project, I have created a folder in this projected called
ReferenceJars_NotInstalled and placed Tomcat 4.1.27’s servlet.jar in this directory and
referenced it in the project’s Java Build Path | Libraries.

In this tutorial, we’ll show how to create a reusable component. One common use of components
is to create a common “border” for the application that includes basic navigation. We’ll be creating
a simple three page application with a navigation bar down the left side and the page title across
the top.
Figure 7 - 6: Border Home Page

28
Navigating to another page results in a similar display
Figure 7 - 7: Border Credo Page

Each page’s content is confined to the silver area in the center. Note that the border adapts itself
to each page: the title “Home” or “Credo” across the top is specific to the page, and the menu
item for the current page doesn’t have an active link (in the above page, “Credo” is set to the
current page, so only “Home” and “Legal” are usable navigation links).

The “T” in the lower right hand corner is the Show Inspector link. It will be described in the next
chapter.

Each of the three pages has a similar HTML template:


Figure 7 - 8: Home.html
<span jwcid="border">
Nothing much doing here on the <b>home</b> page.
Please visit one of our other fine pages.
</span>

Remember that Tapestry components can wrap around other HTML elements or components.
For the border, we have an HTML template where everything on the page is wrapped by the
border component.

Note that we don’t specify any <html> or <body> tags; those are provided by the Border
component.

This illustrates a key concept within Tapestry: embedding vs. wrapping. The Home page embeds
the border component (as we’ll see in the Home page’s specification). This means that the Home
page is implemented using the border component.

However, the border component wraps the content of the Home page, the Home page HTML
template indicates the order in which components (and static HTML elements) are rendered. On
the Home page, the border component ‘bats’ first and performs cleanup.

29
The construction of the Border component is driven by how it differs from page to page. You’ll
see that on each page, the title (in the upper left corner) changes. The names of all three pages
are displayed, but only two of the three will have links (the third, the current page, is just text).
Lastly, each page contains the specific content from its own HTML template.
Figure 7 - 9: Border.html
<span jwcid="shell">
<body jwcid="body">
<table border=0 bgcolor=gray cellspacing=0 cellpadding=4>
<tr valign=top>
<td colspan=3 align=left>
<font size=5 color="White">
<span jwcid="insertPageTitle"/>
</font>
</td>
</tr>
<tr valign=top>
<td align=center width="100">
<font color=white>
<span jwcid="e"><br>
<a jwcid="link"><span jwcid="insertName"/></a>
</span>
</font>
</td>
<td rowspan=2 valign=top bgcolor=silver width="400">
<span jwcid="renderBody"/>
</td>
<td rowspan=2 width=4></td>
</tr>
<tr>
<td>
<span jwcid="inspector"/>
</td>
</tr>
<tr>
<td colspan=3 height=4> </td>
</tr>
</table>
</body>
</span>

The shell component provides the <html> and <head> elements of the response HTML,
including the title for the browser window.
The body component provides the <body> element. It also provides support for
JavaScript related to Rollover buttons, such as the inspector component.
The e component is a Foreach configured to work through a list of page names (provided
by the engine).
The link and insertName components provide inter-page navigation links.
The renderBody component provides the actual content for the page. The Border
component is used on all three pages, but it’s a different instance on each page,
wrapping around different content specific to the page.
The inspector component provides the button on the lower right hand corner of the
page (the “T” in a circle) and will be explained in the next chapter.

The Border component is designed to be usable in other Tapestry applications, so it doesn’t hard
code the list of page names. These must be provided to the component as a parameter. In fact,
the application engine provides the list.

30
Figure 7 - 10: Border.jwc
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE component-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<component-specification
class="tutorial.border.Border"
allow-informal-parameters="no">

<parameter name="title" type="java.lang.String" required="yes" />


<parameter name="pages" required="yes" />

<component id="shell" type="Shell">


<binding name="title" expression="page.engine.specification.name" />
</component>
<component id="insertPageTitle" type="Insert">
<inherited-binding name="value" parameter-name="title" />
</component>
<component id="body" type="Body" />
<component id="e" type="Foreach">
<inherited-binding name="source" parameter-name="pages" />
<binding name="value" expression="pageName" />
</component>
<component id="link" type="PageLink">
<binding name="page" expression="pageName" />
<binding name="disabled" expression="disablePageLink" />
</component>
<component id="insertName" type="Insert">
<binding name="value" expression="pageName" />
</component>
<component id="renderBody" type="RenderBody" />
<component id="inspector" type="contrib:InspectorButton" />
</component-specification>

Declares a required parameter for the border, the title that will appear on the page.
Declares a parameter to specify the list of page names. We don’t specify a particular type
because its pretty unbounded; the framework will accept List, Iterator, or Java array.
We then provide the shell component with its title parameter; this will be the
browser’s window title. We use the application’s name, which is extracted from the
application’s specification.
The <inherited-binding> element allows a component to share parameters. Here the
Border’s title is used as the value parameter of the insertPageTitle component (an
Insert component). Using these inherited bindings simplifies the process of creating
complex component from simple ones.
Likewise, the e component (a Foreach) needs as its source the list of pages, which it
inherits from the Border component’s pages parameters. It has been configured to store
each successive page name into the pageName property of the Border component; this is
necessary so that the Border component can determine which page link to disable (it
disables the current page since we’re already there).
The link component creates a link to the other pages. It has a disabled parameter,
which, when true, causes the link component to not create the hyperlink (though it still
allows the elements it wraps to render). The Java class for the Border component,
tutorial.border.Border, provides a method, getDisabledPageLink(), that returns
true when the pageName instance variable (set by the e component) matches the current
page’s name.

31
This component will raise the Tapestry Inspector in a new window when the Inspector
graphic is clicked.

So, the specification for the Border component must identify the parameters it needs, but also
the components it uses and how they are configured.
Figure 7 - 11: Show Inspector Button

Clicking on the Tapestry Inspector button raises a second window that describes the current page
in the application (this is used when debugging a Tapestry application). The Inspector is
described in the next chapter.

The final mystery is the wrapped component. It is used to render the elements wrapped by the
Border on the page containing the Border. Those elements will vary from page to page; running
the application shows that they are different on the Home, Credo, and Legal pages (different text
appears in the central silver box). There is no limitation on other elements either: Tapestry is
designed to allow components to wrap other components this way, without any arbitrary
limitations.

This means that the different pages could contain forms, images, or any set of components at
all… not just static HTML text.

The specification of the home page shows how the title and pages parameters are set. This title is
static, the literal value “Home” (this isn’t the best approach if localization is a concern).
Figure 7 - 12: Home.page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<page-specification class="org.apache.tapestry.html.BasePage">
<component id="border" type="Border">
<static-binding name="title">Home</static-binding>
<binding name="pages" expression="engine.pageNames"/>
</component>
</page-specification>

The pages property is retrieved from the application engine, which implements a pageNames
JavaBeans property.

32
Figure 7 - 13: BorderEngine.java
package tutorial.border;

import org.apache.tapestry.engine.BaseEngine;

public class BorderEngine extends BaseEngine {

private static final String[] pageNames = { "Home", "Credo", "Legal" };

public String[] getPageNames() {


return pageNames;
}

In order for the application to utilize this new engine, we must specify the engine in the application
file.
Figure 7 - 14: border.application
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<application name="border" engine-class="tutorial.border.BorderEngine" >
<description>Border Application</description>
<page name="Home" specification-path="Home.page"/>
<library id="contrib"
specification-path="/org/apache/tapestry/contrib/Contrib.library"/>
</application>

It should also be noted that because the InspectorButton is part of the “contrib” library, an
external library of Tapestry components that are bundled with the Tapestry distribution, we must
specifically make this library available to the application with the <library> element.

33
Chapter 8: The Tapestry Inspector
Unlike scripting systems (such as JavaServer Pages and the like), Tapestry applications are
gifted with a huge amount of information about how they are implemented. The same component
object model that allows Tapestry to perform so many ordinary functions can be leveraged to
provide some unusual functionality.

Run the border tutorial from the previous chapter and click on the show Inspector button (the “T”
in the lower right corner). A new window will launch, containing the Inspector:
Figure 8 - 1: Tapestry Inspector

The Inspector displays live information from the running application; in fact, it is simply another
part of the application (the drop-down list of pages will include the Inspector page itself, titled
contrib:Inspector). The Inspector is most often used to debug HTML generation by viewing
the HTML templates. It is also very useful in debugging problems where the wrong data is
displayed, since it allows the developer to navigate to the particular components and see directly
what properties are used.

Navigation
The inspector allows the user to navigate to any page and any component on any page. The drop
down list in the upper left corner lists all pages in the application; changing the selection
immediately updates the inspector.

Next to the drop down list is the component path, a list of nested component ids, starting with
“page,” to represent the page. Clicking on any id on the page changes the information displayed
below.

Underneath the component navigation tools are a set of tab buttons for the different inspector
views.

34
Specification View
Figure 8 - 2: Specification View

The specification view shows several sets of information about the selected component.

First shown are basic properties, such as the specification path and Java class.

Each formal parameter is displayed. Unbound parameters will show no value in the Binding
column.

Beneath formal parameters are informal parameters (the Border page/component has none, so
there is nothing to see). Informal parameters are usually mapped directly to HTML attributes.
They are most often used with components that generate a single HTML tag, such as the
ActionLink, DirectLink, or TextField components.

If the component contains assets, they are shown next.

Any helper beans for the component are displayed last.

On the right side is a list of each embedded component and its type. Clicking on the component
will navigate to the selected component.

35
Template View
Figure 8 - 3: Template View

The template view shows the HTML template for the page/component. It shows dynamic tags in
bold, and makes the component id a clickable link (which navigates to the component, but
maintains the Template View). This allows the developer to quickly drill down through
components.

Properties View
Figure 8 - 4: Properties View

The properties view shows persistent properties stored by the page (or any components on the
page). Most pages do not store any persist ant state (it is more commonly stored in the
application’s visit object).

36
Engine View
Figure 8 - 5: Engine View

The engine view shows information about the running application engine, as well as some details
from the application specification.

Under Operations are two buttons: the first restarts the application. The second (when enabled8)
resets the application, which forces a reload of all component specifications and HTML templates.
This is useful during development since it allows for incremental development without stopping
and restarting the servlet container, which may otherwise be necessary depending on the servlet
container you are using.

Below the operations is a binary dump of the application engine. This is useful when developing
to see how large the serialized state is, and perhaps gleam how it might be trimmed.

Further below (and not visible in the screen shot above) is a dump of the request context. This is
a vast amount of data also displayed when an unexpected exception is thrown.

8
By default, the reset service (used by the reset button) is disabled. To enable it, set the JVM
system property org.apache.tapestry.enable-reset-service to true. The service is
disabled since it is too tempting a target for denial of service attack.

37
Chapter 9: Tapestry Workbench
The Tapestry distribution includes an application, the Workbench, which is used to show off
interesting Tapestry components and features.
Figure 9 - 1: Workbench

The Workbench is divided into several areas, as shown by the tabs across the top of the page.
Over time, the Workbench will expand and the number of tabs will increase.

In addition to the Inspector (which, you can see, can be disabled via the textbox in the lower right
hand corner), the workbench has a useful feature which can be activated using the checkbox in
the lower left hand corner. When enabled, the complete (and verbose) information available
about the request, session, and context (normally displayed by the Inspector’s engine view) is
shown at the bottom of each page.

38
Figure 9 - 2: Workbench (Showing Requests)

This feature can be very useful if you are interested in exactly how Tapestry forms and links work.

39
Chapter 10: Localization
One of the most powerful and useful features of the Tapestry framework is the way it assists with
localization of a web application. This is normally and ugly area of web applications, with
tremendous amounts of ad-hoc coding necessary.

Because Tapestry does such a strong job of separating the presentation of a component (its
HTML template) from its control logic (its specification and Java class) it becomes easy for it to
perform localization automatically. It’s as simple as providing additional localized HTML templates
for the component and letting the framework select the proper one.

However, the static text of an application, provided by the HTML templates, is not all.

Applications also have assets (images, stylesheets, and the like) that must be localized: that
fancy button labeled “Search” is fine for your English clients, but your French clients will require a
similar button labeled “Recherché”.

Again, the framework assists, because it can look for localized versions of the assets as it runs.

A demonstration of localization is built into the Workbench, under the L10N9 tab. The page allows
the user to select a new language for the application:
Figure 10 - 1: L10N Page (English)

Selecting “German” from the list and clicking the “Change” button brings you to a new page that
acknowledges your selection:

9
The “10” refers to the number of letters between “L” and “N” in the word “LOCALIZATION”.

40
Figure 10 - 2: Locale Changed (German)

Clicking the button (it’s labeled “Return” in German) returns you to the L10N page to select a new
language:
Figure 10 - 3: L10N Page (German)

The neat thing here is that the L10N page has been localized to German as well; it shows the
equivalent German text, the options in the popup list are in German, and the “Change” button has
been replaced with the German equivalent.

Localization of HTML Templates


Localization of HTML templates is automatic. When Tapestry reads a template, it looks for a
localized version of it. In this example, in addition to the English language Localization.html,
three additional files were created: Localization_de.html, Localization_fr.html, and
Localization_it.html.

Tapestry tracks the locale for each user using either an HTTP Cookie, or the HttpSession. It
makes sure that all templates for all components on the page use the best available template; it
does a standard search.

41
Localization of Assets
In the L10N pages, there are images that are also localized. Tapestry has a hand in this as well.
As with HTML templates, Tapestry search for matches based on the user’s locale.

Both context assets (assets that are part of the WAR) and private assets (assets stored in the
Java frameworks) can be localized. This is demonstrated on the L10N page: the “Change” button
is a private asset; the “Back” button is a context asset.

Other Options for Localization


In some cases, different localizations of a component may be very similar, perhaps having only
one or two small snippets of text that is different. In those cases, it may be easier on the
developer not to localize the HTML template, but to replace the variant text with an Insert
component.

The page can read a localized strings file (a .properties file) to get appropriate localized text.
This saves the bother of maintaining multiple HTML templates. This is the same approach taken
by the Apache Struts framework.

All components on a page share the single locale for the page, but each performs its own search
for its HTML template. This means that some components may not have to be localized, if they
never contain any static HTML text. This is sometimes the case for reusable components, even
navigational borders.

42
Chapter 11: Further Study
The preceding chapters cover many of the basic aspects of Tapestry. You should be comfortable
with the basic Tapestry concepts:

• Separation of presentation, business, and control logic


• Use of JavaBeans properties as the source of dynamic data
• How bindings access JavaBeans properties to provide data to components
• How components wrap each other, allowing for the creation of very complicated
components through aggregation
• Different types of page properties (transient, dynamic, and persistent)

Tapestry is capable of quite a bit more. Also available within the Tapestry distribution is the
example for a Virtual Library application (Vlib).

Vlib is a full blown J2EE application which makes use of Tapestry as its front end, and a set of
session and entity Enterprise JavaBeans as its back end.

Vlib also demonstrates some of the other aspects of developing a Tapestry application. It shows
how to create pages that are bookmarkable (meaning that their URL includes enough information
to reconstruct them in a subsequent session). It shows how to handle logging in to an application,
and how to protect pages from being accessed until the user is logged in. It has many
specialized, reusable components for creating links to pages about books and people.

43

Das könnte Ihnen auch gefallen