tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Tapestry > Cookbook
Date Wed, 05 Jan 2011 00:58:00 GMT
<html>
<head>
    <base href="https://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1810/9/12/_/styles/combined.css?spaceKey=TAPESTRY&amp;forWysiwyg=true" type="text/css">
    </head>
<body style="background: white;" bgcolor="white" class="email-body">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
    <h2><a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Cookbook">Cookbook</a></h2>
    <h4>Page <b>edited</b> by             <a href="https://cwiki.apache.org/confluence/display/~hlship">Howard M. Lewis Ship</a>
    </h4>
        <br/>
                         <h4>Changes (1)</h4>
                                 
    
<div id="page-diffs">
            <table class="diff" cellpadding="0" cellspacing="0">
            <tr><td class="diff-snipped" >...<br></td></tr>
            <tr><td class="diff-unchanged" >{include:Error Page Recipe} <br>{include:Extending the If Component} <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">{include:Meta-Programming Page Content} <br></td></tr>
        </table>
</div>                            <h4>Full Content</h4>
                    <div class="notificationGreySide">
        <h1><a name="Cookbook-Contents"></a>Contents</h1>

<div>
<ul>
    <li><a href='#Cookbook-Contents'>Contents</a></li>
    <li><a href='#Cookbook-Introduction'>Introduction</a></li>
    <li><a href='#Cookbook-DefaultParameter'>Default Parameter</a></li>
    <li><a href='#Cookbook-OverridingExceptionReporting'>Overriding Exception Reporting</a></li>
<ul>
    <li><a href='#Cookbook-Version1%3AReplacingtheExceptionReportPage'>Version 1: Replacing the Exception Report Page</a></li>
    <li><a href='#Cookbook-Version2%3AOverridingtheRequestExceptionHandler'>Version 2: Overriding the RequestExceptionHandler</a></li>
    <li><a href='#Cookbook-Version3%3ADecoratingtheRequestExceptionHandler'>Version 3: Decorating the RequestExceptionHandler</a></li>
</ul>
    <li><a href='#Cookbook-SupportingInformalParameters'>Supporting Informal Parameters</a></li>
    <li><a href='#Cookbook-CreatingComponentLibraries'>Creating Component Libraries</a></li>
<ul>
    <li><a href='#Cookbook-Step1%3AChooseabasepackagename'>Step 1: Choose a base package name</a></li>
    <li><a href='#Cookbook-Step2%3ACreateyourpagesand%2Forcomponents'>Step 2: Create your pages and/or components</a></li>
    <li><a href='#Cookbook-Step3%3AChooseavirtualfoldername'>Step 3: Choose a virtual folder name</a></li>
    <li><a href='#Cookbook-Step4%3AConfigurethevirtualfolder'>Step 4: Configure the virtual folder</a></li>
    <li><a href='#Cookbook-Step5%3AConfigurethemoduletoautoload'>Step 5: Configure the module to autoload</a></li>
    <li><a href='#Cookbook-Step6%3AExtendingClientAccess'>Step 6: Extending Client Access</a></li>
    <li><a href='#Cookbook-Step7%3AVersioningAssets'>Step 7: Versioning Assets</a></li>
    <li><a href='#Cookbook-Conclusion'>Conclusion</a></li>
    <li><a href='#Cookbook-AnoteaboutAssets'>A note about Assets</a></li>
</ul>
    <li><a href='#Cookbook-SwitchingCases'>Switching Cases</a></li>
    <li><a href='#Cookbook-EnumComponentParameter'>Enum Component Parameter</a></li>
    <li><a href='#Cookbook-ServingTapestryPagesasServletErrorPages'>Serving Tapestry Pages as Servlet Error Pages</a></li>
    <li><a href='#Cookbook-ExtendingtheIfComponent'>Extending the If Component</a></li>
</ul></div>

<h1><a name="Cookbook-Introduction"></a>Introduction</h1>

<p>The Tapestry Cookbook is a collection of tips and tricks for commonly occurring patterns in Tapestry.</p>

<h1><a name="Cookbook-DefaultParameter"></a>Default Parameter</h1>

<p>Many of the components provided with Tapestry share a common behavior: if the component's id matches a property of the container, then some parameter of the component (usually value) defaults to that property.</p>

<p>This is desirable, in terms of not having to specify the component's id and then specify the same value as some other parameter.</p>

<p>Let's say you have created a component, <tt>RichTextEditor</tt>, which operates like a normal TextArea component, but provides a JavaScript rich text editor.  You might start with something like:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class RichTextEditor <span class="code-keyword">implements</span> Field
{
  @Property
  @Parameter(required=<span class="code-keyword">true</span>)
  <span class="code-keyword">private</span> <span class="code-object">String</span> value;

  . . . <span class="code-comment">// Lots more code not shown here
</span>}
</pre>
</div></div>

<p>However, the weakness here is when you make use of the component. You template may look like:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-xml">
    <span class="code-tag">&lt;t:label for=<span class="code-quote">"profile"</span>/&gt;</span>
    <span class="code-tag">&lt;br/&gt;</span>
    <span class="code-tag">&lt;t:richtexteditor t:id=<span class="code-quote">"profile"</span> value=<span class="code-quote">"profile"</span>/&gt;</span>
</pre>
</div></div>

<p>Every component has a unique id; if you don't assign one with the <tt>t:id</tt> attribute, Tapestry will assign a less meaningful one. Component ids can end up inside URLs or used as query parameter names, so using meaningful ids helps if you are ever stuck debugging a request. The most common case of using autoconnect is form control components such as TextField and friends ... or this RichTextEditor.</p>

<p>This repetition can be avoided by adding the autoconnect attribute to the @Parameter annotation:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  @Property
  @Parameter(required=<span class="code-keyword">true</span>, autoconnect=<span class="code-keyword">true</span>)
  <span class="code-keyword">private</span> <span class="code-object">String</span> value;
</pre>
</div></div>

<p>This can now be written as <tt>&lt;t:richtexteditor t:id="profile"/&gt;</tt>. The unwanted repetition is gone: we set the id of the component and the property it edits in a single pass.   </p>

<p>If there is no matching property, then a runtime exception will be thrown when loading the page because the value parameter is required and not bound.</p>
<h1><a name="Cookbook-OverridingExceptionReporting"></a>Overriding Exception Reporting</h1>

<p>One of Tapestry's best features is its comprehensive exception reporting. The level of detail is impressive and useful.</p>

<p>Of course, one of the first questions anyone asks is "How do I turn it off?" This exception reporting is very helpful for developers but its easy to see it as terrifying for potential users. Not that you'd have have runtime exceptions in production, of course, but even so ...</p>

<h2><a name="Cookbook-Version1%3AReplacingtheExceptionReportPage"></a>Version 1: Replacing the Exception Report Page</h2>

