tapestry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Tapestry > Page And Component Classes
Date Fri, 03 Sep 2010 21:22:01 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/Page+And+Component+Classes">Page
And Component Classes</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" >Although these two classes have the
same fully qualified class name, they are distinct classes because they are loaded by different
class loaders. <br> <br></td></tr>
            <tr><td class="diff-changed-lines" >{gliffy:name=Class <span class="diff-changed-words">Loaders|size=L|version=<span
class="diff-deleted-chars"style="color:#999;background-color:#fdd;text-decoration:line-through;">3</span><span
class="diff-added-chars"style="background-color: #dfd;">4</span>}</span> <br></td></tr>
            <tr><td class="diff-unchanged" > <br>In a Tapestry application,
most application classes, including your services, are loaded from the middle class loader.
 When a page or component is passed as a parameter to a service, a failure occurs <br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
        </table>
</div>                            <h4>Full Content</h4>
                    <div class="notificationGreySide">
        <h2><a name="PageAndComponentClasses-PageAndComponentClasses"></a>Page
And Component Classes</h2>

<h3><a name="PageAndComponentClasses-What%27sthedifferencebetweenapageandacomponent%3F"></a>What's
the difference between a page and a component?</h3>

<p>There's very little difference between the two. Pages clases must be in the <em>root-package</em>.<tt>pages</tt>
package; components must be in the<br/>
<em>root-package</em>.<tt>components</tt>.  Pages may provide event
handlers for certain page-specific events (such as activate and passivate).  Components may
have parameters.</p>

<p>Other than that, they are more equal than they are different. They may have templates
or may render themselves in code (pages usually have a template, components are more likely
to render only in code).</p>

<p>The major difference is that Tapestry page templates may be stored in the web context
directory, as if they were static files (they can't be accessed from the client however; a
specific rule prevents access to files with the <tt>.tml</tt> extension).</p>

<div class='panelMacro'><table class='warningMacro'><colgroup><col width='24'><col></colgroup><tr><td
valign='top'><img src="/confluence/images/icons/emoticons/forbidden.gif" width="16"
height="16" align="absmiddle" alt="" border="0"></td><td>It is possible that
this feature may be removed in a later release. It is preferred that page templates be stored
on the classpath, like component templates.</td></tr></table></div>

<h3><a name="PageAndComponentClasses-HowdoIstoremypageclassesinadifferentpackage%3F"></a>How
do I store my page classes in a different package?</h3>

<p>Tapestry is very rigid here; you can't. Page classes must go in <em>root-package</em>.<tt>pages</tt>,
component classes in <em>root-package</em>.<tt>components</tt>, etc.</p>

<p>You are allowed to create sub-packages, to help organize your code better and more
logically. For example, you might have _root-package.<tt>pages.account.ViewAccount</tt>,
which would have the page name "account/viewaccount"<style type='text/css'>
.FootnoteMarker, .FootnoteNum a {
  background: transparent url(/confluence/download/resources/com.adaptavist.confluence.footnoteMacros:footnote/gfx/footnote.png)
no-repeat top right;
  padding: 1px 2px 0px 1px;
  border-left: 1px solid #8898B8;
  border-bottom: 1px solid #6B7C9B;
  margin: 1px;
  text-decoration: none;
}
.FootnoteNum a {
  margin-top: 2px;
  margin-right: 0px;
}
.FootnoteNum {
  font-size: x-small;
  text-align: right;
  padding-bottom: 4px;
}
.footnote-th1 {
  text-align: right;
}
.Footnote {
  padding-left: 7px;
  margin-bottom: 4px;
  border: 1px none #DDDDDD;
  writingMode: tb-rl;
}
.accessibility {
     display: none;
     visibility: hidden;
}
@media aural,braille,embossed {
        .FootnoteMarker, .FootnoteNum a {
         border: 1px solid #000000;
         background: #ffffff none;
    }
    .accessibility {
         display: run-in;
         visibility: visible;
    }
}
</style>
<script type='text/javascript' language='JavaScript'>
//<!--\n
var effectInProgress = {};
var despamEffect = function (id,effectType,duration) {
  if ((effectInProgress[id]) || (typeof(Effect)=="undefined") || (typeof(Effect[effectType])=="undefined"))
return;
  new Effect[effectType](id);
  effectInProgress[id]=true;
  setTimeout('effectInProgress[\"'+id+'\"]=false;',duration*1000);
};
var oldFootnoteId = '';
var footnoteHighlight = function(id,pulsateNum) {
  if (oldFootnoteId!='') document.getElementById('Footnote'+oldFootnoteId).style['borderStyle']
= 'none';
  oldFootnoteId = id;
  document.getElementById('Footnote'+id).style['borderStyle'] = 'solid';
  despamEffect('Footnote'+id,'Highlight',1)
  if (pulsateNum) despamEffect('FootnoteNum'+id,'Pulsate',3)
}
var footnoteMarkerHighlight = function(id) {
  if (oldFootnoteId!='') document.getElementById('Footnote'+oldFootnoteId).style['borderStyle']
= 'none';
  oldFootnoteId = '';
  despamEffect('FootnoteMarker'+id,'Pulsate',3)
}
//-->
</script>

