Poutpourri!</> <para> So far, these examples have been a little bit cut-and-dried. Lets do a meatier example that uses a few more interesting components. Let's play Hangman! </> <para> Our Hangman application consists of four pages. The Home page allows a new game to be started, which includes selecting the difficulty of the game (how many wrong guesses you are allowed). </> <figure> <title>Hangman Home Page</> <mediaobject> <imageobject> <imagedata fileref="images/hangman-home.jpg" format="jpg"> </imageobject> </> </figure> <para> The main page is the Guess page, where the partially filled out word is displayed, and the user can make guesses (from a shrinking list of possible letters): </> <figure> <title>Hangman Guess Page</> <mediaobject> <imageobject> <imagedata fileref="images/hangman-guess.jpg" format="jpg"> </imageobject> </> </figure> <para> After you give up, or when you make too many mistakes, you end up on the the Failed page: </> <figure> <title>Hangman Failed Page</> <mediaobject> <imageobject> <imagedata fileref="images/hangman-lose.jpg" format="jpg"> </imageobject> </> </figure> <para> But, if you guess all the letters, you are sent to the Success page: </> <figure> <title>Hangman Success Page</> <mediaobject> <imageobject> <imagedata fileref="images/hangman-win.jpg" format="jpg"> </imageobject> </> </figure> <section id="hangman.visit"> <title>The Visit Object</> <para> The center of this application is an object that represents game, an object of class <classname>HangmanGame</>. This object is used to track the word being guessed, the letters that have been used, the number of misses and the letters that have been correctly guessed. </> <para> This object is a property of the <emphasis>visit</> object. What's the visit object? The visit object is a holder of all information about a single client's visit to your web application. It contains data and methods that are needed by the pages and components of your application. </> <para> The visit object is owned and created by the engine object. It is serialized and de-serialized with the engine. </> <para> The application specification includes a little extra segment at the bottom to specify the class of the visit object. </> <figure> <title>Hangman.application</> <programlisting><![CDATA[<?xml version="1.0"?> <!DOCTYPE application PUBLIC "-//Primix Solutions//Tapestry Specification 1.0//EN" "http://tapestry.sourceforge.net/dtd/Tapestry_1_0.dtd"> <application> <name>Tapestry Hangman</name> <engine-class>com.primix.tapestry.engine.SimpleEngine</engine-class> <page> <name>Home</name> <specification-path>/tutorial/hangman/Home.jwc</specification-path> </page> <page> <name>Guess</name> <specification-path>/tutorial/hangman/Guess.jwc</specification-path> </page> <page> <name>Failed</name> <specification-path>/tutorial/hangman/Failed.jwc</specification-path> </page> <page> <name>Success</name> <specification-path>/tutorial/hangman/Success.jwc</specification-path> </page> <properties> <property> <name>com.primix.tapestry.visit-class</name> <value>tutorial.hangman.Visit</value> </property> </properties> </application>]]></programlisting></figure> <para> These properties are simple named values associated with the application specification. When the application engine needs to create the visit object, it creates an instance of the named class. </> <para> So, returning from that distraction, the game object is a property of the visit object, which is accessible from any page (via the page's visit property). </> </section> <section id="hangman.home-page"> <title>The Home Page</> <para> The Home page's job is to collect the difficulty and initiate a game: </> <figure> <title>Home.java</> <programlisting><![CDATA[public class Home extends BasePage implements IActionListener { public static final int EASY = 10; public static final int MEDIUM = 5; public static final int HARD = 3; private int misses; private String error; public void detach() { misses = 0; error = null; super.detach(); } public int getMisses() { return misses; } public void setMisses(int value) { misses = value; fireObservedChange("misses", value); } public String getError() { return error; } public IActionListener getFormListener() { return this; } public void actionTriggered(IComponent component, IRequestCycle cycle) throws RequestCycleException { if (misses == 0) { error = "Please select a game difficulty."; return; } Visit visit = (Visit)getVisit(); visit.start(misses); cycle.setPage("Guess"); } }]]></programlisting></figure> <para> We're seeing all the familiar ideas: The <varname>misses</> property is a persistent page property (which means the page will "remember" the value previously selected by the user). </> <para> We use a common trick for simple pages: the page contains a single <classname>Form</> component, so we use the page itself as the form's listener, and have the page implement the <classname>IActionListener</> interface. </> <para> This saves a bit of code for creating an inner class as the form listener. </> <para> Initially, we don't select a difficulty level, and the user can click "Play!" without selecting a value from the list, so we check that. </> <para> Otherwise, we get the visit object and ask it to start a new game with the selected number of misses. We then jump to the Guess page to start accepting guesses from the user. </> <para> The interesting part of the Home page HTML template is the form: </> <figure> <title>Home.html (excerpt)</> <programlisting><![CDATA[<jwc id="form"> <jwc id="group"> <jwc id="ifError"> <font size=+2 color=red><jwc id="insertError"/></font> </jwc> <table> <tr> <td><jwc id="inputEasy"/></td> <td>Easy game; you are allowed ten misses.</td> </tr> <tr> <td><jwc id="inputMedium"/></td> <td>Medium game; you are allowed five misses.</td> </tr> <tr> <td><jwc id="inputHard"/></td> <td>Hard game; you are only allowed three misses.</td> </tr> <tr> <td></td> <td><input type="submit" value="Play!"></td> </tr> </table> </jwc> </jwc>]]></programlisting></figure> <para> Here, the interesting components are <varname>group</>, <varname>inputEasy</>, <varname>inputMedium</> and <varname>inputHard</>. <varname>group</> is type <classname>RadioGroup</>, a wrapper that must go around the <classname>Radio</> components (the other three). The <classname>RadioGroup</> determines what property of the page is to be read and updated (its bound to the <varname>misses</> property). Each <classname>Radio</> button is associated with a particular value to be assigned to the property, when that radio button is selected by the user. </> <para> This comes together in the Home page specification: </> <figure> <title>Home.jwc (excerpt)</> <programlisting><![CDATA[<component> <id>group</id> <type>RadioGroup</type> <bindings> <binding> <name>selected</name> <property-path>misses</property-path> </binding> </bindings> </component> . . . <component> <id>inputEasy</id> <type>Radio</type> <bindings> <field-binding> <name>value</name> <field-name>tutorial.hangman.Home.EASY</field-name> </field-binding> </bindings> </component> <component> <id>inputMedium</id> <type>Radio</type> <bindings> <field-binding> <name>value</name> <field-name>tutorial.hangman.Home.MEDIUM</field-name> </field-binding> </bindings> </component> <component> <id>inputHard</id> <type>Radio</type> <bindings> <field-binding> <name>value</name> <field-name>tutorial.hangman.Home.HARD</field-name> </field-binding> </bindings> </component>]]></programlisting></figure> <para> The <sgmltag class=starttag>field-binding</> element is very useful in this case; it is similar to a <sgmltag class=starttag>static-binding</> but, instead of supplying the value in place, it uses a reference to a public static field of some class or interface. </> <para> This is a good thing, since if you decide to make a <varname>HARD</> game only allow two mistakes, you can make the change in exactly one place .. your Java code. </> <para> So the end result is: when the user clicks the radio button for a Hard game, the static constant <varname>HARD</> is assigned to the page's <varname>misses</> property. </> </section> <section id="hangman.guess-page"> <title>The Guess Page</> <para> This is the page where uses make letter guesses. The page has four sections: </> <itemizedlist> <listitem> <para> A display of the word, with underscores replacing unguessed letters. </> </> <listitem> <para> A status area, showing the number of bad guesses and an optional error message after an invalid guess. </> </> <listitem> <para> A list of letters that may be guessed. Letters disappear after they are used. </> </> <listitem> <para> An option to give up and see the word, terminating the game. </> </> </> <para> Let's start with the HTML template this time: </> <figure> <title>Guess.html (excerpt) <programlisting><![CDATA[<h1>Make a Guess</h1> <font size=+3> <jwc id="insertGuess"/> </font> <p> You have made <jwc id="insertMissed"/> bad guesses, out of a maximum of <jwc id="insertMaxMisses"/>. <jwc id="ifError"> <p> <font size=+3 color=red><jwc id="insertError"/></font> </jwc> <p>Guess: <font size=+1> <jwc id="e"> <jwc id="guess"><jwc id="insertLetter"/></jwc> </jwc> </font> <p><jwc id="giveUp">Give up?</jwc>]]></programlisting></figure> <para> Most of these components should be fairly obvious by now; let's focus on the components that allow the user to guess a letter. This could have been implemented in a number of ways using more radio buttons, a drop down list or a text field the user could type into. In this example, we chose to simply create a series of links, one for each letter the user may still guess. </> <para> Let's look at the specification for those three components (<varname>e</>, <varname>guess</> and <varname>insertLetter</>). </> <figure> <title>Guess.jwc (excerpt)</> <programlisting><![CDATA[<component> <id>e</id> <type>Foreach</type> <bindings> <binding> <name>source</name> <property-path>unused</property-path> </binding> </bindings> </component> <component> <id>guess</id> <type>Direct</type> <bindings> <binding> <name>listener</name> <property-path>guessListener</property-path> </binding> <binding> <name>context</name> <property-path>components.e.value</property-path> </binding> </bindings> </component> <component> <id>insertLetter</id> <type>Insert</type> <bindings> <binding> <name>value</name> <property-path>components.e.value</property-path> </binding> </bindings> </component>]]></programlisting></figure> <para> Component <varname>e</> is simply a <classname>Foreach</>, the <varname>source</> is the <varname>unused</> property of the page (we'll see in a moment how the page gets this list of unused letters from the game object). </> <para> Component <varname>insertLetter</> inserts the current letter from the list of unused letters. It gets this current letter directly from the <varname>e</> component. On successive iterations, a <classname>Foreach</> component's <varname>value</> property is the value for the iteration. </> <para> Component <varname>guess</> is type <classname>Direct</>, which creates a hyperlink on the page and notifies its listener when the user clicks the link. Just knowing that the component was clicked isn't very helpful though; the application needs to know which letter was actually clicked. </> <para> Passing that kind of information along is accomplished by setting the <varname>context</> parameter for the component. The <varname>context</> parameter is a <classname>String</>, or array of <classname>String</>s, that will be encoded into the URL for the hyperlink. When the component's listener is notified, it is passed the same <classname>String</>s. </> <para> The context is often used to encode primary keys of objects, names of columns or other information specific to the application. </> <para> In this case, the context is simply the letter to be guessed. </> <para> All of this comes together in the Java code for the Guess page. </> <figure> <title>Guess.java (excerpt)</> <programlisting><![CDATA[public IDirectListener getGuessListener() { return new IDirectListener() { public void directTriggered(IDirect direct, String[] context, IRequestCycle cycle) throws RequestCycleException { makeGuess(context[0], cycle); } }; } private void makeGuess(String guess, IRequestCycle cycle) throws RequestCycleException { HangmanGame game = getGame(); char letter = guess.charAt(0); try { game.guess(letter); } catch (GameException ex) { error = ex.getMessage(); if (game.getFailed()) cycle.setPage("Failed"); return; } // A good guess. if (game.getDone()) cycle.setPage("Success"); } }]]></PROGRAMLISTING></FIGURE> <para> So the <varname>listener</> for the <varname>guess</> component gets the first String in the context and invokes the makeGuess() method. We pass the guessed letter to the game object which throws a GameException if the guess is invalid. </> <para> The method <function>HangmanGame.getFailed()></> returns <literal>true</> when all the missed guesses are used up, at which point we go to the <varname>Failed</> page to tell the user what the word was. </> <para> On the other hand, if an exception isn't thrown, then the guess was good. <function>getDone()</> returns <literal>true</> if all letters have been guessed, in which go to the <varname>Success</> page. </> <para> If all letters weren't guessed, we stay on the <varname>Guess</> page, which will display the word with the guessed letter filled in, and with fewer options in the list of possible guesses. </> </section> <section id="hangman.limitations"> <title>Limitations</> <para> This is a very, very simple implementation of the game. For example, it's easy to cheat; you can give up, then use your browser's back button to return to the <varname>Guess</> page and keep guessing (with accuracy, if your memory is any good). </section> </chapter>