<p>Let's start with a page that fires an exception from an event handler method.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Index.tml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
<span class="code-tag">&lt;html <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"</span>&gt;</span>
    <span class="code-tag">&lt;head&gt;</span>
        <span class="code-tag">&lt;title&gt;</span>Index<span class="code-tag">&lt;/title&gt;</span>
    <span class="code-tag">&lt;/head&gt;</span>
    <span class="code-tag">&lt;body&gt;</span>
        <span class="code-tag">&lt;p&gt;</span>
            <span class="code-tag">&lt;t:actionlink t:id=<span class="code-quote">"fail"</span>&gt;</span>click for exception<span class="code-tag">&lt;/t:actionlink&gt;</span>
        <span class="code-tag">&lt;/p&gt;</span>
    <span class="code-tag">&lt;/body&gt;</span>
<span class="code-tag">&lt;/html&gt;</span>
</pre>
</div></div>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Index.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> com.example.tapestry2523.pages;

<span class="code-keyword">public</span> class Index
{
    void onActionFromFail()
    {
        <span class="code-keyword">throw</span> <span class="code-keyword">new</span> RuntimeException(<span class="code-quote">"Failure inside action event handler."</span>);
    }
}
</pre>
</div></div>

<p>With production mode disabled, clicking the link displays the default exception report page:</p>

<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/20645557/error1.png?version=1&amp;modificationDate=1273652962000" title="Default exception report" style="border: 0px solid black" /></span></p>

<p>The easy way to override the exception report is to provide an ExceptionReport page that overrides the one provided with the framework.</p>

<p>This is as easy as providing a page named "ExceptionReport". It must implement the <a href="http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry5/services/ExceptionReporter.html" class="external-link" rel="nofollow">ExceptionReporter</a> interface.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>ExceptionReport.tml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
<span class="code-tag">&lt;html <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"</span>&gt;</span>
    <span class="code-tag">&lt;head&gt;</span>
        <span class="code-tag">&lt;title&gt;</span>Exception<span class="code-tag">&lt;/title&gt;</span>
    <span class="code-tag">&lt;/head&gt;</span>

    <span class="code-tag">&lt;body&gt;</span>

        <span class="code-tag">&lt;p&gt;</span>An exception has occurred:
            <span class="code-tag">&lt;strong&gt;</span>${exception.message}<span class="code-tag">&lt;/strong&gt;</span>
        <span class="code-tag">&lt;/p&gt;</span>

        <span class="code-tag">&lt;p&gt;</span>
            Click
            <span class="code-tag">&lt;t:pagelink page=<span class="code-quote">"index"</span>&gt;</span>here<span class="code-tag">&lt;/t:pagelink&gt;</span>
            to restart.
        <span class="code-tag">&lt;/p&gt;</span>

    <span class="code-tag">&lt;/body&gt;</span>

<span class="code-tag">&lt;/html&gt;</span>
</pre>
</div></div>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>ExceptionReport.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> com.example.tapestry2523.pages;

<span class="code-keyword">import</span> org.apache.tapestry5.annotations.Property;
<span class="code-keyword">import</span> org.apache.tapestry5.services.ExceptionReporter;

<span class="code-keyword">public</span> class ExceptionReport <span class="code-keyword">implements</span> ExceptionReporter
{
    @Property
    <span class="code-keyword">private</span> Throwable exception;

    <span class="code-keyword">public</span> void reportException(Throwable exception)
    {
        <span class="code-keyword">this</span>.exception = exception;
    }
}
</pre>
</div></div>

<p>The end result is a customized exception report page.</p>

<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/20645557/error2.png?version=1&amp;modificationDate=1273652962000" title="Customized Exception Report Page" style="border: 0px solid black" /></span></p>


<h2><a name="Cookbook-Version2%3AOverridingtheRequestExceptionHandler"></a>Version 2: Overriding the RequestExceptionHandler</h2>

<p>The previous example will display a link back to the Index page of the application. Another alternative is to display the error &lt;on&gt; the Index page.  This requires a different approach: overriding the service responsible for reporting request exceptions.</p>

<p>The service <a href="http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry5/services/RequestExceptionHandler.html" class="external-link" rel="nofollow">RequestExceptionHandler</a> is responsible for this.</p>

<p>By replacing the default implementation of this service with our own implementation, we can take control over exactly what happens when a request exception occurs.</p>

<p>We'll do this in two steps. First, we'll extend the Index page to serve as an ExceptionReporter. Second, we'll override the default RequestExceptionHandler to use the Index page instead of the ExceptionReport page. Of course, this is just one approach.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Index.tml (partial)</b></div><div class="codeContent panelContent">
<pre class="code-xml">
    <span class="code-tag">&lt;t:if test=<span class="code-quote">"message"</span>&gt;</span>
        <span class="code-tag">&lt;p&gt;</span>
            An unexpected exception has occurred:
            <span class="code-tag">&lt;strong&gt;</span>${message}<span class="code-tag">&lt;/strong&gt;</span>
        <span class="code-tag">&lt;/p&gt;</span>
    <span class="code-tag">&lt;/t:if&gt;</span>
</pre>
</div></div>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Index.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class Index <span class="code-keyword">implements</span> ExceptionReporter
{
    @Property
    @Persist(PersistenceConstants.FLASH)
    <span class="code-keyword">private</span> <span class="code-object">String</span> message;

    <span class="code-keyword">public</span> void reportException(Throwable exception)
    {
        message = exception.getMessage();
    }

    void onActionFromFail()
    {
        <span class="code-keyword">throw</span> <span class="code-keyword">new</span> RuntimeException(<span class="code-quote">"Failure inside action event handler."</span>);
    }
}
</pre>
</div></div>

<p>The above defines a new property, message, on the Index page. The @Persist annotation indicates that values assigned to the field will persist from one request to another. The use of FLASH for the persistence strategy indicates that the value will be used until the next time the page renders, then the value will be discarded.</p>

<p>The message property is set from the thrown runtime exception.</p>

<p>The remaining changes take place inside AppModule.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>AppModule.java (partial)</b></div><div class="codeContent panelContent">
<pre class="code-java">
    <span class="code-keyword">public</span> RequestExceptionHandler buildAppRequestExceptionHandler(
            <span class="code-keyword">final</span> Logger logger,
            <span class="code-keyword">final</span> ResponseRenderer renderer,
            <span class="code-keyword">final</span> ComponentSource componentSource)
    {
        <span class="code-keyword">return</span> <span class="code-keyword">new</span> RequestExceptionHandler()
        {
            <span class="code-keyword">public</span> void handleRequestException(Throwable exception) <span class="code-keyword">throws</span> IOException
            {
                logger.error(<span class="code-quote">"Unexpected runtime exception: "</span> + exception.getMessage(), exception);

                ExceptionReporter index = (ExceptionReporter) componentSource.getPage(<span class="code-quote">"Index"</span>);

                index.reportException(exception);

                renderer.renderPageMarkupResponse(<span class="code-quote">"Index"</span>);
            }
        };
    }

    <span class="code-keyword">public</span> void contributeServiceOverride(
            MappedConfiguration&lt;<span class="code-object">Class</span>, <span class="code-object">Object</span>&gt; configuration,

            @Local
            RequestExceptionHandler handler)
    {
        configuration.add(RequestExceptionHandler.class, handler);
    }
