CVS: TapestryBook/doc Chapter-02-GettingStarted.doc,NONE,1.1 Chapter-01-Introduction.doc,1.1,1.2 Tapestry-HighLevelOutline.doc,1.3,NONE

Howard Lewis Ship <[email protected]>
Newsgroups gmane.comp.java.tapestry.cvs
Message-ID <[email protected]>
Update of /cvsroot/tapestry/TapestryBook/doc
In directory sc8-pr-cvs1:/tmp/cvs-serv8664/doc

Modified Files:
	Chapter-01-Introduction.doc 
Added Files:
	Chapter-02-GettingStarted.doc 
Removed Files:
	Tapestry-HighLevelOutline.doc 
Log Message:
Finish off chapter two.

--- NEW FILE: Chapter-02-GettingStarted.doc ---
ÐÏࡱá
 if the player has guessed all letters in the word.
     * 
     **/

    public boolean isWin()
    {
        return _win;
    }

    /**
     *  Returns an array of letters that have been guessed by the player, 
     *  with an underscore for each unguessed position.  Once the 
     *  player loses, this returns the array of actual letters in 
     *  the target word.
     * 
     *  <p>
     *  The caller must not modify this array.
     * 
     **/

    public char[] getLetters()
    {
        return _letters;
    }

    /**
     *  Returns the number of incorrect guesses remaining.
     *  An incorrect guess when this is already zero results
     *  in a loss.
     * 
     **/

    public int getIncorrectGuessesLeft()
    {
        return _incorrectGuessesLeft;
    }

    /**
     *  Returns an array of flags indicating which letters have already 
     *  been guessed.  There are 26 flags, one for each letter, starting
     *  with 'A' at index 0, up to 'Z' at index 25.
     * 
     *  <p>The caller must not modify this array.
     * 
     **/

    public boolean[] getGuessedLetters()
    {
        return _guessed;
    }

    /**
     *  Initializes the Game with a new target word.  This resets the
     *  incorrectGuessesLeft count, and initializes the array of
     *  letters and letters guessed. 
     * 
     **/

    public void start(String word)
    {
        _targetWord = word;
        _incorrectGuessesLeft = 5;
        _win = false;

        int count = word.length();

        _letters = new char[count];

        for (int i = 0; i < count; i++)
            _letters[i] = '_';

        for (int i = 0; i < 26; i++)
            _guessed[i] = false;
    }

    /**
     *  The player makes a guess.  If the letter has already been 
     *  guessed, then no change occurs.  Otherwise, there's a check 
     *  to see if the guess fills in any positions in the target word.
     *  This may result in a win.  If the guess doesn't match any
     *  letter of the word, then a failure occurs; when enough 
     *  failures occur, the game results in a loss.
     * 
     *  @return true if further guesses are allowed (this guess did 
     *  not result in a win or a loss), or false if further guesses 
     *  are not allowed (the player guessed the word, or used up
     *  all possible incorrect guesses).
     * 
     **/

    public boolean makeGuess(char letter)
    {
        char ch = Character.toLowerCase(letter);

        if (ch < 'a' || ch > 'z')
            throw new IllegalArgumentException("Must provide an alphabetic character.");

        int index = ch - 'a';

        // If the player (somehow) guesses the same letter more than once, 
	  // it does not affect state of the game.

        if (_guessed[index])
            return true;

        _guessed[index] = true;

        boolean good = false;
        boolean complete = true;

        for (int i = 0; i < _letters.length; i++)
        {
            if (_letters[i] != '_')
                continue;

            if (_targetWord.charAt(i) == ch)
            {
                good = true;
                _letters[i] = ch;
                continue;
            }

            // An empty slot that does not match
            // the guess, so the word is not
            // complete.

            complete = false;
        }

        if (good)
        {
            _win = complete;

            return !complete;
        }

        if (_incorrectGuessesLeft == 0)
        {
            // Replace the letters array with the solution

            _letters = _targetWord.toCharArray();

            return false;
        }

        _incorrectGuessesLeft--;

        // Not a good guess, but not a loss yet

        return true;
    }

	/**
	 *  Returns the word the player is trying to guess.
	 * 
	 **/
	
    public String getTargetWord()
    {
        return _targetWord;
    }
}
 Although not tied to the user interface, the Game class must provide some support for the interface, but does so in a generic fashion.  This support takes the form of properties that are exposed to the user interface, such as the number of incorrect guesses remaining, or the list of letters already guessed.  The Game class does not have any explicit knowledge of the user interface.  In addition, Game provides methods that can be called by the user interface to start a new game, or to process a guess made by the player.
