Modified: websites/production/tapestry/content/component-events.html
==============================================================================
--- websites/production/tapestry/content/component-events.html (original)
+++ websites/production/tapestry/content/component-events.html Wed Sep 20
12:29:16 2017
@@ -27,6 +27,16 @@
</title>
<link type="text/css" rel="stylesheet" href="/resources/space.css" />
+ <link href='/resources/highlighter/styles/shCoreCXF.css'
rel='stylesheet' type='text/css' />
+ <link href='/resources/highlighter/styles/shThemeCXF.css' rel='stylesheet'
type='text/css' />
+ <script src='/resources/highlighter/scripts/shCore.js'
type='text/javascript'></script>
+ <script src='/resources/highlighter/scripts/shBrushJava.js'
type='text/javascript'></script>
+ <script src='/resources/highlighter/scripts/shBrushXml.js'
type='text/javascript'></script>
+ <script src='/resources/highlighter/scripts/shBrushPlain.js'
type='text/javascript'></script>
+ <script>
+ SyntaxHighlighter.defaults['toolbar'] = false;
+ SyntaxHighlighter.all();
+ </script>
<link href="/styles/style.css" rel="stylesheet" type="text/css"/>
@@ -67,36 +77,118 @@
</div>
<div id="content">
- <div id="ConfluenceContent"><p><strong>Component
events</strong> are Tapestry's way of conveying a user's interactions with the
web page, such as clicking links and submitting forms, to designated methods in
your page and component classes. When a component event is triggered, Tapestry
calls the event handler method you've provided, if any, in the containing
component's class.</p><parameter
ac:name="style">float:right</parameter><parameter ac:name="title">Related
Articles</parameter><parameter
ac:name="class">aui-label</parameter><rich-text-body><parameter
ac:name="showLabels">false</parameter><parameter
ac:name="showSpace">false</parameter><parameter ac:name="title">Related
Articles</parameter><parameter ac:name="cql">label = "request-processing" and
space = currentSpace()</parameter></rich-text-body><p>Let's look at a simple
example. Here's a portion of the template for a page (let's call it "Review")
that lists documents and lets a user click to edit any one of th
em.</p><parameter ac:name="language">xml</parameter><parameter
ac:name="title">Review.tml (partial)</parameter><plain-text-body><p>
Select document to edit: </p>
+ <div id="ConfluenceContent"><p><strong>Component
events</strong> are Tapestry's way of conveying a user's interactions with the
web page, such as clicking links and submitting forms, to designated methods in
your page and component classes. When a component event is triggered, Tapestry
calls the event handler method you've provided, if any, in the containing
component's class.</p><div class="aui-label" style="float:right" title="Related
Articles">
+
+
+
+
+
+
+
+
+<h3>Related Articles</h3>
+
+<ul class="content-by-label"><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="page-navigation.html">Page Navigation</a>
+
+
+ </div>
+ </li><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="page-life-cycle.html">Page Life Cycle</a>
+
+
+ </div>
+ </li><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="component-rendering.html">Component
Rendering</a>
+
+
+ </div>
+ </li><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="component-events.html">Component Events</a>
+
+
+ </div>
+ </li><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="component-events-faq.html">Component Events
FAQ</a>
+
+
+ </div>
+ </li><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="request-processing.html">Request
Processing</a>
+
+
+ </div>
+ </li></ul>
+</div>
+
+
+<p>Let's look at a simple example. Here's a portion of the template for a page
(let's call it "Review") that lists documents and lets a user click to edit any
one of them.</p><div class="code panel pdl" style="border-width: 1px;"><div
class="codeHeader panelHeader pdl" style="border-bottom-width:
1px;"><b>Review.tml (partial)</b></div><div class="codeContent panelContent
pdl">
+<pre class="brush: xml; gutter: false; theme: Default"
style="font-size:12px;"><p> Select document to edit: </p>
<t:loop source="documents" value="document">
<div>
<t:actionlink" t:id="edit" context="document.id">
${document.name} </t:actionlink>
</div>
</t:loop>
-</plain-text-body><p>Notice that Review.tml contains an ActionLink component
in a loop. For each rendering within the loop, the ActionLink component creates
a component event request URL, with the event type set to "action". In this
case, each URL might look like:</p><p><code><span
class="external-link">   
http://localhost:8080/review.edit/3</span></code></p><p>This URL identifies the
<strong>page</strong> that contains the component ("review"),
the <strong>Component id</strong> of the component within the page
("edit"), and the <strong>context</strong> value ("3", the "id" property of the
document). <em>Additional context values, if any, are appended to the
path.</em> (The URL may also contain the <strong>event name</strong>, unless,
as here, it is "action".)</p><p>There's no direct mapping from URL to a piece
of code. Instead, when the user clicks on the link, the ActionLink component
triggers events. And then Tapestry ensures that the correct bits of code (yo
ur event handler method, see below) get invoked for those events.</p><p>This
demonstrates a critical difference between Tapestry and a more traditional,
action oriented framework. The URL doesn't say what happens when the link is
clicked, it identifies <em>which component is responsible</em> when the link is
clicked.</p><p>Often, a navigation request (originating with the user) will
spawn a number of flow-of-control requests. For example, a form component may
trigger an action event, which will then trigger notification events to
announce when the form submission is about to be processed, and whether it was
successful or not, and those event could be further handled by the page
component.</p><h1 id="ComponentEvents-EventHandlerMethods">Event Handler
Methods</h1><p>When a component event occurs, Tapestry invokes any event
handler methods that you have identified for that event. You can identify your
event handler methods via a naming convention (see Method Naming Convention
below), o
r via the @<a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/OnEvent.html">OnEvent</a>
annotation.</p><parameter
ac:name="language">java</parameter><plain-text-body>@OnEvent(component="edit")
+</pre>
+</div></div><p>Notice that Review.tml contains an ActionLink component in a
loop. For each rendering within the loop, the ActionLink component creates a
component event request URL, with the event type set to "action". In this case,
each URL might look like:</p><p><code><span
class="external-link">   
http://localhost:8080/review.edit/3</span></code></p><p>This URL identifies the
<strong>page</strong> that contains the component ("review"),
the <strong>Component id</strong> of the component within the page
("edit"), and the <strong>context</strong> value ("3", the "id" property of the
document). <em>Additional context values, if any, are appended to the
path.</em> (The URL may also contain the <strong>event name</strong>, unless,
as here, it is "action".)</p><p>There's no direct mapping from URL to a piece
of code. Instead, when the user clicks on the link, the ActionLink component
triggers events. And then Tapestry ensures that the correct bits of code (your
eve
nt handler method, see below) get invoked for those events.</p><p>This
demonstrates a critical difference between Tapestry and a more traditional,
action oriented framework. The URL doesn't say what happens when the link is
clicked, it identifies <em>which component is responsible</em> when the link is
clicked.</p><p>Often, a navigation request (originating with the user) will
spawn a number of flow-of-control requests. For example, a form component may
trigger an action event, which will then trigger notification events to
announce when the form submission is about to be processed, and whether it was
successful or not, and those event could be further handled by the page
component.</p><h1 id="ComponentEvents-EventHandlerMethods">Event Handler
Methods</h1><p>When a component event occurs, Tapestry invokes any event
handler methods that you have identified for that event. You can identify your
event handler methods via a naming convention (see Method Naming Convention
below), or via
the @<a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/OnEvent.html">OnEvent</a>
annotation.</p><div class="code panel pdl" style="border-width: 1px;"><div
class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">@OnEvent(component="edit")
void editDocument(int docId)
{
this.selectedId = docId;
// do something with the document here
}
-</plain-text-body><p>Tapestry does two things here:</p><ul><li>Because of the
annotation, it identifies method editDocument() as the method to invoke
whenever the component whose ID is "edit" triggers an event.</li><li>Because
there is a method parameter, when the link is clicked the context value of the
request is converted from a string to an integer and passed in as the method's
value parameter.<br clear="none"><br clear="none"></li></ul>
+</pre>
+</div></div><p>Tapestry does two things here:</p><ul><li>Because of the
annotation, it identifies method editDocument() as the method to invoke
whenever the component whose ID is "edit" triggers an event.</li><li>Because
there is a method parameter, when the link is clicked the context value of the
request is converted from a string to an integer and passed in as the method's
value parameter.<br clear="none"><br clear="none"></li></ul>
<div class="confluence-information-macro
confluence-information-macro-information"><p class="title">Added in
5.3</p><span class="aui-icon aui-icon-small aui-iconfont-info
confluence-information-macro-icon"></span><div
class="confluence-information-macro-body">
</div></div>
<div class="error"><span class="error">Unknown macro: {div}</span>
<p>Starting in release 5.3, Tapestry will throw an exception if the component
identified for the event handler method doesn't exist in the containing
component's template. This helps prevent typos.</p>
-</div><p>In the above example, the editDocument() method will be invoked when
any event occurs in in the "edit" component (and has at least one context
value).</p><p>For some components, more than one type of event can occur, in
which case you will want to be more specific:</p><parameter
ac:name="language">java</parameter><plain-text-body>@OnEvent(value="action",
component="edit")
+</div><p>In the above example, the editDocument() method will be invoked when
any event occurs in in the "edit" component (and has at least one context
value).</p><p>For some components, more than one type of event can occur, in
which case you will want to be more specific:</p><div class="code panel pdl"
style="border-width: 1px;"><div class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">@OnEvent(value="action", component="edit")
void editDocument(int docId)
{
this.selectedId = docId;
// do something with the document here
}
-</plain-text-body><p>For the OnEvent annotation, the
<em><code>value</code></em> attribute identifies the name of the event to
match. We specified "action" because the ActionLink component triggers the
"action" event, as noted in the Component Events section of its <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/ActionLink.html">javadocs</a>.</p><p>Alternatively,
we can use the EventLink component, in which case the name of the event is
determined by us – either through the "event" parameter or the element's
ID:</p><parameter ac:name="language">xml</parameter><parameter
ac:name="title">An EventLink that emits the "delete"
event</parameter><plain-text-body><t:eventlink event="delete"
context="document.id"> ${document.name}
</t:eventlink></plain-text-body><p>which is equivalent to:</p><parameter
ac:name="language">xml</parameter><parameter ac:name="title">An EventLink that
emits the "delete" event</pa
rameter><plain-text-body><a t:type="eventlink" t:id="delete"
context="document.id"> ${document.name} </a></plain-text-body><p>Note
that if you omit the <code>component</code> part of the OnEvent annotation,
then you'll receive notifications from <em>all</em> contained components,
possibly including nested components (due to event
bubbling).</p><rich-text-body><p>You should usually specify exactly which
component(s) you wish to receive events from. Using @OnEvent on a method and
not specifying a specific component ID means that the method will be invoked
for events from <em>any</em> component.</p></rich-text-body><p>To support
testing, it's a common practice to give event handler methods
<em>package-private</em> visibility, as in the examples on this page, although
technically they may have any visibility (even private).</p><p>A single event
handler method may receive notifications from many different
components.</p><p>As elsewhere, the comparison of event type and compon
ent ID is case-insensitive.</p><h2
id="ComponentEvents-MethodNamingConvention">Method Naming Convention</h2><p>As
an alternative to the use of annotations, you may name your event handling
methods following a certain convention, and Tapestry will find and invoke your
methods just as if they were annotated.</p><p>This style of event handler
methods start with the prefix "on", followed by the name of the event. You may
then continue by adding "From" and a capitalized component id (remember that
Tapestry is case insensitive about event names and component IDs). So, for
example, a method named onValidateFromSave() will be invoked whenever a
"Validate" event is triggered by a component whose component ID is
"save".</p><p>The previous example may be rewritten as:</p><parameter
ac:name="language">java</parameter><plain-text-body>void onActionFromEdit(int
docId)
+</pre>
+</div></div><p>For the OnEvent annotation, the <em><code>value</code></em>
attribute identifies the name of the event to match. We specified "action"
because the ActionLink component triggers the "action" event, as noted in the
Component Events section of its <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/ActionLink.html">javadocs</a>.</p><p>Alternatively,
we can use the EventLink component, in which case the name of the event is
determined by us – either through the "event" parameter or the element's
ID:</p><div class="code panel pdl" style="border-width: 1px;"><div
class="codeHeader panelHeader pdl" style="border-bottom-width: 1px;"><b>An
EventLink that emits the "delete" event</b></div><div class="codeContent
panelContent pdl">
+<pre class="brush: xml; gutter: false; theme: Default"
style="font-size:12px;"><t:eventlink event="delete"
context="document.id"> ${document.name} </t:eventlink></pre>
+</div></div><p>which is equivalent to:</p><div class="code panel pdl"
style="border-width: 1px;"><div class="codeHeader panelHeader pdl"
style="border-bottom-width: 1px;"><b>An EventLink that emits the "delete"
event</b></div><div class="codeContent panelContent pdl">
+<pre class="brush: xml; gutter: false; theme: Default"
style="font-size:12px;"><a t:type="eventlink" t:id="delete"
context="document.id"> ${document.name} </a></pre>
+</div></div><p>Note that if you omit the <code>component</code> part of the
OnEvent annotation, then you'll receive notifications from <em>all</em>
contained components, possibly including nested components (due to event
bubbling).</p><div class="confluence-information-macro
confluence-information-macro-tip"><span class="aui-icon aui-icon-small
aui-iconfont-approve confluence-information-macro-icon"></span><div
class="confluence-information-macro-body"><p>You should usually specify exactly
which component(s) you wish to receive events from. Using @OnEvent on a method
and not specifying a specific component ID means that the method will be
invoked for events from <em>any</em> component.</p></div></div><p>To support
testing, it's a common practice to give event handler methods
<em>package-private</em> visibility, as in the examples on this page, although
technically they may have any visibility (even private).</p><p>A single event
handler method may receive notifications from many dif
ferent components.</p><p>As elsewhere, the comparison of event type and
component ID is case-insensitive.</p><h2
id="ComponentEvents-MethodNamingConvention">Method Naming Convention</h2><p>As
an alternative to the use of annotations, you may name your event handling
methods following a certain convention, and Tapestry will find and invoke your
methods just as if they were annotated.</p><p>This style of event handler
methods start with the prefix "on", followed by the name of the event. You may
then continue by adding "From" and a capitalized component id (remember that
Tapestry is case insensitive about event names and component IDs). So, for
example, a method named onValidateFromSave() will be invoked whenever a
"Validate" event is triggered by a component whose component ID is
"save".</p><p>The previous example may be rewritten as:</p><div class="code
panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">void onActionFromEdit(int docId)
{
this.selectedId = docId;
// do something with the document here
}
-</plain-text-body><rich-text-body><p>Many people prefer the naming convention
approach, reserving the annotation just for situations that don't otherwise
fit.</p></rich-text-body><h2 id="ComponentEvents-MethodReturnValues">Method
Return Values</h2><p>Main Article: <a href="page-navigation.html">Page
Navigation</a></p><p>For page navigation events (originating in components such
as EventLink, ActionLink and Form), the value returned from an event handler
method determines how Tapestry will render a
response.</p><ul><li><strong>Null</strong>: For no value, or null, the current
page (the page containing the component) will render the
response.</li><li><strong>Page</strong>: For the name of a page, or a page
class or page instance, a render request URL will be constructed and sent to
the client as a redirect to that page.</li><li><strong>URL</strong>: For a
java.net.URL, a redirect will be sent to the client. (In Tapestry 5.3.x and
earlier, this only works for non-Ajax requests.)</li><
li><strong>Zone body</strong>: In the case of an Ajax request to update a
zone, the component event handler will return the new zone body, typically via
an injected component or block.</li><li><strong>HttpError</strong>: For an <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/HttpError.html">HttpError</a>,
an error response is sent to the client.</li><li><strong>Link</strong>: For a
<a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/Link.html">Link</a>,
a redirect is sent to the client.</li><li><strong>Stream</strong>: For a <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/StreamResponse.html">StreamResponse</a>,
a stream of data is sent to the client</li><li><strong>boolean:</strong>
<em>true</em> prevents the event from bubbling up further; <em>false</em> lets
it bubble up. See Event Bubbling, below.</li></ul><p>See <a href="page-navi
gation.html">Page Navigation</a> for more details.</p><h2
id="ComponentEvents-MultipleMethodMatches">Multiple Method Matches</h2><p>In
some cases, there may be multiple event handler methods matching a single
event. In that case, Tapestry invokes them in the following
order:</p><ul><li>Base class methods before sub-class methods.</li><li>Matching
methods within a class in alphabetical order.</li><li>For a single method name
with multiple overrides, by number of parameters, descending.</li></ul><p>Of
course, ordinarily would you <em>not</em> want to create more than one method
to handle an event.</p><p>When a sub-class overrides an event handler method of
a base class, the event handler method is only invoked once, along with any
other base class methods. The subclass can change the <em>implementation</em>
of the base class method via an override, but can't change the <em>timing</em>
of when that method is invoked. See <a class="external-link"
href="https://issues.apache.org/jira/br
owse/TAP5-51">issue TAP5-51</a>.</p><h2
id="ComponentEvents-EventContext">Event Context</h2><p>The context values (the
context parameter to the EventLink or ActionLink component) can be any object.
However, only a simple conversion to string occurs. (This is in contrast to
Tapestry 4, which had an elaborate type mechanism with the odd name
"DataSqueezer".)</p><p>Again, whatever your value is (string, number, date), it
is converted into a plain string. This results in a more readable URL.</p><p>If
you have multiple context values (by binding a list or array of objects to the
<em>context</em> parameter of the EventLink or ActionLink), then each one, in
order, will be added to the URL.</p><p>When an event handler method is invoked,
the strings are converted back into values, or even objects. A <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ValueEncoder.html">ValueEncoder</a>
is used to convert between client-side strings and server-side
objects. The <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/ValueEncoderSource.html">ValueEncoderSource</a>
service provides the necessary value encoders.</p><p>As shown in the example
above, most of the parameters passed to the event handler method are derived
from the values provided in the event context. Each successive method parameter
matches against a value provided in the event context (the context parameter of
the ActionLink component; though many components have a similar context
parameter).</p><p>In many cases it is helpful to have direct access to the
context (for example, to adapt to cases where there are a variable number of
context values). The context values may be passed to an event handler method as
parameter of the following types:</p><ul><li><a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/EventContext.html">EventContext</a></li><li>Object[]</li><li>List<Obj
ect></li></ul><p>The latter two should be avoided, they may be removed in a
future release. In all of these cases, the context parameter acts as a freebie;
it doesn't match against a context value as it represents <em>all</em> context
values.</p><plain-text-body>Object onActionFromEdit(EventContext context)
+</pre>
+</div></div><div class="confluence-information-macro
confluence-information-macro-information"><span class="aui-icon aui-icon-small
aui-iconfont-info confluence-information-macro-icon"></span><div
class="confluence-information-macro-body"><p>Many people prefer the naming
convention approach, reserving the annotation just for situations that don't
otherwise fit.</p></div></div><h2
id="ComponentEvents-MethodReturnValues">Method Return Values</h2><p>Main
Article: <a href="page-navigation.html">Page Navigation</a></p><p>For page
navigation events (originating in components such as EventLink, ActionLink and
Form), the value returned from an event handler method determines how Tapestry
will render a response.</p><ul><li><strong>Null</strong>: For no value, or
null, the current page (the page containing the component) will render the
response.</li><li><strong>Page</strong>: For the name of a page, or a page
class or page instance, a render request URL will be constructed and sent to
the c
lient as a redirect to that page.</li><li><strong>URL</strong>: For a
java.net.URL, a redirect will be sent to the client. (In Tapestry 5.3.x and
earlier, this only works for non-Ajax requests.)</li><li><strong>Zone
body</strong>: In the case of an Ajax request to update a zone, the component
event handler will return the new zone body, typically via an injected
component or block.</li><li><strong>HttpError</strong>: For an <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/HttpError.html">HttpError</a>,
an error response is sent to the client.</li><li><strong>Link</strong>: For a
<a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/Link.html">Link</a>,
a redirect is sent to the client.</li><li><strong>Stream</strong>: For a <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/StreamResponse.html">StreamResponse</a>,
a stream of data is sent to
the client</li><li><strong>boolean:</strong> <em>true</em> prevents the event
from bubbling up further; <em>false</em> lets it bubble up. See Event Bubbling,
below.</li></ul><p>See <a href="page-navigation.html">Page Navigation</a> for
more details.</p><h2 id="ComponentEvents-MultipleMethodMatches">Multiple Method
Matches</h2><p>In some cases, there may be multiple event handler methods
matching a single event. In that case, Tapestry invokes them in the following
order:</p><ul><li>Base class methods before sub-class methods.</li><li>Matching
methods within a class in alphabetical order.</li><li>For a single method name
with multiple overrides, by number of parameters, descending.</li></ul><p>Of
course, ordinarily would you <em>not</em> want to create more than one method
to handle an event.</p><p>When a sub-class overrides an event handler method of
a base class, the event handler method is only invoked once, along with any
other base class methods. The subclass can change the <em>
implementation</em> of the base class method via an override, but can't change
the <em>timing</em> of when that method is invoked. See <a
class="external-link"
href="https://issues.apache.org/jira/browse/TAP5-51">issue TAP5-51</a>.</p><h2
id="ComponentEvents-EventContext">Event Context</h2><p>The context values (the
context parameter to the EventLink or ActionLink component) can be any object.
However, only a simple conversion to string occurs. (This is in contrast to
Tapestry 4, which had an elaborate type mechanism with the odd name
"DataSqueezer".)</p><p>Again, whatever your value is (string, number, date), it
is converted into a plain string. This results in a more readable URL.</p><p>If
you have multiple context values (by binding a list or array of objects to the
<em>context</em> parameter of the EventLink or ActionLink), then each one, in
order, will be added to the URL.</p><p>When an event handler method is invoked,
the strings are converted back into values, or even object
s. A <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ValueEncoder.html">ValueEncoder</a>
is used to convert between client-side strings and server-side objects. The <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/ValueEncoderSource.html">ValueEncoderSource</a>
service provides the necessary value encoders.</p><p>As shown in the example
above, most of the parameters passed to the event handler method are derived
from the values provided in the event context. Each successive method parameter
matches against a value provided in the event context (the context parameter of
the ActionLink component; though many components have a similar context
parameter).</p><p>In many cases it is helpful to have direct access to the
context (for example, to adapt to cases where there are a variable number of
context values). The context values may be passed to an event handler method as
parameter of
the following types:</p><ul><li><a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/EventContext.html">EventContext</a></li><li>Object[]</li><li>List<Object></li></ul><p>The
latter two should be avoided, they may be removed in a future release. In all
of these cases, the context parameter acts as a freebie; it doesn't match
against a context value as it represents <em>all</em> context values.</p><div
class="code panel pdl" style="border-width: 1px;"><div class="codeContent
panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">Object onActionFromEdit(EventContext context)
{
if (context.getCount() > 0) {
this.selectedId = context.get(0);
@@ -105,7 +197,9 @@ void editDocument(int docId)
alertManager.warn("Please select a document.");
return null;
}
-}</plain-text-body><h2
id="ComponentEvents-AccessingRequestQueryParameters">Accessing Request Query
Parameters</h2><p>A parameter may be annotated with the @RequestParameter
annotation; this allows query parameters (?name1=value1&name2=value2, etc)
to be extracted from the request, converted to the correct type, and passed to
the method. Again, this doesn't count against the event context
values.</p><p>See the example in the <a href="link-components-faq.html">Link
Components FAQ</a>.</p><h2 id="ComponentEvents-MethodMatching">Method
Matching</h2><p>An event handler method will only be invoked <em>if the context
contains at least as many values as the method has parameters</em>. Methods
with too many parameters will be silently skipped.</p><p>Tapestry will silently
skip over a method if there are insufficient values in the context to satisfy
the number of parameters requested.</p><p>EventContext parameters, and
parameters annotated with @RequestParameter, do not count against th
is limit.</p><h2 id="ComponentEvents-MethodOrdering">Method
Ordering</h2><p>When multiple methods match within the same class, Tapestry
will invoke them in ascending alphabetical order. When there are multiple
overrides of the same method name, Tapestry invokes them in descending order by
number of parameters. In general, these situations don't happen ... in most
cases, only a single method is required to handle a specific event form a
specific component.</p><p>An event handler method may return the value
<code>true</code> to indicate that the event has been handled; this immediately
stops the search for additional methods in the same class (or in base classes)
or in containing components.</p><h1 id="ComponentEvents-EventBubbling">Event
Bubbling</h1><p>The event will bubble up the component hierarchy, first to the
containing component, then <em>that</em> component's containing component or
page, and so on, until it is <em>aborted</em> by an event handler method
returning <em>true</e
m> or a non-null value.</p><p>Returning a boolean value from an event handler
method is special. Returning <em>true</em> will abort the event with no result;
use this when the event is fully handled without a return value and no further
event handlers (in the same component, or in containing components) should be
invoked.</p><p>Returning <em>false</em> is the same as returning null; event
processing will continue to look for more event handlers, in the same component
or its parent.</p><p>When an event bubbles up from a component to its
container, the origin of the event is changed to be the component. For example,
a Form component inside a BeanEditForm component may trigger a success event.
The page containing the BeanEditForm may listen for that event, but it will be
from the BeanEditForm component (which makes sense, because the id of the Form
inside the BeanEditForm is part of the BeanEditForm's implementation, not its
public interface).</p><p>If you want to handle events that ha
ve bubbled up from nested component, you'll soon find that you don't have easy
access to the component ID of the firing component. In practical terms this
means that you'll want to trigger custom events for the events triggered by
those nested components (see Triggering Events, below), and use that custom
event name in your event handler method.</p><h1
id="ComponentEvents-EventMethodExceptions">Event Method Exceptions</h1><p>Event
methods are allowed to throw any exception (not just runtime exceptions). If an
event method does throw an exception, Tapestry will catch the thrown exception
and ultimately display the exception report page.</p><p>In other words, there's
no need to do this:</p><parameter
ac:name="language">java</parameter><plain-text-body> void
onActionFromRunQuery()
+}</pre>
+</div></div><h2 id="ComponentEvents-AccessingRequestQueryParameters">Accessing
Request Query Parameters</h2><p>A parameter may be annotated with the
@RequestParameter annotation; this allows query parameters
(?name1=value1&name2=value2, etc) to be extracted from the request,
converted to the correct type, and passed to the method. Again, this doesn't
count against the event context values.</p><p>See the example in the <a
href="link-components-faq.html">Link Components FAQ</a>.</p><h2
id="ComponentEvents-MethodMatching">Method Matching</h2><p>An event handler
method will only be invoked <em>if the context contains at least as many values
as the method has parameters</em>. Methods with too many parameters will be
silently skipped.</p><p>Tapestry will silently skip over a method if there are
insufficient values in the context to satisfy the number of parameters
requested.</p><p>EventContext parameters, and parameters annotated with
@RequestParameter, do not count against this limi
t.</p><h2 id="ComponentEvents-MethodOrdering">Method Ordering</h2><p>When
multiple methods match within the same class, Tapestry will invoke them in
ascending alphabetical order. When there are multiple overrides of the same
method name, Tapestry invokes them in descending order by number of parameters.
In general, these situations don't happen ... in most cases, only a single
method is required to handle a specific event form a specific
component.</p><p>An event handler method may return the value <code>true</code>
to indicate that the event has been handled; this immediately stops the search
for additional methods in the same class (or in base classes) or in containing
components.</p><h1 id="ComponentEvents-EventBubbling">Event Bubbling</h1><p>The
event will bubble up the component hierarchy, first to the containing
component, then <em>that</em> component's containing component or page, and so
on, until it is <em>aborted</em> by an event handler method returning
<em>true</em> or a
non-null value.</p><p>Returning a boolean value from an event handler method
is special. Returning <em>true</em> will abort the event with no result; use
this when the event is fully handled without a return value and no further
event handlers (in the same component, or in containing components) should be
invoked.</p><p>Returning <em>false</em> is the same as returning null; event
processing will continue to look for more event handlers, in the same component
or its parent.</p><p>When an event bubbles up from a component to its
container, the origin of the event is changed to be the component. For example,
a Form component inside a BeanEditForm component may trigger a success event.
The page containing the BeanEditForm may listen for that event, but it will be
from the BeanEditForm component (which makes sense, because the id of the Form
inside the BeanEditForm is part of the BeanEditForm's implementation, not its
public interface).</p><p>If you want to handle events that have bubb
led up from nested component, you'll soon find that you don't have easy access
to the component ID of the firing component. In practical terms this means that
you'll want to trigger custom events for the events triggered by those nested
components (see Triggering Events, below), and use that custom event name in
your event handler method.</p><h1
id="ComponentEvents-EventMethodExceptions">Event Method Exceptions</h1><p>Event
methods are allowed to throw any exception (not just runtime exceptions). If an
event method does throw an exception, Tapestry will catch the thrown exception
and ultimately display the exception report page.</p><p>In other words, there's
no need to do this:</p><div class="code panel pdl" style="border-width:
1px;"><div class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;"> void onActionFromRunQuery()
{
try
{
@@ -116,23 +210,27 @@ void editDocument(int docId)
throw new RuntimeException(ex);
}
}
-</plain-text-body><p>Instead, you may simply say:</p><parameter
ac:name="language">java</parameter><plain-text-body> void
onActionFromRunQuery() throws JDBCException
+</pre>
+</div></div><p>Instead, you may simply say:</p><div class="code panel pdl"
style="border-width: 1px;"><div class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;"> void onActionFromRunQuery() throws JDBCException
{
dao.executeQuery();
}
-</plain-text-body><p>Your event handler method may even declare that it
"throws Exception" if that is more convenient.</p><h1
id="ComponentEvents-InterceptingEventExceptions">Intercepting Event
Exceptions</h1><p>When an event handler method throws an exception (checked or
runtime), Tapestry gives the component and its containing page a chance to
handle the exception, before continuing on to report the
exception.<plain-text-body>{float:right|background=#eee|padding=0 1em}
- *JumpStart Demo:*
- [Handling A Bad
Context|http://jumpstart.doublenegative.com.au/jumpstart/examples/infrastructure/handlingabadcontext/1]
-{float}</plain-text-body>Tapestry triggers a new event, of type "exception",
passing the thrown exception as the context. In fact, the exception is wrapped
inside a <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/runtime/ComponentEventException.html">ComponentEventException</a>,
from which you may extract the event type and
context.</p><p>Thus:</p><parameter
ac:name="language">java</parameter><plain-text-body> Object
onException(Throwable cause)
+</pre>
+</div></div><p>Your event handler method may even declare that it "throws
Exception" if that is more convenient.</p><h1
id="ComponentEvents-InterceptingEventExceptions">Intercepting Event
Exceptions</h1><p>When an event handler method throws an exception (checked or
runtime), Tapestry gives the component and its containing page a chance to
handle the exception, before continuing on to report the exception.</p><div
class="navmenu" style="float:right; background:#eee; margin:3px; padding:0 1em">
+<p> <strong>JumpStart Demo:</strong><br clear="none">
+ <a class="external-link"
href="http://jumpstart.doublenegative.com.au/jumpstart/examples/infrastructure/handlingabadcontext/1"
rel="nofollow">Handling A Bad Context</a></p></div>Tapestry triggers a new
event, of type "exception", passing the thrown exception as the context. In
fact, the exception is wrapped inside a <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/runtime/ComponentEventException.html">ComponentEventException</a>,
from which you may extract the event type and context.<p>Thus:</p><div
class="code panel pdl" style="border-width: 1px;"><div class="codeContent
panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;"> Object onException(Throwable cause)
{
message = cause.getMessage();
return this;
}
-</plain-text-body><p>The return value of the exception event handler
<em>replaces</em> the return value of original event handler method. For the
typical case (an exception thrown by an "activate" or "action" event), this
will be a <a href="page-navigation.html">navigational response</a> such as a
page instance or page name.</p><p>This can be handy for handling cases where
the data in the URL is incorrectly formatted.</p><p>In the above example, the
navigational response is the page itself.</p><p>If there is no exception event
handler, or the exception event handler returns null (or is void), then the
exception will be passed to the <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/RequestExceptionHandler.html">RequestExceptionHandler</a>
service, which (in the default configuration) will render the exception
page.</p><h1 id="ComponentEvents-TriggeringEvents">Triggering
Events</h1><p><plain-text-body>{float:right|background=#ee
e|padding=0 1em}
- *JumpStart Demo:*
- [AJAX Components
CRUD|http://jumpstart.doublenegative.com.au/jumpstart/together/ajaxcomponentscrud/persons]
-{float}</plain-text-body>If you want your own component to trigger events,
just call the <a rel="nofollow">triggerEvent</a> method of <a
class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ComponentResources.html">ComponentResources</a>
from within the component class.</p><p>For example, the following triggers an
"updateAll" event. A containing component can then respond to it, if desired,
with an "onUpdateAll()" method in its own component class.</p><parameter
ac:name="language">java</parameter><parameter ac:name="title">Your component
class (partial)</parameter><plain-text-body>@Inject
+</pre>
+</div></div><p>The return value of the exception event handler
<em>replaces</em> the return value of original event handler method. For the
typical case (an exception thrown by an "activate" or "action" event), this
will be a <a href="page-navigation.html">navigational response</a> such as a
page instance or page name.</p><p>This can be handy for handling cases where
the data in the URL is incorrectly formatted.</p><p>In the above example, the
navigational response is the page itself.</p><p>If there is no exception event
handler, or the exception event handler returns null (or is void), then the
exception will be passed to the <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/RequestExceptionHandler.html">RequestExceptionHandler</a>
service, which (in the default configuration) will render the exception
page.</p><h1 id="ComponentEvents-TriggeringEvents">Triggering
Events</h1><p></p><div class="navmenu" style="float:right; backg
round:#eee; margin:3px; padding:0 1em">
+<p> <strong>JumpStart Demo:</strong><br clear="none">
+ <a class="external-link"
href="http://jumpstart.doublenegative.com.au/jumpstart/together/ajaxcomponentscrud/persons"
rel="nofollow">AJAX Components CRUD</a></p></div>If you want your own
component to trigger events, just call the <a rel="nofollow">triggerEvent</a>
method of <a class="external-link"
href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ComponentResources.html">ComponentResources</a>
from within the component class.<p>For example, the following triggers an
"updateAll" event. A containing component can then respond to it, if desired,
with an "onUpdateAll()" method in its own component class.</p><div class="code
panel pdl" style="border-width: 1px;"><div class="codeHeader panelHeader pdl"
style="border-bottom-width: 1px;"><b>Your component class
(partial)</b></div><div class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">@Inject
ComponentResources componentResources;
 ...
private void timeToUpdate() {
@@ -140,7 +238,8 @@ private void timeToUpdate() {
if (wasHandled) {
...
}
-} </plain-text-body><p>The third parameter to triggerEvent is a
ComponentEventCallback, which you'll only need to implement if you want to get
the return value of the handler method. The return value of <span
class="il">triggerEvent</span>() says if the <span class="il">event</span> was
handled or not.</p><p> </p></div>
+} </pre>
+</div></div><p>The third parameter to triggerEvent is a
ComponentEventCallback, which you'll only need to implement if you want to get
the return value of the handler method. The return value of <span
class="il">triggerEvent</span>() says if the <span class="il">event</span> was
handled or not.</p><p> </p></div>
</div>
<div class="clearer"></div>
Modified: websites/production/tapestry/content/component-libraries.html
==============================================================================
--- websites/production/tapestry/content/component-libraries.html (original)
+++ websites/production/tapestry/content/component-libraries.html Wed Sep 20
12:29:16 2017
@@ -27,6 +27,15 @@
</title>
<link type="text/css" rel="stylesheet" href="/resources/space.css" />
+ <link href='/resources/highlighter/styles/shCoreCXF.css'
rel='stylesheet' type='text/css' />
+ <link href='/resources/highlighter/styles/shThemeCXF.css' rel='stylesheet'
type='text/css' />
+ <script src='/resources/highlighter/scripts/shCore.js'
type='text/javascript'></script>
+ <script src='/resources/highlighter/scripts/shBrushJava.js'
type='text/javascript'></script>
+ <script src='/resources/highlighter/scripts/shBrushXml.js'
type='text/javascript'></script>
+ <script>
+ SyntaxHighlighter.defaults['toolbar'] = false;
+ SyntaxHighlighter.all();
+ </script>
<link href="/styles/style.css" rel="stylesheet" type="text/css"/>
@@ -67,7 +76,41 @@
</div>
<div id="content">
- <div id="ConfluenceContent"><p> </p><parameter
ac:name="hidden">true</parameter><parameter
ac:name="atlassian-macro-output-type">BLOCK</parameter><rich-text-body><p>How
to create a library of your custom
components</p></rich-text-body><rich-text-body><p>This page has not yet been
fully updated for Tapestry 5.4. Things are different and simpler in 5.4 than in
previous releases.</p></rich-text-body><h1
id="ComponentLibraries-CreatingComponentLibraries">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><parameter ac:name="style">float:right</parameter><parameter
ac:name="title">Related Articles</parameter><parameter
ac:name="class">aui-label</parameter><rich-text-body><par
ameter ac:name="showLabels">false</parameter><parameter
ac:name="showSpace">false</parameter><parameter ac:name="title">Related
Articles</parameter><parameter ac:name="cql">label = "components" and space =
currentSpace()</parameter></rich-text-body><p>A Tapestry component library
consists of components (and optionally mixins, pages and component base
classes). 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><parameter a
c:name="language">xml</parameter><parameter
ac:name="title">pom.xml</parameter><plain-text-body><project>
+ <div id="ConfluenceContent"><p> </p><div
class="confluence-information-macro confluence-information-macro-warning"><span
class="aui-icon aui-icon-small aui-iconfont-error
confluence-information-macro-icon"></span><div
class="confluence-information-macro-body"><p>This page has not yet been fully
updated for Tapestry 5.4. Things are different and simpler in 5.4 than in
previous releases.</p></div></div><h1
id="ComponentLibraries-CreatingComponentLibraries">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><div class="aui-label" style="float:right" title="Related
Articles">
+
+
+
+
+
+
+
+
+<h3>Related Articles</h3>
+
+<ul class="content-by-label"><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="component-reference.html">Component
Reference</a>
+
+
+ </div>
+ </li><li>
+ <div>
+ <span class="icon aui-icon aui-icon-small
aui-iconfont-page-default" title="Page">Page:</span> </div>
+
+ <div class="details">
+ <a href="component-libraries.html">Component
Libraries</a>
+
+
+ </div>
+ </li></ul>
+</div>
+
+
+<p>A Tapestry component library consists of components (and optionally mixins,
pages and component base classes). 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 pdl"
style="border-width: 1px;"><div class="codeHeader panelHeader pdl"
style="border-bottom-width: 1px;"><b>pom.xml</b></div><div class="codeContent
panelContent pdl">
+<pre class="brush: xml; gutter: false; theme: Default"
style="font-size:12px;"><project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>happylib</artifactId>
@@ -134,7 +177,9 @@
<tapestry-release-version>5.4-beta-28</tapestry-release-version>
</properties>
</project>
-</plain-text-body><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
id="ComponentLibraries-Step1:Chooseabasepackagename">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 <code>org.examples.happylib</code>.</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
id="ComponentLibraries-Step2:Createyourpagesand/orcomponents">Step 2: Create
your pages and/or components</h2><p>Our component is very simple:</p><parameter
ac:name="language">java</parameter><parameter ac:name="title">HappyIcon
.java</parameter><plain-text-body>package org.example.happylib.components;
+</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
id="ComponentLibraries-Step1:Chooseabasepackagename">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 <code>org.examples.happylib</code>.</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
id="ComponentLibraries-Step2:Createyourpagesand/orcomponents">Step 2: Create
your pages and/or components</h2><p>Our component is very simple:</p><div
class="code panel pdl" style="border-width: 1px;"><div class="codeHeader
panelHeade
r pdl" style="border-bottom-width: 1px;"><b>HappyIcon.java</b></div><div
class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">package org.example.happylib.components;
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.MarkupWriter;
@@ -155,7 +200,9 @@ public class HappyIcon
return false;
}
}
-</plain-text-body><p>HappyIcon appears inside the components sub-package. The
happyIcon field is injected with the the Asset for the file
<code>happy.jpg</code>. The path specified with the @Path annotation is
relative to the <code>HappyIcon.class</code> file; it should be stored in the
project under
<code>src/main/resources/org/example/happylib/components</code>.</p><p>Tapestry
ensures that the <code>happy.jpg</code> asset can be accessed from the client
web browser; the src attribute of the <img> tag will be a URL that
directly accesses the image file ... there's no need to unpackage the
<code>happy.jpg</code> file. This works for any asset file stored under the
library's root package.</p><p>This component renders out an
<code><img></code> tag for the icon.</p><p>Often, a component library
will have many different components, or even pages.</p><h2
id="ComponentLibraries-Step3:Chooseavirtualfoldername">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 can include the HappyIcon component in
the template using any of the following, which are all
equivalent:</p><ul><li><t:happy.happyicon/></li><li><t:happy.icon/></li><li><img
t:type="happy.happyicon"/></li><li><img
t:type="happy/icon"/></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><parameter ac:name="language">xml</parameter><p
lain-text-body><html
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
+</pre>
+</div></div><p>HappyIcon appears inside the components sub-package. The
happyIcon field is injected with the the Asset for the file
<code>happy.jpg</code>. The path specified with the @Path annotation is
relative to the <code>HappyIcon.class</code> file; it should be stored in the
project under
<code>src/main/resources/org/example/happylib/components</code>.</p><p>Tapestry
ensures that the <code>happy.jpg</code> asset can be accessed from the client
web browser; the src attribute of the <img> tag will be a URL that
directly accesses the image file ... there's no need to unpackage the
<code>happy.jpg</code> file. This works for any asset file stored under the
library's root package.</p><p>This component renders out an
<code><img></code> tag for the icon.</p><p>Often, a component library
will have many different components, or even pages.</p><h2
id="ComponentLibraries-Step3:Chooseavirtualfoldername">Step 3: Choose a virtual
folder name</h2><p>In Tapestry, components that h
ave 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 can include the HappyIcon component in the template
using any of the following, which are all
equivalent:</p><ul><li><t:happy.happyicon/></li><li><t:happy.icon/></li><li><img
t:type="happy.happyicon"/></li><li><img
t:type="happy/icon"/></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 pdl" style="border-width: 1px;
"><div class="codeContent panelContent pdl">
+<pre class="brush: xml; gutter: false; theme: Default"
style="font-size:12px;"><html
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xmlns:h="tapestry-library:happy">
...
@@ -164,7 +211,9 @@ public class HappyIcon
...
</html>
-</plain-text-body><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
id="ComponentLibraries-Step4:Configurethevirtualfolder">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><parameter
ac:name="language">java</parameter><parameter
ac:name="title">HappyModule.java</parameter><plain-text-body>package
org.example.happylib.services;
+</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
id="ComponentLibraries-Step4:Configurethevirtualfolder">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 pdl"
style="border-width: 1px;"><div class="codeHeader panelHeader pdl"
style="border-bottom-width: 1px;"><b>HappyModule.java</b></div><div
class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">package org.example.happylib.services;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.services.LibraryMapping;
@@ -176,7 +225,9 @@ public class HappyModule
configuration.add(new LibraryMapping("happy", "org.example.happylib"));
}
}
-</plain-text-body><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><rich-text-body><p>It is
possible to add a mapping for "core", the core library for Tapestry components;
all the built-in Tapestry components (TextField, BeanEditForm, Grid, etc.) are
actually in the core library. When Tapestry doesn't find a component in your
application, it next searches inside the "core" library. 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). A
dding to "core" is sometimes reasonable, if you ensure that there is virtually
no chance of a naming conflict (via different modules contributing packages to
core with conflicting class names).</p></rich-text-body><h2
id="ComponentLibraries-Step5:Configurethemoduletoautoload">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><parameter
ac:name="language">xml</parameter><parameter ac:name="title">pom.xml
(partial)</parameter><plain-text-body> <plugin>
+</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="confluence-information-macro confluence-information-macro-note"><span
class="aui-icon aui-icon-small aui-iconfont-warning
confluence-information-macro-icon"></span><div
class="confluence-information-macro-body"><p>It is possible to add a mapping
for "core", the core library for Tapestry components; all the built-in Tapestry
components (TextField, BeanEditForm, Grid, etc.) are actually in the core
library. When Tapestry doesn't find a component in your application, it next
searches inside the "core"
library. 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 you ensure that there is virtually no chance of a naming
conflict (via different modules contributing packages to core with conflicting
class names).</p></div></div><h2
id="ComponentLibraries-Step5:Configurethemoduletoautoload">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 pdl"
style="border-width: 1px;"><div class="codeHeader panelHeader pdl"
style="border-bottom-width: 1px;"><b>pom.xml (partial)</b></div><div
class="codeContent panelContent pdl">
+<pre class="brush: xml; gutter: false; theme: Default"
style="font-size:12px;"> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
@@ -187,15 +238,20 @@ public class HappyModule
</archive>
</configuration>
</plugin>
-</plain-text-body><h2 id="ComponentLibraries-Step6:ExtendingClientAccess">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><parameter
ac:name="language">java</parameter><plain-text-body>public static void
contributeRegexAuthorizer(Configuration<String> configuration)
+</pre>
+</div></div><h2 id="ComponentLibraries-Step6:ExtendingClientAccess">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 pdl" style="border-width:
1px;"><div class="codeContent panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">public static void
contributeRegexAuthorizer(Configuration<String> configuration)
{
configuration.add("^org/example/happylib/.*\\.jpg$");
}
-</plain-text-body><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|gif|png).</p><h2
id="ComponentLibraries-Step7:VersioningAssets">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 is 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 in Tapestry 5.1 and earlier this causes a problem when a
later versi
on of the library changes the file. This is discussed in detail in <a
class="external-link"
href="http://developer.yahoo.com/performance/rules.html#expires"
rel="nofollow">Yahoo's Performance Best Practices</a>.</p><p>To handle this
problem in Tapestry 5.1 and earlier, 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><parameter
ac:name="language">java</parameter><plain-text-body>public static void
contributeClasspathAssetAliasManager(MappedConfiguration<String, String>
configuration)
+</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|gif|png).</p><h2
id="ComponentLibraries-Step7:VersioningAssets">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 is 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 in Tapestry 5.1 and earlier this causes a problem when a
later version of
the library changes the file. This is discussed in detail in <a
class="external-link"
href="http://developer.yahoo.com/performance/rules.html#expires"
rel="nofollow">Yahoo's Performance Best Practices</a>.</p><p>To handle this
problem in Tapestry 5.1 and earlier, 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 pdl" style="border-width: 1px;"><div class="codeContent
panelContent pdl">
+<pre class="brush: java; gutter: false; theme: Default"
style="font-size:12px;">public static void
contributeClasspathAssetAliasManager(MappedConfiguration<String, String>
configuration)
{
configuration.add("happylib/1.0", "org/example/happylib");
}
-</plain-text-body><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>
+</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>
<div class="confluence-information-macro
confluence-information-macro-information"><p class="title">Added in
5.2</p><span class="aui-icon aui-icon-small aui-iconfont-info
confluence-information-macro-icon"></span><div
class="confluence-information-macro-body">
</div></div>