<sup id='FootnoteMarker1'>
    <a name='FootnoteMarker1'
        href='#Footnote1'
        onClick='footnoteHighlight("1",true);'
        alt='Footnote: Click here to display the footnote'
        title='Footnote: Click here to display the footnote'
        class='FootnoteMarker'>
            1
    </a>
</sup>
</p>

<p>In addition, it is possible to define additional root packages for the application:</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 contributeComponentClassResolver(Configuration&lt;LibraryMapping&gt; configuration)
{
       configuration.add(<span class="code-keyword">new</span> LibraryMapping(<span
class="code-quote">"", "</span>com.example.app.tasks"));
       configuration.add(<span class="code-keyword">new</span> LibraryMapping(<span
class="code-quote">"", "</span>com.example.app.chat"));
}
</pre>
</div></div>

<p>LibraryMappings are used to resolve a library prefix to one or more package names.
 The empty string represents the application itself; the above example<br/>
adds two additional root packages; you might see additional pages under <tt>com.example.app.tasks.pages</tt>,
for example.</p>

<div class='panelMacro'><table class='warningMacro'><colgroup><col width='24'><col></colgroup><tr><td
valign='top'><img src="/confluence/images/icons/emoticons/forbidden.gif" width="16"
height="16" align="absmiddle" alt="" border="0"></td><td>Tapestry doesn't check
for name collisions, and the order the packages are searched for pages and components is not
defined. In general, if you<br/>
can get by with a single root package for your application, that is better.</td></tr></table></div>

<h3><a name="PageAndComponentClasses-Whydomyinstancevariableshavetobeprivate%3F"></a>Why
do my instance variables have to be private?</h3>

<p>Tapestry does a large amount of transformation to your simple POJO classes as it
loads them into memory. In many cases, it must locate every read or write of an instance variable
and change its behavior; for example, reading a field that is a component parameter will<br/>
cause a property of the containing page or component to be read.</p>

<p>Limiting fields to private means that Tapestry can do the necessary processing one
class at a time, as needed, at runtime. More complex<br/>
Aspect Orient Programming systems such as AspectJ can perform similar transformations (and
much more complex ones), but requires a dedicated build step (or the introduction of a JVM
agent).</p>

<h3><a name="PageAndComponentClasses-Whydon%27tmyinformalparametersshowupintherenderedmarkup%3F"></a>Why
don't my informal parameters show up in the rendered markup?</h3>

<p>Getting informal parameters to work is in two steps.  First, you must make a call
to the <tt>ComponentResources.renderInformalParameters()</tt> method, but just
as importantly, you must tell Tapestry that you want the component to support<br/>
informal parameters, using the <tt>SupportsInformalParameters</tt> annotation.
Here's a hypothetical component that displays an image based on the value of a <tt>Image</tt>
object (presumably, a database entity):</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 DBImage
{
  @Parameter(required=<span class="code-keyword">true</span>)
  <span class="code-keyword">private</span> Image image;

  @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>,
image.toClientURL(), <span class="code-quote">"class"</span>, <span class="code-quote">"db-image"</span>);

    resources.renderInformalParameters(writer);

    writer.end();

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


<h3><a name="PageAndComponentClasses-WhydoIgetjava.lang.LinkageErrorwhenIinvokepublicmethodsofmypageclasses%3F"></a>Why
do I get java.lang.LinkageError when I invoke public methods of my page classes?</h3>

<p>In Tapestry, there are always <em>two</em> versions of page (or component)
classes.  The first version is the version loaded by standard class loader: the simple POJO
version that you wrote.</p>

<p>The second version is much more complicated; it's the transformed version of your
code, with lots of extra hooks and changes to allow the class to operate inside Tapestry.
This includes implementing<br/>
new interfaces and methods, adding new constructors, and changing access to existing fields
and methods.</p>

