Unlike many other web frameworks, such as Struts or WebWork , Tapestry does not "plug into" an external templating system such as JavaServer Pages or Velocity . Instead, Tapestry integrates its own templating system.
Tapestry templates are designed to look like valid HTML files (component HTML templates will just be snippets of HTML rather than complete pages). Tapestry "hides" its extensions into special attributes of ordinary HTML elements.
Don't be fooled by the terminology; we say "HTML templates" because that is the prevalent use of Tapestry ... but Tapestry is equally adept at WML or XML.
The general rule of thumb is that a page's HTML template is simply an HTML file, stored in the context root directory. That is, you'll have a MyPage.html HTML template, a WEB-INF/ MyPage.page page specification, and a MyPage class, in some Java package.
Tapestry always starts knowing the name of the page and the location of the page's specification when it searches for the page's HTML template. Starting with this, it performs the following search:
Tapestry templates contain a mix of the following elements:
Components can be placed anywhere inside a template, simply by adding the jwcid attribute to any existing tag. For example:
<html> <head> <title>Example HTML Template</title> </head> <body <span jwcid="border"> Hello, <span jwcid="@Insert" value="ognl:user.name">Joe User</span> </span> </body> </html>
The first span is a reference to a declared component ; the type and parameters of the component are declared in the page's specification.
The second span is an
implicit component
; the type of the component is
Insert
. The value parameter is bound to the
OGNL
expression
user.name
.
The point of all this is that the HTML template should preview properly in a WYSIWYG HTML editor. Unlike Velocity or JSPs, there are no strange directives to get in the way of a preview (or necessitate a special editting tool), Tapestry hides what's needed inside existing tags; at worst, it adds a few non-standard attributes (such as jwcid) to tags. This rarely causes a problem with most HTML editors.
Templates may contain components using two different styles. Declared components are little more than a placeholder; the type of the component is defined elsewhere, in the page (or component) specification.
Alternately, an implicit component can be defined in place, by preceding the component type with an "@" symbol. Tapestry includes over forty components with the framework, additional components may be created as part of your application, or may be provided inside a component library.
In the above example, a <span> was used for both components. Tapestry doesn't care what tag is used for a component, as long as the start and end tags for components balance (it doesn't even care if the case of the start tag matches the case of the end tag). The example could just as easily use <div> or <fred>, the rendered page sent back to the client web browser will be the same.
The default attribute name is
jwcid
, but there are occasions when that is not desirable. The
org.apache.tapestry.jwcid-attribute-name
configuration property
allows you to control the template parser's behavior.
In Tapestry, each component is responsible for rendering itself and its body . A component's body is the portion of its page's template that its tags encloses. The Tapestry HTML template parser is responsible for dividing up the template into chunks: blocks of static HTML, component start tags (recognized by the jwcid attribute) and matching component end tags. It is quite forgiving about case, quotes (which may be single quotes, double quotes, or even omitted), and missing close tags (except for components, which must be balanced).
Note:More correct would be to say "its container's template" as a component may be contained within another component. For simplicities sake, we'll describe this as if it was always a simple two-level heirarchy even though practical Tapestry applications can be many levels deep.
In most cases, a component will make use of its body; it simply controls if, when and how often its body is rendered (when rendering the HTML response sent to the client). Other components, such as Insert , have no use for their bodies, which they discard. Each component declares in its own specification (the allow-body attribute of the <component-specification> ) whether is allows or discards its body.
In the previous example, the
Insert
component had a body, the text "Joe User". This supports WYSIWYG preview;
the text will be displayed when previewing. Since the
Insert
component discards its body, this text will not be used at runtime, instead
the OGNL expression
user.name
will be evaluated and the result inserted into the response.
If you put a component inside the body of an Insert (or any other component that discards its body), then Tapestry will throw an exception. You aren't allowed to create a component simply to discard it.
Every component in Tapestry has its own id. In the above example, the first
component has the id "border". The second component is anonymous; the
framework provides a unique id for the component since one was not supplied
in the HTML template. The framework provided id is built from the
component's type; this component would have an id of
$Insert
; other
Insert
components would have ids
$Insert$0
,
$Insert$1
, etc.
A component's id must only be unique within its immediate container. Pages are top-level containers, but components may have their own templates, and so can also contain other components.
Implicit components can also have a specific id, by placing the id in front of the "@" symbol:
<span jwcid="insert@Insert" value="ognl:user.name">Joe User</span>
The component is still implicit; nothing about the component would go in the
specification, but the id of the component would be
insert
.
Providing explicit ids for your components is rarely required, but often beneficial. It is especially useful for form control components.
Each component may only appear once in the template. You simply can't use the same component repatedly ... but you can duplicate a component fairly easily; make the component a declared component, then use the copy-of attribute of the <component> element to create clones of the component with new ids.
Components are configured by binding their parameters. In a page or component specification, the <binding> element is used to bind component parameters.
Inside an HTML template, attributes of the tag are used to bind parameters. This can be very succinct. In some cases where an OGNL expression is used, the value can become quite long or complex ... in which case, converting the component to be a declared component (that is, defined in the page or component specification) and using the <binding> element will be more manageable.
Tapestry will merge together parameter bindings in the specification with those provided directly in the template. Generally speaking, conflicts (the same parameter bound in both places) will be an error. The exception is when the parameter bound in the HTML template, as an attribute, is a literal string value ... in which case, Tapestry assumes that the attribute value is there for WYSIWYG purposes and is quietly ignored.
Components may have both formal and informal parameters. The component specification defines each formal parameters using the <parameter> element, and a component indicates whether it accepts or rejects informal parameters with the allow-informal-parameters attribute of the <component-specification> element.
Informal parameters are not limited to simply strings; using binding reference prefixes, it is possible for them to be OGNL expressions, references to assets, or anything else.
If a component does not allow informal parameters, then attempting to bind any parameters (beyond the formal set of parameters for the component) is an error. The exception to this is literal values in the template, which are (again) assumed to be there for WYSIWYG purposes, and quietly ignored.
Two final notes about informal parameters:
Components may accept two types of parameters: formal and informal . Formal parameters are those defined in the component's specification, using the <parameter> element. Informal parameters are additional parameters, beyond those known when the component was created.
The majority of components that accept informal parameters simply emit the informal parameters as additional attributes. Why is that useful? Because it allows you to specify common HTML attributes such as class or id, or JavaScript event handlers, without requiring that each component define each possible HTML attribute (the list of which expands all the time).
Note:If you are used to developing with JSPs and JSP tags, this will be quite a difference. JSP tags have the equivalent of formal parameters (they are called "tag attributes"), but nothing like informal parameters. Often a relatively simply JSP tag must be bloated with dozens of extra attributes, to support arbitrary HTML attributes.
Informal and formal parameters can be specified in either the specification
or in the template. Informal parameters
are not
limited to literal strings, you may use the
ognl:
and
message:
prefixes with them as well.
Not all components allow informal parameters; this is controlled by the allow-informal-parameters attribute of the <component-specification> element. Many components do not map directly to an HTML element, those are the ones that do not allow informal parameters. If a component forbids informal parameters, then any informal parameters in the specification or the template will result in errors, with one exception: static strings in the HTML template are simply ignored when informal parameters are forbidden; they are presumed to be there only to support WYSIWYG preview.
Another conflict can occur when the HTML template specified an attribute
that the component needs to render itself. For example, the
DirectLink
component generates a
<a>
tag, and needs to control the href attribute. However, for preview purposes,
it often will be written into the HTML template as:
<a jwcid="@DirectLink" listener="listener:. . ." href="#"> . . . </a>
This creates a conflict: will the template href (the literal string "#") be used, or the dynamically generated URL produced by the DirectLink component, or both? The answer is: the component wins. The href attribute in the template is ignored.
Each component declares a list of reserved names using the <reserved-parameter> element; these are names which are not allowed as informal parameters, because the component generates the named attribute itself, and doesn't want the value it writes to be overriden by an informal parameter. Case is ignored when comparing attribute names to reserved names.
For the most part, a Tapestry page or component template consists of just static HTML intermixed with tags representing components (containing the jwcid attribute). The overarching goal is to make the Tapestry extensions completely invisible.
Tapestry supports a limited number of additional directives that are not about component placement, but instead address other concerns about integrating the efforts of HTML developers with the Java developers responsible for the running application.
Tapestry includes a number of
localization features
localization features. As we've seen, it is possible to access the messages
for a page or component using the
message:
prefix on a component parameter.
What about the static text in the template itself? How does that get translated? One possibility would be to make use of the Insert component for each piece of text to be displayed, for example:
<span jwcid="@Insert" value="message:hello">Hello</span>
This snippet will get the
hello
message from the page's message catalog and insert it into the response. The
text inside the <span> tag is useful for WYSIWYG preview, but will be
discarded at runtime in favor of a message string from the catalog, such as
"Hello", "Hola" or "Bonjour" (depending on the selected locale).
Because, in an internationalized application, this scenario will occur with great frequency, Tapestry includes a special directive to perform the equivalent function:
<span key="hello">Hello</span>
This is not an
Insert
component, but behaves in a similar way. The tag used
must be
<span>. You do not use the
message:
prefix on the message key (
hello
). You can't use OGNL expressions.
Normally, the <span> does not render, just the message. However, if you specify any additional attributes in the <span> tag (such as, commonly, id, class, or style, to specify a CSS style), then the <span> will render around the message. For example, the template:
<span class="error" key="invalid-access">Invalid Access</span>
might render as:
<span class="error">You do not have the necessary access.</span>
In this example, the placeholder text "Invalid Access" was replaced with a much longer message acquired from the message catalog.
In rare cases, your message may have pre-formatted HTML inside it. Normally,
output is filtered, so that any reserved HTML characters in a message string
are expanded to HTML entities. For example, a < will be expanded to
< If this is not desired, add the attribute value
raw="true"
to the <span>. This defeats the filtering, and text in the message is
passed through as-is.
HTML templates in Tapestry serve two purposes. On the one hand, they are used to dynamically render pages that end up in client web browsers. On the other hand, they allow HTML developers to use WYSIWYG editors to modify the pages without running the full application.
We've already seen two ways in which Tapestry accomidates WYSIWYG preview. Informal component parameters may be quietly dropped if they conflict with reserved names defined by the component. Components that discard their body may enclose static text used for WYSIWYG prefix.
In some cases, we need even more direct control over the content of the template. Consider, for example, the following HTML template:
<table> <tr> <th>First Name</th> <th>Last Name</h> </tr> <tr jwcid="loop"> <td><span jwcid="insertFirstName">John</span></td> <td><span jwcid="insertLastName">Doe</span></td> </tr> <tr> <td>Frank</td> <td>Smith</td> </tr> <tr> <td>Jane</td> <td>Jones</td> </tr> </table>
This is part of the HTML template that writes out the names of a list of
people, perhaps from some kind of database. When this page renders, the
loop
component (presumably a
For
, such details being in the page's specification) will render its body zero
or more times. So we might see rows for "Frank Miller", "Alan Moore" and so
forth (depending on the content of the database). However, every listing
will also include "Frank Smith" and "Jane Jones" ... because the HTML
developer left those two rows in, to ensure that the layout of the table was
correct with more than one row.
Tapestry allows a special jwcid,
$remove$
, for this case. A tag so marked is not a component, but is instead
eliminated from the template. It is used, as in this case, to mark sections
of the template that are just there for WYSIWYG preview.
Normally,
$remove$
would not be a valid component id, because it contains a dollar sign.
With this in mind, the template can be rewritten:
<table> <tr> <th>First Name</th> <th>Last Name</h> </tr> <tr jwcid="loop"> <td><span jwcid="insertFirstName">John</span></td> <td><span jwcid="insertLastName">Doe</span></td> </tr> <tr jwcid="$remove$"> <td>Frank</td> <td>Smith</td> </tr> <tr jwcid="$remove$"> <td>Jane</td> <td>Jones</td> </tr> </table>
With the
$remove$
blocks in place, the output is as expected, one table row for each row read
from the database, and "Frank Smith" and "Jane Jones" nowhere to be seen.
It's not allowed to put components inside a removed block. This is effectively the same rule that prevents components from being put inside discarded component bodies. Tapestry will throw an exception if a template violates this rule.
In Tapestry, components can have their own templates. Because of how components integrate their own templates with their bodies (the portion from their container's template), you can do a lot of interesting things. It is very common for a Tapestry application to have a Border component: a component that produces the <html>, <head>, and <body> tags (along with additional tags to reference stylesheets), plus some form of navigational control (typically, a nested table and a collection of links and images).
Once again, maintaining the ability to use WYSIWYG preview is a problem. Consider the following:
<html> <head> <title>Home page</title> <link ref="stylesheet" href="style.css" type="text/css"/> </head> <body> <span jwcid="border"> <!-- Page specific content: --> <form jwcid=". . ."> . . . </form> </span> </body> </html>
It is quite common for Tapestry applications to have a
Border
component, a component that is used by pages to provide the <html>,
<head>, and <body> tags, plus common navigational features
(menus, copyrights, and so forth). In this example, it is presumed that the
border
component is a reference to just such as component.
When this page renders, the page template will provide the <html>,
<head> and <body> tags. Then when the
border
component renders, it will
again
render those tags (possibly with different attributes, and mixed in with
much other stuff).
If we put a
$remove$
on the <html> tag in the page template, the entire page will be
removed, causing runtime exceptions.
Instead, we want to identify that the portion of the template
inside
the <body> tag (on the page template) as the only part that should be
used. The
$content$
component id is used for this purpose:
<html> <head> <title>Home page</title> <link ref="stylesheet" href="style.css" type="text/css"/> </head> <body jwcid="$content$"> <span jwcid="border"> <!-- Page specific content: --> <form jwcid=". . ."> . . . </form> </span> </body> </html>
The <body> tag, the text preceding its open tag, the </body>
tag, and the text following it are all removed. It's as if the template
consisted only of the <span> tag for the
border
component.