blob: f605f72a7c07ad0b5388efcc1aed73920ce2a53a [file]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2004, 2005 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<document>
<properties>
<title>Creating Tapestry components</title>
</properties>
<body>
<section name="Creating Tapestry Components">
<p>
Tapestry is a component based web application framework; components, objects which
implement the
<a href="../apidocs/org/apache/tapestry/IComponent.html">
IComponent
</a>
interface, are the fundamental building blocks of Tapestry. Additional objects, such
as the the engine,
<a href="../apidocs/org/apache/tapestry/IMarkupWriter.html">
IMarkupWriter
</a>
and the request cycle are infrastructure. The following figure identifies the core
Tapestry classes and interfaces.
</p>
<img alt="Core Tapestry Classes and Interfaces"
src="../images/UsersGuide/core-classes.png" />
<p>
Tapestry components can be simple or complex. They can be specific to a single
application or completely generic. They can be part of an application, or they can
be packaged into a
<a href="#components.libraries">component library</a>
.
</p>
<p>
All the techniques used with pages work with components as well ... pages are a
specialized kind of Tapestry component. This includes
<a href="state.html#state.page-properties">specified properties</a>
(including persistent properties) and
<a href="listenermethods.html">listener method</a>
s.
</p>
<p>
Components fit into the overall page rendering process because they implement the
<a href="../apidocs/org/apache/tapestry/IRender.html">IRender</a>
interface. Components that inherit from
<a href="../apidocs/org/apache/tapestry/BaseComponent.html">
BaseComponent
</a>
will use an HTML template. Components that inherit from
<a
href="../apidocs/org/apache/tapestry/AbstractComponent.html">
AbstractComponent
</a>
will render output in Java code, by implementing method
<code>renderComponent()</code>
.
</p>
<p>
The components provided with the framework are not special in any way: they don't
have access to any special APIs or perform any special down-casts. Anything a
framework component can do, can be done by your own components.
</p>
<subsection name="Component Specifications">
<p>
Every component has a component specification, a file ending with a .jwc
extension, whose root element is
<a href="spec.html#spec.component-specification">
&lt;component-specification&gt;
</a>
.
</p>
<p>
Each component's specification defines the basic characteristics of the
component:
</p>
<ul>
<li>
The Java class for the component (which defaults to
<a
href="../apidocs/org/apache/tapestry/BaseComponent.html">
BaseComponent
</a>
)
</li>
<li>
Whether the component uses its body, or discards it (the allow-body
attribute, which defaults to yes)
</li>
<li>
<p>
The name, type and other information for each
<em>formal</em>
parameter.
</p>
</li>
<li>
<p>
Whether the component allows informal parameters or discards them (the
allow-informal-parameters attribute, which defaults to yes)
</p>
</li>
<li>
<p>
The names of any
<em>reserved parameters</em>
which may
<em>not</em>
be used as informal parameters.
</p>
</li>
</ul>
<p>
Beyond those additions, a component specification is otherwise the same as a
<a href="spec.html#spec.page-specification">&lt;page-specification&gt;</a>
.
</p>
<p>
When a component is referenced in an HTML template (using the @
<em>Type</em>
syntax), or in a specification (as the type attribute of a
<a href="spec.html#spec.component">&lt;component&gt;</a>
element), Tapestry must locate and parse the component's specification (this is
only done once, with the result cached for later).
</p>
<p>Tapestry searches for components in the following places:</p>
<ul>
<li>
As specified in a
<a href="spec.html#spec.component-type">&lt;component-type&gt;</a>
element (within the application specification).
</li>
<li>
In the same folder as the application specification, which is typically the
WEB-INF folder.
</li>
<li>
In the WEB-INF/
<em>servlet-name</em>
folder (
<em>servlet-name</em>
is the name of the Tapestry
<a
href="../apidocs/org/apache/tapestry/ApplicationServlet.html">
ApplicationServlet
</a>
for the application).
</li>
<li>In the WEB-INF folder.</li>
<li>In the root context directory.</li>
</ul>
<span class="info">
<strong>Note:</strong>
<p>
The second option, WEB-INF/
<em>servlet-name</em>
, exists to support the rare case of a single WAR file containing multiple
Tapestry applications.
</p>
</span>
<p>
Generally, the
<em>correct</em>
place is in the WEB-INF folder.
<a href="#components.libraries">Components packaged into libraries</a>
have a different (and simpler) search.
</p>
</subsection><!-- components.spec -->
<subsection name="Coding components">
<p>
When creating a new component by subclassing
<a
href="../apidocs/org/apache/tapestry/AbstractComponent.html">
AbstractComponent
</a>
, you must implement the
<code>renderComponent()</code>
method. This method is invoked when the component's container (typically, but
not always, a page) invokes its own
<code>renderBody()</code>
method.
</p>
<source xml:space="preserve">
protected void renderComponent(<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">IMarkupWriter</a> writer, <a
href="../apidocs/org/apache/tapestry/IRequestCycle.html">IRequestCycle</a> cycle)
{
. . .
}
</source>
<p>
The
<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">
IMarkupWriter
</a>
object is used to produce output. It contains a number of
<code>print()</code>
methods that output text (the method is overloaded for different types). It also
contains
<code>printRaw()</code>
methods -- the difference being that
<code>print()</code>
uses a filter to convert certain characters into HTML entities.
</p>
<p>
<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">
IMarkupWriter
</a>
also includes methods to simplify creating markup style output: that is,
elements with attributes.
</p>
<p>For example, to create a &lt;a&gt; link:</p>
<source xml:space="preserve">
public void renderComponent(<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">IMarkupWriter</a> writer, <a
href="../apidocs/org/apache/tapestry/IRequestCycle.html">IRequestCycle</a> cycle)
{
. . .
writer.begin("a");
writer.attribute("url", url);
writer.attribute("class", styleClass);
renderBody(writer, cycle);
writer.end(); // close the &lt;a&gt;
}
</source>
<p>
The
<code>begin()</code>
method renders an open tag (the &lt;a&gt;, in this case). The
<code>end()</code>
method renders the corresponding &lt;/a&gt;. As you can see, writing attributes
into the tag is equally simple.
</p>
<p>
The call to
<code>renderBody()</code>
is used to render
<em>this</em>
component's body. A component doesn't have to render its body; the standard
<a href="../components/general/image.html">Image</a>
component doesn't render its body (and its component specification indicates
that it discards its body). The
<a href="../components/general/if.html">If</a>
component decides whether or not to render its body, and the
<a href="../components/general/for.html">For</a>
component may render its body multiple times.
</p>
<p>A component that allows informal parameters can render those as well:</p>
<source xml:space="preserve">
writer.beginEmpty("img");
writer.attribute("src", imageURL);
renderInformalParameters(writer, cycle);
</source>
<p>
This example will add any informal parameters for the component as additional
attributes within the &lt;img&gt; element. These informal parameters can be
specified in the page's HTML template, or within the
<a href="spec.html#spec.component">&lt;component&gt;</a>
tag of the page's specification. Note the use of the
<code>beginEmpty()</code>
method, for creating a start tag that is not balanced with an end tag (or a call
to the
<code>end()</code>
method).
</p>
</subsection><!-- components.coding -->
<subsection name="Component Parameters">
<p>
A Tapestry page consists of a number of components. These components communicate
with, and coordinate with, the page (and each other) via
<em>parameters</em>
.
</p>
<p>
A formal component parameter has a unique name, and may be optional or required.
Optional parameters may have a default value. The
<a href="spec.html#spec.parameter">&lt;parameter&gt;</a>
component specification element is used to define formal component parameters.
</p>
<p>
In a traditional desktop application, components have
<em>properties</em>
. A controller may set the properties of a component, but that's it: properties
are write-and-forget.
</p>
<p>
The Tapestry model is a little more complex. A component's parameters are
<em>bound</em>
to properties of the enclosing page (or component). The component is allowed to
read its parameter, to access the page property the parameter is bound to. A
component may also
<em>update</em>
its parameter, to force a change to the bound page property. In fact, behind the
scenes, each component parameter has a
<em>binding</em>
object, an instance of type
<a href="../apidocs/org/apache/tapestry/IBinding.html">
IBinding
</a>
, that is used to read or update the property.
</p>
<p>
The vast majority of components simply read their parameters. Updating
parameters is more rare; the most common components that update their parameters
are form element components such as
<a href="../components/form/textfield.html">TextField</a>
or
<a href="../components/form/checkbox.html">Checkbox</a>
.
</p>
<p>
Because bindings are often in the form of
<a href="http://www.ognl.org">OGNL</a>
expressions, the property bound to a component parameter may not directly be a
property of the page ... using a property sequence allows great flexibility.
</p>
<img alt="Parameter Bindings" src="../images/UsersGuide/parameter-bindings.png" />
<p>
Using
<a href="http://www.ognl.org">OGNL</a>
, the
<a href="../components/form/textfield.html">TextField</a>
component's value parameter is bound to the LineItem's quantity property, using
the OGNL expression lineItem.quantity, and the
<a href="../components/general/insert.html">Insert</a>
component's value parameter is bound to the Product's name property using the
OGNL expression lineItem.product.name.
</p>
<p>
When using localized messages (the message: prefix) or literal strings (no
prefix), there is still a binding object, just a binding of a different type.
Not all bindings are writable. OGNL expressions may be writeable, if the
expression identifies a property that is itself writeable. Most other types of
bindings are read only.
</p>
<p>
To access a component parameter inside Java code is simply a matter of defining
an accessor method. For example, if your component has a title parameter, then
you define a getTitle() accessor method:
</p>
<source xml:space="preserve">
public abstract String getTitle();
public void renderComponent(<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">IMarkupWriter</a> writer, <a
href="../apidocs/org/apache/tapestry/IRequestCycle.html">IRequestCycle</a> cycle)
{
writer.begin("a");
writer.attribute("href", . . .);
writer.attribute("title", getTitle());
. . .
}
</source>
<p>
When your code invokes getTitle(), the binding for the title parameter will be
used to obtain a value, which is returned. Likewise, invoking setTitle() will
use the binding for the title parameter to update the bound value.
</p>
<span class="info">
<strong>Note:</strong>
<p>
If you are upgrading from Tapestry 3.0, you may be wondering "how do I
specify parameter direction now?". Parameter direction was a hint you would
provide to Tapestry that would tell Tapestry when it was appropriate to copy
values into, or out of, component parameter properties. This is no longer
necessary in Tapestry 4.0 -- the runtime code generation for parameter
properties is much more sophisticated. All parameters are now similar to
Tapestry 3.0's auto direction, but much smarter. Tapestry 3.0 auto
parameters were only useable with required parameters and were inefficient.
In Tapestry 4.0, parameter values are cached such that the OGNL expression
does not have to be evaluated every time the parameter is accessed and
things still work properly for optional parameters.
</p>
</span>
<p>
There are two ways to set default values for parameters. You may provide a
default-value attribute in the
<a href="spec.html#spec.parameter">&lt;parameter&gt;</a>
element. This is, effectively, a binding to use if no binding is provided.
</p>
<source xml:space="preserve">
&lt;parameter name="title" default-value="literal:Link to current thread"/&gt;
</source>
<p>
Remember that outside of the template, all
<a href="bindings.html">binding reference</a>
s, including the default-value attribute, default to OGNL expressions.
Therefore, it is necessary to prefix the default value with the literal: prefix
to ensure that Tapestry doesn't treat it as an expression.
</p>
<p>
What's nice is that the default value doesn't have to be a simple string; it can
be a computed OGNL expression, or a reference to a localized message:
</p>
<source xml:space="preserve">
&lt;parameter name="title" default-value="message:link-title"/&gt;
</source>
<source xml:space="preserve">
link-title=Link to current thread
</source>
<p>
The second approach to defining default values for parameters is to set the
parameter's property from the component's finishLoad() method.
</p>
<source xml:space="preserve">
public abstract void setTitle(String title);
protected void finishLoad()
{
super.finishLoad();
setTitle("Link to current thread");
}
</source>
<p>
Even with parameter defaults, there are times when you want to behave
differently depending on whether a parameter is bound or not bound. The method
isParameterBound() exists for those cases:
</p>
<source xml:space="preserve">
public abstract String getTitle();
public void renderComponent(<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">IMarkupWriter</a> writer, <a
href="../apidocs/org/apache/tapestry/IRequestCycle.html">IRequestCycle</a> cycle)
{
writer.begin("a");
writer.attribute("href", . . .);
if (isParameterBound("title"))
writer.attribute("title", getTitle());
. . .
}
</source>
<p>
Using isParameterBound() is most useful with parameters whose type is a
primitive type. In the previous example, we could simply invoke getTitle() and
see if the result is null. For, say, an int property, we would need a way to
distinguish between 0 and no value provided ... that's what isParameterBound()
is for.
</p>
<p>
Note that you always pass the name of the
<em>parameter</em>
to the isParameterBound() method, even when you've used the property attribute
of the
<a href="spec.html#spec.parameter">&lt;parameter&gt;</a>
element to use a different property name:
</p>
<source xml:space="preserve">
&lt;parameter name="title" property="titleParameter"/&gt;
</source>
<source xml:space="preserve">
public abstract String getTitleParameter();
public void renderComponent(<a
href="../apidocs/org/apache/tapestry/IMarkupWriter.html">IMarkupWriter</a> writer, <a
href="../apidocs/org/apache/tapestry/IRequestCycle.html">IRequestCycle</a> cycle)
{
writer.begin("a");
writer.attribute("href", . . .);
if (isParameterBound("title"))
writer.attribute("title", getTitleParameter());
. . .
}
</source>
<p>
When Tapestry enhances a class to add a component property, it (by default)
caches the value of the binding for the duration of the component's render. That
is, while a component is rendering, it will (at most) use the parameter's
binding once, and store the result internally, clearing the cached value as the
component finishes rendering. The parameter property
<em>can be accessed when the component is not rendering</em>
(an important improvement from Tapestry 3.0), the result simply is not cached
(each access to the property when the component is not rendering is another
access via the binding object).
</p>
<p>
This caching behavior is not always desired; in some cases, the component
operates best with caching disabled. The
<a href="spec.html#spec.parameter">&lt;parameter&gt;</a>
element's cache parameter can be set to "false" to defeat this caching.
</p>
<p>
However, for the majority of binding types (most types except for "ognl"), the
value obtained is invariant ... it will always be the same value. Values
obtained from invariant bindings are
<em>always</em>
cached
<em>indefinately</em>
(not just for the component's render). In other words, literal string values,
localized messages and so forth are accessed via the binding just once. This is
great for
<em>efficiency</em>
; after "warming up", a Tapestry page will render faster the second time
through, because so many component parameters are invariant and already in place
inside component properties.
</p>
<p>
On the other hand,
<em>informal parameters</em>
are not cached at all; the values for such parameters are always re-obtained
from the binding object on each use.
</p>
<span class="info">
<strong>Note:</strong>
<p>
When using 3.0 DTDs with Tapestry 4.0, parameters with direction "auto" are
<em>not cached</em>
. Other direction types (or no direction specified) are cached. There is no
real support for direction "custom" in 4.0 ... all parameters will be
realized as parameter properties.
</p>
</span>
</subsection><!-- components.parameters -->
<subsection name="Component Libraries">
<p>
Tapestry has a very advanced concept of a
<em>component library</em>
. A component library contains both Tapestry components and Tapestry pages (not
to mention engine services).
</p>
<subsection name="Referencing Library Components">
<p>
Before a component library may be used, it must be listed in the application
specification. Often, an application specification is
<em>only</em>
needed so that it may list the libraries used by the application. Libraries
are identified using the
<a href="spec.html#spec.library">&lt;library&gt;</a>
element.
</p>
<p>
The
<a href="spec.html#spec.library">&lt;library&gt;</a>
element provides an
<em>id</em>
for the library, which is used to reference components (and pages) within
the library. It also provides a path to the library's specification. This is
a complete path for a .library file on the classpath. For example:
</p>
<source xml:space="preserve">
&lt;application name="Example Application"&gt;
&lt;library id="contrib" specification-path="/org/apache/tapestry/contrib/Contrib.library"/&gt;
&lt;/application&gt;</source>
<p>
In this example, Contrib.library defines a set of components, and those
component can be accessed using contrib: as a prefix. In an HTML template,
this might appear as:
</p>
<source xml:space="preserve">
&lt;span jwcid="palette@contrib:Palette" . . . /&gt;
</source>
<p>
This example defines a component with id
<code>palette</code>
. The component will be an instance of the Palette component, supplied
within the contrib component library. If an application uses multiple
libraries, they will each have their own prefix. Unlike JSPs and JSP tag
libraries, the prefix is set once, in the application specification, and is
used consistently in all HTML templates and specifications within the
application.
</p>
<p>The same syntax may be used in page and component specifications:</p>
<source xml:space="preserve">
&lt;component id="palette" type="contrib:Palette"&gt;
. . .
&lt;/component&gt;
</source>
</subsection><!-- components.libraries.ref -->
<subsection name="Library component search path">
<p>
<a href="#components.spec">Previously</a>
, we described the search path for components and pages within the
application. The rules are somewhat different for components and pages
within a library.
</p>
<p>
Tapestry searches for library component specifications in the following
places:
</p>
<ul>
<li>
As specified in a
<a href="spec.html#spec.component-type">&lt;component-type&gt;</a>
element (with the library specification).
</li>
<li>In the same package folder as the library specification.</li>
</ul>
<p>
The search for page specifications is identical: as defined in the library
specification, or in the same package folder.
</p>
</subsection><!-- components.libraries.search -->
<subsection name="Using Private Assets">
<p>
Often, a component must be packaged up with images, stylesheets or other
resources (collectively termed "assets") that are needed at runtime. A
reference to such an asset can be created using the
<a href="spec.html#spec.asset">&lt;asset&gt;</a>
element of the page or component specification. For example:
</p>
<source xml:space="preserve">
&lt;asset name="logo" path="images/logo_200.png"/&gt;
&lt;component id="image" type="Image"&gt;
&lt;binding name="image" value="asset:logo"/&gt;
&lt;/component&gt;
</source>
<p>
In this case, if the component is packaged as
/com/example/mylibrary/MyComponent.jwc, then the asset will be
/com/examples/mylibrary/images/logo_200.png. Further, the asset path will be
localized.
</p>
<p>
All assets (classpath, context or external) are converted into instances of
<a href="../apidocs/org/apache/tapestry/IAsset.html">
IAsset
</a>
and treated identically by components (such as
<a href="../components/general/image.html">Image</a>
). As in this example, relative paths are allowed: they are interpreted
relative to the specification (page or component) they appear in.
</p>
<p>
The Tapestry framework will ensure that an asset will be converted to a
valid URL that may be referenced from a client web browser ... even though
the actual service is inside a JAR or otherwise on the classpath, not
normally referenceable from the client browser.
</p>
<p>
The
<em>default</em>
behavior is to serve up the
<em>localized</em>
resource using the asset service. In effect, the framework will read the
contents of the asset and pipe that binary content down to the client web
browser.
</p>
<p>
An alternate behavior is to have the framework copy the asset to a fixed
directory. This directory should be mapped to a known web folder; that is,
have a URL that can be referenced from a client web browser. In this way,
the web server can more efficiently serve up the asset, as a static resource
(that just happens to be copied into place in a just-in-time manner).
</p>
<p>
This behavior is controlled by a pair of
<a href="configuration.html#configuration.properties">
configuration properties
</a>
: org.apache.tapestry.asset.dir and org.apache.tapestry.asset.URL.
</p>
</subsection><!-- components.libraries.classpath-assets -->
<subsection name="Library Specifications">
<p>
A library specification is a file with a .library extension. Library
specifications use a root element of
<a href="spec.html#spec.library-specification">
&lt;library-specification&gt;
</a>
, which supports a subset of the attributes allowed within an
<a href="spec.html#spec.application">&lt;application&gt;</a>
element (but allowing the
<em>same</em>
nested elements). Often, the library specification is an empty placeholder,
used to an establish a search location for page and component
specifications:
</p>
<source xml:space="preserve">
&lt;!DOCTYPE library-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"&gt;
&lt;library-specification/&gt;
</source>
<p>
It is allowed that components in one library be constructed using components
provided by another library. The referencing library's specification may
contain
<a href="spec.html#spec.library">&lt;library&gt;</a>
elements that identify some other library.
</p>
</subsection><!-- comopnents.libraries.spec -->
<subsection name="Libraries and Namespaces">
<p>
Tapestry organizes components and pages (but
<em>not</em>
engine services) into
<em>namespaces</em>
. Namespaces are closely related to, but not exactly the same as, the
library prefix established using the
<a href="spec.html#spec.library">&lt;library&gt;</a>
element in an application or library specification.
</p>
<p>
Every Tapestry application consists of a default namespace, the application
namespace. This is the namespace used when referencing a page or component
without a prefix. When a page or component can't be resolved within the
application namespace, the framework namespace is searched. Only if the
component (or page) is not part of the framework namespace does an error
result.
</p>
<p>
In fact, it is possible to override both pages and components provided by
the framework. This is frequently used to change the look and feel of the
default StateSession or Exception page. In theory, it is even possible to
override fundamental components such as
<a href="../components/general/insert.html">Insert</a>
or
<a href="../components/general/for.html">For</a>
!
</p>
<p>
Every component provides a namespace property that defines the namespace (an
instance of
<a
href="../apidocs/org/apache/tapestry/INamespace.html">
INamespace
</a>
) that the component belongs to.
</p>
<p>
You rarely need to be concerned with namespaces, however. The rare exception
is when a page from a library wishes to make use of the
<a href="../components/link/pagelink.html">PageLink</a>
or
<a href="../components/link/externallink.html">ExternalLink</a>
components to create a link to
<em>another page</em>
within the same namespace. This is accomplished (in the source page's HTML
template) as:
</p>
<source xml:space="preserve">
&lt;a href="#" jwcid="@PageLink" page="OtherPage" namespace="ognl:namespace"&gt; ... &lt;/a&gt;
</source>
</subsection><!-- components.libraries.namespace -->
</subsection><!-- components.libraries -->
</section>
</body>
</document>