Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: fixed language param of code macro
Wiki Markup
{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
languagexml
titleIndex.tml
langxml

<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 the imports in place for now):

Code Block
languagejava
titleIndex.java
langjava

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
languagexml
titleIndex.tml (partial)
langxml

  <p>
    <t:actionlink t:id="start">start guessing</t:actionlink>
  </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:

Code Block
languagejava
titleIndex.java
langjava

package com.example.tutorial.pages;

public class Index
{
  void onActionFromStart()
  {

  }
}

...

How about this: add the @Log annotation to the method:

Code Block
languagejava
titleIndex.java (partial)
langjava

  import org.apache.tapestry5.annotations.Log;

  . . .

  @Log
  void onActionFromStart()
  {

  }

When you next click the link you should see the following in the Eclipse console:

No Format

[DEBUG] pages.Index [ENTER] onActionFromStart()
[DEBUG] pages.Index [ EXIT] onActionFromStart
[INFO] AppModule.TimingFilter Request time: 3 ms
[INFO] AppModule.TimingFilter Request time: 5 ms

...

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.

Code Block
languagejava
titleGuess.java

package com.example.tutorial.pages;

public class Guess
{
  private int target;

  void setup(int target)
  {
    this.target = target;
  }
}

With that in mind, we can modify Index to invoke this new setup() method:

Code Block
languagejava
titleIndex.java (revised)
langjava

package com.example.tutorial.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;
  }
}

...

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

Code Block
languagejava
titlesrc/main/resources/com/example/tutorial/pages/Guess.tml
langxml

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

...

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

Code Block
languagejava
langjava

  @Property
  private int target;

...

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 @Persist annotation is for:

Code Block
languagejava
langjava

  @Property  
  @Persist
  private int target;

...

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
languagexml
titleGuess.tml (revised)
langxml

<html t:type="layout" title="Guess The Number"
  xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
  xmlns:p="tapestry:parameter">

  <p:sidebar>
    <p>
      The secret number is: ${target}.
  </p>
  </p:sidebar>

  <strong>Guess #${guessCount}</strong>

  <p>Make a guess from the options below:</p>

  <ul>
    <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 /tutorial1/guess.makeguess/3. That's the page name, "Guess", the component id, "makeGuess", and the context value, "3".

Code Block
languagejava
titleGuess.java (revised)
langjava

package com.example.tutorial.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
languagejava
titleGuess.java (partial)
langjava

  @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
languagejava
titleGuess.java (partial)
langjava

  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
languagexml
titleGuess.tml (partial)
langxml

  <strong>Guess #${guessCount}</strong>

  <t:if test="message">
    <p>
      <strong>${message}</strong>
    </p>
  </t:if>

...

We can wrap up with the GameOver page:

Code Block
languagejava
titleGameOver.java

package com.example.tutorial.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;
  }
}
Code Block
languagexml
titleGameOver.tml

<html t:type="layout" title="Game Over"
  xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
  xmlns:p="tapestry:parameter">

  <p>
    You guessed the number
    <strong>${target}</strong>
    in
    <strong>${guessCount}</strong>
    guesses.
  </p>
  
</html>

...