</pre>
</div></div>

<p>First we define the new service using a service builder method. This is an alternative to the <tt>bind()</tt> method; we define the service, its interface type (the return type of the method) and the service id (the part that follows "build" is the method name) and provide the implementation inline. A service builder method must return the service implementation, here implemented as an inner class.</p>

<p>The Logger resource that is passed into the builder method is the Logger appropriate for the service. ResponseRenderer and ComponentSource are two services defined by Tapestry.</p>

<p>With this in place, there are now two different services that implement the RequestExceptionHandler interface: the default one built into Tapestry (whose service id is "RequestExceptionHandler") and the new one defined in this module, "AppRequestExceptionHandler").  Without a little more work, Tapestry will be unable to determine which one to use when an exception does occur.</p>

<p>Tapestry has a pipeline for resolving injected dependencies; the ServiceOverride service is one part of that pipeline. Contributions to it are used to override an existing service, when the injection is exclusively by type. </p>

<p>Here we inject the AppRequestExceptionHandler service and contribute it as the override for type RequestExceptionHandler. The @Local annotation is used to select the RequestHandler service defined by this module, AppModule. Once contributed into ServiceOverride, it becomes the default service injected throughout the Registry.</p>

<p>This finally brings us to the point where we can see the result:</p>

<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/20645557/error3.png?version=1&amp;modificationDate=1273652962000" title="Errors show on Index page" style="border: 0px solid black" /></span></p>

<h2><a name="Cookbook-Version3%3ADecoratingtheRequestExceptionHandler"></a>Version 3: Decorating the RequestExceptionHandler</h2>

<p>A third option is available: we don't define a <em>new</em> service, but instead <em>decorate</em> the existing RequestExceptionHandler service. This approach means we don't have to make a contribution to the ServiceOverride service.</p>

<p>Service decoration is a powerful facility of Tapestry that is generally used to "wrap" an existing service with an interceptor that provides new functionality such as logging, security, transaction management or other cross-cutting concerns. The interceptor is an object that implements the same interface as the service being decorated, and usually delegates method invocations to it.</p>

<p>However, there's no requirement that an interceptor for a service actually invoke methods on the service; here we contribute a new implementation that <em>replaces</em> the original:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>AppModule.java (partial)</b></div><div class="codeContent panelContent">
<pre class="code-java">
    <span class="code-keyword">public</span> RequestExceptionHandler decorateRequestExceptionHandler(
            <span class="code-keyword">final</span> Logger logger,
            <span class="code-keyword">final</span> ResponseRenderer renderer,
            <span class="code-keyword">final</span> ComponentSource componentSource,
            @Symbol(SymbolConstants.PRODUCTION_MODE)
            <span class="code-object">boolean</span> productionMode,
            <span class="code-object">Object</span> service)
    {
        <span class="code-keyword">if</span> (!productionMode) <span class="code-keyword">return</span> <span class="code-keyword">null</span>;

        <span class="code-keyword">return</span> <span class="code-keyword">new</span> RequestExceptionHandler()
        {
            <span class="code-keyword">public</span> void handleRequestException(Throwable exception) <span class="code-keyword">throws</span> IOException
            {
                logger.error(<span class="code-quote">"Unexpected runtime exception: "</span> + exception.getMessage(), exception);

                ExceptionReporter index = (ExceptionReporter) componentSource.getPage(<span class="code-quote">"Index"</span>);

                index.reportException(exception);

                renderer.renderPageMarkupResponse(<span class="code-quote">"Index"</span>);
            }
        };
    }
</pre>
</div></div>

<p>As with service builder methods and service configuration method, decorator methods are recognized by the "decorate" prefix on the method name. As used here, the rest of the method name is used to identify the service to be decorated (there are other options that allow a decorator to be applied to many different services).</p>

<p>A change in this version is that when in development mode (that is, when <em>not</em> in production mode) we use the normal implementation.  Returning null from a service decoration method indicates that the decorator chooses not to decorate.</p>

<p>The Logger injected here is the Logger for the service being decorated, the default RequestExceptionHandler service.</p>

<p>Otherwise, we return an interceptor whose implementation is the same as the new service in version #2.</p>

<p>The end result is that in development mode we get the full exception report, and in production mode we get an abbreviated message on the application's Index page.</p>
<h1><a name="Cookbook-SupportingInformalParameters"></a>Supporting Informal Parameters</h1>

<p>Informal parameters are additional parameters beyond the formal parameters defined for a component using the <a href="http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry5/annotations/Parameter.html" class="external-link" rel="nofollow">Parameter</a> annotation.</p>

<p>A component that closely emulates a particular HTML element should also support informal parameters. You&apos;ll find that many of the built-in Tapestry components, such as Form, Label and TextField, do exactly that.</p>

<p>Normally, specifying additional parameters for a component, beyond its formal parameters, does nothing: the additional parameters are ignored.</p>

<p>The <a href="http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry5/annotations/SupportsInformalParameters.html" class="external-link" rel="nofollow">SupportsInformalParameters</a> annotation is used to identify a component for which informal parameters are to be kept.</p>

<p>The example is an Img component, a replacement for the &lt;img&gt; tag. Its src parameter will be an asset.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
@SupportsInformalParameters
<span class="code-keyword">public</span> class Img
{
  @Parameter(required=<span class="code-keyword">true</span>, allowNull=<span class="code-keyword">false</span>, defaultPrefix=BindingConstants.ASSET)
  <span class="code-keyword">private</span> Asset src;

  @Inject
  <span class="code-keyword">private</span> ComponentResources resources;

  <span class="code-object">boolean</span> beginRender(MarkupWriter writer)
  {
     writer.element(<span class="code-quote">"img"</span>,
                    <span class="code-quote">"src"</span>, src);

     resources.renderInformalParameters(writer);

     writer.end();

     <span class="code-keyword">return</span> <span class="code-keyword">false</span>;
  }
}
</pre>
</div></div>

<p>The call to renderInformalParameters() is what converts and outputs the informal parameters. It should occur <em>after</em> your code has rendered attributes into the element (earlier written attributes will <em>not</em> be overwritten by later written attributes).</p>

<p>Returning false from beginRender() ensures that the body of the component is not rendered, which makes sense for a &lt;img&gt; tag, which has no body.</p>

<p>Another option is to use the <a href="http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry5/corelib/mixins/RenderInformals.html" class="external-link" rel="nofollow">RenderInformals</a> mixin:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class Img
{
  @Parameter(required=<span class="code-keyword">true</span>, allowNull=<span class="code-keyword">false</span>, defaultPrefix=BindingConstants.ASSET)
  <span class="code-keyword">private</span> Asset src;

  @Mixin
  <span class="code-keyword">private</span> RenderInformals renderInformals;

  void beginRender(MarkupWriter writer)
  {
     writer.element(<span class="code-quote">"img"</span>,
                    <span class="code-quote">"src"</span>, src);
  }

  <span class="code-object">boolean</span> beforeRenderBody(MarkupWriter writer)
  {
    writer.end();

    <span class="code-keyword">return</span> <span class="code-keyword">false</span>;
  }
}
</pre>
</div></div>

