tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Tapestry > Implementing the Hi-Lo Guessing Game
Date Sat, 10 Dec 2011 02:17:00 GMT
<html>
<head>
    <base href="https://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/2042/9/12/_/styles/combined.css?spaceKey=TAPESTRY&amp;forWysiwyg=true"
type="text/css">
    </head>
<body style="background: white;" bgcolor="white" class="email-body">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
    <h2><a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Implementing+the+Hi-Lo+Guessing+Game">Implementing
the Hi-Lo Guessing Game</a></h2>
    <h4>Page <b>edited</b> by             <a href="https://cwiki.apache.org/confluence/display/~bobharner">Bob
Harner</a>
    </h4>
        <div id="versionComment">
        <b>Comment:</b>
        Fixed some of the errors reported by Axel Werner in TAP5-1753 (thanks, Alex!)<br
/>
    </div>
        <br/>
                         <h4>Changes (3)</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" >h1. Index Page <br> <br></td></tr>
            <tr><td class="diff-changed-lines" >Let&#39;s get to work on the
Index page and template. <span class="diff-added-words"style="background-color: #dfd;">Make
Index.tml look like this:</span> <br></td></tr>
            <tr><td class="diff-unchanged" > <br>{code:lang=xml|title=Index.tml}
<br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
            <tr><td class="diff-unchanged" >{code} <br> <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">And
edit the corresponding Java class, Index.java, removing its body: <br> <br>{code:lang=java|title=Index.java}
<br>public class Index <br>{ <br>} <br>{code} <br> <br></td></tr>
            <tr><td class="diff-unchanged" >Running the application gives us our
start: <br> <br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
            <tr><td class="diff-unchanged" > <br>{code:lang=java|title=Index.java
(partial)} <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">
 import org.apache.tapestry5.annotations.Log; <br> <br>  . . . <br> <br></td></tr>
            <tr><td class="diff-unchanged" >  @Log <br>  void onActionFromStart()
<br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
    
            </table>
    </div>                            <h4>Full Content</h4>
                    <div class="notificationGreySide">
        <style type='text/css'>/*<![CDATA[*/
