...
Scrollbar
...
Let's start building a basic Hi-Lo Guessing game.
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:
...
Let's get to work on the Index page and template. Make Index.tml look like this:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<html t:type="layout" title="Hi/Lo Guess" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"> <p> I'm thinking of a number between one and ten ... </p> <p> <a href="#">start guessing</a> </p> </html> |
And edit the corresponding Java class, Index.java, removing its body (but leaving you can leave the imports in place for now):
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
package com.example.tutorial1.pages;
public class Index
{
}
|
Running the application gives us our start:
...
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 <a> tag to an ActionLink component:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<p> <t:actionlink t:id="start">start guessing</t:actionlink> </p> |
...
If you click the link now, you'll get an error:
Tapestry is telling us that we need to provide some kind of event handler for that event. What does that look like?
...
Once again, Tapestry gives us options; if you don't like naming conventions, there's an @OnEvent 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 Tapestry Users' Guide. We'll be sticking with the naming convention approach for the tutorial.
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:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
package com.example.tutorialtutorial1.pages; public class Index { void onActionFromStart() { } } |
...
How about this: add the @Log annotation to the method:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
import org.apache.tapestry5.annotations.Log; . . . @Log void onActionFromStart() { } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
package com.example.tutorialtutorial1.pages; public class Guess { private int target; void setup(int target) { this.target = target; } } |
Create that Guess.java file in the same folder as Index.java. Next, we can modify Index to invoke the setup()
method of our new Guess page class:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
package com.example.tutorialtutorial1.pages; import java.util.Random; import org.apache.tapestry5.annotations.InjectPage; import org.apache.tapestry5.annotations.Log; public class Index { private final Random random = new Random(System.nanoTime()); @InjectPage private Guess guess; @Log Object onActionFromStart() { int target = random.nextInt(10) + 1; guess.setup(target); return guess; } } |
...
Note |
---|
All fields in a Tapestry page or component class must be private non-public. |
Once we have that Guess page instance, we can invoke methods on it normally.
...
So ... let's click the link and see what we get:
Ah! We didn't create a Guess page template. Tapestry was really expecting us to create one, so we better do so.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<html t:type="layout" title="Guess The Number" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"> <p> The secret number is: ${target}. </p> </html> |
Hit the browser's back button, then click the "start guessing" link again. We're getting closer:
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.
...
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.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<html t:type="layout" title="Guess The Number" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <p> The secret number is: ${target}. </p> <strong>Guess number ${guessCount}</strong> <p>Make a guess from the options below:</p> <ul class="list-inline"> <t:loop source="1..10" value="current"> <li> <t:actionlink t:id="makeGuess" context="current">${current} </t:actionlink> </li> </t:loop> </ul> </html> |
...
Info |
---|
The URL for the ActionLink will be |
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
package com.example.tutorialtutorial1.pages; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; public class Guess { @Property @Persist private int target, guessCount; @Property private int current; void setup(int target) { this.target = target; guessCount = 1; } void onActionFromMakeGuess(int value) { guessCount++; } } |
...
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:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
@Property @Persist(PersistenceConstants.FLASH) private String message; @InjectPage private GameOver gameOver; |
...
Next, we need some more logic in the onActionFromMakeGuess()
event handler method:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
Object onActionFromMakeGuess(int value) { if (value == target) { gameOver.setup(target, guessCount); return gameOver; } guessCount++; message = String.format("Your guess of %d is too %s.", value, value < target ? "low" : "high"); return null; } |
...
In the template, we just need to add some markup to display the message:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<strong>Guess number ${guessCount}</strong> <t:if test="message"> <p> <strong>${message}</strong> </p> </t:if> |
...
Code Block | ||||
---|---|---|---|---|
| ||||
package com.example.tutorialtutorial1.pages; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; public class GameOver { @Property @Persist private int target, guessCount; void setup(int target, int guessCount) { this.target = target; this.guessCount = guessCount; } } |
...
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.
Next up, we'll start delving into : let's find out how Tapestry handles HTML forms and user input.
...
Next: Using BeanEditForm To Create User Forms
Scrollbar |
---|