<p>This variation splits the rendering of the tag in two pieces, so that the RenderInformals mixin can operate (after beginRender() and before beforeRenderBody()).</p>

<h1><a name="Cookbook-CreatingComponentLibraries"></a>Creating Component Libraries</h1>

<p>Nearly every Tapestry application includes a least a couple of custom components, specific to the application. What's exciting about Tapestry is how easy it is to package components for reuse across many applications ... and the fact that applications using a component library need no special configuration.</p>

<p>A Tapestry component library consists of components (as well as component base class, pages and mixins). In addition, a component library will have a module that can define new services (needed by the components) or configure other services present in Tapestry. Finally, components can be packaged with <em>assets</em>: resources such as images, stylesheets and JavaScript libraries that need to be provided to the client web browser.</p>

<p>We're going to create a somewhat insipid component that displays a large happy face icon.</p>

<p>Tapestry doesn't mandate that you use any build system, but we'll assume for the moment that you are using Maven 2. In that case, you'll have a pom.xml file something like the following:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>pom.xml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
<span class="code-tag">&lt;project&gt;</span>
  <span class="code-tag">&lt;modelVersion&gt;</span>4.0.0<span class="code-tag">&lt;/modelVersion&gt;</span>
  <span class="code-tag">&lt;groupId&gt;</span>org.example<span class="code-tag">&lt;/groupId&gt;</span>
  <span class="code-tag">&lt;artifactId&gt;</span>happylib<span class="code-tag">&lt;/artifactId&gt;</span>
  <span class="code-tag">&lt;version&gt;</span>1.0-SNAPSHOT<span class="code-tag">&lt;/version&gt;</span>
  <span class="code-tag">&lt;packaging&gt;</span>jar<span class="code-tag">&lt;/packaging&gt;</span>
  <span class="code-tag">&lt;name&gt;</span>happylib Tapestry 5 Library<span class="code-tag">&lt;/name&gt;</span>

  <span class="code-tag">&lt;dependencies&gt;</span>
    <span class="code-tag">&lt;dependency&gt;</span>
      <span class="code-tag">&lt;groupId&gt;</span>org.apache.tapestry<span class="code-tag">&lt;/groupId&gt;</span>
      <span class="code-tag">&lt;artifactId&gt;</span>tapestry-core<span class="code-tag">&lt;/artifactId&gt;</span>
      <span class="code-tag">&lt;version&gt;</span>${tapestry-release-version}<span class="code-tag">&lt;/version&gt;</span>
    <span class="code-tag">&lt;/dependency&gt;</span>

    <span class="code-tag">&lt;dependency&gt;</span>
      <span class="code-tag">&lt;groupId&gt;</span>org.testng<span class="code-tag">&lt;/groupId&gt;</span>
      <span class="code-tag">&lt;artifactId&gt;</span>testng<span class="code-tag">&lt;/artifactId&gt;</span>
      <span class="code-tag">&lt;version&gt;</span>5.1<span class="code-tag">&lt;/version&gt;</span>
      <span class="code-tag">&lt;classifier&gt;</span>jdk15<span class="code-tag">&lt;/classifier&gt;</span>
      <span class="code-tag">&lt;scope&gt;</span>test<span class="code-tag">&lt;/scope&gt;</span>
    <span class="code-tag">&lt;/dependency&gt;</span>
  <span class="code-tag">&lt;/dependencies&gt;</span>

  <span class="code-tag">&lt;build&gt;</span>
    <span class="code-tag">&lt;plugins&gt;</span>
      <span class="code-tag">&lt;plugin&gt;</span>
        <span class="code-tag">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="code-tag">&lt;/groupId&gt;</span>
        <span class="code-tag">&lt;artifactId&gt;</span>maven-compiler-plugin<span class="code-tag">&lt;/artifactId&gt;</span>
        <span class="code-tag">&lt;configuration&gt;</span>
          <span class="code-tag">&lt;source&gt;</span>1.5<span class="code-tag">&lt;/source&gt;</span>
          <span class="code-tag">&lt;target&gt;</span>1.5<span class="code-tag">&lt;/target&gt;</span>
          <span class="code-tag">&lt;optimize&gt;</span>true<span class="code-tag">&lt;/optimize&gt;</span>
        <span class="code-tag">&lt;/configuration&gt;</span>
      <span class="code-tag">&lt;/plugin&gt;</span>

      <span class="code-tag">&lt;plugin&gt;</span>
           <span class="code-tag">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="code-tag">&lt;/groupId&gt;</span>
           <span class="code-tag">&lt;artifactId&gt;</span>maven-jar-plugin<span class="code-tag">&lt;/artifactId&gt;</span>
           <span class="code-tag">&lt;configuration&gt;</span>
           <span class="code-tag">&lt;archive&gt;</span>
             <span class="code-tag">&lt;manifestEntries&gt;</span>
               <span class="code-tag">&lt;Tapestry-Module-Classes&gt;</span>org.example.happylib.services.HappyModule<span class="code-tag">&lt;/Tapestry-Module-Classes&gt;</span>
             <span class="code-tag">&lt;/manifestEntries&gt;</span>
           <span class="code-tag">&lt;/archive&gt;</span>
           <span class="code-tag">&lt;/configuration&gt;</span>
       <span class="code-tag">&lt;/plugin&gt;</span>

    <span class="code-tag">&lt;/plugins&gt;</span>
  <span class="code-tag">&lt;/build&gt;</span>

  <span class="code-tag">&lt;repositories&gt;</span>
    <span class="code-tag">&lt;repository&gt;</span>
      <span class="code-tag">&lt;id&gt;</span>codehaus.snapshots<span class="code-tag">&lt;/id&gt;</span>
      <span class="code-tag">&lt;url&gt;</span>http://snapshots.repository.codehaus.org<span class="code-tag">&lt;/url&gt;</span>
    <span class="code-tag">&lt;/repository&gt;</span>
    <span class="code-tag">&lt;repository&gt;</span>
      <span class="code-tag">&lt;id&gt;</span>OpenQA_Release<span class="code-tag">&lt;/id&gt;</span>
      <span class="code-tag">&lt;name&gt;</span>OpenQA Release Repository<span class="code-tag">&lt;/name&gt;</span>
      <span class="code-tag">&lt;url&gt;</span>http://archiva.openqa.org/repository/releases/<span class="code-tag">&lt;/url&gt;</span>
    <span class="code-tag">&lt;/repository&gt;</span>
  <span class="code-tag">&lt;/repositories&gt;</span>

  <span class="code-tag">&lt;properties&gt;</span>
    <span class="code-tag">&lt;tapestry-release-version&gt;</span>5.2.0<span class="code-tag">&lt;/tapestry-release-version&gt;</span>
  <span class="code-tag">&lt;/properties&gt;</span>