This kind of isolation from the user interface is very important, because it means the Game class can be tested without having to run the Tapestry application, which in turn means the code can be fully tested inside an automated test suite.  Making code testable is always a worthy goal, because no matter how simple the code is, when you write tests, you find bugs.  
Project Layout
In order to build, test and deploy a Tapestry application, or any web application for that matter, a particular project layout is needed, as shown in figure 2.3.
<< Figure 2.3 --- layout of project, showing src dir, context, context/WEB-INF, etc. >>
Figure 2.3 Layout of a web application project.  The structure purposely resembles an “exploded WAR”.
Web applications are normally deployed as a WAR (web application archive) file.  A WAR is similar to an ordinary JAR with a few exceptions.  The root directory of a WAR contains files that may be downloaded to the client web browser.  This includes static HTML pages, style sheets, images and other assets such as animations or sound files.
A WAR also includes a special folder, WEB-INF, which contains resources needed to run the web application.  These resources include a deployment descriptor, web.xml, used by the servlet container to identify the servlets provided in the WAR.   The compiled code for the application is placed into WEB-INF/classes and any libraries needed by the application are placed into WEB-INF/lib.
 The structure of our project largely resembles that of an “exploded WAR” (a WAR which has been unpacked to the file system).  Maintaining this structure allows easy running and debugging of the web application using the open-source Jetty servlet container, a topic covered later in this chapter.
Tapestry works within the WAR layout mandated by the Java Servlet API.  Tapestry HTML templates are placed directly into the context directory.  This is a natural place for them, since any references to static assets (the images, style sheets and whatnot) will display properly when the templates are viewed as local files in a web browser or previewed in a HTML editor.  In fact, one of the key benefits of Tapestry is that the HTML templates continue to be viewable in this way.
In a Tapestry application, each page is constructed from three related elements: an HTML template, a page specification and a Java class.  The template will be stored directly within the context directory.  The Java class will be compiled, and the class file stored under WEB-INF/classes.  The page specification, an XML file somewhat similar to the web deployment descriptor, is stored in WEB-INF.
Pages, Components and Parameters
Tapestry applications are composed of a number of pages, and individual pages are composed of components.  Most of Tapestry is in the relationship between pages and the components contained with the page.
Components have some similarities to a JSP tag; both components and tags have parameters that may be optional or required.  Unlike a JSP tag, a Tapestry component parameter is strongly typed (JSP tag parameters are always strings).  Also, unlike a JSP tag, a Tapestry component parameter is both readable and writable.  Writable parameters allow for a much richer, more dynamic relationship between a page and its components.  For example, the suite of form-related components have the ability to read page properties via their parameters when rendering an HTML form, but when that form is submitted, the exact same components can update page properties through the same parameters.
In fact, this give and take, with components both reading and updating the properties of their page, is central to the dynamic process of rendering a Tapestry page.  In fact, ultimately, the page primarily acts as an
 intermediary, brokering the transfer of information between its components.
Tying all of these objects, properties and parameters together is Tapestry’s expression language, OGNL, the Object Graph Navigation Library.  OGNL is actually a separate open-source project that is used by Tapestry.  Simple OGNL expressions are the names of JavaBean properties, such as “color” or “pageName”.  OGNL expressions can also be sequences of property names, such as “visit.game”.  OGNL is, in fact, a very rich language, and OGNL expressions can include constants, conditionals, arithmetic, comparisons and more.  Virtually any valid Java expression is a valid OGNL expression, except that there is no need to worry about type conversions; OGNL takes care of that automatically.
OGNL expressions allow components to reach, through the page, to a wide range of domain objects.  This basic infrastructure allows Tapestry components to be simple and flexible, yet very powerful.
Home Page
Every page in a Tapestry application has a unique name.  Tapestry names must be valid Java identifiers and, by convention, they are named like classes, with a leading upper-case letter.
An end-user will begin using a Tapestry application by pointing their web browser at the application’s servlet.  If you are running the Hangman application locally using Tomcat or Jetty, the application URL is  HYPERLINK "http://localhost:8080/hangman1/app" http://localhost:8080/hangman1/app.  The default behavior when starting a Tapestry application is to display (or “render”, in Tapestry terms) the page named “Home”.
In the Hangman application, the Home page is by far the simplest.  As shown in figure 2.3, the Home page has only one small bit of interaction, a link to start a new game.
<< Figure 2.3 forthcoming --- screen shot of home page >>
Figure 2.3 The Home page of the Tapestry Hangman application
Like any page, the Home page is a combination of a specification, an HTML template, and a Java class. 
Home Page Specification
Tapestry starts by locating and reading the specification.  Page specifications are validated XML files with a “.page” extension. which are stored in the WEB-INF folder. Listing 2.1 is the page specification for the Home page.

Listing 2.1 Home.page page specification
<?xml version="1.0"?>
<!DOCTYPE page-specification PUBLIC                                        #1
	"-//Howard Lewis Ship//Tapestry Specification 1.4//EN"               |
	"http://tapestry.sf.net/dtd/Tapestry_1_4.dtd">                       |
	      	
