The Source for Java Technology Collaboration
User: Password:



John Reynolds

John Reynolds's Blog

Echo-ing the Tapestry Hangman example

Posted by johnreynolds on August 18, 2004 at 11:29 AM | Comments (4)

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:
        
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);
}

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.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Oh no, did I start a new benchmark war!
    John,

    Interesting article. Despite the subject of my posting, I agree that implementing the same example with different technologies is useful for understanding the differences.

    Regarding your hopes that the JSF EG will look into supporting both the Tapestry and Echo application development styles, I hope so to. Note though that just as you can use a custom ViewHandler with JSF 1.0/1.1 to enable the Tapestry development style as I showed in my article, you can do the same to enable the Echo application style. With a custom ViewHandler, the JSF views can be created by pure Java classes in the same manner as you show in this blog. I have an example of this in my JSF book (O'Reilly).

    The main difference between Echo and JSF in this regard at the moment (besides the need for a custom ViewHandler) is probably that Echo provides better layout components and client-side scripting supported input components out-of-the box.

    It would be great if bridges/wrappers could be developed for Tapestry, Echo, and other similar frameworks' components and JSF components, so we don't have to reimplement all great components for every framework ...

    Posted by: bergsten on August 18, 2004 at 11:06 PM

  • Blessed be the peace-makers ;-)
    Perhaps we should get the UN to broker an agreement?

    Thanks for the inspiration Hans.

    Posted by: johnreynolds on August 19, 2004 at 04:58 AM

  • Wicket Hangman?
    This morning I saw a discussion on The Server Side about a new web component framework called Wicket.

    Can Wicket-Hangman be far behind? Who's willing to accept the challenge? ;-)

    Posted by: johnreynolds on August 19, 2004 at 06:28 AM

  • State management
    I didn't see it mentioned in the article, but I thought I would point at that one major difference between Echo and its competitors is its strong state management. It is very adept at building "web applications" (ie. applications that have the full feel and responsiveness of desktop apps). Try the window management stuff. Just the idea that you can precisely control the inventory of your app's windows is compelling.

    Posted by: tlaurenzo on August 19, 2004 at 09:16 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds