| <?xml version="1.0" encoding="utf-8"?> |
| <!-- $Id$ --> |
| <!DOCTYPE book PUBLIC |
| "-//OASIS//DTD DocBook XML V4.1.2//EN" |
| "http://www.oasis-open.org/docbook/xml/4.0/docbookx.dtd" [ |
| <!ENTITY % TapestryLinks SYSTEM "../common/TapestryLinks.xml"> |
| %TapestryLinks; |
| ]> |
| <!-- Conventions: |
| |
| Component ids are <varname> |
| Java packages and class names are <classname> |
| Tapestry component aliases are <classname> |
| |
| In-line code snippets use <function> |
| Property paths and JavaBeans property names used <varname> |
| --> |
| <book> |
| <title>Tapestry Tutorial</title> |
| <bookinfo> |
| <author> |
| <firstname>Howard</firstname> |
| <surname>Lewis Ship</surname> |
| </author> |
| <copyright> |
| <year>2000</year> |
| <year>2001</year> |
| <year>2002</year> |
| <year>2003</year> |
| <holder>The Apache Software Foundation</holder> |
| </copyright> |
| </bookinfo> |
| <chapter id="intro"> |
| <title>Introduction</title> |
| |
| <warning> |
| <para> |
| This Tutorial is extremely out of date. A new tutorial should be ready |
| before 3.0 reaches GA. |
| </para> |
| </warning> |
| |
| <para> |
| Tapestry is a new application framework for developing web applications. It uses |
| a component object model to represent the pages of a web application. This is |
| similar to spirit to using the Java Swing component object model to build GUIs. |
| </para> |
| <para> |
| Just like using a GUI toolkit, there's some preparation and some basic ideas that must be cleared |
| before going to more ambitious things. Nobody writes a word processor off the top of their head |
| as their first GUI project; nobody should attempt a full-featured e-commerce site as their first |
| attempt using Tapestry. |
| </para> |
| <para> |
| The goal of Tapestry is to eliminate most of the coding in a web application. Under Tapestry, |
| nearly all code is directly related to application functionality, with very little "plumbing". If you |
| have previously developed a web application using Microsoft Active Server Pages, JavaServer |
| Pages or Java Servlets, you may take for granted all the plumbing: writing servlets, assembling |
| URLs, parsing URLs, managing objects inside the &HttpSession;, etc. |
| </para> |
| <para> |
| Tapestry takes care of nearly all of that, for free. It allows for the development of rich, highly |
| interactive applications. |
| </para> |
| <para> |
| This tutorial will start with basic concepts, such as the "Hello World" application, and will |
| gradually build up to more sophisticated examples. |
| </para> |
| <para> |
| The tutorial uses &Jetty;, |
| a freely available servlet engine, which is packaged with |
| the Tapestry |
| distribution. |
| </para> |
| <para> |
| The format of this tutorial is to describe (visually and with text) an application within the tutorial, |
| then describe how it is constructed, using code excerpts. The reader is best served by having an |
| IDE open so that they can look at the code in detail, as well as run the applications. |
| </para> |
| </chapter> |
| <chapter id="setup"> |
| <title>Setting up the Tutorial</title> |
| <para> |
| This document expects that you will have extracted the full Tapestry distribution to your |
| <filename class="directory">C:</filename> drive |
| <footnote> |
| <para> |
| If you are using Solaris or another non-Windows operating system, you're expected |
| to be savvy enough to translate to a sensibly constructed file system. |
| </para> |
| </footnote> |
| </para> |
| <para> |
| This will have created a directory <filename class="directory">C:\Tapestry-<replaceable>x.x</replaceable> |
| </filename> |
| and, beneath it, several more directories. |
| <footnote> |
| <para> |
| The three numbers are the release number. At the time of this writing, the release |
| was 2.2, but this is constantly changing. Simply adjust the actual pathname to |
| reflect the release of Tapestry you downloaded. |
| </para> |
| </footnote> |
| </para> |
| <para> |
| The source code for the Tutorial is distributed as a JAR file, |
| <filename>src/examples-src.jar</filename>. A precompiled WAR file, |
| <filename>lib/tutorial.war</filename> is included in the distribution. |
| </para> |
| |
| |
| <para> |
| The Tapestry distribution includes an &Ant; build file that allows the |
| Tutorial to be directly executed. Ant release 1.5 is required. |
| </para> |
| |
| <para> |
| From the Tapestry root directory, |
| execute the command <command>ant -emacs run-tutorial</command>, which will launch the |
| Jetty server for the Tutorial. |
| </para> |
| <para> |
| Once Jetty is running, you can access the Tutorials using the URL |
| <ulink url="http://localhost:8080/tutorial"> |
| <filename>http://localhost:8080/tutorial</filename> |
| </ulink>. |
| </para> |
| <figure> |
| <title>Tutorial Index Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/tutorial-index.png" format="PNG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| </chapter> |
| |
| <chapter id="hello"> |
| <title>Hello World</title> |
| <para> |
| In this first example, we'll create a very simple "Hello World" kind of application. It won't have |
| any real functionality but it'll demonstrate the simplest possible variation of a number of key |
| aspects of the framework. |
| </para> |
| <para> |
| We'll define our application, define the lone page of our application, configure everything and |
| launch it. |
| </para> |
| <para> |
| The code for this section of the tutorial is in the Java package <classname>tutorial.hello</classname>, i.e., |
| <filename class="directory">C:\Tapestry-<replaceable>x.x.x</replaceable>\examples\Tutorial\src\tutorial\hello</filename>. |
| </para> |
| <section id="hello.engine"> |
| <title>Application Engine</title> |
| <para> |
| As each new client connects to the application, an instance of the application engine is created for |
| them. The application engine is used to track that client's activity within the application. |
| </para> |
| <para> |
| The application engine is an instance, or subclass of, the Tapestry class |
| &SimpleEngine;. |
| </para> |
| <para> |
| In these first few examples, we have no additional behavior to add to the provided base class, so |
| we simply use &SimpleEngine; directly. |
| </para> |
| </section> |
| <section id="hello.descriptor"> |
| <title>Web Deployment Descriptor</title> |
| <para> |
| The application servlet is a "bridge" between the servlet container and the application engine. Its |
| job is simply to create (on the first request) or locate (on subsequent requests) the application |
| engine. |
| </para> |
| |
| |
| <para> |
| All Tapestry applications use the same servlet class, however its configuration is different. Part |
| of the configuration is to identify the location of the <emphasis>application specification</emphasis> |
| which is like a master index of all the pages in the application. |
| </para> |
| |
| <para> |
| The tutorial is a rare case; it is a single WAR that contains multiple Tapestry applications. This |
| isn't a problem ... each Tapestry application has its own servlet and has its own configuration. |
| The following figure shows the deployment descriptor for the Tapestry Tutorial (but excludes |
| the additional sections for the other applications within the WAR). |
| </para> |
| |
| <figure> |
| <title>Tutorial Deployment Descriptor (partial)</title> |
| <programlisting> |
| <![CDATA[ |
| <?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> |
| <display-name>Tapestry Tutorial</display-name> |
| |
| <servlet> |
| <servlet-name>hello</servlet-name> |
| <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class> |
| <init-param> |
| <param-name>org.apache.tapestry.application-specification</param-name> |
| <param-value>/tutorial/hello/HelloWorld.application</param-value> |
| </init-param> |
| <load-on-startup>0</load-on-startup> |
| </servlet> |
| |
| <servlet-mapping> |
| <servlet-name>hello</servlet-name> |
| <url-pattern>/hello</url-pattern> |
| </servlet-mapping> |
| |
| <session-config> |
| <session-timeout>15</session-timeout> |
| </session-config> |
| |
| <welcome-file-list> |
| <welcome-file>index.html</welcome-file> |
| </welcome-file-list> |
| </web-app> |
| |
| ]]> |
| </programlisting> |
| </figure> |
| </section> |
| <section id="hello.appspec"> |
| <title>Application Specification</title> |
| <para> |
| The application specification is used to describe the application to the Tapestry framework. It |
| provides the application with a name, an engine class, and a list of pages. |
| </para> |
| <para> |
| This specification is a file that is located on the Java class path. In a deployed Tapestry |
| application, the specification lives with the application's class files, in the |
| <filename class="directory">WEB-INF/classes</filename> directory of a War file. |
| </para> |
| <figure> |
| <title>HelloWorld.application</title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE application PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| <application |
| name="Hello World Tutorial" |
| engine-class="org.apache.tapestry.engine.&SimpleEngine;"> |
| |
| <page name="Home" |
| specification-path="/tutorial/hello/Home.page"/> |
| |
| </application> |
| </programlisting> |
| </figure> |
| <para> |
| Our application is very simple; we give the application a name, use the standard engine, and define |
| a single page, named "Home". In Tapestry, pages and components are specified with the path to their |
| specification file (a file that end with '.page' for page specifications or '.jwc' |
| for component specifications). |
| </para> |
| <para> |
| Page "Home" has a special meaning to Tapestry: when you first launch a Tapestry application, it |
| loads and displays the "Home" page. All Tapestry applications are required to have such a home |
| page. |
| </para> |
| </section> |
| <section id="hello.home-page-spec"> |
| <title>Home Page Specification</title> |
| <para> |
| The page specification defines the Tapestry component responsible for the page. In this first |
| example, our component is very simple. |
| </para> |
| <figure> |
| <title>Home.page</title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE page-specification PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| |
| <page-specification class="org.apache.tapestry.html.&BasePage;"/> |
| </programlisting> |
| </figure> |
| <para> |
| This simply says that <classname>Home</classname> is a kind of page. We use the supplied Tapestry class |
| &BasePage; since we aren't adding any behavior to the page. |
| </para> |
| </section> |
| <section id="hello.home-page-template"> |
| <title>Home Page Template</title> |
| <para> |
| Finally, we get to the content of our application. This file is also a Java resource; |
| it isn't directly |
| visible to the web server. It has the same location and name as the component specification, |
| except that it ends in "html". |
| </para> |
| <figure> |
| <title>Home.html</title> |
| <programlisting><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| |
| <html> |
| <head> |
| <title>Hello World</title> |
| </head> |
| |
| <body> |
| |
| Welcome to your first <b>Tapestry Application</b>. |
| |
| </body> |
| </html>]]></programlisting> |
| </figure> |
| </section> |
| <section id="hello.run"> |
| <title>Run the Application</title> |
| <para> |
| You should already be running the Jetty server in a window, and have a browser running the |
| tutorials page. Select the first option, Hello World, from the list. You will be presented with the |
| first (and only) page generated by Tapestry for this application: |
| </para> |
| <figure> |
| <title>Hello World Application</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/hello-world.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Not much of an application ... there's no interactivity. It might as well be a static web page, but it's |
| a start. Remember, there was no JavaServer page here, and no HTML directly visible to the web |
| server. We used the Tapestry framework to assembly an application consisting of a single component. |
| </para> |
| <para> |
| In the following chapters, we'll see how to add dynamic content and then true interactivity. |
| </para> |
| </section> |
| </chapter> |
| <chapter id="dynamic"> |
| <title>Dynamic Content</title> |
| <para> |
| In this chapter, we'll create a new web application that will show some dynamic content. We'll also |
| begin to show some interactivity by adding a link to the page. |
| Our dynamic content will simply be to show the current date and time. The interactivity will be a |
| link to refresh the page. It all looks like this: |
| </para> |
| <figure> |
| <title>Dynamic Application</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/dynamic.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Clicking the word "here" will update the page showing the new data and time. Not incredibly |
| interactive, but it's a start. |
| </para> |
| <para> |
| The code for this section of the tutorial is in the package |
| <classname>tutorial.simple</classname>. |
| </para> |
| <para> |
| The application specification is almost identical to the Hello World example: |
| </para> |
| <figure> |
| <title>Simple.application</title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE application PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| |
| <application name="Simple Tutorial" engine-class="org.apache.tapestry.engine.&SimpleEngine;"> |
| <page name="Home" specification-path="/tutorial/simple/Home.page"/> |
| </application> |
| </programlisting> |
| </figure> |
| <para> |
| Things only begin to get more interesting when we look at the HTML template for the home |
| page: |
| </para> |
| <figure> |
| <title>Home.html</title> |
| <programlisting><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html> |
| <head> |
| <title>Simple</title> |
| </head> |
| <body> |
| |
| This application demonstrates some dynamic behavior using Tapestry components. |
| |
| <p>The current date and time is: <b><span jwcid="insertDate">Current Date</span></b> |
| |
| <p>Click <a jwcid="refresh">here</a> to refresh. |
| |
| </body> |
| </html>]]></programlisting> |
| </figure> |
| <para> |
| This looks like ordinary HTML, except for the special <varname>jwcid</varname> |
| attribute. "jwc" is short for "Java Web Component"; these attributes |
| identify the tag as a placeholder for a dynamic Tapestry component. |
| </para> |
| <para> |
| We have two components. The first inserts the current date and time into the HTML response. |
| The second component |
| creates a hyperlink that refreshes the page when clicked.</para> |
| <para> |
| One of the goals of Tapestry is that the HTML should have the minimum amount of special |
| markup. This is demonstrated here ... the dynamic component tags blend into the standard HTML of the |
| template. We also don't confuse the HTML by explaining exactly what an <varname>insertDate</varname> |
| or <varname>refresh</varname> |
| is; that comes out of the specification (described shortly). The ids used here are meaningful only |
| to the developer |
| <footnote> |
| <para>Of course, good and consistent naming is important. |
| </para> |
| </footnote>, the particular type and configuration of each component is defined in the |
| component specification. |
| </para> |
| <para>Tapestry doesn't really care what HTML tag you use, as long as you balance the tag correctly. In fact, it ignores the tag entirely: the |
| <varname>refresh</varname> component above could just has easily been identified with a <span> tag, or any other tag for that matter. Tapestry is only interested in the <emphasis>structure</emphasis> of the HTML template. The fact that you can use meaningful tags is a convienience; it allows a Tapestry HTML template to be previewed in a <acronym>WYSIWYG</acronym> HTML editor, such as HomeSite. Additionally, Tapestry edits out the content of tags for components that don't wrap around other content: the <varname>insertDate</varname> component in this example. This allows a preview |
| values to be kept in the template. |
| </para> |
| <para> |
| Very significant is the fact that a Tapestry component can |
| <emphasis>wrap</emphasis> around other elements of the |
| template. The <varname>refresh</varname> component wraps around the word "here". |
| What this means is that the |
| <varname>refresh</varname> component will get a chance to emit some HTML (an |
| <sgmltag class="starttag">a</sgmltag> hyperlink tag), then emit the |
| HTML it wraps (the word "here"), then get a chance to emit more HTML (the |
| <sgmltag class="endtag">a</sgmltag> closing tag). |
| </para> |
| <para> |
| What's more important is that the component can not only wrap static HTML from the template |
| (as shown in this example), but may wrap around other Tapestry components and those |
| components may themselves wrap text and components, to whatever depth is required. |
| </para> |
| <para> |
| And, as we'll see in later chapters, a Tapestry component itself may have a template and more |
| components inside of it. In a real application, the single page of HTML produced by the |
| framework may be the product of dozens of components, effectively "woven" from dozens of |
| HTML templates. |
| </para> |
| <para> |
| Again, the HTML template doesn't define what the components are, it is simply a mix of static |
| HTML that will be passed directly back to the client web browser, with a few placeholders (the |
| tags with the <varname>jwcid</varname> attribute) for where dynamic content will be plugged in. |
| </para> |
| <para> |
| The page's component specification defines what types of components are used and how data |
| moves between the application, page and any components. |
| </para> |
| <figure> |
| <title>Home.page</title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE page-specification PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| |
| <page-specification class="tutorial.simple.Home"> |
| |
| <component id="insertDate" type="&Insert;"> |
| <binding name="value" expression="currentDate"/> |
| </component> |
| |
| <component id="refresh" type="&PageLink;"> |
| <static-binding name="page">Home</static-binding> |
| </component> |
| |
| </page-specification> |
| </programlisting> |
| </figure> |
| <para> |
| Here's what all that means: The <varname>Home</varname> page is implemented with a custom class, |
| <classname>tutorial.simple.Home</classname>. It contains two components, |
| <varname>insertDate</varname> and <varname>refresh</varname>. |
| </para> |
| <para> |
| The two components used within this page are provided by the Tapestry framework. |
| </para> |
| <para> |
| The <varname>insertDate</varname> component is type &Insert;. |
| &Insert; components have a <varname>value</varname> parameter used to |
| specify what should be inserted into the HTML produced by the page. The |
| <varname>insertDate</varname> |
| component has its <varname>value</varname> parameter bound to a JavaBeans property of its container |
| (the page), the <varname>currentDate</varname> property. |
| </para> |
| <para> |
| The <varname>refresh</varname> component is type &PageLink;, meaning it creates a |
| link to some other page in the |
| application. &PageLink; components have a parameter, named <varname>page</varname>, |
| which defines the name of the |
| page to navigate to. The name is matched against a page named in the application specification. |
| </para> |
| <para> |
| In this case, we only have one page in our application (named "Home"), so we can use a static |
| binding for the page parameter. A static binding provides a value for the component parameter |
| statically, the same value every time. The value is defined right in the specification. |
| </para> |
| <para> |
| That just leaves the implementation of the Home page component: |
| </para> |
| <figure> |
| <title>Home.java</title> |
| <programlisting> |
| package tutorial.simple; |
| |
| import java.util.Date; |
| import org.apache.tapestry.html.BasePage; |
| |
| public class Home extends &BasePage; |
| { |
| public Date getCurrentDate() |
| { |
| return new Date(); |
| } |
| } |
| </programlisting> |
| </figure> |
| <para> |
| <classname>Home</classname> implements a read-only JavaBeans property, <varname>currentDate</varname>. |
| This is the same <varname>currentDate</varname> |
| that the <varname>insertDate</varname> component needs. When asked for the current date, the |
| <varname>Home</varname> object returns |
| a new instance of the <classname>java.util.Date</classname> object. |
| </para> |
| <para> |
| The <varname>insertDate</varname> component converts objects into strings by invoking |
| <function>toString()</function> on the object. |
| </para> |
| <para> |
| Now all the bits and pieces are working together. |
| </para> |
| <para> |
| Run the application, and use the View Source command to examine the HTML generated by |
| by framework. |
| </para> |
| <figure> |
| <title>HTML generated for Home page</title> |
| <programlisting><![CDATA[ |
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html> |
| <head> |
| <title>Simple</title> |
| </head> |
| <body> |
| |
| This application demonstrates some dynamic behavior using Tapestry components. |
| |
| <p>The current date and time is: <b>Fri Nov 23 17:05:53 PST 2001</b> |
| |
| <p>Click <a href="/tutorial/simple...">here</a> to refresh. |
| |
| </body> |
| </html> |
| |
| ]]></programlisting> |
| </figure> |
| <para> |
| This should look very familiar, in that it is mostly the same as the HTML template for the page. |
| Tapestry not only inserted simple text (the current date and time, |
| obtained from an <classname>java.util.Date</classname> object), but the |
| <varname>refresh</varname> component inserted the <sgmltag class="starttag">a</sgmltag> and |
| <sgmltag class="endtag">a</sgmltag> tags, and created an appropriate URL for the href attribute. |
| </para> |
| </chapter> |
| <chapter id="hangman"> |
| <title>Hangman</title> |
| <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> |
| <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). |
| </para> |
| <figure> |
| <title>Hangman Home Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/hangman-home.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </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): |
| </para> |
| <figure> |
| <title>Hangman Guess Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/hangman-guess.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| After you give up, or when you make too many mistakes, you end up on the the Failed page: |
| </para> |
| <figure> |
| <title>Hangman Failed Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/hangman-lose.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| But, if you guess all the letters, you are sent to the Success page: |
| </para> |
| <figure> |
| <title>Hangman Success Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/hangman-win.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <section id="hangman.visit"> |
| <title>The Visit Object</title> |
| <para> |
| The center of this application is an object that represents game, an object of class |
| <classname>HangmanGame</classname>. 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> |
| <para> |
| This object is a property of the <emphasis>visit</emphasis> 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> |
| <para> |
| The visit object is owned and created by the engine object. It is serialized and de-serialized with |
| the engine. |
| </para> |
| <para> |
| The application specification includes a little extra segment at the bottom to specify the class of |
| the visit object. |
| </para> |
| <figure> |
| <title>Hangman.application</title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE application PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| |
| <application name="Tapestry Hangman" engine-class="org.apache.tapestry.engine.&SimpleEngine;"> |
| |
| <property name="org.apache.tapestry.visit-class">tutorial.hangman.Visit</property> <co id="hangman.app.visit"/> |
| |
| <page name="Home" specification-path="/tutorial/hangman/Home.page"/> |
| |
| <page name="Guess" specification-path="/tutorial/hangman/Guess.page"/> |
| |
| <page name="Failed" specification-path="/tutorial/hangman/Failed.page"/> |
| |
| <page name="Success" specification-path="/tutorial/hangman/Success.page"/> |
| |
| </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()</function> must be implemented instead. |
| </para> |
| </callout> |
| </calloutlist> |
| </para> |
| <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). |
| </para> |
| </section> |
| <section id="hangman.home-page"> |
| <title>The Home Page</title> |
| <para> |
| The Home page's job is to collect the difficulty and initiate a game: |
| </para> |
| <figure> |
| <title>Home.java</title> |
| <programlisting> |
| public class Home extends &BasePage; |
| { |
| 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 void formSubmit(IRequestCycle cycle) |
| { |
| 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</varname> property is a persistent page property (which means |
| the page will "remember" the value previously selected by the user). |
| </para> |
| <para> |
| We use a common trick for simple pages: the page contains a single |
| &Form; component, so we use |
| the page itself as the form's listener, and have the page implement the |
| &IActionListener; interface. |
| </para> |
| <para> |
| This saves a bit of code for creating an inner class as the form listener. |
| </para> |
| <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> |
| <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> |
| <para> |
| The interesting part of the Home page HTML template is the form: |
| </para> |
| <figure> |
| <title>Home.html (excerpt)</title> |
| <programlisting><![CDATA[ |
| <form jwcid="form"> |
| |
| <span jwcid="group"> |
| |
| <span jwcid="ifError"> |
| <font size=+2 color=red><span jwcid="insertError"/></font> |
| </span> |
| |
| <table> |
| <tr> |
| <td><input jwcid="inputEasy"/></td> |
| <td>Easy game; you are allowed ten misses.</td> |
| </tr> |
| |
| <tr> |
| <td><input jwcid="inputMedium"/></td> |
| <td>Medium game; you are allowed five misses.</td> |
| </tr> |
| |
| <tr> |
| <td><input jwcid="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> |
| |
| </span> |
| </form> |
| ]]></programlisting> |
| </figure> |
| <para> |
| Here, the interesting components are <varname>group</varname>, |
| <varname>inputEasy</varname>, <varname>inputMedium</varname> and <varname>inputHard</varname>. |
| <varname>group</varname> 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</varname> 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> |
| <para> |
| This comes together in the Home page specification: |
| </para> |
| <figure> |
| <title>Home.page (excerpt)</title> |
| <programlisting> |
| <component id="group" type="&RadioGroup;"> |
| <binding name="selected" expression="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</sgmltag> is like a |
| <sgmltag class="starttag">static-binding</sgmltag>, 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> |
| <para> |
| This is a good thing, since if you decide to make a <varname>HARD</varname> |
| game only allow two mistakes, you can |
| make the change in exactly one place .. your Java code. |
| </para> |
| </callout> |
| </calloutlist> |
| </para> |
| <para> |
| So the end result is: when the user clicks the radio button for a Hard game, the static constant |
| <varname>HARD</varname> is assigned to the page's <varname>misses</varname> property. |
| </para> |
| </section> |
| <section id="hangman.guess-page"> |
| <title>The Guess Page</title> |
| <para> |
| This is the page where uses make letter guesses. The page has four sections: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <para> |
| A display of the word, with underscores replacing unguessed letters. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| A status area, showing the number of bad guesses and an optional error message after an |
| invalid guess. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| A list of letters that may be guessed. Letters disappear after they are used. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| An option to give up and see the word, terminating the game. |
| </para> |
| </listitem> |
| </itemizedlist> |
| <para> |
| Let's start with the HTML template this time: |
| </para> |
| <figure> |
| <title>Guess.html (excerpt)</title> |
| <programlisting><![CDATA[ |
| <h1>Make a Guess</h1> |
| |
| <font size=+3> |
| <span jwcid="insertGuess"/> |
| </font> |
| |
| <p> |
| |
| You have made <span jwcid="insertMissed"/> bad guesses, |
| out of a maximum of <span jwcid="insertMaxMisses"/>. |
| |
| <span jwcid="ifError"> |
| <p> |
| <font size=+3 color=red><span jwcid="insertError"/></font> |
| </span> |
| |
| <p>Guess: |
| <font size=+1> |
| <span jwcid="e"> |
| <a jwcid="guess"><span jwcid="insertLetter"/></a> |
| </span> |
| </font> |
| |
| <p><a jwcid="giveUp">Give up?</a> |
| ]]></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> |
| <para> |
| Let's look at the specification for those three components (<varname>e</varname>, |
| <varname>guess</varname> and <varname>insertLetter</varname>). |
| </para> |
| <figure> |
| <title>Guess.jwc (excerpt)</title> |
| <programlisting> |
| <component id="e" type="&Foreach;"> |
| <binding name="source" expression="unused"/> |
| </component> |
| |
| <component id="guess" type="&DirectLink;"> |
| <binding name="listener" expression="listeners.makeGuess"/> |
| <binding name="parameters" expression="components.e.value"/> |
| </component> |
| |
| <component id="insertLetter" type="&Insert;"> |
| <binding name="value" expression="components.e.value"/> |
| </component> |
| </programlisting> |
| </figure> |
| <para> |
| Component <varname>e</varname> is simply a &Foreach;, |
| the <varname>source</varname> is the <varname>unused</varname> property of the page (we'll see in a |
| moment how the page gets this list of unused letters from the game object). |
| </para> |
| <para> |
| Component <varname>insertLetter</varname> inserts the current letter from the list of unused letters. |
| It gets this current letter directly from the <varname>e</varname> component. |
| On successive iterations, a &Foreach; component's |
| <varname>value</varname> property is the value for the iteration. |
| </para> |
| <para> |
| Component <varname>guess</varname> is type &DirectLink;, 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> |
| <para> |
| Passing that kind of information along is accomplished by setting the |
| <varname>parameters</varname> parameter for the |
| component. The <varname>parameters</varname> parameter is an object, or |
| array or objects, that will be encoded into the |
| URL for the hyperlink. When the component's listener is notified, it |
| can obtain the array of objects from the &IRequestCycle; |
| <footnote> |
| <para> |
| Tapestry takes care of converting objects into strings when constructing the URL, then |
| converts those strings back into objects when the link is clicked. Your listener method |
| will be able to get <emphasis>copies</emphasis> of the original parameters. |
| </para> |
| </footnote>. |
| </para> |
| <para> |
| These <emphasis>service parameters</emphasis> |
| are often used to encode primary keys of objects, names of columns or other |
| information specific to the application. |
| </para> |
| <para> |
| In this case, the service parameters consist of a single value, |
| the letter to be guessed. |
| </para> |
| <para> |
| All of this comes together in the Java code for the Guess page. |
| </para> |
| <figure> |
| <title>Guess.java (excerpt)</title> |
| <programlisting> |
| public void makeGuess(&IRequestCycle; cycle) |
| { |
| Object[] parameters = cycle.getServiceParameters(); |
| char letter = ((Character)parameters[0]).charValue(); |
| HangmanGame game = getGame(); |
| |
| 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> |
| |
| The component specification showed how data was encoded into the URL as the |
| service parameters; here we see how the |
| <function>makeGuess()</function> listener |
| method has access to the service parameters and uses them. |
| The listener method extracts the letter and informs the game object, which throws an exception |
| if the letter is not in the word being guessed. |
| </para> |
| <para> |
| The method <function>HangmanGame.getFailed()</function> returns |
| <literal>true</literal> when all the missed guesses are used up, at |
| which point we go to the <varname>Failed</varname> page to tell the user what the word was. |
| </para> |
| <para> |
| On the other hand, if an exception isn't thrown, then the guess was good. |
| <function>getDone()</function> returns <literal>true</literal> |
| if all letters have been guessed, in which go to the <varname>Success</varname> page. |
| </para> |
| <para> |
| If all letters weren't guessed, we stay on the <varname>Guess</varname> page, |
| which will display the word with the |
| guessed letter filled in, and with fewer options in the list of possible guesses. |
| </para> |
| </section> |
| <section id="hangman.limitations"> |
| <title>Limitations</title> |
| <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</varname> |
| page and keep guessing (with accuracy, if your memory is any good). |
| </para> |
| </section> |
| </chapter> |
| <chapter id="reuse"> |
| <title>Creating Reusable Components</title> |
| <para> |
| In this tutorial, we'll show how to create a reusable component. One common use of components |
| it to create a common "border" for the application that includes basic navigation. We'll be |
| creating a simple, three page application with a navigation bar down the left side. |
| </para> |
| <figure> |
| <title>Border Home Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/border-home.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Navigating to another page results in a similar display: |
| </para> |
| <figure> |
| <title>Border Credo Page</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/border-credo.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Each page's content is confined to the silver area in the center. Note that the border adapts itself |
| to each page: the title "Home" or "Credo" is specific to the page, and the current page doesn't |
| have an active link (in the above page, "Credo" is the current page, so only "Home" and "Legal" |
| are usable as navigation links). |
| </para> |
| <para> |
| The "i" in the gear is the Show Inspector link. It will be described in the next chapter. |
| </para> |
| <para> |
| Because this tutorial is somewhat large, we'll only be showing excerpts from some of the files. |
| The complete source of the tutorial examples is available seperately, in the |
| <classname>tutorial.border</classname> package. |
| </para> |
| <para> |
| Each of the three pages has a similar HTML template: |
| </para> |
| <figure> |
| <title>Home.html</title> |
| <programlisting> |
| <span jwcid="border"> |
| |
| Nothing much doing here on the <b>home</b> page. Visit one of our other |
| fine |
| pages. |
| |
| </span> |
| </programlisting> |
| </figure> |
| <para> |
| Remember that Tapestry components can wrap around other HTML elements or components. |
| For the border, we have an HTML template where everything on the page is wrapped by the |
| <varname>border</varname> component. |
| </para> |
| <para> |
| Note that we don't specify any |
| <sgmltag class="starttag">html</sgmltag> or |
| <sgmltag class="starttag">body</sgmltag> tags; those are provided by the <classname>Border</classname> |
| component (as well as the matching close tags). |
| </para> |
| <para> |
| This illustrates a key concept within Tapestry: embedding vs. wrapping. The |
| <classname>Home</classname> page embeds |
| the <varname>border</varname> component (as we'll see in the <classname>Home</classname> page's specification). |
| This means that the <classname>Home</classname> |
| page is implemented using the <varname>border</varname> component. |
| </para> |
| <para> |
| However, the <varname>border</varname> component wraps the content of the <classname>Home</classname> page, |
| the <classname>Home</classname> page |
| HTML template indicates the <emphasis>order</emphasis> in which components (and static HTML elements) are |
| renderred. On the <classname>Home</classname> page, the <varname>border</varname> component 'bats' first and cleanup. |
| </para> |
| <para> |
| The construction of the <classname>Border</classname> component is driven by how it differs from page to page. |
| You'll |
| see that on each page, the title (in the upper left corner) changes. The names of all three pages are |
| displayed, but only two of the three will have links (the third, the current page, is just text). |
| Lastly, each page contains the specific content from its own HTML template. |
| </para> |
| <figure> |
| <title>Border.html</title> |
| <programlisting> |
| <span jwcid="shell"> <co id="reuse.border.shell"/> |
| <body jwcid="body"> <co id="reuse.border.body"/> |
| <table border=0 bgcolor=gray cellspacing=0 cellpadding=4> |
| <tr valign=top> |
| <td colspan=3 align=left> |
| <font size=5 color="White"><jwc id="insertPageTitle"/></font> |
| </td> |
| </tr> |
| <tr valign=top> |
| <td align=right> |
| <font color=white> |
| <span jwcid="e"> <co id="reuse.border.e"/> |
| <br><a jwcid="link"><span jwcid="insertName"/></a> <co id="reuse.border.insertName"/> |
| </span> |
| </font> |
| </td> |
| <td rowspan=2 valign=top bgcolor=silver> |
| <span jwcid="renderBody"/> <co id="reuse.border.renderBody"/> |
| </td> |
| <td rowspan=2 width=4></td> |
| </tr> |
| <tr> |
| <td><span jwcid="inspector"/></td> <co id="reuse.border.showInspector"/> |
| </tr> |
| <tr> |
| <td colspan=3 height=4> </td> |
| </tr> |
| </table> |
| </body> |
| </span> |
| </programlisting> |
| </figure> |
| <para> |
| <calloutlist> |
| <callout arearefs="reuse.border.shell"> |
| <para> |
| The <varname>shell</varname> component provides the <sgmltag class="starttag">html</sgmltag> and |
| <sgmltag class="starttag">head</sgmltag> elements of the response HTML. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.body"> |
| <para> |
| The <varname>body</varname> components provides the <sgmltag class="starttag">body</sgmltag> element. |
| It also provides support for JavaScript related to &Rollover; buttons, such as |
| the <varname>showInspector</varname> component. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.e"> |
| <para> |
| The <varname>e</varname> component is a &Foreach; configured to work through |
| a list of page names (provided by the engine). |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.insertName"> |
| <para> |
| The <varname>link</varname> and <varname>insertName</varname> |
| components provide the inter-page navigation links. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.renderBody"> |
| <para> |
| The <varname>renderBody</varname> component provides the actual content for the page. The |
| <classname>Border</classname> component is used on all three pages, but its a different |
| instance on each page, wrapping around different content specific to the page. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.showInspector"> |
| <para> |
| The <varname>showInspector</varname> component provides the button below the |
| page names (the italicized "i" in a |
| circle) and will be explained shortly. |
| </para> |
| </callout> |
| </calloutlist> |
| </para> |
| <para> |
| The <classname>Border</classname> component is designed to be usable in other Tapestry |
| applications, so it doesn't hard |
| code the list of page names. These must be provided to the component as a parameter. |
| In fact, the |
| application engine provides the list. |
| </para> |
| <figure> |
| <title>Border.jwc </title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE component-specification PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| |
| <component-specification class="tutorial.border.Border" allow-informal-parameters="no"> |
| |
| <parameter name="title" java-type="java.lang.String" required="yes"/> <co id="reuse.border.spec.title"/> |
| |
| <parameter name="pages" required="yes"/> <co id="reuse.border.spec.pages"/> |
| |
| <component id="shell" type="&Shell;"> |
| <binding name="title" expression="page.engine.specification.name"/> <co id="reuse.border.spec.shell-title"/> |
| </component> |
| |
| <component id="insertPageTitle" type="&Insert;"> |
| <inherited-binding name="value" parameter-name="title"/> <co id="reuse.border.spec.insertPageTitle"/> |
| </component> |
| |
| <component id="body" type="&Body;"/> |
| |
| <component id="e" type="&Foreach;"> <co id="reuse.border.spec.e"/> |
| <inherited-binding name="source" parameter-name="pages"/> |
| <binding name="value" expression="pageName"/> |
| </component> |
| |
| <component id="link" type="&PageLink;"> <co id="reuse.border.spec.link"/> |
| <binding name="page" expression="pageName"/> |
| <binding name="disabled" expression="disablePageLink"/> |
| </component> |
| |
| <component id="insertName" type="&Insert;"> |
| <binding name="value" expression="pageName"/> |
| </component> |
| |
| <component id="renderBody" type="&RenderBody;"/> |
| |
| <component id="inspector" type="&InspectorButton;"/> <co id="resuse.border.spec.InspectorButton"/> |
| </component-specification> |
| </programlisting> |
| </figure> |
| <para> |
| <calloutlist> |
| <callout arearefs="reuse.border.spec.title"> |
| <para> |
| Declares a required parameter for the border, the title that will appear on the |
| page. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.spec.pages"> |
| <para> |
| Declares a parameter to specify the list of page names. We don't specify a |
| particular type because its pretty unbounded; the framework will accept |
| &List;, &Iterator; or a Java array. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.spec.shell-title"> |
| <para> |
| We then provide the <varname>shell</varname> component with its <varname>title</varname> parameter; |
| this will be the window title. We |
| use the application's name, with is extracted from the application's specification. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.spec.insertPageTitle"> |
| <para> |
| The <sgmltag class="starttag">inherited-binding</sgmltag> element allows a component to |
| share its parameters. Here the <classname>Border</classname>'s <varname>title</varname> |
| is used as the <varname>value</varname> parameter of the <varname>insertPageTitle</varname> |
| component (an &Insert;). |
| Using these |
| inherited bindings simplifies the process of creating complex components from simple ones. |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.spec.e"> |
| <para> |
| Likewise, the <varname>e</varname> component (a &Foreach;) |
| needs as its source the list of pages, which it inherits from |
| the <classname>Border</classname> component's <varname>pages</varname> parameter. |
| It has been configured to store each succesive page |
| name into the <varname>pageName</varname> property of the <classname>Border</classname> component; |
| this is necessary so that the <classname>Border</classname> |
| component can determine which page link to disable (it disables the current page since we're |
| already there). |
| </para> |
| </callout> |
| <callout arearefs="reuse.border.spec.link"> |
| <para> |
| The <varname>link</varname> component creates the link to the other pages. It has a |
| <varname>disabled</varname> parameter; which, |
| when true, causes the link component to not create the hyperlink (though it still allows the |
| elements it wraps to render). The Java class for the <classname>Border</classname> component, |
| <classname>tutorial.border.Border</classname>, provides a method, <function>getDisablePageLink()</function>, |
| that returns true |
| when the <varname>pageName</varname> instance variable (set by the <varname>e</varname> component) |
| matches the current page's name. |
| </para> |
| </callout> |
| <callout arearefs="resuse.border.spec.InspectorButton"> |
| <para> |
| This component will raise the Tapestry Inspector in a new window when clicked. |
| </para> |
| </callout> |
| </calloutlist> |
| </para> |
| <para> |
| So, the specification for the <classname>Border</classname> component must identify the |
| parameters it needs, but also the |
| components it uses and how they are configured. |
| </para> |
| <figure> |
| <title>Show Inspector Button</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/show-inspector.gif" format="GIF"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Clicking on the button raises a second window that describes the current page in the application |
| (this is used when debugging a Tapestry applicaton). The Inspector is described in the next |
| chapter. |
| </para> |
| <para> |
| The final mystery is the <varname>wrapped</varname> component. It is used to render the elements wrapped by the |
| <classname>Border</classname> on the page containing the <classname>Border</classname>. |
| Those elements will vary from page to page; running |
| the application shows that they are different on the home, credo and legal pages (different text |
| appears in the central light-grey box). There is no limitation on the elements either: Tapestry is |
| specifically designed to allow components to wrap other components in this way, without any |
| arbitrary limitations. |
| </para> |
| <para> |
| This means that the different pages could contain forms, images or any set of components at all, |
| not just static HTML text. |
| </para> |
| <para> |
| The specification for the home page shows how the title and pages parameters are set. The title is |
| static, the literal value "Home" (this isn't the best approach if localization is a concern). |
| </para> |
| <figure> |
| <title>Home page specification</title> |
| <programlisting> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE page-specification PUBLIC |
| "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" |
| "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> |
| |
| <page-specification class="org.apache.tapestry.html.BasePage"> |
| |
| <component id="border" type="Border"> |
| <static-binding name="title">Home</static-binding> |
| <binding name="pages" expression="engine.pageNames"/> |
| </component> |
| |
| </page-specification> |
| </programlisting> |
| </figure> |
| <para> |
| The <varname>pages</varname> property is retrieved from the application engine, |
| which implements a <varname>pageNames</varname> JavaBeans property: |
| </para> |
| <figure> |
| <title>BorderEngine.java (excerpt)</title> |
| <programlisting><![CDATA[ |
| private static final String[] pageNames = |
| { "Home", "Credo", "Legal" }; |
| |
| public String[] getPageNames() |
| { |
| return pageNames; |
| } |
| ]]></programlisting> |
| </figure> |
| <para> |
| How did Tapestry know that the type 'Border' |
| corresponded to the specification <filename>/tutorial/border/Border.jwc</filename>? Only because we defined |
| an alias in the application specification: |
| </para> |
| <figure> |
| <title>Border.application (excerpt)</title> |
| <programlisting> |
| <component-alias type="Border" specification-path="/tutorial/border/Border.jwc"/> |
| </programlisting> |
| </figure> |
| <para> |
| Had we failed to do this, we would have had to specify the complete resource path, |
| <filename>/tutorial/border/Border.jwc</filename>, on each page's specification, instead of the short alias |
| 'Border'. There is no magic about the existing Tapestry component types |
| (&Insert;, &Foreach;, &PageLink;, etc. ... they each have an |
| alias pre-registered into every application specification. These short |
| aliases are simply a convienience. |
| </para> |
| </chapter> |
| <chapter id="inspector"> |
| <title>The Tapestry Inspector</title> |
| <para> |
| Unlike scripting systems (such as JavaServer Pages and the like), Tapestry applications are gifted |
| with a huge amount of information about how they are implemented. The same component |
| object model that allows Tapestry to perform so many ordinary functions can be leveraged to |
| provide some unusual functionality. |
| </para> |
| <para> |
| Run the Border tutorial from the previous chapter and click on the show inspector button (the |
| gear icon in the lower right corner). A new window will launch, containing the Inspector: |
| </para> |
| <figure> |
| <title>Tapestry Inspector</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/Inspector-Spec.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The Inspector displays live information from the running application; in fact, it is simply another |
| part of the application (the drop-down list of pages will include the Inspector page itself). The |
| Inspector is most often used to debug HTML generation by viewing the HTML templates. |
| It is also very useful in debugging problems where the wrong data is displayed, since it |
| allows the developer to navigate to the particular components and see directly what properties |
| are used. |
| </para> |
| <section id="inspector.navigation"> |
| <title>Navigation</title> |
| <para> |
| The inspector allows the user to navigate to any page and any component on a page. |
| The drop down list in the upper left corner lists all pages in the application; changing |
| the selection immediately updates the Inspector. |
| </para> |
| <para>Next to the drop down list is the component path; a list of nested component ids, starting |
| with "page" to represent the page. Clicking on any id in the path changes the information displayed |
| below. |
| </para> |
| <para> |
| Underneath the component navigation tools are a set of tab buttons for the different |
| inspector views. |
| </para> |
| </section> |
| <section id="inspector.specification"> |
| <title>Specification View</title> |
| <figure> |
| <title>Specification View</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/Inspector-Spec.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The specification view shows several sets of information about the selected component. |
| </para> |
| <para> |
| First shown are basic properties, such as the specification path and Java class. |
| </para> |
| <para> |
| Each formal parameter is displayed. Unbound parameters will show no value in |
| the Binding column. |
| </para> |
| <para> |
| Beneath formal parameters are informal parameters (the <classname>Border</classname> |
| component has none, so there is nothing to see). Informal parameters are |
| usually mapped directly to HTML attributes. They are most often used with |
| components that generate a single HTML tag, such as the &ActionLink;, |
| &DirectLink; or &TextField; components. |
| </para> |
| <para> |
| If the component contains assets, they are shown next. |
| </para> |
| <para> |
| Any helper beans for the component are displayed last. |
| </para> |
| <para> |
| On the right side is a list of each embedded component and its type. Clicking |
| the component id will navigate to the selected component. |
| </para> |
| </section> |
| <section id="inspector.template"> |
| <title>Template View</title> |
| <figure> |
| <title>Template View</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/Inspector-Template.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The template view shows the HTML template for the component. It shows dynamic tags in bold, |
| and makes the component id a clickable link (which navigates to the component, but maintains |
| the Template View). This allows the developer to quickly drill down through the components. |
| </para> |
| </section> |
| <section id="inspector.properties"> |
| <title>Properties View</title> |
| <figure> |
| <title>Properties View</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/Inspector-Properties.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The properties view shows persistant properties stored by the page (or any components on |
| the page). Most pages do not store any persistent state (it is more often stored |
| in the application's visit object). |
| </para> |
| </section> |
| <section id="inspector.engine"> |
| <title>Engine View</title> |
| <figure> |
| <title>Engine View</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/Inspector-Engine.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The engine view shows information about the running application engine, as well as some details |
| from the application specification. |
| </para> |
| <para> |
| Under Operations are two buttons: the first restarts the application. The |
| second (when enabled |
| <footnote> |
| <para> |
| By default, the reset service (used by the reset button) is disabled. |
| To enable it, set the JVM system property |
| <varname>org.apache.tapestry.enable-reset-service</varname> to true. |
| The service is disabled since it is too tempting a target for a denial |
| of service attack. |
| </para> |
| </footnote>) resets the application, which forces a reload of all component specifications |
| and HTML templates. This is useful during development, since it allows for incremental development |
| without stopping and restarting the servlet container. |
| </para> |
| <para> |
| Below the operations is a binary dump of the application engine. This is useful when |
| developing to see how large the serialized state is, and perhaps gleam how it might be trimmed. |
| </para> |
| <para> |
| Further below (and not visible in the screen shot above), is a dump of the request context. This |
| is that vast amount of data also displayed when an unexpected exception is thrown. |
| </para> |
| </section> |
| <section id="inspector.logging"> |
| <title>Logging View</title> |
| <figure> |
| <title>Logging View (Level Selection)</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/Inspector-Logging.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The Logging view allows dynamic integration with the |
| <ulink url="http://jakarta.apache.org/log4j">Log4J</ulink> logging framework. The top half |
| of the page allows the logging level of any category to be |
| dynamically set. This is useful when debugging, since logging output for specific |
| classes |
| <footnote> |
| <para> |
| By convention, logging categories match the complete class name |
| of the corresponding class. All Tapestry logging categories |
| conform to this convention. |
| </para> |
| </footnote> |
| can be individually enabled or disable. |
| </para> |
| <para> |
| The right side is a small second form, allowing new categories to be created. |
| This can be useful to make broad changes in logging levels. For instance, creating |
| a category "org.apache.tapestry" would allow the logging level of all Tapestry classes to be |
| set in a single place. |
| </para> |
| </section> |
| </chapter> |
| <chapter id="workbench"> |
| <title>Tapestry Workbench</title> |
| <para> |
| The Tapestry tutorial includes an additional application, the Workbench, which is used to show off |
| interesting Tapestry components and features. |
| </para> |
| |
| <figure> |
| <title>Workbench</title> |
| |
| |
| <mediaobject> |
| <imageobject> |
| <imagedata format="JPEG" fileref="images/workbench-home.jpg"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| <para> |
| The Workbench is divided into several areas, as shown by the tabs across the top of the page. |
| Over time, the Workbench and this tutorial document will expand together, and the number of tabs will |
| increase. |
| </para> |
| <para> |
| In addition to the Inspector, the Workbench has a useful feature which can be activated using the checkbox at the bottom. |
| When enabled, the complete (and verbose) information available about the request, session and context (normally |
| displayed by the Inspector's engine view) is shown at the bottom of each page. |
| </para> |
| |
| <figure> |
| <title>Workbench (Showing Requests)</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata format="JPEG" fileref="images/workbench-home-expanded.jpg"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| <para> |
| This feature can be very useful if you are interested in exactly how Tapestry forms and links work. |
| </para> |
| </chapter> |
| <chapter id="local"> |
| <title>Localization</title> |
| <para> |
| One of the most powerful and useful features of the Tapestry framework is the way in which it |
| assists with localization of a web application. This is normally an ugly area in web applications, |
| with tremendous amounts of ad-hoc coding necessary. |
| </para> |
| <para> |
| Because Tapestry does such a strong job of seperating the presentation of a component (its |
| HTML template) from its control logic (its specification and Java class) it becomes easy for it to |
| perform localization automatically. It's as simple as providing additional localized HTML |
| templates for the component, and letting the framework select the proper one. |
| </para> |
| <para> |
| However, the static text of an application, provided by the HTML templates, is not all. |
| </para> |
| <para> |
| Applications also have assets (images, stylesheets and the like) that must also be localized: that |
| fancy button labeled "Search" is fine for your English clients, but your |
| French clients will require |
| a similar button labeled "Recherche". |
| </para> |
| <para> |
| Again, the framework assists, because it can look for localized versions of the assets as it runs. |
| </para> |
| <para> |
| The locale application demostrates this. It is a very simply application that demonstrates changing |
| the locale of a running application |
| <footnote> |
| <para> |
| All the translations were performed using |
| <ulink url="http://world.altavista.com/">Babelfish</ulink>, and are probably quite laughable to |
| someone who actually speaks the alternate |
| languages. |
| </para> |
| </footnote> |
| </para> |
| <para> |
| A demonstration of localization is built into the Workbench, under the <acronym>L10N</acronym> |
| <footnote> |
| <para> |
| The "10" refers to the number of letters between 'l' and 'n' in the word 'localization' |
| </para> |
| </footnote> tab. |
| The page allows the user to select a new language for the application: |
| </para> |
| <figure> |
| <title>L10N Page (English)</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/localize-home-english.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Selecting "German" from the list and clicking the "Change" button brings you to a new page that |
| acknowledges your selection: |
| </para> |
| <figure> |
| <title>Locale Changed (German)</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/localize-changed-german.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| Clicking the button (it's labeled "Return" in German) returns you to the L10N page to |
| select a new language: |
| </para> |
| <figure> |
| <title>L10N Page (German)</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/localize-home-german.jpg" format="JPEG"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The neat thing here is that the <classname>L10N</classname> page has been localized into |
| German as well; it shows |
| equivalent German text, the options in the popup list are in German, and the "Change" button |
| has been replaced with a German equivalent. |
| </para> |
| |
| <section id="locale.template"> |
| <title>Localization of HTML Templates</title> |
| |
| <para> |
| Localization of HTML templates ia automatic. When Tapestry reads a template, it looks for a localized version of it. |
| In this example, in addition to the English language <filename>Localization.html</filename>, three additional files were created: |
| <filename>Localization_de.html</filename>, <filename>Localization_fr.html</filename> and <filename>Localization_it.html</filename>. |
| </para> |
| |
| <para> |
| Tapestry tracks the locale for each user using either an HTTP Cookie, or the &HttpSession;. It makes sure that all templates for all components |
| on the page use the best available template; it does a standard search. |
| </para> |
| |
| </section> |
| |
| <section id="locale.assets"> |
| <title>Localization of Assets</title> |
| |
| <para> |
| In the L10N pages, there are images that are also localized. |
| Tapestry has a hand in this as well. As with HTML templates, Tapestry |
| searches for matches based on the user's locale. |
| |
| </para> |
| |
| <para> |
| Both context assets (assets that are part of the WAR) and private assets (assets that are stored in Java frameworks) can be localized. This is demonstrated |
| on the L10N page: the "Change" button is a private asset; the "Back" button is a context asset. |
| |
| </para> |
| |
| </section> |
| |
| <section id="locale.other-options"> |
| <title>Other Options for Localization</title> |
| <para> |
| In some cases, different localizations of the a component will be very similar, perhaps having only |
| one or two small snippets of text that is different. |
| In those cases, it may be easier on the developer to not localize the HTML template, but to |
| replace the variant text with an |
| &Insert; component. |
| </para> |
| <para> |
| The page can read a localized strings file (a <filename>.properties</filename> file) to get |
| appropriate localized text. This |
| saves the bother of maintaining multiple HTML templates. This is the same approach taken |
| by the Apache Struts framework. |
| </para> |
| <para> |
| All components on a page share the single locale for the page, but each performs its own search |
| for its HTML template. This means that some components may not have to be localized, if they |
| never contain any static HTML text. This is sometimes the case for reusable components, even |
| navigational borders. |
| </para> |
| </section> |
| </chapter> |
| <chapter id="summary"> |
| <title>Further Study</title> |
| <para> |
| The preceding chapters cover many of the basic aspects of Tapestry. You should be comfortable |
| with basic Tapestry concepts: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <para>Seperation of presentation, business and control logic |
| </para> |
| </listitem> |
| <listitem> |
| <para>Use of JavaBeans properties as the source of dynamic data |
| </para> |
| </listitem> |
| <listitem> |
| <para>How bindings access JavaBeans properties to provide data to components |
| </para> |
| </listitem> |
| <listitem> |
| <para>How components wrap each other, allowing for the creation of very complicated |
| components through aggregation |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Different types of page properties (transient, dynamic, persistent) |
| </para> |
| </listitem> |
| </itemizedlist> |
| <para> |
| Tapestry is capable of quite a bit more. Also available within the Tapestry Examples package |
| (along with the tutorial code and this document) is the Virtual Library application (Vlib). |
| </para> |
| <para> |
| Vlib is a full-blown J2EE application, that makes use of Tapestry as its front end, and a set of |
| session and entity Enterprise JavaBeans as its back end. |
| </para> |
| <para> |
| Vlib also demonstrates some of the other aspects of developing a Tapestry application. It shows |
| how to create pages that are bookmarkable (meaning that their URL includes enough information |
| to reconstruct them in a subsequent session). It shows how to handle logging in to an application, |
| and how to protect pages from being accessed until the user is logged in. It has many specialized |
| reusable components for creating links to pages about books and people. |
| </para> |
| </chapter> |
| </book> |