<page-specification class="hangman1.Home"/>

(annotation) <#1 Page specifications must use this exact DOCTYPE declaration. >
This is about as simple as a page specification can get; its only purpose is to identify the page class, hangman1.Home.  By convention, the class name for a page is the same as the page’s name, though this decision is ultimately made by the developer.
In addition, there is nothing that keeps a single page class from being used for multiple pages.  Each page will have a distinct instance of the page class, just as each component in a page is a distinct instance of the component class.
Home Page Template
Tapestry continues after processing the page specification and locates the HTML template for the page.  The HTML template has an extension of “.HTML” and is located directly within the context directory.  The majority of the HTML template is standard, static HTML.  In the very simple Home page template, only two Tapestry extensions are used.
First, the <body> tag of the template is written as:

<BODY jwcid="@Body">

Second, the portion of the template that provides the link to start the game is written as:

<a href="#" jwcid="@DirectLink" 
   listener="[[ listeners.start ]]">
    <img src="images/start.png" width="250" height="23"
        border="0" alt="Start">
</a>

Both of these changes declare Tapestry components within the template giving us our first whiff of a dynamic web application, rather than a static web page.  The attribute, “jwcid” is the indicator that Tapestry uses to identify components.  “jwcid” is simply “Java Web Component id”.  The template defines two different components, one of type Body and the other of type DirectLink.
There are two ways to use components in Tapestry: declared components and implicit components.  For declared components, the type and configuration of the component is stored inside the page specification.  For implicit components, like both examples seen here, the type and configuration is declared right in the HTML template.  The “@” symbol is the trigger for Tapestry that these components are implicitly defined.  Later, we’ll show examples of declared components.
The Body component replaces the <body> tag of a Tapestry page.  Its purpose is to help all the components on the page organize any client-side JavaScript into one large block.  Although the Hangman application doesn’t use any components that require the Body component, it’s a good habit to always use a Body component in a Tapestry page.   Unlike most components, the Body component does not have any parameters.
Much like a JSP tag, a Tapestry component can wrap around other components and static HTML.  Ultimately, each component controls if, or even how often, it will render its body.
The DirectLink is much more interesting; DirectctLink is used to create a kind of callback into the application.  It creates an HTML <a> tag, with a URL that, when clicked by the end-user, causes a specific listener method to be executed.  This is one of the two primary ways that interaction occurs in Tapestry; the other being user-submitted forms.
Tapestry components may have any number of parameters, both optional and required.  The DirectLink component has several optional parameters, and one required parameter, named listener.  The listener parameter is used to find the listener method to execute when the end-user clicks the link visible in his web browser.  The double brackets (“[[ … ]]”) around the attribute value informs Tapestry that the value is an expression to be evaluated, rather than a literal constant.
Listener methods are instance methods, implemented by the page, that have a specific method signature:

public void method(IRequestCycle cycle)
throws RequestCycleException

Tapestry is flexible enough that the throws clause can be omitted.  The method must always be public, return void, and take a single parameter of type IRequestCycle.
Ultimately, we want the DirectLink to invoke the method start(), from listing 2.x.

Listing 2.x Home.java

package hangman1;

import net.sf.tapestry.IRequestCycle;
import net.sf.tapestry.html.BasePage;

public class Home extends BasePage
{
	public void start(IRequestCycle cycle)
	{
		// Get the visit object and cast it 
		// to the application-specific class, Visit.
		
		Visit visit = (Visit)getVisit();
		
		visit.startGame(cycle);
	}
}

So, ignoring for a moment what the Visit object is, how does the OGNL expression “listeners.start” end up executing this method?  On the one side, we have the DirectLink component, which isn’t actually looking for a method, or even a method name, at all; it’s looking for an object that implements the IActionListener interface (this interface has a single method, actionTriggered()).  On the other side, we have the Home class, which has the method we want executed when the link is clicked, but doesn’t implement the IActionListener interface.  What’s needed is something in the middle to bridge these two sides.  Like many things in Tapestry, resolving this comes down to a question of objects, methods and properties.  
The object, in this case, is an instance of class Home because that is the class listed in the page’s specification.  All pages and components inherit a property, listeners, from a base class in the framework.  This listeners property is that bridge between objects and methods that we’re looking for.  It is an instance of ListenerMap, identifying all the listener methods available in the page class.  It exposes a property for each method, providing an instance of IActionListener that will invoke the listener method.  Under the covers, the ListenerMap is using reflecti
on to dynamically invoke the page’s listener method, but that isn’t relevant to either the DirectLink component, or to the listener method; all that counts is that the end-user clicks the link and the listener method is executed.
A class may have any number of listener methods, each with a unique and individual name.  Listener methods inherited from superclasses are also available through the listeners property.
A Quick Comparison with Servlets
For all that the previous discussion about DirectLink and listener methods was unavoidably long-winded, in the end, we’ve shown that creating a link and getting an application-specific method to execute when the link is clicked is extremely simple.
Let’s see what would be involved in accomplishing the same thing using standard servlets and JSPs.  In this simple example, the JSP is very straight forward.  The DirectLink component is replaced by a standard HTML link to a servlet we’ll provide.

