Skip to main content

Echo-ing the Tapestry Hangman example

Posted by johnreynolds on August 18, 2004 at 11:29 AM PDT

Hangman: the "Hello World" of web component frameworks?

To get the most out of this article, please
download the source and war files. If you have Tomcat
5.0
installed, you should be able to deploy the war and run the application.


Hans Bergsten recently wrote about using Java Server Faces to
re-implement the
hangman game example
described in Chapter 2 of Howard M. Lewis
Ship's Tapestry
in Action
(Manning). Hans used the hangman game to demonstrate
how JSF could be improved by implementing a custom ViewHandler
instead of relying on JSP pages. It's a good article, and I recommend
reading it if you haven't already.



In my experience, one of the best ways to grok the differences
between application frameworks is to implement the same example using
each framework. Inspired by Hans Bergsten, I set out to convert
Ship's example hangman game from
Tapestry
to Echo. I had a
lot of fun doing this, and you can now download the sources to
compare hangman implementations in Tapestry, JSF, and Echo.


Echo, Tapestry and JSF are component-based web frameworks. All
three are presentation layer frameworks, concerned with generating
and handling user interfaces for web applications. Echo and Tapestry
are open-source projects, and JSF is a specification with at least
one open-source implementation (MyFaces).
Of the three, Echo is unique in that it does not require the
programmer to know anything about HTML. Writing an Echo application
is a lot more like writing a desktop application then writing a web
application.


I really like Tapestry, and I have great hopes for JSF, but
I've got to admit that Echo makes me smile. I've been writing GUI
applications since I helped write Tandy's
Deskmate2
in the mid-80's, and Echo is a whole lot simpler for
me to grok then HTML and XML infused toolkits. If you are used to
writing applications using Swing, Awt, or Tcl, you'll understand
Echo.

Echo applications are Java Servlets.
I used Tomcat
5.0
to host my application, but any Java Servlet container should
work. To create an Echo application, you must create three classes:

  1. A class that extends nextapp.echo.EchoServer (I named mine EchoHangmanServlet)
  2. A class that extends nextapp.echo.EchoInstance (I named mine HangmanInstance)
  3. A class that extends nextapp.echo.ContentPane implements
    nextapp.echo.ActionListener () (I named mine GamePane)


EchoHangmanServlet is the Java Servlet, HangmanInstance
handles a specific session of the application, and GamePane defines
all of the Components that make up the application (and handles
events generated by the Components).
Echo masks all HTML and
Javascript issues from the developer. As far as you are concerned,
the Components render themselves and generate events. There is a lot
of client-side Javascript under the covers, but you can remain
blissfully unaware unless you choose to write your own components.
Programs are made up of Content Panes full of Components rather then
stateless HTML pages and Javascript. This may be more abstraction
then many are comfortable with, but it certainly simplifies the
programming model.

Echo “ships” with a number of built-in Components,
but I only needed three of them to implement the tapestry
application: Grid, Label, and Button. A companion project, EchoPoint,
provides dozens of high quality open-source components. If you've
ever seen a Javascript component on a web-page, you'll probably find
that the EchoPoint project team has ported it to Echo.


The Grid components are used to control Component layout. The
Label components provide instructions and feedback, and the Button
components are used to enable user feedback. Button and Label both
support images, so it was fairly easy for me to reuse the images from
Howard Lewis Ship's Tapestry example. I am very grateful for this as
it saved me a great deal of time. I also reused Howard's Game and
WordSource classes. The “business logic” is identical,
only the presentation code has changed.

Echo Component Layout:

Here is the meat of the
layout logic in my GamePane:


[prettify]        
public GamePane(HangmanInstance hangmanInstance, boolean firstGame, String wordToGuess) 
{
    super();
    this.hangmanInstance = hangmanInstance;
    this.wordToGuess = wordToGuess;
    game.start(wordToGuess);

    // Set the background color of the window
    setBackground(new Color(69,103,69));
                
    // Create a grid to position the elements
    layoutGrid = new Grid();
    layoutGrid.setCellMargin(10);
                
    // The title
    titleLabel.setIcon(new HttpImageReference("images/echo-hangman.png"));
    layoutGrid.add(0,0,titleLabel);

    // The number of guesses left
    guessesLeftCountLabel.setIcon(imageMapper.get('5'));
    Grid.Cell cell = new Grid.Cell();
    cell.setVerticalAlignment(EchoConstants.CENTER); 
    cell.setHorizontalAlignment(EchoConstants.RIGHT);
    cell.add(guessesLeftCountLabel);
    layoutGrid.add(1,0,cell);

    cell = new Grid.Cell();
    cell.setVerticalAlignment(EchoConstants.CENTER);
    cell.add(guessesLeftLabel);
    layoutGrid.add(2,0,cell);
                
    // The scaffold (big component, needs to span multiple columns)
    cell = new Grid.Cell();
    cell.setColumnSpan(3);
    cell.add(scaffoldLabel);
    layoutGrid.add(1,1,cell);
                
    // The correct guesses (big component, needs to span multiple columns)
    cell = new Grid.Cell();
    cell.setColumnSpan(3);
    cell.add(createGuessGrid());
    layoutGrid.add(0,2,cell);

    // The letter chooser (big component, needs to span multiple columns)
    cell = new Grid.Cell();
    cell.setColumnSpan(3);
    cell.add(createChooserGrid());
}

