blob: 57087697617d007b02443f65c046be23499d568b [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="coding-components">
<title>Designing new components</title>
<para>
Creating new components using Tapestry is designed to be quite simple.
</para>
<para>
Components are typically created through aggregation, that is, by combining existing
components using an HTML template and specification.
</para>
<para>
You will almost always want to define a short alias
for your new component in the application specification. This insulates developers from minor
name changes to the component specification, such as moving the component
to a different Java package.
</para>
<para>
Like pages, components should reset their state back to
default values when the page they are contained within is returned to the pool.
</para>
<para>
Most components do not have any state. A component which does should
implement the &PageDetachListener; interface,
and implement the <function>pageDetached()</function> method.
</para>
<para>
The <function>pageDetached()</function> method is invoked from the page's
<function>detatch()</function> method, which is invoked at the very end
of the request cycle, just before the page is returned to the page pool.
</para>
<section id="coding-components.base-class">
<title>Choosing a base class</title>
<para>
There are two basic types of components: those that use an HTML template, and those that don't.
</para>
<para>
Nearly all of the base components provided with the Tapestry framework don't
use templates. They inherit from the class
&AbstractComponent;. Such
components must implement the protected <function>renderComponent()</function> method.
</para>
<para>
Components that use templates inherit from a subclass of
&AbstractComponent;:
&BaseComponent;. They should leave the <function>renderComponent()</function>
method alone.
</para>
<para>
In some cases, a new component can be written just by combining existing components
(this often involves using inherited bindings). Such a codeless
component will consist of just a specification and an HTML
template and will use the &BaseComponent; class without subclassing. This
is even more possible when using <link linkend="components.helper-beans">helper beans</link>.
</para>
</section>
<section id="coding-components.parameters">
<title>Parameters and Bindings</title>
<para>
You may create a component that has parameters. Under Tapestry, component parameters are a
kind of "named slot" that can be wired up as
a source (or sink) of data in a number of ways. This "wiring up" is
actually accomplished using binding objects.
</para>
<tip>
<title>Connected Parameters</title>
<para>
Most components use "in" parameters and can have Tapestry
<link linkend="components.connected-params">connect the parameters to properties
of the component</link> automatically. This discussion
reveals some inner workings of Tapestry
that developers most often
no longer need to be aware of.
</para>
</tip>
<para>
The page loader, the object that converts a component specification into an actual
component, is responsible for creating and assigning the bindings. It uses the method
<function>setBinding()</function> to assign a binding with a name. Your
component can retrieve the binding by name using <function>getBinding()</function>.
</para>
<para>
For example, lets create a component that allows the color of
a span of text to be specified using a <classname>java.awt.Color</classname> object. The
component has a required parameter named <varname>color</varname>. The class's
<function>renderComponent()</function> method is below:
</para>
<informalexample>
<programlisting>
protected void renderComponent(&IMarkupWriter; writer, &IRequestCycle; cycle)
throws RequestCycleException
{
&IBinding; colorBinding = getBinding("color");
Color color = (Color)colorBinding.getObject("color", Color.class);
String encodedColor = &RequestContext;.encodeColor(color);
writer.begin("font");
writer.attribute("color", encodedColor);
renderWrapped(writer, cycle);
writer.end();
}</programlisting>
</informalexample>
<para>
The call to <function>getBinding()</function> is relatively expensive, since
it involves rummaging around in a &Map; and then
casting the result from <classname>java.lang.Object</classname> to <classname>org.apache.tapestry.IBinding</classname>.
</para>
<para>
Because bindings are typically set once and then read frequently by the component,
implementing them as private instance variables is much more efficient. Tapestry
allows for this as an optimization on frequently used components.
</para>
<para>
The <function>setBinding()</function> method in
&AbstractComponent; checks to see if there is a read/write
JavaBeans property named "<replaceable>name</replaceable>Binding" of
type &IBinding;. In this example, it
would look for the methods <function>getColorBinding()</function> and <function>setColorBinding()</function>.
</para>
<para>
If the methods are found, they are invoked from
<function>getBinding()</function> and <function>setBinding()</function> instead of updating the
&Map;.
</para>
<para> This changes the example to:</para>
<informalexample>
<programlisting>
<emphasis>private &IBinding; colorBinding;
public void setColorBinding(&IBinding; value)
{
colorBinding = value;
}
public &IBinding; getColorBinding()
{
return colorBinding;
}
</emphasis>
protected void renderComponent(&IMarkupWriter; writer, &IRequestCycle; cycle)
throws RequestCycleException
{
Color color = (Color)<emphasis>colorBinding</emphasis>.getObject("color", Color.class);
String encodedColor = &RequestContext;.encodeColor(color);
writer.begin("font");
writer.attribute("color", encodedColor);
renderWrapped(writer, cycle);
writer.end();
}</programlisting>
</informalexample>
<para>
This is a trade off; slightly more code for slightly better performance.
There is also a memory bonus; the
&Map; used by &AbstractComponent; to store the binding will never be created.
</para>
</section>
<section id="coding-components.persistent-state">
<title>Persistent Component State</title>
<para>
As with pages, individual components may have state that persists between request cycles.
This is rare for non-page components, but still possible and useful in special circumstances.
</para>
<para>
A client that must persist some client state uses its page's <varname>changeObserver</varname>.
It simply posts <classname>ObservedChangeEvents</classname> with itself (not its page) as the
source. In practice, it still simply invokes the <function>fireObservedChange()</function> method.
</para>
<para>
In addition, the component should implement the interface
&PageDetachListener;, and implement
the method <function>pageDetached()</function>, and, within that method, reset all
instance variables to default values, just as a page does (in its <function>detach()</function> method).
</para>
</section>
<section id="coding-components.assets">
<title>Component Assets</title>
<para>
Tapestry components are designed for easy re-use. Most
components consist of a specification, a Java class and an HTML template.
</para>
<para>
Some components may need more; they may have additional image
files, sounds, Flash animations, QuickTime movies or whatever. These are
collectively called "assets".
</para>
<para>
Assets come in three flavors: external, context and private.
</para>
<itemizedlist>
<listitem>
<para>An external asset is just a fancy way of packaging a URL at an arbitrary web site.
</para>
</listitem>
<listitem>
<para>A context asset represents a file with a URL
relative to the web server containing the Tapestry application.</para>
</listitem>
<listitem>
<para>A private asset is a file within the classpath,
that is, packaged with the component in a Java Archive (JAR) file.
Obviously, such assets are not normally visible to the web server.
</para>
</listitem>
</itemizedlist>
<para>
Components which use assets don't care what flavor they are; they
simply rely on the method <function>buildURL()</function> to provide a
URL they can incorporate into the HTML they generate. For example, the
&Image; component has an image parameter that is used to
build the <varname>src</varname> attribute of an HTML <sgmltag class="starttag">img</sgmltag> element.
</para>
<para>
Assets figure prominently into three areas: reuse, deployment and localization.
</para>
<para>
Internal and private assets may be localized: when
needed, a search occurs for a localized version, relative to a base name
provided in the component specification.
</para>
<para>
Private assets simplify both re-use and deployment. They allow a re-usable Tapestry
component, even one with associated images, style sheets (or other assets) to be incorporated
into a Tapestry application without any special consideration. For example, the standard exception
page makes use of a private asset to access its stylesheet.
</para>
<para>
In a traditional web application, private assets would need to be packaged
separately from the 'component' code and placed into some pre-defined
directory visible to the web server.
</para>
<para>
Under Tapestry, the private assets are distributed with the component
specification, HTML templates and Java code, within a Java Archive (JAR)
file, or within the <filename class="directory">WEB-INF/classes</filename> directory of a
Web Application Archive (WAR) file. The resources are located within the
running application's classpath.
</para>
<para>
The Tapestry framework takes care of making the private
assets visible to the client web browser. This occurs in
one of two ways:
</para>
<itemizedlist>
<listitem>
<para>The private assets are copied out of the
classpath and to a directory visible to the web server.
This requires some additional configuration.
</para>
</listitem>
<listitem>
<para>
The assets are dynamically accessed from the class
path using the asset service.
</para>
</listitem>
</itemizedlist>
<para>
Copying assets out of the classpath and onto the web
site is the best solution for final deployment, since it allows the assets
to be retrieved as static files, an operation
most web servers are optimized for.
</para>
<para>
Dynamically accessing assets requires additional operations in Java code.
These operations are not nearly as efficient as static
access. However, dynamic access is much more convenient during development
since much less configuration (in this case, copying of assets) is
necessary before testing the application.
</para>
<para>
As with many things in Tapestry, the components using assets
are blind as to how the assets are made visible to the client.
</para>
<para>
Finally, every component has an <varname>assets</varname> property that is an
unmodifiable &Map;. The assets in the
&Map;
are accessible as if they were properties of the &Map;. In
other words, the property path <varname>assets.welcome</varname> is valid, if the
component defines an asset named 'welcome'.
</para>
</section>
</chapter>