<span class="code-tag">&lt;/project&gt;</span>
</pre>
</div></div>

<p>You will need to modify the Tapestry release version number ("5.2.0" in the listing above) to reflect the current version of Tapestry when you create your component library.</p>

<p>We'll go into more detail about the relevant portions of this POM in the later sections.</p>

<h2><a name="Cookbook-Step1%3AChooseabasepackagename"></a>Step 1: Choose a base package name</h2>

<p>Just as with Tapestry applications, Tapestry component libraries should have a <em>unique</em> base package name. In this example, we'll use <tt>org.examples.happylib</tt>.</p>

<p>As with an application, we'll follow the conventions: we'll place the module for this library inside the services package, and place pages and components under their respective packages.</p>

<h2><a name="Cookbook-Step2%3ACreateyourpagesand%2Forcomponents"></a>Step 2: Create your pages and/or components</h2>

<p>Our component is very simple:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>HappyIcon.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.example.happylib.components;

<span class="code-keyword">import</span> org.apache.tapestry5.Asset;
<span class="code-keyword">import</span> org.apache.tapestry5.MarkupWriter;
<span class="code-keyword">import</span> org.apache.tapestry5.annotations.Path;
<span class="code-keyword">import</span> org.apache.tapestry5.ioc.annotations.Inject;

<span class="code-keyword">public</span> class HappyIcon
{
    @Inject
    @Path(<span class="code-quote">"happy.jpg"</span>)
    <span class="code-keyword">private</span> Asset happyIcon;

    <span class="code-object">boolean</span> beginRender(MarkupWriter writer)
    {
        writer.element(<span class="code-quote">"img"</span>, <span class="code-quote">"src"</span>, happyIcon);
        writer.end();

        <span class="code-keyword">return</span> <span class="code-keyword">false</span>;
    }
}
</pre>
</div></div>

<p>HappyIcon appears inside the components sub-package. The happyIcon field is injected with the the Asset for the file <tt>happy.jpg</tt>. The path specified with the @Path annotation is relative to the <tt>HappyIcon.class</tt> file; it should be stored in the project under <tt>src/main/resources/org/example/happylib/components</tt>.</p>

<p>Tapestry ensures that the <tt>happy.jpg</tt> asset can be accessed from the client web browser; the src attribute of the &lt;img&gt; tag will be a URL that directly accesses the image file ... there's no need to unpackage the <tt>happy.jpg</tt> file. This works for any asset file stored under the library's root package.</p>

<p>This component renders out an <tt>&lt;img&gt;</tt> tag for the icon.</p>

<p>Often, a component library will have many different components, or even pages.</p>

<h2><a name="Cookbook-Step3%3AChooseavirtualfoldername"></a>Step 3: Choose a virtual folder name</h2>

<p>In Tapestry, components that have been packaged in a library are referenced using a virtual folder name. It's effectively as if the application had a new root-level folder containing the components.</p>

<p>In our example, we'll use "happy" as the folder name. That means the application will include the HappyIcon component in the template as:</p>

<ul>
	<li><tt>&lt;t:happy.happyicon/&gt;</tt> or <tt>&lt;t:happy.icon/&gt;</tt></li>
	<li><tt>&lt;img t:type="happy.happyicon"/&gt;</tt> or <tt>&lt;img t:type="happy/icon/"&gt;</tt></li>
</ul>


<p>Why "icon" vs. "happyicon"? Tapestry notices that the folder name, "happy" is a prefix or suffix of the class name ("HappyIcon") and creates an alias that strips off the prefix (or suffix). To Tapestry, they are completely identical: two different aliases for the same component class name.</p>

<p>The above naming is somewhat clumsy, and can be improved by introducing an additional namespace into the template:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-xml">
&lt;html <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"</span>
  <span class="code-keyword">xmlns:h</span>=<span class="code-quote">"tapestry-library:happy"</span>&gt;

  ...

  <span class="code-tag">&lt;h:icon/&gt;</span>

  ...
<span class="code-tag">&lt;/html&gt;</span>
</pre>
</div></div>

<p>The special namespace mapping for sets up namespace prefix "h:" to mean the same as "happy/". It then becomes possible to reference components within the happy virtual folder directly.</p>

<h2><a name="Cookbook-Step4%3AConfigurethevirtualfolder"></a>Step 4: Configure the virtual folder</h2>

<p>Tapestry needs to know where to search for your component class. This is accomplished in your library's IoC module class, by making a <em>contribution</em> to the ComponentClassResolver service configuration.</p>

<p>At application startup, Tapestry will read the library module along with all other modules and configure the ComponentClassResolver service using information in the module:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>HappyModule.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.example.happylib.services;

<span class="code-keyword">import</span> org.apache.tapestry5.ioc.Configuration;
<span class="code-keyword">import</span> org.apache.tapestry5.services.LibraryMapping;

<span class="code-keyword">public</span> class HappyModule
{
    <span class="code-keyword">public</span> <span class="code-keyword">static</span> void contributeComponentClassResolver(Configuration&lt;LibraryMapping&gt; configuration)
    {
        configuration.add(<span class="code-keyword">new</span> LibraryMapping(<span class="code-quote">"happy"</span>, <span class="code-quote">"org.example.happylib"</span>));
    }
}
</pre>
</div></div>

<p>The ComponentClassResolver service is responsible for mapping libraries to packages; it takes as a contribution a collection of these LibraryMapping objects. Every module may make its own contribution to the ComponentClassResolver service, mapping its own package ("org.example.happylib") to its own folder ("happy").</p>

<p>This module class is also where you would define new services that can be accessed by your components (or other parts of the application).</p>

<div class='panelMacro'><table class='noteMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td>It is possible to add a mapping for "core". "core" is the core library for Tapestry components; all the built-in Tapestry components (TextField, BeanEditForm, Grid, etc.) are actually in the core library. All Tapestry does is search inside the "core" library when it does find a component in the application. Contributing an additional package as "core" simply extends the number of packages searched for core components (it doesn't replace Tapestry's default package, org.apache.tapestry5.corelib). Adding to "core" is sometimes reasonable, if there is virtually no chance of a naming conflict (via different modules contributing packages to core with conflicting class names).</td></tr></table></div>

<h2><a name="Cookbook-Step5%3AConfigurethemoduletoautoload"></a>Step 5: Configure the module to autoload</h2>

<p>For Tapestry to load your module at application startup, it is necessary to put an entry in the JAR manifest. This is taken care of in the pom.xml above:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>pom.xml (partial)</b></div><div class="codeContent panelContent">
<pre class="code-xml">
      <span class="code-tag">&lt;plugin&gt;</span>
           <span class="code-tag">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="code-tag">&lt;/groupId&gt;</span>
           <span class="code-tag">&lt;artifactId&gt;</span>maven-jar-plugin<span class="code-tag">&lt;/artifactId&gt;</span>
           <span class="code-tag">&lt;configuration&gt;</span>
           <span class="code-tag">&lt;archive&gt;</span>
             <span class="code-tag">&lt;manifestEntries&gt;</span>
             <span class="code-tag">&lt;Tapestry-Module-Classes&gt;</span>org.example.happylib.services.HappyModule<span class="code-tag">&lt;/Tapestry-Module-Classes&gt;</span>
             <span class="code-tag">&lt;/manifestEntries&gt;</span>
           <span class="code-tag">&lt;/archive&gt;</span>
           <span class="code-tag">&lt;/configuration&gt;</span>
       <span class="code-tag">&lt;/plugin&gt;</span>
