About the Resultant Project

What you have in front of you is a simple Tapestry application with some simple examples of all the major players in a typical Tapestry application.

Pages

A page is the basic unit of work for a tapestry application. Its a screen, not an action. When you design your page, you think in the scope of a screen. Typically a page will encompass just an action, but it doesn't need to. An example: a user login sequence. You hit the page, it shows a login and a password field and a submit button. You hit submit, it performs validation, and returns with a congratulating message and your login information (perhaps it sends an email). Or, it may explain whats wrong with the input. Below the form are links to register or have an email sent to you. Clicking on them merely refreshes the page with a new form in place allowing you to register. Ditto with the forget password form. Naturally, you might do well to factor each one of these scenarios out into a different page, but you dont have to. They can all be in the scope of one page.

In a working Tapestry application, the main page is the page named Home.html. In the project, all pages are looked for in $groupId/$artifactId/pages, so in the project it will be org.example.myapp.pages.Home. All pages have a corresponding html file. The html file is located at the root of the web context. In the project, this corresponds to the webapp folder. Thus, webapp/Home.html corresponds to org.example.mapp.pages.Home. You can correctly infer that a sub-package would be nested one directory in the webapp folder. Thus, org.example.myapp.pages.users.UserHome corresponds to webapp/users/UserHome.html.

At this point, you've gained over a Struts action or a CGI if only because it's simpler working with objects and listeners than URLs, but there's more.

Components

The real gains come from components. Components are the most fun. Even if you never leverage the framework's infrastructure to build your own components, you can leverage the components that already exist. In the markup of your page, <span jwcid="@Insert" value= "literal:Hello, world!"> creates an instance of the Insert component, for example. In our example project, we have a simple component, Echo, defined. The java class for these components is going to be under the $groupId/$artifactId/components folder, or, in this case org.example.mapp.components.Echo. The html for a component is found in the WEB-INF folder. Thus, WEB-INF/Echo.html corresponds to org.example.mapp.components.Echo.

Using the resultant component is as simple as inserting the following in the markup of another component or page. The Echo component has one parameter (it's the only accessor on the object, and it's annotated with the @Parameter annotation. It has an accessor for getValue(), thus, the parameter is simply value in HTML.

<span jwcid = "@Echo" value= "This is to be echoed!"/>

Note: were the component defined in a subfolder (webapp/subfolder/Echo.html) and package (org.example.myapp.components.subfolder.Echo), then using it would change slightly to include the subfolder:

<span jwcid = "@subfolder/Echo" value= "This is to be echoed!"/>

For More

  • The 360 on creating components. This is an invaluable resource, however you will want to also take away that, for a component class extending BaseComponent, you may have a corresponding .html file and that will be rendered instead. You don't have to use renderComponent for everything.

Engine Services

An engine service is Tapestry's window on a lower level http request/response cycle. You are a few steps above raw Servlets, in that you're working with Tapestry and Tapestry knows how to plug engine services in, whereas it doesn't have any particular connectin with a servlet. An engine service, put another way, is where you'd handle things that are either systemic to a lot of pages/components/the application, or you need to produce something Tapestry itself doesn't produce: a graph, or an image, for example. They are often created in conjunction with a component. For example, in the resultant project there is a component called widgets/RoundedCorner. Using it, you can specify

  <img src="#" corner = "SE" alt="" jwcid="@widgets/RoundedCorner" width = "12" />

and it'll in turn produce an image of a rounded corner to your specifications, using Java2D to draw the images dynamically. The component included is lacking the polish of a feature-complete component (caching of images, for example.), but it demonstrates the fundamentals. The component takes parameters, the parameters are tunneled the to the engine service, and the engine service draws the response using Java2D and gives the component a URL that it may use to render the resultant image (whcih it in turn uses as the src parameter of an img tag. This cooperation makes engine services your freind.

For More

  • The Tapestry Workbench Application:The tapestry workbench features a "Chart" example. The chart example uses jCharts, and is rendered as an image. This is a fine example of the cooperation between Tapestry and the component used for charting, and an engine service. The Tapestry source code (available from SVN, or download) typically has these "examples" included, and you may find the source for the workbench there.
  • It can not be overstated: the Tapestry framework itself relies on engine services for the core components! The DirectLink uses an engine service to coordinate it's magic and invoke listeners and the @Image component uses the asset service to furnish context assets, for example.

Application State Objects (ASO)

An application state object is useful for storing objects at different scopes broader than a single page or a request of a single page. They correspond to traditional session objects, and servlet context objects. You can get access to an application state object at any time by "injecting" it into your client code (your pages or components). If there already isn't an ASO of the type and scope you want, Tapestry creates it for you. For example, say that you want to log a person in, and then store that authenticated User object in session, in an object designed for keeping track of the currently logged in user. You would access an ASO, set the User object on it, and move on

In the example project, we have two application state objects, one called Global, and another called Visit. In our application, we've created these two application scope objects, but we also wanted them to have access to beans defined in our Spring context. We plugged in a custom application state factory. Everything is pretty usefully setup, but overridable. Our custom factory is responsible for dressing up the ASO before any client code gets to use it, which is perfect for passing Spring context objects. In this case, we configure the factory to have the beans provided, and we transfer these beans to the ASO inside the factory. The configuration between the two collaborating objects can be seen in src/webapp/WEB-INF/hivemodule.xml:

 ...
<!--
 This creates the factory object that in turn
 creates the Application State Object
-->
<service-point id="visitFactory" interface="org.example.myapp.application.aso.VisitFactory">
 <invoke-factory>
  <construct class="org.example.myapp.application.aso.VisitFactory">
   <set-object property="utilities" value="spring:utilities"/>
  </construct>
 </invoke-factory>
</service-point>
 ...

The element below is the registry of applicaton state objects. You may have as many application state objects as you'd like defined. If you don't want to have Spring beans injected into the Application State object, then the configuration shown in comments in the configuration file apply.

<contribution configuration-id="tapestry.state.ApplicationObjects">
 ...
 <state-object name="visit"  scope="session">
  <invoke-factory object = "service:visitFactory"/>
 </state-object>
 ...
</contribution>

From there, you may use the ASO's any time you want in a component or page by telling Tapestry you want a reference to one. Tapestry takes care of the rest.

      @InjectState("visit")  
      public abstract Visit getVisit() ;

      public void authenticate (IRequestCycle cycle )
      { 
        User usr=getVisit().getUser();
        if( usr!=null  && usr.isAdministrator())
        { 
         // then do something meaningful  
        }
      }
   

It's important to remember that Tapestry will create the object if it doesn't already exist, which sometimes means that a session will be created, even if you didn't intend for it be. Tapestry provides a meaningful way of determining whether the object exists without actually accessing (and thus triggering its creation) the ASO: the @InjectStateFlag annotation. The above may be rewritten more efficiently as:

      @InjectStateFlag( "visit") 
      abstract public boolean isVisitCreated() ;

      @InjectState( "visit")  
      public abstract Visit getVisit() ;
      
      
      public void authenticate (IRequestCycle cycle )
      { 
           if(isVisitCreated() )// that is to say, theres already an 
                                // object in place, so you don't have to worry 
           {    
                User usr=getVisit().getUser();
                if( usr!=null  && usr.isAdministrator())
                { 
                        // then do something meaningful  
                }
           }
            
      }

For More