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 Fri, 03 Sep 2010 23:24:00 GMT
<html>
<head>
    <base href="https://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1810/9/8/_/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:Component Libraries} <br>{include:Switching Cases} <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">{include:Enum Parameter Recipe} <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>
<ul>
    <li><a href='#Cookbook-autoconnectattribute'>autoconnect attribute</a></li>
</ul>
    <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-Step3%3ACreateyourpagesand%2Forcomponents'>Step 3: Create your pages and/or components</a></li>
    <li><a href='#Cookbook-Step2%3AChooseavirtualfoldername'>Step 2: Choose a virtual folder name</a></li>
    <li><a href='#Cookbook-Step3%3AConfigurethelibrary'>Step 3: Configure the library</a></li>
    <li><a href='#Cookbook-Step4%3AConfigurethemoduletoautoload'>Step 4: Configure the module to autoload</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>
</ul></div>

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

<p>The Tapestry Cookbook is a collection of tips and tricks for commonly occuring 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>Making this work involves two concepts: default parameter methods (methods that can compute a default value for a parameter), and a service, <tt><a href="http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry5/services/ComponentDefaultProvider.html" class="external-link" rel="nofollow">ComponentDefaultProvider</a></tt>.</p>

<p>Let's say you have a component, <tt>OutputGadget</tt>, whose job is to output some information about an entity type, <tt>Gadget</tt>.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class OutputGadget
{
  @Property
  @Parameter(required=<span class="code-keyword">true</span>)
  <span class="code-keyword">private</span> Gadget gadget;

  @Inject
  <span class="code-keyword">private</span> ComponentDefaultProvider defaultProvider;

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

  Binding defaultGadget()
  {
    <span class="code-keyword">return</span> defaultProvider.defaultBinding(<span class="code-quote">"gadget"</span>, resources);
  }
}
</pre>
</div></div>

<p>This can now be used as <tt>&lt;t:outputgadget t:id="currentGadget"/&gt;</tt>, assuming <tt>currentGadget</tt> is a property of the container.</p>

<p>If there is no matching property, then the <tt>defaultGadget()</tt> method will return null, and a runtime exception will be thrown because the gadget parameter is required and not bound.</p>

<p>The principal attribute on the Parameter annotation is not needed in the specific case; in some cases, a default for some other parameter may be based on the bound type of another parameter, the principal attribute forces the parameter to be resolved first. In many Tapestry form components, the value parameter is principal, so that the validate and translate parameters can computer defaults, based on the type and annotations bound to the value parameter.</p>

<h2><a name="Cookbook-autoconnectattribute"></a>autoconnect attribute</h2>

<p>Because this is such a common idiom, it has been made simpler for you. Rather than writing the code above, you can just use the autoconnect attribute of the Parameter annotation. This, effectively, creates the <tt>defaultGadget()</tt> method for you. In this case the code of component <tt>OutputGadget</tt> can be reduced to:</p>

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

}
</pre>
</div></div>
<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 an 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. <br/>
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<br/>
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-java">
&lt;project&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
  &lt;groupId&gt;org.example&lt;/groupId&gt;
  &lt;artifactId&gt;happylib&lt;/artifactId&gt;
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
  &lt;packaging&gt;jar&lt;/packaging&gt;
  &lt;name&gt;happylib Tapestry 5 Library&lt;/name&gt;

  &lt;dependencies&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.apache.tapestry&lt;/groupId&gt;
      &lt;artifactId&gt;tapestry-core&lt;/artifactId&gt;
      &lt;version&gt;${tapestry-release-version}&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
      &lt;groupId&gt;org.testng&lt;/groupId&gt;
      &lt;artifactId&gt;testng&lt;/artifactId&gt;
      &lt;version&gt;5.1&lt;/version&gt;
      &lt;classifier&gt;jdk15&lt;/classifier&gt;
      &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;

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

      &lt;plugin&gt;
           &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
           &lt;artifactId&gt;maven-jar-plugin&lt;/artifactId&gt;
           &lt;configuration&gt;
           &lt;archive&gt;
             &lt;manifestEntries&gt;
               &lt;Tapestry-Module-Classes&gt;org.example.happylib.services.HappyModule&lt;/Tapestry-Module-Classes&gt;
             &lt;/manifestEntries&gt;
           &lt;/archive&gt;
           &lt;/configuration&gt;
       &lt;/plugin&gt;

    &lt;/plugins&gt;
  &lt;/build&gt;

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

  &lt;properties&gt;
    &lt;tapestry-release-version&gt;5.2.0&lt;/tapestry-release-version&gt;
  &lt;/properties&gt;
&lt;/project&gt;
</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-Step3%3ACreateyourpagesand%2Forcomponents"></a>Step 3: 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 librarie's root package.</p>

<p>This component renders out an img tag for the icon.</p>

<p>Typically, a component library will have many different components and/or mixins, and may even provide pages.</p>

<h2><a name="Cookbook-Step2%3AChooseavirtualfoldername"></a>Step 2: 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-Step3%3AConfigurethelibrary"></a>Step 3: Configure the library</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-Step4%3AConfigurethemoduletoautoload"></a>Step 4: 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-Conclusion"></a>Conclusion</h2>

<p>That's it! 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>

<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 librarie'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>

<div class="error"><span class="error">Unable to render {include}</span> Couldn't find a page to include called: <a href="/confluence/pages/createpage.action?spaceKey=TAPESTRY&amp;title=Enum+Parameter+Recipe&amp;linkCreation=true&amp;fromPageId=20645529" class="createlink">Enum Parameter Recipe</a></div>



    </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=13&originalVersion=12">View Changes</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message