</pre>
</div></div>

<h2><a name="Cookbook-Step6%3AExtendingClientAccess"></a>Step 6: Extending Client Access</h2>

<p>As of Tapestry 5.2, a new step is needed: extending access for the assets. This is accomplished in your library's module class, HappyModule:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> <span class="code-keyword">static</span> void contributeRegexAuthorizer(Configuration&lt;<span class="code-object">String</span>&gt; configuration)
{
    configuration.add(<span class="code-quote">"^org/example/happylib/.*\\.jpg$"</span>);
}
</pre>
</div></div>

<p>This contribution uses a regular expression to identify that any resource on the classpath under the org/example/happylib folder with a jpg extension is allowed. If you had a mix of different image types, you could replace jpg with (jpg&#124;gif&#124;png).</p>

<h2><a name="Cookbook-Step7%3AVersioningAssets"></a>Step 7: Versioning Assets</h2>

<p>Classpath assets, those packaged in JAR files (such as the happy.jpg asset) are retrieved by the client web browser using a URL that reflects the package name. Tapestry users a special virtual folder, /assets, under the context folder for this purpose.</p>

<p>The image file here would exposed to the web browser via the URL /happyapp/assets/org/example/happylib/components/happy.jpg (this assumes that the application was deployed as happyapp.war).</p>

<p>Tapestry uses a far-future expiration date for classpath assets; this allows browsers to aggressively cache the file, but this causes a problem should a later version of the library change the file. This is discussed in detail in <a href="http://developer.yahoo.com/performance/rules.html#expires" class="external-link" rel="nofollow">Yahoo's Performance Best Practices</a>.</p>

<p>To handle this problem, you should map your library assets to a versioned folder. This can be accomplished using another contribution from the HappyModule, this time to the ClasspathAssetAliasManager service whose configuration maps a virtual folder underneath /assets to a package:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> <span class="code-keyword">static</span> void contributeClasspathAssetAliasManager(MappedConfiguration&lt;<span class="code-object">String</span>, <span class="code-object">String</span>&gt; configuration)
{
    configuration.add(<span class="code-quote">"happylib/1.0"</span>, <span class="code-quote">"org/example/happylib"</span>);
}
</pre>
</div></div>

<p>With this in place, and the library and applications rebuilt and redeployed, the URL for happy.jpg becomes /happyapp/assets/happylib/1.0/components/happy.jpg. This is shorter, but also incorporates a version number ("1.0") that can be changed in a later release.</p>

<h2><a name="Cookbook-Conclusion"></a>Conclusion</h2>

<p>That's it&#33; Autoloading plus the virtual folders for components and for assets takes care of all the issues related to components. Just build your JARs, setup the JAR Manifest, and drop them into your applications.</p>



<div class='panelMacro'><table class='infoMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/information.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b>Added in 5.2</b><br /></td></tr></table></div>
<div style="border-right: 20px solid #D8E4F1;border-left: 20px solid #D8E4F1;"></div>

<h2><a name="Cookbook-AnoteaboutAssets"></a>A note about Assets</h2>

<p>Tapestry automatically creates a mapping for assets inside your JAR. In the above example, the icon image will be exposed as <tt>/assets/</tt><em>application version</em><tt>/happy/components/happy.jpg</tt> (the application version number is incorporated into the URL). The "happy" portion is a virtual folder that maps to the library's root package (as folder <tt>org/example/happylib</tt> on the Java classpath).</p>

<p>The application version is a configurable value.</p>

<p>In Tapestry 5.1 and earlier, it was necessary to explicitly create a mapping, via a contribution to the ClasspathAssetAliasManager service, to expose library assets. This is no longer necessary in Tapestry 5.2.</p>
<h1><a name="Cookbook-SwitchingCases"></a>Switching Cases</h1>

<p>With Tapestry's <tt>If</tt> component you can only test one condition at a time. In order to distinguish multiple cases, you'd have to write complex nested if/else constructs in your page template and have a checker method for each test inside your page class.</p>

<p>In cases where you have to distinguish multiple cases, the <tt>Delegate</tt> component comes in. It delegates rendering to some other component, for example a <tt>Block</tt>. For each case you have, you basically wrap the content inside a <tt>Block</tt> that doesn't get rendered by default. You then place a Delegate component on your page and point it to a method inside your page class that will decide which of your Blocks should be rendered.</p>

<p>Imagine for example a use case, where you want to distinguish between 4 cases and you have an int property called <tt>whichCase</tt> that should be tested against. Your page template would look as follows:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>SwitchMe.tml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
<span class="code-tag">&lt;html <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"</span>&gt;</span>
    <span class="code-tag">&lt;body&gt;</span>
        <span class="code-tag">&lt;h1&gt;</span>Switch<span class="code-tag">&lt;/h1&gt;</span>

        <span class="code-tag">&lt;t:delegate to=<span class="code-quote">"case"</span>/&gt;</span>

        <span class="code-tag">&lt;t:block t:id=<span class="code-quote">"case1"</span>&gt;</span>
            Here is the content for case1.
        <span class="code-tag">&lt;/t:block&gt;</span>

        <span class="code-tag">&lt;t:block t:id=<span class="code-quote">"case2"</span>&gt;</span>
            Here is the content for case2.
        <span class="code-tag">&lt;/t:block&gt;</span>
        
        <span class="code-tag">&lt;t:block t:id=<span class="code-quote">"case3"</span>&gt;</span>
            Here is the content for case3.
        <span class="code-tag">&lt;/t:block&gt;</span>
        
        <span class="code-tag">&lt;t:block t:id=<span class="code-quote">"case4"</span>&gt;</span>
            Here is the content for case4.
        <span class="code-tag">&lt;/t:block&gt;</span>
    <span class="code-tag">&lt;/body&gt;</span>
<span class="code-tag">&lt;/html&gt;</span>
</pre>
</div></div>

<p>You can see, that the <tt>Delegate</tt> component's <tt>to</tt> parameter is bound to the case property of your page class. In your page class you therefore have a <tt>getCase()</tt> method that is responsible for telling the <tt>Delegate</tt> component which component should be rendered. For that we are injecting references to the <tt>Block}}s defined in your page template into the page class and return the according {{Block</tt> in the <tt>getCase()</tt> method.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>SwitchMe.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class SwitchMe
{
    @Persist
    <span class="code-keyword">private</span> <span class="code-object">int</span> whichCase;

    @Inject
    <span class="code-keyword">private</span> Block case1, case2, case3, case4;

    <span class="code-keyword">public</span> <span class="code-object">Object</span> getCase()
    {
        <span class="code-keyword">switch</span> (whichCase)
        {
            <span class="code-keyword">case</span> 1:
                <span class="code-keyword">return</span> case1;
            <span class="code-keyword">case</span> 2:
                <span class="code-keyword">return</span> case2;
            <span class="code-keyword">case</span> 3:
                <span class="code-keyword">return</span> case3;
            <span class="code-keyword">case</span> 4:
                <span class="code-keyword">return</span> case4;
            <span class="code-keyword">default</span>:
                <span class="code-keyword">return</span> <span class="code-keyword">null</span>;
        }
    }
}
</pre>
</div></div>

<p>Happy switching!</p>

<h1><a name="Cookbook-EnumComponentParameter"></a>Enum Component Parameter</h1>

<p>It's not uncommon to create a component that has a bit of complex behavior that you want to be able to easily control, and an enumerated type (a Java enum) seems like the right approach. </p>

<p>Our example comes from Tapestry's Select component, which has a blankOption parameter that an enum type.</p>

<p>Let's start with the enum type itself:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>BlankOption.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> <span class="code-keyword">enum</span> BlankOption
{
    /**
     * Always include the blank option, even <span class="code-keyword">if</span> the underlying property is required.
     */
    ALWAYS,

    /**
     * Never include the blank option, even <span class="code-keyword">if</span> the underlying property is optional.
     */
    NEVER,

    /**
     * The <span class="code-keyword">default</span>: include the blank option <span class="code-keyword">if</span> the underlying property is optional.
     */
    AUTO;
}
</pre>
</div></div>

<p>Next, we define the parameter:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Select.java (partial)</b></div><div class="codeContent panelContent">
<pre class="code-java">

    /**
     * Controls whether an additional blank option is provided. The blank option precedes all other options and is never
     * selected. The value <span class="code-keyword">for</span> the blank option is always the empty string, the label may be the blank string; the
     * label is from the blankLabel parameter (and is often also the empty string).
     */
    @Parameter(value = <span class="code-quote">"auto"</span>, defaultPrefix = BindingConstants.LITERAL)
    <span class="code-keyword">private</span> BlankOption blankOption;
</pre>
</div></div>

<p>Note the use of literal as the default prefix; this allows us to use the name of the option in our template, e.g. <tt>&lt;t:select blankoption="never" .../&gt;</tt>.  Without the default prefix setting, "never" would be interpreted as a property expression (and you'd see an error when you loaded the page).</p>

<p>The final piece of the puzzle is to inform Tapestry how to convert from a string, such as "never", to a BlankOption value.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>TapestryModule.java (partial)</b></div><div class="codeContent panelContent">
<pre class="code-java">
    <span class="code-keyword">public</span> <span class="code-keyword">static</span> void contributeTypeCoercer(Configuration&lt;CoercionTuple&gt; configuration)
    {
       . . .
       
       add(configuration, BlankOption.class);

       . . .
    }

    <span class="code-keyword">private</span> <span class="code-keyword">static</span> &lt;T <span class="code-keyword">extends</span> Enum&gt; void add(Configuration&lt;CoercionTuple&gt; configuration, <span class="code-object">Class</span>&lt;T&gt; enumType)
    {
        configuration.add(CoercionTuple.create(<span class="code-object">String</span>.class, enumType, StringToEnumCoercion.create(enumType)));
    }
</pre>
</div></div>

<p>The TypeCoercer service is ultimately responsible for converting the string to a BlankOption, but we have to tell it how, by contributing an appropriate CoercionTuple. The CoercionTuple identifies the source and target types (String and BlankOption), and an object to perform the coercion (an instance of StringToEnumCoercion, via the <tt>create()</tt> static method).</p>
<h1><a name="Cookbook-ServingTapestryPagesasServletErrorPages"></a>Serving Tapestry Pages as Servlet Error Pages</h1>

<p>Do you want to dress up your site and use a snazzy Tapestry page instead of the default 404 error page.  Using modern servlet containers, this is a snap!</p>

<p>Simply upgrade your application web.xml to the 2.4 version, and make a couple of changes:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>web.xml</b></div><div class="codeContent panelContent">
<pre class="code-java">
&lt;?xml version=<span class="code-quote">"1.0"</span> encoding=<span class="code-quote">"UTF-8"</span>?&gt;

&lt;web-app xmlns=<span class="code-quote">"http:<span class="code-comment">//java.sun.com/xml/ns/j2ee"</span> xmlns:xsi=<span class="code-quote">"http://www.w3.org/2001/XMLSchema-instance"</span>
</span>  xsi:schemaLocation=<span class="code-quote">"http:<span class="code-comment">//java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"</span>
</span>  version=<span class="code-quote">"2.4"</span>&gt;

  &lt;display-name&gt;Cookbook&lt;/display-name&gt;
  &lt;context-param&gt;
    &lt;param-name&gt;tapestry.app-<span class="code-keyword">package</span>&lt;/param-name&gt;
    &lt;param-value&gt;com.example.cookbook&lt;/param-value&gt;
  &lt;/context-param&gt;

  &lt;filter&gt;
    &lt;filter-name&gt;app&lt;/filter-name&gt;
    &lt;filter-class&gt;org.apache.tapestry5.TapestryFilter&lt;/filter-class&gt;
  &lt;/filter&gt;
  &lt;filter-mapping&gt;
    &lt;filter-name&gt;app&lt;/filter-name&gt;
    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
    &lt;dispatcher&gt;ERROR&lt;/dispatcher&gt;
  &lt;/filter-mapping&gt;

  &lt;error-page&gt;
    &lt;error-code&gt;404&lt;/error-code&gt;
    &lt;location&gt;/error404&lt;/location&gt;
  &lt;/error-page&gt;

&lt;/web-app&gt;
</pre>
</div></div>

<p>Tapestry's filter must be marked as a handler for both standard requests and errors. That's accomplished with the <tt>&lt;dispatcher&gt;</tt> elements inside the <tt>&lt;filter-mapping</tt>.</p>

<p>You must then map error codes to Tapestry URLs.  In this case, the 404 error is send to the <tt>/error404</tt> resource, which is really Tapestry page "Error404".</p>

<p>We'll create a simple Error404 page, one that displays a message and (in development mode) displays the details about the incoming request.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Error404.tml</b></div><div class="codeContent panelContent">
<pre class="code-java">
&lt;html xmlns:t=<span class="code-quote">"http:<span class="code-comment">//tapestry.apache.org/schema/tapestry_5_1_0.xsd"</span>&gt;
</span>  &lt;head&gt;
    &lt;title&gt;Resource not found.&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;

    &lt;h1&gt;
      Page or resource not found.&lt;/h1&gt;

    &lt;t:<span class="code-keyword">if</span> test=<span class="code-quote">"! productionMode"</span>&gt;
      &lt;t:renderobject object=<span class="code-quote">"request"</span>/&gt;
    &lt;/t:<span class="code-keyword">if</span>&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre>
</div></div>

<p>The page simply makes the request and productionMode properties available:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>Error404.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> com.example.cookbook.pages;

<span class="code-keyword">import</span> org.apache.tapestry5.SymbolConstants;
<span class="code-keyword">import</span> org.apache.tapestry5.annotations.Property;
<span class="code-keyword">import</span> org.apache.tapestry5.ioc.annotations.Inject;
<span class="code-keyword">import</span> org.apache.tapestry5.ioc.annotations.Symbol;
<span class="code-keyword">import</span> org.apache.tapestry5.services.Request;

<span class="code-keyword">public</span> class Error404
{
    @Property
    @Inject
    <span class="code-keyword">private</span> Request request;

    @Property
    @Inject
    @Symbol(SymbolConstants.PRODUCTION_MODE)
    <span class="code-keyword">private</span> <span class="code-object">boolean</span> productionMode;
}
</pre>
</div></div>

<p>The end-result:</p>


<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23336861/Snapz+Pro+XScreenSnapz002.png?version=1&amp;modificationDate=1283560657000" style="border: 0px solid black" /></span></p>

<div class='panelMacro'><table class='infoMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/information.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td>An issue with an application that has a root Index page is that any invalid path, which would normally generate a 404 error, is instead routed to the Index page (the invalid path looks like page's activation context).</td></tr></table></div>
<h1><a name="Cookbook-ExtendingtheIfComponent"></a>Extending the If Component</h1>

<p>The <a href="http://tapestry.apache.org/current/tapestry-core/ref/org/apache/tapestry5/corelib/components/If.html" class="external-link" rel="nofollow">If</a> component can be made very flexible; its main parameter, <tt>test</tt>, does not <em>have</em> to be bound to a boolean value, it merely has to be bound to a value that can be <a href="/confluence/display/TAPESTRY/TypeCoercer+Service" title="TypeCoercer Service">coerced</a> to boolean.</p>

<p>For example, I'm working on an application that does a lot of <a href="http://lucene.apache.org/java/docs/index.html" class="external-link" rel="nofollow">Lucene</a> searches, and represents the  results as a SearchResult object.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>SearchResult.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class SearchResult&lt;T&gt; {
  <span class="code-keyword">public</span> <span class="code-keyword">final</span> <span class="code-object">Class</span>&lt;T&gt; itemType;
  <span class="code-keyword">public</span> <span class="code-keyword">final</span> List&lt;T&gt; items;
  <span class="code-keyword">public</span> <span class="code-keyword">final</span> <span class="code-object">int</span> size;
  <span class="code-keyword">public</span> <span class="code-keyword">final</span> <span class="code-object">int</span> pages;
  <span class="code-keyword">public</span> <span class="code-keyword">final</span> <span class="code-object">int</span> firstIndex;
  <span class="code-keyword">public</span> <span class="code-keyword">final</span> <span class="code-object">int</span> lastIndex;

  <span class="code-keyword">public</span> SearchResult(<span class="code-object">Class</span>&lt;T&gt; type, List&lt;T&gt; items, <span class="code-object">int</span> size, <span class="code-object">int</span> pages, <span class="code-object">int</span> firstIndex,
      <span class="code-object">int</span> lastIndex) {
    <span class="code-keyword">this</span>.itemType = type;
    <span class="code-keyword">this</span>.items = items;
    <span class="code-keyword">this</span>.size = size;
    <span class="code-keyword">this</span>.pages = pages;
    <span class="code-keyword">this</span>.firstIndex = firstIndex;
    <span class="code-keyword">this</span>.lastIndex = lastIndex;
  }

  <span class="code-keyword">public</span> <span class="code-object">boolean</span> isEmpty() {
    <span class="code-keyword">return</span> size == 0;
  }
}
</pre>
</div></div>

