blob: 9123214206ed457c84f34cf9ef132b62ca3b84d6 [file] [log] [blame]
<!-- $Id$ -->
<!--
Copyright 2004 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.
-->
<chapter id="components">
<title>Creating Tapestry components</title>
<section id="components.intro">
<title>Introduction</title>
<para>
Tapestry is a component based web application framework; components, objects which implement
the &IComponent; interface, are the fundamental building blocks of Tapestry. Additional objects,
such as the the engine, &IMarkupWriter; and the request cycle are infrastructure. The following figure
identifies the core Tapestry classes and interfaces.
</para>
<figure>
<title>Core Tapestry Classes and Interfaces</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/core-classes.png" format="PNG"/>
</imageobject>
</mediaobject>
</figure>
<para>
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 <link linkend="components.libraries">component library</link>.
</para>
<para>
All the techniques used with pages work with components as well ... pages are a specialized kind
of Tapestry component. This includes
<link linkend="state.page-properties">specified properties</link> (including persistent properties)
and &listener-method;s.
</para>
<para>
Components fit into the overall page rendering process because they implement the &IRender; interface.
Components that inherit from &BaseComponent; will use an HTML template. Components that inherit
from &AbstractComponent; will render output in Java code, by implementing method
<function>renderComponent()</function>.
</para>
<para>
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.
</para>
</section> <!-- components.intro -->
<section id="components.spec">
<title>Component Specifications</title>
<para>
Every component has a component specification, a file ending in <filename>.jwc</filename> whose
root element is &spec.component-specification;.
</para>
<para>
Each component's specification defines the basic characteristics of the component:
<itemizedlist>
<listitem>
<para>The Java class for the component (which defaults to &BaseComponent;)</para>
</listitem>
<listitem>
<para>Whether the component uses its body, or discards it (the <literal>allow-body</literal> attribute,
which defaults to <literal>yes</literal>)</para>
</listitem>
<listitem>
<para>
The name, type and other information for each <emphasis>formal</emphasis>
parameter.
</para>
</listitem>
<listitem>
<para>
Whether the component allows informal parameters or discards them
(the <literal>allow-informal-parameters</literal> attribute, which defaults to
<literal>yes</literal>)
</para>
</listitem>
<listitem>
<para>
The names of any <emphasis>reserved parameters</emphasis> which may <emphasis>not</emphasis>
be used as informal parameters.
</para>
</listitem>
</itemizedlist>
</para>
<para>
Beyond those additions, a component specification is otherwise the same as a &spec.page-specification;.
</para>
<para>
When a component is referenced in an HTML template (using the <literal>@<replaceable>Type</replaceable></literal>
syntax), or in a specification (as the <literal>type</literal> attribute of
a &spec.component; element), Tapestry must locate and parse the component's specification (this is only done once, with the
result cached for later).
</para>
<para>
Tapestry searches for components in the following places:
<itemizedlist>
<listitem>
<para>As specified in a &spec.component-type; element (with the application specification)</para>
</listitem>
<listitem>
<para>In the same folder (typically, <filename>WEB-INF</filename> ) as the
application specification</para>
</listitem>
<listitem>
<para>
In the <filename>WEB-INF/<replaceable>servlet-name</replaceable></filename> folder
(<replaceable>servlet-name</replaceable> is the name of the Tapestry &ApplicationServlet;
for the application)
<footnote>
<para>
This is a very rare option that will only occur when a single WAR file contains
multiple Tapestry applications.
</para>
</footnote>
</para>
</listitem>
<listitem>
<para>
In the <filename>WEB-INF</filename> folder
</para>
</listitem>
<listitem>
<para>
In the root context directory
</para>
</listitem>
</itemizedlist>
</para>
<para>
Generally, the <emphasis>correct</emphasis> place is in the
<filename>WEB-INF</filename> folder. <link linkend="components.libraries">Components packaged into
libraries</link> have a different (and simpler) search.
</para>
</section> <!-- components.spec -->
<section id="components.coding">
<title>Coding components</title>
<para>
When creating a new component by subclassing &AbstractComponent;, you must write the
<function>renderComponent()</function> method. This method is invoked when the components container (typically, but not always,
a page) invokes its own <function>renderBody()</function> method.
</para>
<programlisting>
protected void renderComponent(&IMarkupWriter; writer, &IRequestCycle; cycle)
{
. . .
}
</programlisting>
<para>
The &IMarkupWriter; object is used to produce output. It contains a number of <function>print()</function> methods
that output text (the method is overloaded for different types). It also contains <function>printRaw()</function>
methods -- the difference being that <function>print()</function> uses a filter to convert certain characters
into HTML entities.
</para>
<para>
&IMarkupWriter; also includes methods to simplify creating markup style output: that is, elements with attributes.
</para>
<para>
For example, to create a <sgmltag class="starttag">a</sgmltag> link:
<informalexample>
<programlisting>
writer.begin("a");
writer.attribute("url", url);
writer.attribute("class", styleClass);
renderBody(writer, cycle);
writer.end(); // close the &lt;a&gt;
</programlisting>
</informalexample>
</para>
<para>
The <function>begin()</function> method renders an open tag (the <sgmltag class="starttag">a</sgmltag>, in
this case). The <function>end()</function> method renders
the corresponding <sgmltag class="closetag">a</sgmltag>. As you can see, writing attributes into the tag
is very simple.
</para>
<para>
The call to <function>renderBody()</function> is used to render <emphasis>this</emphasis> component's
body. A component doesn't have to render its body; the standard &Image; component doesn't render its
body (and its component specification indicates that it discards its body). The &Conditional; component
decides whether or not to render its body, and the
&Foreach; component may render its body multiple times.
</para>
<para>
A component that allows informal parameters can render those as well:
<informalexample><programlisting>
writer.beginEmpty("img");
writer.attribute("src", imageURL);
renderInformalParameters(writer, cycle);
</programlisting>
</informalexample>
</para>
<para>
This example will add any informal parameters for the component
as additional attributes within the <sgmltag class="starttag">img</sgmltag> element. These informal parameters
can be specified in the page's HTML template, or within the &spec.component; tag of the page's specification. Note the use
of the <function>beginEmpty()</function> method, for creating a start tag that is not balanced with an end tag (or
a call to the <function>end()</function> method).
</para>
</section> <!-- components.coding -->
<section id="components.parameters">
<title>Component Parameters</title>
<para>
A Tapestry page consists of a number of components. These components communicate with, and coordinate with,
the page (and each other) via <emphasis>parameters</emphasis>.
</para>
<para>
A component parameter has a unique name and a type (a Java class, interface, or primitive type name).
The &spec.parameter; component specification element is used to define formal component parameters.
</para>
<para>
In a traditional desktop application, components have <emphasis>properties</emphasis>. A controller may
set the properties of a component, but that's it: properties are write-and-forget.
</para>
<para>
The Tapestry model is a little more complex. A component's parameters are <emphasis>bound</emphasis>
to properties of the enclosing page. The component is allowed to read its parameter, to access
the page property the parameter is bound to. A component may also <emphasis>update</emphasis> its
parameter, to force a change to the bound page property.
</para>
<para>
The vast majority of components simply read their parameters. Updating parameters is more rare; the most
common components that update their parameters are form control components such as &TextField; or &Checkbox;.
</para>
<para>
Because bindings are in the form of &OGNL; expressions, the property bound to a component parameter
may not directly be a property of the page ... using a property sequence allows great flexibility.
</para>
<figure>
<title>Parameter Bindings</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/parameter-bindings.png" format="PNG"/>
</imageobject>
<caption>
<para>
Using &OGNL;, the &TextField; component's <varname>value</varname> parameter is bound
to the <classname>LineItem</classname>'s <varname>quantity</varname> property, using
the OGNL expression <literal>lineItem.quantity</literal>, and the &Insert; component's
<varname>value</varname> parameter is bound to the <classname>Product</classname>'s
<varname>name</varname> property using the OGNL expression <literal>lineItem.product.name</literal>.
</para>
</caption>
</mediaobject>
</figure>
<para>
Not all parameter bindings are writable. So far, the examples have been for parameters bound using
the &spec.binding; specification element (or the equivalent use of the <literal>ognl:</literal> prefix
in an HTML template). <emphasis>Invariant bindings</emphasis> are also possible--these are bindings directly to fixed
values that never change and can't be updated. The &spec.static-binding; element is invariant; it's
HTML template equivalent is a attribute with no prefix. Likewise, the
&spec.message-binding; element, and the <literal>message:</literal> prefix on an attribute, are invariant.
</para>
<section id="components.parameters.bindings">
<title>Using Bindings</title>
<para>
To understand how Tapestry parameters work, you must understand how the binding objects work (even
though, as we'll see, the binding objects are typically hidden). When a component needs access to
a bound parameter value, it will invoke the method <code>getObject()</code> on &IBinding;
</para>
<figure>
<title>Reading a Parameter</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/read-binding.png" format="PNG"/>
</imageobject>
<caption>
<para>
The <function>getObject()</function> method on &IBinding; will (if the binding is dynamic) evaluate the
OGNL expression (provided in the &spec.binding; specification element) to access a property of the
page. The result is that cast or otherwise coerced to a type useful to the component.
</para>
</caption>
</mediaobject>
</figure>
<para>
Updating a parameter is the same way, except that the method is
<function>setObject()</function>. Most of the implementations of &IBinding; (those for literal strings
and localize messages), will throw an exception immediately, since they are invariant.
</para>
<figure>
<title>Writing a Parameter</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/write-binding.png" format="PNG"/>
</imageobject>
<caption>
<para>
The <function>setObject()</function> method will use OGNL to update a page property.
</para>
</caption>
</mediaobject>
</figure>
<para>
These flows are complicated by the fact that parameters may be optional; so not only do you need to acquire the
correct binding object (method <function>getBinding()</function> defined in &IComponent;),
but your code must be prepared for that object to be null (if the parameter
is optional).
</para>
</section> <!-- components.parameters.bindings -->
<section id="components.parameters.connected">
<title>Connected Parameter Properties</title>
<para>
Accessing and manipulating the &IBinding; objects is tedious, so Tapestry has an alternate approach. Parameters may
be represented as <emphasis>connected parameter properties</emphasis> that hide the existence of
the binding objects entirely. If you component needs to know the value bound to a parameter, it can
read the connected parameter property. If it wants to update the property bound to the parameter, the component
will update the connected parameter. This is a much more natural approach, but requires a little bit of setup.
</para>
<para>
As with <link linkend="state.page-properties">specified properties</link>, Tapestry will fabricate an enhanced subclass with
the necessary instance variables, accessor methods, and cleanup code.
</para>
<para>
Connected parameters are controlled by the <varname>direction</varname>
attribute of the &spec.parameter; element.
<footnote>
<para>
The name, "direction", made sense initially, but is now a bit confusing. It probably
should have been called "processing" or "connection-type".
</para>
</footnote>
There are four values:
<literal>in</literal>, <literal>form</literal>, <literal>auto</literal>
and <literal>custom</literal>. The default is <literal>custom</literal>, which
<emphasis>does not</emphasis> create a connected parameter property at all.
</para>
<section id="components.parameters.connected.in">
<title>Direction: in</title>
<para>
The majority of component parameters are read-only, and are only actually used
within the component's <function>renderComponent()</function> method ... the method that
actually produces HTML output. For such components, direction <literal>in</literal>
is the standard, efficient choice.
</para>
<para>
The connected parameter for each component is set just before <function>renderComponent()</function>
is invoked. The parameter is reset to its initial value just after
<function>renderComponent()</function> is invoked.
</para>
<para>
Each component has a &ParameterManager;, whose responsibility is to set and reset connected
parameter properties.
</para>
<figure>
<title>ParameterManager and <function>renderComponent()</function></title>
<mediaobject>
<imageobject>
<imagedata fileref="images/parameter-manager.png" format="PNG"/>
</imageobject>
<caption>
<para>
The &ParameterManager; will read the values bound to each parameter, and update the
connected parameter property before the component's <function>renderComponent()</function>
method is invoked. The &ParameterManager; cleans up after <function>renderComponent()</function>
is invoked.
</para>
</caption>
</mediaobject>
</figure>
<para>
For invariant bindings (literal strings and such), the ParameterManager will only set the connected parameter property once,
and does not reset the property after <function>renderComponent()</function>.
</para>
<warning>
<para>
If your component has any &listener-method;s that need to access a parameter value, then you
can't use direction <literal>in</literal> (or direction <literal>form</literal>). Listener methods are
invoked outside of the page rendering process, when value stored in the
connected parameter property is not set. You must use direction <literal>auto</literal> or
<literal>custom</literal> in such cases.
</para>
</warning>
</section> <!-- components.parameters.connected.in -->
<section id="components.parameters.connected.form">
<title>Direction: form</title>
<para>
Components, such as &TextField; or &Checkbox;, that produce form control elements are the
most likely candidates for updating their parameters. The read a parameter (usually named
<varname>value</varname>) when they render. When the form is submitted, the same components
read a query parameter and update their <varname>value</varname> parameter.
</para>
<para>
The <literal>form</literal> direction simplifies this. For the most part, <literal>form</literal>
is the same as <literal>in</literal>. The diffference is, when the form is submitted, after the
component's <function>renderComponent()</function> method has been invoked, the connected parameter property
is read and used to update the binding (that is, invoke the binding object's <function>setObject()</function>
method).
</para>
</section> <!-- components.parameters.connected.form -->
<section id="components.parameters.connected.auto">
<title>Direction: auto</title>
<para>
The previous direction values, <literal>in</literal> and <literal>form</literal>, have limitations. The value may only be
accessed from within the component's <function>renderComponent()</function> method. That's often insufficient,
especially when the component has a &listener-method; that needs access to a parameter.
</para>
<para>
Direction <literal>auto</literal> doesn't use the &ParameterManager;. Instead, the connected parameter property
is <emphasis>synthetic</emphasis>. Reading the property immediately turns around and invokes
&IBinding;'s <function>getObject()</function> method. Updating the property invokes
the &IBinding;'s <function>setObject()</function> function.
</para>
<para>
This can be a bit less efficient than direction <literal>in</literal>, as the &OGNL; expression may be
evaluated multiple times. In Tapestry 3.0, the parameter must also be required. Future releases
of Tapestry will relax these limitations.
</para>
<note>
<title>Removing parameter directions</title>
<para>
Parameter directions are a bit of a sore spot: you must make too many decisions about how to use them, especially
in terms of render-time-only vs. &listener-method;. Direction <literal>auto</literal> is too limited and
possibly too inefficient. Tapestry 3.1 should address these limitations by improving direction
<literal>auto</literal>. Instead of specifying a direction, you'll specify how long the component can cache the
value obtained from the binding object (no caching, or only while the component is rendering, or until
the page finishes rendering).
</para>
</note>
</section> <!-- components.parameters.connected.auto -->
<section id="components.parameters.connected.custom">
<title>Direction: custom</title>
<para>
The <literal>custom</literal> direction, which is the default, <emphasis>does not</emphasis>
create a connected parameter property. Your code is still responsible for
accessing the &IBinding; object (via the <function>getBinding()</function> method of
&IComponent;) and for invoking methods on the binding object.
</para>
</section> <!-- components.parameters.connected.custom -->
</section> <!-- components.parameters.connected -->
</section> <!-- components.parameters -->
<section id="components.libraries">
<title>Component Libraries</title>
<para>
Tapestry has a very advanced concept of a <emphasis>component library</emphasis>. A component library contains both Tapestry components and Tapestry pages
(not to mention engine services).
</para>
<section id="components.libraries.ref">
<title>Referencing Library Components</title>
<para>
Before a component library may be used, it must be listed in the application specification. Often, an application specification is <emphasis>only</emphasis>
needed so that it may list the libraries used by the application. Libraries are identified using the &spec.library; element.
</para>
<para>
The &spec.library; element provides an <emphasis>id</emphasis> 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 <filename>.library</filename> file on the classpath. For example:
<example>
<title>Referencing a Component Library</title>
<programlisting><![CDATA[
<application name="Example Application">
<library id="contrib" specification-path="/org/apache/tapestry/contrib/Contrib.library"/>
</application>]]></programlisting>
</example>
</para>
<para>
In this example, <filename>Contrib.library</filename> defines a set of components, and those component can be accessed using
<literal>contrib:</literal> as a prefix. In an HTML template, this might appear as:
<informalexample>
<programlisting><![CDATA[
<span jwcid="palette@contrib:Palette" . . . />
]]></programlisting>
</informalexample>
</para>
<para>
This example defines a component with id <literal>palette</literal>. The component will be an instance of the Palette component, supplied within
the <literal>contrib</literal> 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.
</para>
<para>
The same syntax may be used in page and component specifications:
<informalexample>
<programlisting><![CDATA[
<component id="palette" type="contrib:Palette">
. . .
</component>
]]></programlisting>
</informalexample>
</para>
</section> <!-- components.libraries.ref -->
<section id="components.libraries.search">
<title>Library component search path</title>
<para>
<link linkend="components.spec">Previously</link>, we described the search path for components and pages within the application. The rules are somewhat different
for components and pages within a library.
</para>
<para>
Tapestry searches for library component specifications in the following places:
<itemizedlist>
<listitem>
<para>As specified in a &spec.component-type; element (with the library specification)</para>
</listitem>
<listitem>
<para>In the same package folder as the
library specification</para>
</listitem>
</itemizedlist>
</para>
<para>
The search for page specifications is identical: as defined in the library specification, or in the same package folder.
</para>
</section> <!-- components.libraries.search -->
<section id="components.libraries.private-assets">
<title>Using Private Assets</title>
<para>
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 &spec.private-asset; element of
the page or component specification. For example:
<informalexample>
<programlisting><![CDATA[
<private-asset name="logo" resource-path="images/logo_200.png"/>
<component id="image" type="Image">
<binding name="image" expression="assets.logo"/>
</component>
]]></programlisting>
</informalexample>
</para>
<para>
All assets (private, context or external) are converted into instances of &IAsset; and treated identically by
components (such as &Image;). As in this example, relative paths are allowed: they are interpreted relative
to the specification (page or component) they appear in.
</para>
<para>
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.
</para>
<para>
The <emphasis>default</emphasis> behavior is to serve up the <emphasis>localized</emphasis> 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.
</para>
<para>
An alternate behavior is to have the framework copy the asset to a fixed directory. This directory should be mapped
to a know 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).
</para>
<para>
This behavior is controlled by a pair of <link linkend="configuration.search-path">configuration properties</link>:
<literal>org.apache.tapestry.asset.dir</literal> and <literal>org.apache.tapestry.asset.URL</literal>.
</para>
</section> <!-- components.libraries.private-assets -->
<section id="components.libraries.spec">
<title>Library Specifications</title>
<para>
A library specification is a file with a <filename>.library</filename> extension. Library specifications
use a root element of &spec.library-specification;, which supports a subset of the attributes
allowed within an &spec.application; element (but allowing the <emphasis>same</emphasis> nested elements). Often, the library specification is an empty placeholder, used
to an establish a search location for page and component specifications:
<informalexample><programlisting><![CDATA[
<!DOCTYPE library-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<library-specification/>
]]></programlisting></informalexample>
</para>
<para>
It is allowed that components in one library be constructed using components provided by another library.
The referencing library's specification may contain
&spec.library; elements that identify some other library.
</para>
</section> <!-- comopnents.libraries.spec -->
<section id="components.libraries.namespace">
<title>Libraries and Namespaces</title>
<para>
Tapestry organizes components and pages (but <emphasis>not</emphasis> engine services) into
<emphasis>namespaces</emphasis>. Namespaces are closely related to, but not exactly the same as,
the library prefix established using the &spec.library; element in an application or library specification.
</para>
<para>
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 namespaceis searched. Only if the component (or page) is not part of the framework namespace does an error result.
</para>
<para>
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
&Insert; or &Foreach;!
</para>
<para>
Every component provides a <varname>namespace</varname> property that defines the namespace (an instance
of &INamespace;) that the component belongs to.
</para>
<para>
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 &PageLink; or &ExternalLink; components to create a link to <emphasis>another page</emphasis> within
the same namespace. This is accomplished (in the source page's HTML template) as:
<informalexample>
<programlisting><![CDATA[
<a href="#" jwcid="@PageLink" page="OtherPage" namespace="ognl:namespace"> ... </a>]]>
</programlisting>
</informalexample>
</para>
</section> <!-- components.libraries.namespace -->
</section> <!-- components.libraries -->
</chapter>