<a href=”startGame”> … </a>

Next, we need to add a few lines to the application deployment descriptor, web.xml:

<servlet>
	<servlet-name>startGame</servlet-name>
	<servlet-class>StartGameServlet</servlet-class>
</servlet>
	
<servlet-mapping>
	<servlet-name>startGame</servlet-name>
	<url-pattern>/startGame</url-pattern>
</servlet-mapping>

Finally, we need the actual servlet, shown in listing 2.x.

Listing 2.x StartGameServlet.java

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class StartGameServlet extends HttpServlet
{
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
		HttpSession session = request.getSession(true);
		
		Visit visit = new Visit();
		session.setAttribute("visit", visit);
		
		visit.startGame(request, response);
    }

}

This servlet creates a Visit instance and stores it for later use in the HttpSession.  The Visit object is responsible for performing a forward to the JSP page that will render the response.  The implementation of this Visit class may use the same Game and WordList domain objects used by the real Tapestry application.
Extending this comparison from one single interaction to the scores of interactions in a typical web application really underscores the amount of developer effort wasted in many web application projects.  Certainly, as developers become more experienced, they will find shortcuts, hacks and kludges to streamline this effort.  Unfortunately, different developers are quite likely to create their own suite of shortcuts, hacks and kludges.  In a large team effort, getting the bits and pieces of the application written by different developers interoperating properly can become quite a challenge because of the impedance caused by all the developer’s individual schemes.  When using Tapestry, this is rarely an issue because Tapestry defines a standard way for different parts of the application to interoperate … using objects, methods and properties.
Visit Object
Before we can finish our discussion of the Home page, we must touch on what the Visit object is.  Simply put, the Visit object is a global space for storing application logic and data.  All web applications eventually store some form of client-specific server-side state.  A typical web application will make use of the HttpSession to store this state.  The HttpSession acts like a map, storing named attributes.  The values can be any kind of object. Once an HttpSession is created, it will persist as long as the client continues to send requests to the servlet container, as will any attributes stored within the session.
Simple as this seems, in real applications, a lot of code must be written to retrieve attribute values from the HttpSession, cast them to the right type, create them on the fly as needed, and delete them when no longer needed.
Here again, Tapestry steps in to rethink this model in terms of objects, methods and properties.  In chapter XXX we’ll cover how Tapestry allows page properties to be stored persistently between requests, which is fine for values that are used only within a single page.
For more global data, used throughout an application, Tapestry allows for a Visit object.  A Visit is an object that gets created when first needed and is then stored into the HttpSession.  Tapestry doesn’t know or care about the type of object; a configuration value is used to determine what class to instantiate.
Developer code never has to worry about the HttpSession.  The method getVisit() will create the Visit object as needed, and store it into the HttpSession.  The HttpSession itself is created only as needed.
For our Hangman application, the Visit is responsible for page flow.  It acts like a façade around the WordSource and Game objects, and handles the process of starting a new game and the handling of guesses made by the player.
Listing 2.x is the source of the Visit object.

Listing 2.x The Hangman Visit Object

package hangman1;

import net.sf.tapestry.IRequestCycle;

public class Visit
{
    private WordSource _wordSource = new WordSource();
    private Game _game = new Game();

    public void startGame(IRequestCycle cycle)
    {
        _game.start(_wordSource.nextWord());

        cycle.setPage("Guess");
    }

    public void makeGuess(IRequestCycle cycle, char ch)
    {
        // If this returns true, then stay on this page and
        // let the player keep guessing.

        if (_game.makeGuess(ch))
            return;

        cycle.setPage(_game.isWin() ? "Win" : "Lose");
    }

    public Game getGame()
    {
        return _game;
    }
}

When Home invokes the startGame() method on Visit, Visit gets a random word and sets up the Game with it.  The call to the IRequestCycle.setPage() is used to identify which page will render the response sent back to the client.  Unless otherwise specified, the response page is the same as the active page.  The Visit always chooses the Guess page, which is the main page in the application.
Guess Page
The meat of the Hangman application is in the Guess page.  The Guess page, as shown in figure 2.1, has a good number of output responsibilities.  The number of remaining incorrect guesses allowed shows up in two places, in two different ways (as a digit image, and as the scaffold and stick figure image).  The partially guessed word must be displayed and each letter must be converted to an image.  The grid of letters must be displayed and links for the unguessed letters must be created.  We’ll break the page into a few interesting pieces, and describe how each piece operates, in terms of the HTML template, the page specification, and the Java class.
Displaying the remaining misses
The first dynamic bit is the part that displays the number of incorrect guesses remaining to the player:

<IMG jwcid="@Image"
   alt="[[ visit.game.incorrectGuessesLeft]]"
   image='[[ getAsset("digit" + visit.game.incorrectGuessesLeft) ]]'
   height="36"
   src="images/Chalkboard_3x8.png"
   width="36" border="0"/>

Once again, we’ll use a component to handle a dynamic task, in this case, coming up with the correct URL for the image.  The type of component is Image.  As expected, an Image component inserts an <IMG> tag into the response.  Here we want it to provide the correct image (one of the hand-drawn digits), and the corresponding “alt” value.
The first expression, “visit.game.incorrectGuessesLeft” is very straight forward; it is retrieves the incorrectGuessesLeft property from the Game object (via the Visit object).  Pages expose a visit property that returns the Visit object, creating it as necessary.
The incorrectGuessesLeft value, a number, is converted to a string.  The other expression, for selecting the image, is more complicated.  It also obtains the incorrectGuessesLeft property, but then uses it as a parameter and invokes a method on the page.  This underscores why OGNL is so useful and powerful; without OGNL this access and manipulation would have to occur in Java code.  The invok
ed method returns the image to use, which is ultimately converted into a URL by the Image component and inserted in the HTML response.
You might expect that the getAsset() method would return the relative path to the correct image file as a string and, in fact, it very nearly does that.  The method returns an instance of IAsset.  IAsset is an interface that defines the location of an image, stylesheet or any other file that may be downloaded to the end-user’s web browser.  Tapestry refers to all such files as “assets”.  This abstraction has some very important uses related to localization, and to packaging components into reusable libraries.  Those uses are covered in more detail in chapter XXX.  Here, we’re using the assets abstraction to map from developer-friendly names for the files, to the actual, more awkward names for those files.
In the page specification, it is possible to define the locations of assets.  These become the assets returned by the getAsset() method.  The page specification for the Guess page declares assets for the letters, digits, underscore as well as all the images of the stick figure on the gallows.  The Guess page specification includes the following lines to define the six digits used in the user interface:

<context-asset name="digit0" path="images/Chalkboard_1x7.png"/>
<context-asset name="digit1" path="images/Chalkboard_1x8.png"/>
<context-asset name="digit2" path="images/Chalkboard_2x7.png"/>
<context-asset name="digit3" path="images/Chalkboard_2x8.png"/>
<context-asset name="digit4" path="images/Chalkboard_3x7.png"/>
<context-asset name="digit5" path="images/Chalkboard_3x8.png"/>

Here, we can see how the aliasing is useful.  The letters and numbers were initially drawn onto a grid, and a slicing tool was used to generate a set of individual files from the cells of the grid.  The file names provided by the slicing tool are not intuitive (they are based on the position in the grid, rather the value of the image, and so are somewhat arbitrary), but the use of assets allows the developer to reference them by more friendly names.  Of course, we could have simply renamed the files output by the slicing tool, but by leaving the file names as is, we can change the original letter grid and regenerate the individual images without having to tediously rename the files again. 
Again, OGNL allows us to accomplish a small task directly in the HTML template that ordinarily would require creating a method in the Java class.  Of course, the decision to do this is entirely up to the developer.  For example, we could change the HTML template to:

<IMG jwcid="@Image"
   alt="[[ visit.game.incorrectGuessesLeft]]"
   image="[[ digitImage ]]"
   height="36"
   src="images/Chalkboard_3x8.png"
   width="36" border="0"/>

We would then implement an accessor method for this new digitImage property:

public IAsset getDigitImage()
{
   Visit visit = (Visit)getVisit();
   int guessesLeft = visit.getGame().getIncorrectGuessesLeft();

   return getAsset("digit" + guessesLeft);
}