<p>In a SearchResult, the <tt>size</tt> property is the overall number of results from the search. The <tt>items</tt> list is a single "page" of those results to present to the user, consisting of items from <tt>firstIndex</tt> to <tt>lastIndex</tt> within the overall set.</p>

<p>In my templates, I have to check to see if the SearchResult exists, then see if it is empty, before we can get to the part that displays the content:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-xml">
<span class="code-tag">&lt;t:if test=<span class="code-quote">"searchResult"</span>&gt;</span>
  <span class="code-tag">&lt;t:if test=<span class="code-quote">"! searchResult.empty"</span>&gt;</span>
    . . .
  <span class="code-tag">&lt;/t:if&gt;</span>
<span class="code-tag">&lt;/t:if&gt;</span>
</pre>
</div></div>

<p>The first test checks to see if <tt>searchResult</tt> is not null (null is treated as false).  The second checks to see if the search result is empty.</p>

<p>What'd I'd like is for the test to look at the <tt>searchResult</tt> directly and treat an empty search result as false, and a non-empty search result is true. This is similar to what Tapestry already does for Collections.</p>

<p>This is just a matter of extending the TypeCoercer service:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>AppModule.java (partial)</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> <span class="code-keyword">static</span> void contributeTypeCoercer(Configuration&lt;CoercionTuple&gt; configuration) {

  add(configuration, SearchResult.class, <span class="code-object">Boolean</span>.class,
      <span class="code-keyword">new</span> Coercion&lt;SearchResult, <span class="code-object">Boolean</span>&gt;() {
        <span class="code-keyword">public</span> <span class="code-object">Boolean</span> coerce(SearchResult input) {
          <span class="code-keyword">return</span> !input.isEmpty();
        }
      });
}

