In this tutorial, we'll cover setting up the most basic Tapestry application, a simple "Hello World" application that displays the current time. We'll then extend it just a bit, adding a touch of interactivity.
The final source for this tutorial is packaged as helloworld.tar.gz .
Our first application will look like the following when it runs:
Tapestry applications always include a page named "Home". The Home page is the first page displayed by the application, when it is first started (that is, when the client web browser first accesses the starting URL).
Tapestry pages are always a combination of a Java class and a template (we could say, "an HTML template", but Tapestry is not limited to just HTML). In many cases, Tapestry will use a built-in Java class when you don't provide one; for a trivial page like ours, we don't need to supply a Java class at all.
We'll start with the HTML template then, which is a file named Home.html in the root of the web application context. In the project, it is stored as src/context/Home.html:
<html> <head> <title>Tutorial: HelloWorld</title> </head> <body> <h1>HelloWorld Tutorial</h1> </body> </html>
There's nothing special in this HTML template, nothing dynamic (not yet, anyway). We could access it as http://localhost:8080/helloworld/Home.html and see the same thing; but notice that in the screen shot the URL is http://localhost:8080/hellworld/app . That means that a servlet, mapped to the /app path within the web application, was responsible for the output you can see in the web browser.
Tapestry applications always use a specific servlet class provided with the framework. This is defined in the web deployment descriptor, web.xml. This file is stored in the project as src/context/WEB-INF/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>Tutorial: HelloWorld</display-name> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app</url-pattern> </servlet-mapping> </web-app>
Here we've given our application a name, "app". We're using the standard Tapestry ApplicationServlet class as our servlet, and mapped it to the path /app. The name you choose for you application is relatively unimportant, and Tapestry will adapt to whatever name you do choose.
The path, on the other hand, needs to be /app. This is not hard-coded into Tapestry, but does require a small amount of configuration if you choose to use another path. As well see in a later tutorial, Tapestry can be quite sophisticated in terms of building application URLs, so don't be too concerned about this aspect of Tapestry just yet.
That's all there is to HelloWorld in this phase. No Java code at all. Before going on to bigger and better things, we're going to add a little bit to this application, to give you a slightly more realistic feel for Tapestry ... but we'll still avoid writing any Java code at all.
We're going to change the Home page to display the current date and time. It will look something like this:
In Tapestry, pretty much any time anything dynamic occurs, there's going to be a component involved. Tapestry components are much like Tapestry pages ... they consist of a template and a Java class (purists may note that there may be an XML file to tie those together, and that the template and the Java class are both optional -- more on this later).
Tapestry components "hide" inside the HTML template. They look like ordinary HTML tags, but have extra attributes in them, often with unusual values. The revised Home.html template:
<html> <head> <title>Tutorial: HelloWorld</title> </head> <body> <h1>HelloWorld Tutorial</h1> <p> The current data and time is: <strong><span jwcid="@Insert" value="ognl:new java.util.Date()">June 26 2005</span></strong> </p> </body> </html>
The <span> tag is the placeholder within the template for the component. The special attribute, jwcid, is Tapestry's clue that this is a component, and not just ordinary HTML.
The "@Insert" value for the jwcid attribute can be thought of as "instance of the Insert component". Insert is one of many built-in Tapestry components. This isn't quite the Java class; it is a component type, which is used by Tapestry to find out about the component, such as what parameters can be configured and what Java class contains the logic for the component. Again, more on that later.
Before we get to the value parameter, a word about the body of the component. The body is the portion of the template enclosed by the component's start (<span>) and end (</span>) tags. Ultimately, the component itself determines when, if, or how many times it will render its body ("render" is the verb used throughout Tapestry meaning "write HTML output").
The Insert component expressly
does not
render its body. Any text inside the component's body is quietly discarded at
runtime. We could, in fact, abbreviate the component within the template to just
<span jwcid="@Insert" value="ognl:new java.util.Date()"/>
and not put any text inside its tags. So why did we?
The answer is previewability , that is, the ability to see, at least approximately, what the page will look like without running the actual application. You can load the Home.html file into a web browser, or a specialized editor such as Dreamweaver or HomeSite, and see what it looks like. For example, if we bypass the Tapestry servlet and access the template directly, we see the following:
That provisional text , "June 26 2005" is not exactly what the application will display at runtime ... but it's close enough; it's not blank and it's approximately right. In a real application with real style sheets and layouts, this would be enough validate that the layout of the running application was correct.
Note:This side track, about previewability, is actually one of the cornerstones of Tapestry: a clean seperation between logic and content. A non-Java HTML developer could edit this template and make significant changes and validate them in their editor of choice without involving a Java developer. As long as the HTML side of the team honors the components, the tags with a jwcid attribute, and doesn't make changes to those elements, the rest of the HTML template can be freely editted. It is only at the junction between content and runtime behavior, that is, inside components, that HTML and Java developers need to work together.
So, what does
value="ognl:new java.util.Date()"
mean? Let's start with the attribute value,
ognl:new java.util.Date
. The "ognl:" prefix signals to Tapestry that this is an expression to be evaluated,
rather than a ordinary, literal string. If we did want the Insert to always render
the same literal string, such as "Tapestry Rocks!", we wouldn't need the prefix, we
could just write
value="Tapestry Rocks!"
.
OGNL is the Object Graph Navigation Language, an open source expression language used by several open-source projects, including Tapestry, WebWork and Spring . OGNL has some astounding capabilities, not just reading and updating object properties, but also includes support for creating new objects entirely (as here), as well as creating lists, maps and arrays of objects.
Here, evaluating the expression results in a new instance of the java.util.Date
class. This Date instance is
bound
to the value parameter of the Insert component. "Bound" is another specialized
Tapestry term, one that concerns the relationship between a component parameter and
a property (or expression) of its container. Here, the component is the Insert
component, the container is the Home page, and the expression is
new java.util.Date
. Binding might look like just an assignment of a property of the Insert component,
but is a bit more; components often use bindings to
update
properties of their container, something we'll see when discussing the form element
components.
In the Java code for the Insert component is the logic that obtains value parameter and converts it into into a string that is rendered into the response.
So, the expression provides the Date instance, the value parameter gives the Insert component access to that value, and the Insert component provides the logic for converting that Date into a string and having it show up on the rendered page. Every time the Insert component renders, it will re-read its value parameter, causing the expression to be evaluated once more, and a new Date instance to be created. You can see this by hitting your browser's refresh button repeatedly; the displayed date will keep changing.
In the next section, we'll see how to create a link to get the displayed date to be updated.
We're going to extend the application once more, adding a refresh link that we can click instead of using the browser's refresh button. The end result looks like:
We created this new link by adding the following to Home.html:
<p> <a href="#" jwcid="@PageLink" page="Home">refresh</a> </p>
Again, anything dynamic in Tapestry is going to involve a component; here it's the PageLink component, one of a family of components that generate callback links into a Tapestry application.
Tapestry automatically creates a URL for this; you can see this URL in the screenshot: http://localhost:8080/workbench/app?page=Home&service=page. That URL provides two critical pieces of information: service=page means "render a page", and page=Home identifies which page to render.
Do I really need a component for that? You might be tempted to change the template to:
<a href="/app?service=page&page=Home">refresh</a>
This is a bad idea . Tapestry is doing more than spewing out a URL, it's session encoding the URL for you 1 and may be doing other useful things that we'll see later. Further, second guessing Tapestry's URLs is never a good idea; the PageLink component will be around in the next release of Tapestry, but the URL format may change between releases.
Another question: what's with the href attribute in the attribute? What does
href="#"
mean? This is another side to previewability: to preview this page, the link
ultimately generated by the PageLink needs to preview as a
link
. An <a> tag without an href attribute is an
anchor
. Again, this is a distinction that is more visible in real applications, supported
by a style sheet.
The href provided in the HTML template is simply discarded in favor of the href attribute generated inside the PageLink components (/app?service=...). You can actually mix and match these component-generated attributes with extra attributes provided in the template; these are called informal parameters , and are covered in a later tutorial.
That covers the PageLink component, at least for now. Next up: the DirectLink Tutorial .
1 Session encoding is an aspect of the Servlet API. Encoding a URL adds information about the server-side session (the HttpSession) if there is one. Although this information can be obtained via HTTP cookies, not all users have cookies enabled in their browser. The servlet specification encourages you to always encode your URLs, and this is simply done for you in Tapestry. It's another example of the basic Tapestry principle: make the simplest choice the correct choice .