The decision to use OGNL expressions, Java code, or some mix of the two is left to the individual developer, according to personal taste and the particular situation.  The modest runtime performance penalty for using OGNL is offset by increased developer productivity.
One important item to notice back in the HTML template is the end of the tag:  It is closed XML style, with a slash just before the right carat.  This indicates that there is no body for the element.  This is necessary as Tapestry requires that the open and close tags for dynamic elements (elements with a jwcid attribute) balance and nest properly; failure to do so will result in a runtime exception.
In fact, Tapestry doesn’t care what the actual tags are, or what case is used for the tags.  Using an <A> tag for a DirectLink component and an <IMG> tag for an Image component is a convenience; it ensures that the HTML template will preview correctly in a WYSIWYG HTML editor.  The tags could just as easily be <span> or <Foo>, as long as they balance properly.
If you check the entry for the Image component in appendix XXX, you’ll see that it takes two parameters; a required image parameter, and an optional border component.  However, if you run the application and view the source of the page, you’ll see that the other attributes from the template, “alt”, “width” and “height”, are still present.  How can this be?
The majority of Tapestry components, including Image and DirectLink, allow informal parameters.  Informal parameters are additional parameters for the component that are simply added to the rendered tag.  Informal parameters can be unevaluated static values, such as for “width”, or expressions, such as for “alt”.  Some informal parameters are ignored; for example, it doesn’t matter that “src” has a value, it is ignored because the Image component will generate a “src” attribute from the image parameter.  Only components that map directly to an HTML tag will accept informal parameters; each component has a specification that defines its formal parameters and its willingness to accept informal parameters.
So, when the Image component renders, it will mix and match the informal parameters with the HTML attributes it generates from formal parameters.  This is a capability missing from JSP tags, where specifying an undeclared parameter is simply an error.  With JSP tags, you are limited to just the parameters provided by the tag, no more.
The next dynamic section is also related to the incorrectGuessesLeft property; it is used to display one of several images for the gallows, showing increasing amounts of the stick figure as the incorrectGuessesLeft property drops towards zero.

<IMG jwcid="@Image"
  image='[[ getImage("scaffold" + visit.game.incorrectGuessesLeft) ]]'
   alt="[Scaffold]" 
   src="images/scaffold.png"   border="0"/>

Again, we use the same trick; we come up with a logical name for the image asset, and map that logical name to an actual file in the page specification.  Of course, each of these image assets must be declared in the page specification, just like all the digit image assets.
Guessed Word Display
Far more interesting is the section of the Guess page that displays the guessed word, or at least, as much of the target word as the player has guessed so far.  Generating this potion of the page starts with the Game object, which has a property, letters, which is an array of each letter of the target word as an individual character.  Each unguessed letter is replaced with an underscore character.
Once again, to maintain the “hand drawn” look and feel, we must convert from these letters to images.

<span jwcid="@Foreach"
   source="[[ visit.game.letters ]]"
   value="[[ letter ]]">
<IMG jwcid="@Image"
   image="[[ letterImage ]]"
   alt="[[ letterLabel ]]"
   height="36"
   src="images/Chalkboard_5x3.png"
   width="36"
   border="0"/>
</span>

Here, we introduce another component type, Foreach.  Foreach is a looping component, it iterates over the list of values provided by its source parameter, and updates its value parameter for each value from the source before rendering its body.  So, on each render of the Foreach’s body, the letter property of the page will be updated with the next letter from the target word.   Although the Foreach’s location in the template is specified using a <span> tag, when it renders, it does not produce any HTML directly; it simply renders the text and components in its body, repeatedly.
Of course, for this to work, we must define a letter property in our page:

private char _letter;

public char getLetter()
{
    return _letter;
}

public void setLetter(char letter)
{
    _letter = letter;
}

Once again, we are using assets to define the correct image to display within the page.  The assets for the letters “a” through “z” are named, simply “a” through “z”.  However, there’s a gotcha for the underscore character; its asset name is “dash”.
Here we use an actual Java method to provide the asset to display:

public IAsset getLetterImage()
{
    if (_letter == '_')
        return getAsset("dash");

    return getAsset("" + _letter);
}

This simple me
thod captures the special rule about replacing the underscore character with the asset named “dash”.  We have full access to this page’s instance variables, and the Foreach component was responsible for invoking setLetter() with the correct letter well before getLetterImage() is invoked by the Image component.
The letters in the list are all lowercase, but the tooltip (generated from the IMG tag’s “alt” attribute) looks better if the letter is uppercase.  This is another, minor, example of the controller (the page) mediating between the model (the Game object) and the view (the HTML template).  This is accomplished by binding the value for the “alt” attribute to the letterLabel property of the page.  The getLetterLabel() accessor method simply converts the letter to upper case and returns it as a String.

public String getLetterLabel()
{
    char upper = Character.toUpperCase(_letter);
 
    return new Character(upper).toString();
}
Guess Selection
Finally, we’re to the fun part, the grid of letters that the player may click on to make guesses.  As usual, the letters are represented as images, to keep that hand scrawled look.  As the player makes guesses, the guessed letter is erased, and either one or more positions in the target word are filled in, or another piece is added to the stick figure.
To accomplish this, we’ll use a combination of components:  A Foreach to iterate over the different letters of the alphabet, a DirectLink to create a link, and an Image to display either the image for the letter, or a blank space for an already guessed letter.

<span jwcid="selectLoop">
<a href="#"
   jwcid="select"
   class="select-letter">
   <IMG jwcid="@Image"
      image="[[ guessImage ]]"
      alt="[[ guessLabel ]]"
      height="36"
      src="images/Chalkboard_5x3.png"
      width="36"
      border="0"/>