/**
 * Create the pushbuttons that allow the user to choose letters
 */
private Component createLetterButtonsGrid() {
  letterGrid = new Grid();
  String alphabet = new String("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  for (int letterIndex=0; letterIndex<26; letterIndex++)
  {
    int x = letterIndex%9;
    int y = letterIndex/9;
    char letter = alphabet.charAt(letterIndex);
    // Create a reference to the letter image
    //HttpImageReference letterImageRef = new HttpImageReference(letterImages[letterIndex]);
    HttpImageReference letterImageRef = (HttpImageReference)imageMapper.get(letter);
    // Create a button using that image
    Button letterButton = new Button(letterImageRef);
    // Set the action command to the letter the button represents
    letterButton.setActionCommand(""+letter);
    letterButton.setIdentifier("letterButton" + letter);
    // Set this GamePane as the listened for the button
    letterButton.addActionListener(this);
        // Add the button to this GamePane
        letterGrid.add(x,y,letterButton);
  }
  return(letterGrid);
}
[/prettify]

This code is pretty simple, I am initializing components, and adding
them to my ContentPane. As you can see, I am using Grid components to
position the components to match the original tapestry example. The
buttons that represent the alphabet are generated by the program and
embedded in their own Grid.

Tapestry Component Layout:

For comparison, in Tapestry implementations all component
positioning is handled by an HTML page. From Howard's Tapestry
example:

<html>
<head>
<title>Tapestry Hangman</title>
<link rel="stylesheet" type="text/css" href="css/hangman.css"/>
</head>
<body jwcid="$content$">
<span jwcid="@Border">
<table>
  <tr>
    <td><img alt="Tapestry Hangman" src="images/tapestry-hangman.png"
           width="197" height="50" border="0"/>
    </td>
    <td width="70" align="right">
      <img jwcid="@Digit" digit="ognl:visit.game.incorrectGuessesLeft"
         src="images/Chalkboard_3x8.png"/>
    </td>
    <td> <img alt="Guesses Left"
            src="images/guesses-left.png" width="164" height="11" border="0"/>
    </td>
  </tr>
  <tr>
    <td></td><td></td>
    <td><img jwcid="@Scaffold" digit="ognl:visit.game.incorrectGuessesLeft"
          src="images/scaffold.png" border="0"/>
    </td>
    </tr>
</table>
<br>
<table>
  <tr valign="center">
    <td width="160">
      <p align="right">
        <img alt="Current Guess" src="images/guess.png"
           align="MIDDLE" width="127" height="20" border="0"/></p>
    </td>
    <td><span jwcid="@Spell">
    <!--- Additional letters from the mockup --->
      <img height="36" alt="A"
        src="images/Chalkboard_1x1.png" width="36"
        border="0"/><img height="36" alt="_"
        src="images/Chalkboard_5x3.png" width="36"
        border="0"/><img height="36" alt="_"
        src="images/Chalkboard_1x5.png" width="36"
        border="0"/><img height="36" alt="_"
        src="images/Chalkboard_5x3.png" width="36"
        border="0"/><img height="36" alt="_"
        src="images/Chalkboard_5x3.png" width="36"
        border="0"/><img height="36" alt="_"
        src="images/Chalkboard_5x3.png" width="36"
        border="0"/><img height="36" alt="_"
        src="images/Chalkboard_5x1.png" width="36"
        border="0"/></span>
    </td>
  </tr>
  <tr>
    <td valign="top">
      <p align="right">
        <img alt="Choose" src="images/choose.png"
             height="20" width="151" border="0"/>
      </p>
    </td>
    <td width="330"><span jwcid="selectLoop">
      <a href="#" jwcid="select"
         class="select-letter"><img jwcid="@Letter"
         letter="ognl:letterForGuessIndex"
         disabled="ognl:letterGuessed" border="0"
         src="images/Chalkboard_5x3.png"/></a></span><span
         jwcid="$remove$">
      <a class="select-letter" href="#"><img height="36" alt="B"
         src="images/Chalkboard_1x2.png" width="36" border="0"/></a>
      <a class="select-letter" href="#"><img height="36" alt="C"
         src="images/Chalkboard_1x3.png" width="36" border="0"/></a>
      <a class="select-letter" href="#"><img height="36" alt="D"
         src="images/Chalkboard_1x4.png" width="36" border="0"/></a>
      <img height="36" alt="-" src="images/letter-spacer.png" width="36"
          border="0"/>
<--! MUCH DELETED FOR SAKE OF BREVITY -->
   </td>
  </tr>
</table></span>
</body>
</html>

To be fair, the HTML layout for the Tapestry components can be
created by any one of a number of excellent HTML editors. One of the
greatest strengths of Tapestry is the complete separation between
layout in HTML and functionality in Java. Echo, in contrast, trades
the separation of concerns for clear, easy to read, easy to maintain
Java code. Programming skills are required to develop layout, but in
small projects where a single developer is handling both tasks, this
isn't much of a concern.

Echo Event Handling:

Event handling in Echo is straightforward. When a component is
created, you can specify the component which should handle any events
generated by the component. The event handling logic for my GamePane
follows:

        
public void actionPerformed(ActionEvent e)
{
  if (e.getActionCommand().equals("start game"))
  {
     startButton.setVisible(false);
     startButton.setEnabled(false);
     chooserGrid.setVisible(true);
     chooserGrid.setEnabled(true);
  }
  else if (e.getActionCommand().equals("new game"))
  {
     hangmanInstance.startAnotherGame();
  }
  else
  {
     hideLetterButton("letterButton" + e.getActionCommand());
     // Pass the letter to the game
     boolean result = game.makeGuess(e.getActionCommand().charAt(0));
     int incorrectGuessesLeft = game.getIncorrectGuessesLeft();
     int numMistakes = 5-incorrectGuessesLeft;
     if (result==false)
     {
        playAgainButton.setVisible(true);
        playAgainButton.setEnabled(true);
        chooserGrid.setVisible(false);
        chooserGrid.setEnabled(false);
        disableAllLetters();
        if( game.isWin())
        {
          winLoseLabel.setIcon(
            new HttpImageReference("images/you-win.png"));
        }
        else
        {
          winLoseLabel.setIcon(
            new HttpImageReference("images/you-lose.png"));
          numMistakes=6;
        }
      }
      updateScaffold(numMistakes);
      updateWordSoFarLabels();
      guessesLeftCountLabel.setIcon(imageMapper.get(new String( ""
                                     + incorrectGuessesLeft)));
   }
}

This is not the prettiest event handler in the world, but it
suffices. Events are returned as Java Strings, and you must perform
string comparisons to determine the proper action. For a more
complex example, the event handling would be spread across multiple
components rather then consolidated in a single component.

Which is better?

I am sure that you know the answer to the question: "Which is better?". As always, the answer is a definite:"It depends".

Tapestry and Echo approach the same problem from different perspectives. If you are experienced in developing user interfaces in code, then you will probably prefer Echo. If you prefer to leave layout to an HTML page designer, then Tapestry will probably be more to your liking. There's no wrong answer here.

What about JSF?

Well, since JSF is the standard blessed by the JCP, then it's going to be an important player. I hope very much that the JSF expert group studies both Tapestry and Echo. The standard should not preclude developers from writing applications in either style.

Javascript Rules!

The not so dirty-little-secret of both Tapestry and Echo is that much of the nifty UI is due to client side Javascript. It would be really great if the Tapestry and EchoPoint commiters would collaborate. I have not delved into details, but surely Tapestry and Echo components could share a lot of Javascript (and hopefully JSF will get in the game too).

In closing....
Echo is just plain fun. I had never heard of Echo before last week, and I downloaded it on Monday. It's Wednesday evening now, and I've been able to successfully hack together a pretty reasonable knock-off of the Hangman application. I've just barely scratched the surface... I could refactor the example as custom Echo components, investigate the EchoPoint HTML layout components, or delve into better understanding the architecture. It's always a delight to come across projects like this.

Obviously I am not in a position to comment on the scalability or resource utilization of Echo, but I can say that it promotes a very clean programming methodology that should be attractive to a large audience of developers.

Check out Echo for yourself!

Update:Check out my blog on creating custom Echo components.

Update: 24Jun05: The new Java web component framework Wicket includes Hangman as one of their example apps.

Related Topics >>