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> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE application PUBLIC "-//Howard Ship//Tapestry Specification 1.1//EN" "http://tapestry.sf.net/dtd/Tapestry_1_1.dtd"> <application name="Tapestry Hangman" engine-class="com.primix.tapestry.engine.SimpleEngine"> <property name="com.primix.tapestry.visit-class">tutorial.hangman.Visit</property> <co id="hangman.app.visit"> <page name="Home" specification-path="/tutorial/hangman/Home.jwc"/> <page name="Guess" specification-path="/tutorial/hangman/Guess.jwc"/> <page name="Failed" specification-path="/tutorial/hangman/Failed.jwc"/> <page name="Success" specification-path="/tutorial/hangman/Success.jwc"/> </application> </programlisting></figure> <para> <calloutlist> <callout arearefs="hangman.app.visit"> <para> This property specifies that the engine should instantiate an instance of <classname>tutorial.hangman.Visit</classname> when a visit object is first required. This is the default way in which the visit object is specified, though if the visit object doesn't have an empty constructor method, the engine method <function>createVisit()</> must be implemented instead. </> </callout> </calloutlist> </> <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 &RadioGroup;, a wrapper that must go around the &Radio; components (the other three). The &RadioGroup; determines what property of the page is to be read and updated (its bound to the <varname>misses</> property). Each &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> <component id="group" type="RadioGroup"> <binding name="selected" property-path="misses"/> </component> <component id="inputEasy" type="Radio"> <field-binding name="value" field-name="tutorial.hangman.Home.EASY"/> <co id="hangman.home.spec.field-binding"> </component> <component id="inputMedium" type="Radio"> <field-binding name="value" field-name="tutorial.hangman.Home.MEDIUM"/> </component> <component id="inputHard" type="Radio"> <field-binding name="value" field-name="tutorial.hangman.Home.HARD"/> </component> </programlisting></figure> <para> <calloutlist> <callout arearefs="hangman.home.spec.field-binding"> <para> A <sgmltag class=starttag>field-binding</> is like a <sgmltag class=starttag>static-binding</>, except that the static value is taken from a public static field of some class. This makes it easy to coordinate behaviors between the specification and the class. </> <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. </> </callout> </calloutlist> </> <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> <component id="e" type="Foreach"> <binding name="source" property-path="unused"/> </component> <component id="guess" type="Direct"> <binding name="listener" property-path="guessListener"/> <binding name="context" property-path="components.e.value"/> </component> <component id="insertLetter" type="Insert"> <binding name="value" property-path="components.e.value"/> </component> </programlisting></figure> <para> Component <varname>e</> is simply a &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 &Foreach; component's <varname>value</> property is the value for the iteration. </> <para> Component <varname>guess</> is type &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 &String;, or array of &String;s, that will be encoded into the URL for the hyperlink. When the component's listener is notified, it is passed the same &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>