</a>
</span>

Two of these components look a little sparse; that’s because we’ve chosen to use the declared component option for them, rather than an implicit component.  For a declared component, we just put the component id in the HTML template (perhaps augmented by an informal parameter or two).  The portion in the HTML template is simply a placeholder, the type and configuration of the component is provided in the page specification:

<component id="selectLoop" type="Foreach">
  <binding name="source" expression="visit.game.guessedLetters"/>
  <binding name="value" expression="letterGuessed"/>
  <binding name="index" expression="guessIndex"/>
</component>

<component id="select" type="DirectLink">
  <binding name="listener" expression="listeners.makeGuess"/>
  <binding name="parameters" expression="letterForGuessIndex"/>
  <binding name="disabled" expression="letterGuessed"/>
</component>

Here again, we are combining the behaviors of different components and using the page to mediate between them.  We are also making use of new features of the Foreach and DirectLink components, by binding additional parameters of the components.
The source of all this data is the guessedLetters property of the Game object; this is an array of twenty-six boolean flags, one for each letter in the alphabet.  Initially, all the flags are false, but as the method makeGuess() is invoked for different letters, the corresponding flags are set.
The Foreach component will loop through the twenty-six flags, and set the letterGuessed property of the page to true or false on each pass through the loop.  In addition, it will set the guessIndex property of the page.  This value starts at zero and increments with each pass through the loop.  The other components simply translate from this ordinal value to a letter in the range of ‘a’ to ‘z’.

private boolean _letterGuessed;
private int _guessIndex;

public boolean isLetterGuessed()
{
    return _letterGuessed;
}

public void setLetterGuessed(boolean letterGuessed)
{
    _letterGuessed = letterGuessed;
}

public int getGuessIndex()
{
    return _guessIndex;
}

public void setGuessIndex(int guessIndex)
{
    _guessIndex = guessIndex;
}

public char getLetterForGuessIndex()
{
    return (char) ('a' + _guessIndex);
}

Getting the right letter image for the current letter within the loop is very similar to the previous examples.  Although the dash will never occur, we do have to substitute a blank image for any letter that has already been guessed.

public IAsset getGuessImage()
{
    if (_letterGuessed)
        return getAsset("space");

    String name = "" + getLetterForGuessIndex()

    return getAsset(name);
}

That covers how we get the image for each letter display, but what about the link that the player uses to make a guess?  Were we to do this using ordinary servlets, we’d define a query parameter whose value is the letter selected.   In Tapestry terms, we need to invoke a specific listener method (as before on the Home page), but also propogate along some additional data, the letter selected by the player.
We’ll again use a DirectLink component, as we did with the link on the Home page, but with two differences.  First, we only want to display the link (the <a> and </a> tags) some of the time; we want to omit the link for letters that have already been guessed, the positions that show up as blank space.
Second, we need a way to know which letter has been selected.  The DirectLink component provides parameters to satisfy both of these needs.
The optional disabled parameter is used to control whether the link renders those tags or not.  A DirectLink component will always render its body, regardless of the setting of the disabled parameter.  This is accomplished by binding the disabled parameter to the letterGuessed property of the page; the same property set by the Foreach component, and used in the getGuessImage() method.
  
<binding name="disabled" expression="letterGuessed"/>
To identify which letter is actually clicked by the player, we will use yet-another component parameter, named “parameters”.  We can bind a single value, or an array or list of values.  The parameters that are provided at the time the DirectLink component renders will be encoded into the URL.  When the link is submitted, the array of parameters is reconstructed, and is available to the listener method.
For this case, we only use a single value, provided by the property letterForGuessIndex:

<binding name="parameters" expression="letterForGuessIndex"/>

Each time the DirectLink component renders, within the Foreach component loop, the value for this property will reflect the current letter in the list, and the URL written into the HTML response will be slightly different.
When the link is clicked, we can get the parameter back.

public void makeGuess(IRequestCycle cycle)
{
    Character guess = (Character)
        cycle.getServiceParameters()[0];

    char ch = guess.charValue();
    Visit visit = (Visit) getVisit();

    visit.makeGuess(cycle, ch);
}