<p>Although these two classes have the same fully qualified class name, they are distinct
classes because they are loaded by different class loaders.</p>


<table width="100%">
    <tr><td align="left"> 
        <table>     
            <caption align="bottom">
                         </caption>

            <tr><td>
                            <img style="border: none" usemap="#GLIFFY_MAP_23335008_Class_Loaders"
src="/confluence/plugins/servlet/gliffyapi/clientdiagramjpeg?cb=75392566&pk=pub&name=Class+Loaders&ceoid=23335008&key=TAPESTRY&size=L&version=4"
alt="A&#32;Gliffy&#32;Diagram&#32;named&#58;&#32;Class&#32;Loaders"
/>
                       </td></tr>
        </table> 
</td></tr>
</table>
<map name='GLIFFY_MAP_23335008_Class_Loaders'></map>

<p>In a Tapestry application, most application classes, including your services, are
loaded from the middle class loader.  When a page or component is passed as a parameter to
a service, a failure occurs<br/>
(how it is reported varies in different JDK releases) because of the class mismatch.</p>

<p>The solution is to define an interface with the methods that the service will invoke
on the page or component instance. The service will expect an object implementing the interface
(and doesn't care what class loader loaded<br/>
the implementing class).</p>

<p>Just be sure to put the interface class in a non-controlled package, such as your
application's <em>root-package</em> (and not <em>root-package</em>.<tt>pages</tt>).</p>

<h3><a name="PageAndComponentClasses-Whichisbetter%2Cusingmagicmethodnames%28i.e.%2C%7B%7BbeginRender%28%29%7D%7D%29orannotations%28i.e.%7B%7BBeginRender%7D%7D%29%3F"></a>Which
is better, using magic method names (i.e., <tt>beginRender()</tt>) or annotations
(i.e. <tt>BeginRender</tt>)?</h3>

<p>There is no single best way; this is where your taste may vary.  Historically, the
annotations came first, and the method naming conventions came later.</p>

<p>The advantage of using the method naming conventions is that the method names are
more concise, which fewer characters to type, and fewer classes to import.</p>

<p>The main disadvantage of the method naming conventions is that the method names are
not meaningful.  <tt>onSuccessFromLoginForm()</tt> is a less meaningful name than
<tt>storeUserCredentialsAndReturnToProductsPage()</tt>, for example.</p>

<p>The second disadvantage is you are more susceptible to off-by-a-character errors.
For example, <tt>onSucessFromLoginForm()</tt> will <em>never</em>
be called because the event name is misspelled; this would not happen using the annotation
approach:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  @OnEvent(value=EventConstants.SUCCESS, component=<span class="code-quote">"loginForm"</span>)
  <span class="code-object">Object</span> storeUserCredentialsAndReturnToProductsPage()
  {
    . . .
  }
</pre>
</div></div>

<p>The compiler will catch a misspelling of the constant <tt>SUCCESS</tt>.
Likewise, local constants can be defined for key components, such as "loginForm".</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>Ultimately, it's developer
choice. HLS prefers the method naming conventions in nearly all cases, especially prototypes
and demos, but can see<br/>
that in some projects and some teams, an annotation-only approach is best.</td></tr></table></div>

<p>____</p>

<p><table class='Footnotes' style='width: 100%; border:none;' cellspacing='0' cellpadding='0'
summary='This table contains one or more notes for references made elsewhere on the page.'>
  <caption class='accessibility'>Footnotes</caption>
  <thead class='accessibility'>
    <tr class='accessibility'>
      <th class='accessibility' id='footnote-th1'>Reference</th>
      <th class='accessibility' id='footnote-th2'>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr name='Footnote1'>
      <td valign='top' class='FootnoteNum' headings='footnote-th1'>
        <a href='#FootnoteMarker1'
          onClick='footnoteMarkerHighlight("1");'
          onMouseOver='footnoteHighlight("1",false);'
          alt='Footnote: Click to return to reference in text'
          title='Footnote: Click to return to reference in text'
          id='FootnoteNum1'>
            1
        </a>
      </td>
      <td id='Footnote1'
        valign='top'
        width='100%'
        class='Footnote'
        headings='footnote-th2'>
          Tapestry would also create an alias "account/view", by stripping off the redundant
"account" suffix. Either name is equally valid in your code, and Tapestry will use the shorter
name, "account/view" in URLs.
      </td>
    </tr>
  </tbody>
</table></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/Page+And+Component+Classes">View
Online</a>
        |
        <a href="https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=23335008&revisedVersion=14&originalVersion=13">View
Changes</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message