table.ScrollbarTable  {border: none;padding: 3px;width: 100%;padding: 3px;margin: 0px;background-color:
#f0f0f0}
table.ScrollbarTable td.ScrollbarPrevIcon {text-align: center;width: 16px;border: none;}
table.ScrollbarTable td.ScrollbarPrevName {text-align: left;border: none;}
table.ScrollbarTable td.ScrollbarParent {text-align: center;border: none;}
table.ScrollbarTable td.ScrollbarNextName {text-align: right;border: none;}
table.ScrollbarTable td.ScrollbarNextIcon {text-align: center;width: 16px;border: none;}

/*]]>*/</style><div class="Scrollbar"><table class='ScrollbarTable'><tr><td
class='ScrollbarPrevIcon'><a href="/confluence/display/TAPESTRY/Exploring+the+Project"><img
border='0' align='middle' src='/confluence/images/icons/back_16.gif' width='16' height='16'></a></td><td
width='33%' class='ScrollbarPrevName'><a href="/confluence/display/TAPESTRY/Exploring+the+Project">Exploring
the Project</a>&nbsp;</td><td width='33%' class='ScrollbarParent'><sup><a
href="/confluence/display/TAPESTRY/Tapestry+Tutorial"><img border='0' align='middle'
src='/confluence/images/icons/up_16.gif' width='8' height='8'></a></sup><a
href="/confluence/display/TAPESTRY/Tapestry+Tutorial">Tapestry Tutorial</a></td><td
width='33%' class='ScrollbarNextName'>&nbsp;<a href="/confluence/display/TAPESTRY/Using+BeanEditForm+To+Create+User+Forms">Using
BeanEditForm To Create User Forms</a></td><td class='ScrollbarNextIcon'><a
href="/confluence/display/TAPESTRY/Using+BeanEditForm+To+Create+User+Forms"><img border='0'
align='middle' src='/confluence/images/icons/forwd_16.gif' width='16' height='16'></a></td></tr></table></div>

<p>Let's start building a basic Hi-Lo Guessing game.</p>

<p>In the game, the computer selects a number between 1 and 10. You try and guess the
number, clicking links. At the end, the computer tells you how many guesses you required to
identify the target number. Even a simple example like this will demonstrate several important
concepts in Tapestry:</p>

<ul>
	<li>Breaking an application into individual pages</li>
	<li>Transferring information from one page to another</li>
	<li>Responding to user interactions</li>
	<li>Storing client information in the server-side session</li>
</ul>


<p>We'll build this little application in small pieces, using the kind of iterative
development that Tapestry makes so easy.</p>

<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23340505/hilo-flow.png?version=2&amp;modificationDate=1286828602000"
style="border: 0px solid black" /></span></p>

<p>Our page flow is very simple, consisting of three pages: Index (the starting page),
Guess and GameOver. The Index page introduces the application and includes a link to start
guessing. The Guess page presents the user with ten links, plus feedback such as "too low"
or "too high". The GameOver page tells the user how many guesses they took before finding
the target number.</p>

<h1><a name="ImplementingtheHi-LoGuessingGame-IndexPage"></a>Index Page</h1>

<p>Let's get to work on the Index page and template. Make Index.tml look like this:</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">
&lt;html t:type=<span class="code-quote">"layout"</span> title=<span class="code-quote">"Hi/Lo
Guess"</span>
  <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_3.xsd"</span>&gt;

  <span class="code-tag">&lt;p&gt;</span>
    I'm thinking of a number between one and ten ... <span class="code-tag">&lt;/p&gt;</span>

  <span class="code-tag">&lt;p&gt;</span>
    <span class="code-tag">&lt;a href=<span class="code-quote">"#"</span>&gt;</span>start
guessing<span class="code-tag">&lt;/a&gt;</span>
  <span class="code-tag">&lt;/p&gt;</span>

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

<p>And edit the corresponding Java class, Index.java, removing its body:</p>

<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
{
}
</pre>
</div></div>

<p>Running the application gives us our start:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/hilo-1.png'><img
src="/confluence/download/thumbnails/23340505/hilo-1.png" style="border: 0px solid black"
/></a></span></p>

<p>However, clicking the link doesn't do anything yet, as its just a placeholder \&lt;a\&gt;
tag, not an actual Tapestry component. Let's think about what should happen when the user
clicks that link:</p>

<ul>
	<li>A random target number between 1 and 10 should be selected</li>
	<li>The number of guesses taken should be reset to 0</li>
	<li>The user should be sent to the Guess page to make a guess</li>
</ul>


<p>Our first step is to find out when the user clicks that "start guessing" link.  In
a typical web application framework, we might start thinking about URLs and handlers and maybe
some sort of XML configuration file.  But this is Tapestry, so we're going to work with components
and methods on our classes.</p>

<p>First, the component.  We want to perform an action (selecting the number) before
continuing on to the Guess page.  The ActionLink component is just what we need; it creates
a link with a URL that will trigger an action event in our code ... but that's getting ahead
of ourselves.  First up, convert the \&lt;a\&gt; tag to an ActionLink component:</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;p&gt;</span>
    <span class="code-tag">&lt;t:actionlink t:id=<span class="code-quote">"start"</span>&gt;</span>start
guessing<span class="code-tag">&lt;/t:actionlink&gt;</span>
  <span class="code-tag">&lt;/p&gt;</span>
</pre>
</div></div>

<p>If you refresh the browser, you'll see that the URL for the "start guessing" link
is now /tutorial1/index.start, which identifies the name of the page ("index") and the id
of the component ("start").</p>

<p>If you click the link, you'll get an error:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/hilo-index-missing-action-error.png'><img
src="/confluence/download/thumbnails/23340505/hilo-index-missing-action-error.png" style="border:
0px solid black" /></a></span></p>

<p>Tapestry is telling us that we need to provide some kind of event handler for that
event.  What does that look like?</p>

<p>An event handler is a method of the Java class with a special name. The name is <tt>on</tt><em>event-name</em><tt>From</tt><em>component-id</em>
... here we want a method named <tt>onActionFromStart()</tt>.  How do we know
that "action" is the right event name?  Because that's what ActionLink does, that's why its
named _Action_Link.</p>

<p>Once again, Tapestry gives us options; if you don't like naming conventions, there's
an @<a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/OnEvent.html"
class="external-link" rel="nofollow">OnEvent</a> annotation you can place on the
method instead, which restores the freedom to name the method as you like. Details about this
approach are in the <a href="/confluence/display/TAPESTRY/Component+Events" title="Component
Events">Tapestry Users' Guide</a>. We'll be sticking with the naming convention approach
for the tutorial.</p>

<p>When handling a component event request (the kind of request triggered by the ActionLink
component's URL), Tapestry will find the component and trigger a component event on it. This
is the callback our server-side code needs to figure out what the user is doing on the client
side.  Let's start with an empty event handler:</p>

<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.tutorial.pages;

<span class="code-keyword">public</span> class Index
{
  void onActionFromStart()
  {

  }
}
</pre>
</div></div>

<p>In the browser, we can re-try the failed component event request by hitting the refresh
button ... or we can restart the application.  In either case, we get the default behavior,
which is simply to re-render the page.</p>

<p>Note that the event handler method does not have to be public; it can be protected,
private, or package private (as in this example). By convention, such methods are package
private, if for no other reason than it is the minimal amount of characters to type.</p>

<p>Hm. Right now you have to trust me that the method got invoked.  That's no good ...
what's a quick way to tell for sure?  One way would be have the method throw an exception,
but that's a bit ugly.</p>

<p>How about this: add the @<a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/Log.html"
class="external-link" rel="nofollow">Log</a> annotation to the method:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>Index.java (partial)</b></div><div
class="codeContent panelContent">
<pre class="code-java">
  <span class="code-keyword">import</span> org.apache.tapestry5.annotations.Log;

  . . .

  @Log
  void onActionFromStart()
  {

  }
</pre>
</div></div>

<p>When you next click the link you should see the following in the Eclipse console:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>[DEBUG] pages.Index [ENTER] onActionFromStart()
[DEBUG] pages.Index [ EXIT] onActionFromStart
[INFO] AppModule.TimingFilter Request time: 3 ms
[INFO] AppModule.TimingFilter Request time: 5 ms
</pre>
</div></div>

<p>The @Log annotation directs Tapestry to log method entry and exit.  You'll get to
see any parameters passed into the method, and any return value from the method ... as well
as any exception thrown from within the method. It's a powerful debugging tool.  This is an
example of Tapestry's meta-programming power, something we'll use quite a bit of in the tutorial.</p>

<p>Why do we see two requests for one click?  Tapestry uses an approach based on the
<a href="http://en.wikipedia.org/wiki/Post/Redirect/Get" class="external-link" rel="nofollow">Redirect
After Post</a> pattern. In fact, Tapestry performs a redirect after each component event.
So the first request was to process the action, and the second request was to re-render the
Index page. You can see this in the browser, because the URL is still "/tutorial1" (the URL
for rendering the Index page).  We'll return to this in a bit.</p>

<p>We're ready for the next step, which involves tying together the Index and Guess
pages. Index will select a target number for the user to Guess, then "pass the baton" to the
Guess page.</p>

<p>Let's start by thinking about the Guess page. It needs a variable to store the target
value in, and it needs a method that the Index page can invoke, to setup that target value.</p>

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

<span class="code-keyword">public</span> class Guess
{
  <span class="code-keyword">private</span> <span class="code-object">int</span>
target;

  void setup(<span class="code-object">int</span> target)
  {
    <span class="code-keyword">this</span>.target = target;
  }
}
</pre>
</div></div>

<p>With that in mind, we can modify Index to invoke this new <tt>setup()</tt>
method:</p>

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

<span class="code-keyword">import</span> java.util.Random;

<span class="code-keyword">import</span> org.apache.tapestry5.annotations.InjectPage;
<span class="code-keyword">import</span> org.apache.tapestry5.annotations.Log;

<span class="code-keyword">public</span> class Index
{
  <span class="code-keyword">private</span> <span class="code-keyword">final</span>
Random random = <span class="code-keyword">new</span> Random(<span class="code-object">System</span>.nanoTime());

  @InjectPage
  <span class="code-keyword">private</span> Guess guess;

  @Log
  <span class="code-object">Object</span> onActionFromStart()
  {
    <span class="code-object">int</span> target = random.nextInt(10) + 1;

    guess.setup(target);

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

<p>The new event handler method now chooses the target number, and tells the Guess page
about it. Because Tapestry is a managed environment, we don't just create an instance of Guess
... it is Tapestry's responsibility to manage the life cycle of the Guess page. Instead, we
ask Tapestry for the Guess page, using the @InjectPage annotation. </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>All fields in a Tapestry page
or component class must be <b>private</b>.</td></tr></table></div>

<p>Once we have that Guess page instance, we can invoke methods on it normally.</p>

<p>Returning a page instance from an event handler method directs Tapestry to send a
client-side redirect to the returned page, rather than sending a redirect for the active page.
Thus once the user clicks the "start guessing" link, they'll see the Guess page.</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>When creating your
own applications, make sure that the objects stored in final variables are thread safe. It
seems counter-intuitive, but final variables are shared across many threads. Ordinary instance
variables are not. Fortunately, the implementation of Random is, in fact, thread safe.</td></tr></table></div>

<p>So ... let's click the link and see what we get:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/guess-template-missing.png'><img
src="/confluence/download/thumbnails/23340505/guess-template-missing.png" style="border: 0px
solid black" /></a></span></p>

<p>Ah! We didn't create a Guess page template.  Tapestry was really expecting us to
create one, so we better do so.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>src/main/resources/com/example/tutorial/pages/Guess.tml</b></div><div
class="codeContent panelContent">
<pre class="code-xml">
&lt;html t:type=<span class="code-quote">"layout"</span> title=<span class="code-quote">"Guess
The Number"</span>
  <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_3.xsd"</span>&gt;

  <span class="code-tag">&lt;p&gt;</span>
  The secret number is: ${target}.
  <span class="code-tag">&lt;/p&gt;</span>
  
<span class="code-tag">&lt;/html&gt;</span>
</pre>
</div></div>

<p>Hit the browser's back button, then click the "start guessing" link again. We're
getting closer:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/guess-no-target-prop.png'><img
src="/confluence/download/thumbnails/23340505/guess-no-target-prop.png" style="border: 0px
solid black" /></a></span></p>

<p>If you scroll down, you'll see the line of the Guess.tml template that has the error.
We have a field named target, but it is private and there's no corresponding property, so
Tapestry was unable to access it.</p>

<p>We just need to write the missing JavaBeans accessor methods <tt>getTarget()</tt>
(and <tt>setTarget()</tt> for good measure).  Or we could let Tapestry write those
methods instead:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  @Property
  <span class="code-keyword">private</span> <span class="code-object">int</span>
target;
</pre>
</div></div>

<p>The @<a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/Property.html"
class="external-link" rel="nofollow">Property</a> annotation very simply directs
Tapestry to write the getter and setter method for you. You only need to do this if you are
going to reference the field from the template.</p>

<p>We are getting very close but there's one last big oddity to handle. Once you refresh
the page you'll see that target is 0!</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/guess-target-zero.png'><img
src="/confluence/download/thumbnails/23340505/guess-target-zero.png" style="border: 0px solid
black" /></a></span></p>

<p>What gives?  We know it was set to at least 1 ... where did the value go?</p>

<p>As noted above, Tapestry sends a redirect to the client after handling the event
request. That means that the rendering of the page happens in an entirely new request. Meanwhile,
at the end of each request, Tapestry wipes out the value in each instance variable.  So that
means that target <em>was</em> a non-zero number during the component event request
... but by the time the new page render request comes up from the web browser to render the
Guess page, the value of the target field has reverted back to its default, zero.</p>

<p>The solution here is to mark which fields have values that should persist from one
request to the next (and next, and next ...).  That's what the @<a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/Persist.html"
class="external-link" rel="nofollow">Persist</a> annotation is for:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  @Property  
  @Persist
  <span class="code-keyword">private</span> <span class="code-object">int</span>
target;
</pre>
</div></div>

<p>This doesn't have anything to do with database persistence (that's coming up in a
later chapter). It means that the value is stored in the HttpSession between requests.</p>

<p>Go back to the Index page and click the link again.  Finally, we have a target number:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/guess-target.png'><img
src="/confluence/download/thumbnails/23340505/guess-target.png" style="border: 0px solid black"
/></a></span></p>

<p>That enough for us to get started. Let's build out the Guess page, and get ready
to let the user make guesses. We'll show the count of guesses, and increment that count when
they make them. We'll worry about high and low and actually selecting the correct value later.</p>

<p>When building Tapestry pages, you sometimes start with the Java code and build the
template to match, and sometime start with the template and build the Java code to match.
Both approaches are valid.  Here, lets start with the markup in the template, then figure
out what we need in the Java code to make it work.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>Guess.tml (revised)</b></div><div
class="codeContent panelContent">
<pre class="code-xml">
&lt;html t:type=<span class="code-quote">"layout"</span> title=<span class="code-quote">"Guess
The Number"</span>
  <span class="code-keyword">xmlns:t</span>=<span class="code-quote">"http://tapestry.apache.org/schema/tapestry_5_3.xsd"</span>
  <span class="code-keyword">xmlns:p</span>=<span class="code-quote">"tapestry:parameter"</span>&gt;

  <span class="code-tag">&lt;p:sidebar&gt;</span>
    <span class="code-tag">&lt;p&gt;</span>
      The secret number is: ${target}.
  <span class="code-tag">&lt;/p&gt;</span>
  <span class="code-tag">&lt;/p:sidebar&gt;</span>

  <span class="code-tag">&lt;strong&gt;</span>Guess #${guessCount}<span
class="code-tag">&lt;/strong&gt;</span>

  <span class="code-tag">&lt;p&gt;</span>Make a guess from the options
below:<span class="code-tag">&lt;/p&gt;</span>

  <span class="code-tag">&lt;ul&gt;</span>
    <span class="code-tag">&lt;t:loop source=<span class="code-quote">"1..10"</span>
value=<span class="code-quote">"current"</span>&gt;</span>
      <span class="code-tag">&lt;li&gt;</span>
        <span class="code-tag">&lt;t:actionlink t:id=<span class="code-quote">"makeGuess"</span>
context=<span class="code-quote">"current"</span>&gt;</span>${current}
        <span class="code-tag">&lt;/t:actionlink&gt;</span>
      <span class="code-tag">&lt;/li&gt;</span>
    <span class="code-tag">&lt;/t:loop&gt;</span>
  <span class="code-tag">&lt;/ul&gt;</span>

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

<p>So it looks like we need a <tt>guessCount</tt> property that starts at
1.</p>

<p>We're also seeing one new component, the Loop component. A Loop component iterates
over the values passed to it in its <tt>source</tt> parameter, and renders it
body once for each value. It updates the property bound to its <tt>value</tt>
parameter before rendering its body.</p>

<p>That special property expression, <tt>1..10</tt>, generates a series
of numbers from 1 to 10, inclusive. usually, when you use the Loop component, you are iterating
over a List or Collection of values, such as the results of a database query.</p>

<p>So, the Loop component is going to set the <tt>current</tt> property
to 1, and render its body (the \&lt;li\&gt; tag, and the ActionLink component).  Then
its going to set the <tt>current</tt> property to 2 and render its body again
... all the way up to 10.</p>

<p>And notice what we're doing with the ActionLink component; its no longer enough to
know the user clicked on the ActionLink ... we need to know <em>which iteration</em>
the user clicked on. The <tt>context</tt> parameter allows a value to be added
to the ActionLink's URL, and we can get it back in the event handler method.</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>The URL for the ActionLink
will be <tt>/tutorial1/guess.makeguess/3</tt>. That's the page name, "Guess",
the component id, "makeGuess", and the context value, "3".</td></tr></table></div>

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

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

<span class="code-keyword">public</span> class Guess
{
  @Property
  @Persist
  <span class="code-keyword">private</span> <span class="code-object">int</span>
target, guessCount;

  @Property
  <span class="code-keyword">private</span> <span class="code-object">int</span>
current;

  void setup(<span class="code-object">int</span> target)
  {
    <span class="code-keyword">this</span>.target = target;
    guessCount = 1;
  }

  void onActionFromMakeGuess(<span class="code-object">int</span> value)
  {
    guessCount++;
  }

}
</pre>
</div></div>

<p>The revised version of Guess includes two new properties: <tt>current</tt>
and <tt>guessCount</tt>. There's also a handler for the action event from the
makeGuess ActionLink component; currently it just increments the count.</p>

<p>Notice that the <tt>onActionFromMakeGuess()</tt> method now has a parameter:
the context value that was encoded into the URL by the ActionLink. When then user clicks the
link, Tapestry will automatically extract the string from the URL,  convert it to an int and
pass that int value into the event handler method.  More boilerplate code you don't have to
write.</p>

<p>At this point, the page is partially operational:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1026x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/guess-1.png'><img
src="/confluence/download/thumbnails/23340505/guess-1.png" style="border: 0px solid black"
/></a></span></p>

<p>Our next step is to actually check the value provided by the user against the target
and provide feedback: either they guessed too high, or too low, or just right. If they get
it just right, we'll switch to the GameOver page.</p>

<p>For wrong guesses, we'll see an update such as:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1019x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/guess_feedback.png'><img
src="/confluence/download/thumbnails/23340505/guess_feedback.png" style="border: 0px solid
black" /></a></span></p>

<p>And correct guesses will send us to the GameOver page:</p>

<p><span class="image-wrap" style=""><a class="confluence-thumbnail-link 1019x746"
href='https://cwiki.apache.org/confluence/download/attachments/23340505/gameover.png'><img
src="/confluence/download/thumbnails/23340505/gameover.png" style="border: 0px solid black"
/></a></span></p>

<p>Let's start with the Guess page; it now needs a new property to store the message
to be displayed to the user, and needs a field for the injected GameOver page:</p>

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

  @InjectPage
  <span class="code-keyword">private</span> GameOver gameOver;
</pre>
</div></div>

<p>First off, we're seeing a variation of the @Persist annotation, where a persistence
<em>strategy</em> is provided by name. <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/PersistenceConstants.html#FLASH"
class="external-link" rel="nofollow">FLASH</a> is a built-in strategy that stores
the value in the session, but only for one request ... it's designed specifically for these
kind of feedback messages.  If you hit F5 in the browser, to refresh, the page will render
but the message will disappear.</p>

<p>Next, we need some more logic in the <tt>onActionFromMakeGuess()</tt>
event handler method:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>Guess.java (partial)</b></div><div
class="codeContent panelContent">
<pre class="code-java">
  <span class="code-object">Object</span> onActionFromMakeGuess(<span class="code-object">int</span>
value)
  {
    <span class="code-keyword">if</span> (value == target)
    {
      gameOver.setup(target, guessCount);
      <span class="code-keyword">return</span> gameOver;
    }

    guessCount++;

    message = <span class="code-object">String</span>.format(<span class="code-quote">"Your
guess of %d is too %s."</span>, value,
        value &lt; target ? <span class="code-quote">"low"</span> : <span
class="code-quote">"high"</span>);

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

<p>Again, very straight-forward. If the value is correct, then we configure the GameOver
page and return it, causing a redirect to that page.  Otherwise, we increment the number of
guesses, and format the message to display to the user.</p>

<p>In the template, we just need to add some markup to display the message:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>Guess.tml (partial)</b></div><div
class="codeContent panelContent">
<pre class="code-xml">
  <span class="code-tag">&lt;strong&gt;</span>Guess #${guessCount}<span
class="code-tag">&lt;/strong&gt;</span>

  <span class="code-tag">&lt;t:if test=<span class="code-quote">"message"</span>&gt;</span>
    <span class="code-tag">&lt;p&gt;</span>
      <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>

<p>This snippet uses Tapestry's <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/If.html"
class="external-link" rel="nofollow">If</a> component.  The If component evaluates
its <tt>test</tt> parameter and, if the value evaluates to true, renders its body.
 The property bound to <tt>test</tt> doesn't have to be a boolean; Tapestry treats
<tt>null</tt> as false, it treats zero as false and non-zero as true, it treats
an empty Collection as false ... and for Strings (such as <tt>message</tt>) it
treats a blank string (one that is null, or consists only of whitespace) as false, and a non-blank
string is true.</p>

<p>We can wrap up with the GameOver page:</p>

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

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

<span class="code-keyword">public</span> class GameOver
{
  @Property
  @Persist
  <span class="code-keyword">private</span> <span class="code-object">int</span>
target, guessCount;

  void setup(<span class="code-object">int</span> target, <span class="code-object">int</span>
guessCount)
  {
    <span class="code-keyword">this</span>.target = target;
    <span class="code-keyword">this</span>.guessCount = guessCount;
  }
}
</pre>
</div></div>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>GameOver.tml</b></div><div class="codeContent
panelContent">
<pre class="code-java">
&lt;html t:type=<span class="code-quote">"layout"</span> title=<span class="code-quote">"Game
Over"</span>
  xmlns:t=<span class="code-quote">"http:<span class="code-comment">//tapestry.apache.org/schema/tapestry_5_3.xsd"</span>
</span>  xmlns:p=<span class="code-quote">"tapestry:parameter"</span>&gt;

  &lt;p&gt;
    You guessed the number
    &lt;strong&gt;${target}&lt;/strong&gt;
    in
    &lt;strong&gt;${guessCount}&lt;/strong&gt;
    guesses.
  &lt;/p&gt;
  
&lt;/html&gt;
</pre>
</div></div>

<p>That wraps up the basics of Tapestry; we've demonstrated the basics of linking pages
together and passing information from page to page in code as well as incorporating data inside
URLs.</p>

<p>There's still more room to refactor this toy application; for example, making it
possible to start a new game from the GameOver page (and doing it in a way that doesn't duplicate
code).  In addition, later we'll see other ways of sharing information between pages that
are less cumbersome than the setup-and-persist approach shown here.</p>

<p>Next up, we'll start delving into how Tapestry handles HTML forms and user input.</p>

<style type='text/css'>/*<![CDATA[*/
table.ScrollbarTable  {border: none;padding: 3px;width: 100%;padding: 3px;margin: 0px;background-color:
#f0f0f0}
table.ScrollbarTable td.ScrollbarPrevIcon {text-align: center;width: 16px;border: none;}
table.ScrollbarTable td.ScrollbarPrevName {text-align: left;border: none;}
table.ScrollbarTable td.ScrollbarParent {text-align: center;border: none;}
table.ScrollbarTable td.ScrollbarNextName {text-align: right;border: none;}
table.ScrollbarTable td.ScrollbarNextIcon {text-align: center;width: 16px;border: none;}

/*]]>*/</style><div class="Scrollbar"><table class='ScrollbarTable'><tr><td
class='ScrollbarPrevIcon'><a href="/confluence/display/TAPESTRY/Exploring+the+Project"><img
border='0' align='middle' src='/confluence/images/icons/back_16.gif' width='16' height='16'></a></td><td
width='33%' class='ScrollbarPrevName'><a href="/confluence/display/TAPESTRY/Exploring+the+Project">Exploring
the Project</a>&nbsp;</td><td width='33%' class='ScrollbarParent'><sup><a
href="/confluence/display/TAPESTRY/Tapestry+Tutorial"><img border='0' align='middle'
src='/confluence/images/icons/up_16.gif' width='8' height='8'></a></sup><a
href="/confluence/display/TAPESTRY/Tapestry+Tutorial">Tapestry Tutorial</a></td><td
width='33%' class='ScrollbarNextName'>&nbsp;<a href="/confluence/display/TAPESTRY/Using+BeanEditForm+To+Create+User+Forms">Using
BeanEditForm To Create User Forms</a></td><td class='ScrollbarNextIcon'><a
href="/confluence/display/TAPESTRY/Using+BeanEditForm+To+Create+User+Forms"><img border='0'
align='middle' src='/confluence/images/icons/forwd_16.gif' width='16' height='16'></a></td></tr></table></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/Implementing+the+Hi-Lo+Guessing+Game">View
Online</a>
        |
        <a href="https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=23340505&revisedVersion=20&originalVersion=19">View
Changes</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message