The parameters encoded into the URL by the DirectLink are available in the listener method as an array of Object, obtainable from the getServiceParameters() method of the IRequestCycle object.  Even when, as in this case, there’s only a single parameter value, an array is returned.  The lone character value is the first, and only, element in the array.
In addition, the value has been converted from a scalar type, char, to a wrapper object type, Character, but it is a simple chore to convert it back.  Importantly, the parameter value is not simply converted to a string … it retains its original type (which is encoded into the URL along with the value).
From here, it’s simply a matter of obtaining the Visit object and letting it do the rest of the processing of the guess … which may result in a win or a loss, or simply more guessing.
Adding this new interaction, the handling of guesses by the player, involved little more than defining the listener method and pointing the DirectLink component at the method.  Without Tapestry, this same functionality would entail not only writing a servlet and registering it into the web deployment descriptor, but creating code to generate the hyperlink in the first place.  This latter code could take the form of scriptlets in the JSP, or a new JSP tag in 
a JSP tag library.  In either case, the HTML in the JSP file would deviate further from ordinary HTML and the ability to preview the web page is diminished.  With Tapestry, the HTML template will continue to look and act like standard HTML.

Win and Lose Pages
The other two pages in the application, Win and Lose, are displayed when the player successfully guesses the word, or when the player exhausts all their incorrect guesses.  There is nothing new on these pages, they duplicate bits and pieces of the Home and Guess pages.  In fact, there’s a bit of unwanted duplication in the HTML templates, the Java code and the page specifications.  In chapter XXX we’ll see how easy it is to create new components that encapsulate this functionality, and remove this duplication.  Remember:  more code is more bugs!
Web.xml Deployment Descriptor
All of these HTML templates and page specifications do not automatically become a web application.  We still need a servlet to act as the bridge between the Servlet API and the Tapestry framework.  Fortunately, this does not require any coding, since the framework includes the necessary servlet.  All that’s necessary is to configure the deployment descriptor, which is the file WEB-INF/web.xml, shown in listing 2.x.

Listing 2.x web.xml deployment descriptor for the Hangman application
<?xml version="1.0"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">

<web-app>	
  <servlet>
    <servlet-name>hangman</servlet-name>
    <servlet-class>net.sf.tapestry.ApplicationServlet</servlet-class> #1
    <init-param>                                                      |#2
      <param-name>net.sf.tapestry.visit-class</param-name>            |
      <param-value>hangman1.Visit</param-value>                       |
    </init-param>                                                     |
    <load-on-startup>1</load-on-startup>                              #3
  </servlet>

  <servlet-mapping>
    <servlet-name>hangman</servlet-name>
    <url-pattern>/app</url-pattern>                                   #4
  </servlet-mapping>
</web-app>

(annotation) <#1 This is the servlet class, provided as part of the framework.>
(annotation) <#2 This is where the class to instantiate as the Visit object is specified.>
(annotation) <#3 Loading the application servlet on startup is a good practice, it helps identify early errors even before the first request is processed.>
(annotation) <#4 By convention, the Tapestry application is mapped to “/app” within the servlet context. >
Summary
In this chapter, we’ve seen the basics of creating a web application using Tapestry.  We’ve seen how a Tapestry application is divided into individual pages, and we’ve seen how those pages are constructed by combining components, an overall HTML template, and a small amount of Java code.  We’ve also seen how Tapestry leverages the Model-View-Controller pattern to isolate domain logic from the user interface.  We’ve also begun to see the “light touch” of Tapestry, where simple properties and short Java methods are woven together to create very complex, dynamic, interactive user interfaces.
This simple application demonstrates some of they key patterns that occur when developing in Tapestry.  It shows how components interact with each other by reading and setting properties.  It shows how the page can act as a mediator, coordinating the domain logic and the needs of embedded components.  We’ve also demonstrated how easy it is to add new interactions to a page, in the form of listener methods.
We’ve also begun to show how Tapestry, by excusing developers from mundane “plumbing” tasks, really frees up developer energies.  Tapestry enables individual developers to implement more complicated behaviors in much less time, and be more confident that their code is bug free.  Tapestry can give projects the one thing money truly can’t buy: time.  Time to test and debug back-end code, time to locate and fix performance problems, time to add new features.


PAGE  18


PAGE  19



The Tapestry Way 	Manning Publications Co.		 PAGE 18

	
The Tapestry Way 	Manning Publications Co.	 PAGE 19

j


j



&

&





H
H
m
e
€
€
Œ
–
™
®

&F
&F
&F
&F
&F
&F
&F
l
&


&


&F



&

]



@
]
‘
¦



]



€

ð
ð
Þaæ¦(


















j


jŒI
í†À@






Index: Chapter-01-Introduction.doc
===================================================================
RCS file: /cvsroot/tapestry/TapestryBook/doc/Chapter-01-Introduction.doc,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -d -r1.1 -r1.2
Binary files /tmp/cvs8HWRxj and /tmp/cvsQV0pYr differ

--- Tapestry-HighLevelOutline.doc DELETED ---



-------------------------------------------------------
This SF.NET email is sponsored by: FREE  SSL Guide from Thawte
are you planning your Web Server Security? Click here to get a FREE
Thawte SSL guide and find the answers to all your  SSL security issues.
http://ads.sourceforge.net/cgi-bin/redirect.pl?thaw0026en
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.