<span class="code-keyword">private</span> <span class="code-keyword">static</span> &lt;S, T&gt; void add(Configuration&lt;CoercionTuple&gt; configuration,
    <span class="code-object">Class</span>&lt;S&gt; sourceType, <span class="code-object">Class</span>&lt;T&gt; targetType, Coercion&lt;S, T&gt; coercion) {
  CoercionTuple&lt;S, T&gt; tuple = <span class="code-keyword">new</span> CoercionTuple&lt;S, T&gt;(sourceType,
      targetType, coercion);

  configuration.add(tuple);
}
</pre>
</div></div>

<p>Inside this thicket of generics and brackets is the code that treats a SearchResult as a boolean:  <tt>return !input.isEmpty();</tt>.</p>

<p>With this in place, the previous template can be simplified:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-xml">
<span class="code-tag">&lt;t:if test=<span class="code-quote">"searchResult"</span>&gt;</span>
  . . .
<span class="code-tag">&lt;/t:if&gt;</span>
</pre>
</div></div>

<p>The single test now implies that <tt>searchResult</tt> is not null and not empty. </p>




    </div>
        <div id="commentsSection" class="wiki-content pageSection">
        <div style="float: right;">
            <a href="https://cwiki.apache.org/confluence/users/viewnotifications.action" class="grey">Change Notification Preferences</a>
        </div>
        <a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Cookbook">View Online</a>
        |
        <a href="https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=20645529&revisedVersion=19&originalVersion=18">View Changes</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message