ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Tapestry: A Component-Centric Framework
Pages: 1, 2

Tapestry and MVC

Model-View-Controller (MVC) is a design paradigm based on separating the user interface of an application from its domain logic. Tapestry's MVC Model 2 implementation is one of the most pure implementations of the pattern.



  • Model: The model could consist of domain objects and/or a database.
  • View: In Tapestry, the view is the simple HTML file with special markers that define the use of components.
  • Controller: The controller here is a XML page Specification (.page file). A Page object (which implements the org.apache.tapestry.IPage interface) also assists the page specification file as the controller.

An Example

The simplest way to understand the Tapestry framework is to see it in action. The following is an example that demonstrates how easily applications can be developed using this framework.

By using an example of an online bank, a user can view his balance, place an order for a check book, or edit his profile. Like every application there is a home page called Home.html.

<html>
  <head>
    <title>An Online Bank </title>
  </head>
  <body>
    <h1> Welcome to MyBank>/h1>    
    <h2>Please select action:>/h2>
    <tr>
      <td> 
      <a href="" jwcid="viewBalanceLink">View Balance</a> </td>
    .
    .
    .
    </tr>
  </body>
</html>

Notice how this is simple HTML, barring one element: jwcid, which means Java web component ID and is unique to Tapestry. As mentioned earlier, every component in the Tapestry framework has its own ID. When this HTML page is being rendered, a component is created and identified by this jwcid. So, how will Tapestry determine which type of component to create? This information has to be specified in the Home.page file. Here is a snippet from Home.page:

<page-specification class="com.mybank.Home">
    <component id="viewBalanceLink" type="DirectLink">
.
.
.
</page-specification>

In this case, a component of the type DirectLink, identified by jwcid viewBalanceLink, will be created. Behind the scenes, before the page is rendered, Tapestry creates a page object of the type org.apache.tapestry.html.BasePage. The second step is to create all the components described in the .page file and associate them with the Page object. Then the Page reads the HTML file for information to render the page. When it encounters the component with the ID viewBalanceLink, the control is transferred to this object for rending the link. This component will output the resultant HTML link to the page to be rendered. In this entire process, the HTML page is used only for reference and is called the template. Here is the code snippet from Home.java:

public abstract class Home extends BasePage{
    
    public abstract AccountBalance getAccountBalancePage();
    .
    .
    .
}

The page is rendered successfully.

But why is the method abstract and where is the implementation? Well, Tapestry does that bit of work for you. When Tapestry comes across an unimplemented getter method, it creates a property for it.

So, what happens when a user clicks on this link? We've put the following code in Home.page:

<component id="viewBalanceLink" type="DirectLink">
  <binding name="listener" value="listener:onClickViewBalanceLink"/>
</component>

Here we can see a new expression: listener. When a user clicks the link, the listener that has a binding with that component will be called by Tapestry. So, a listener method has to be defined in the page's class:

public IPage onClickViewBalanceLink() {
        //do something to give a Account Balance Page
}

The effect of clicking the link in this scenario is to display the account balance page. So, we have a AccountBalance.html, AccountBalance.page, and the Page object class AccountBalance.java. But how do we define a relation between the home page and AccountBalance page? Have a look at this:

public abstract class Home extends BasePage{
    .
    .
    @InjectPage("AccountBalance")
    public abstract AccountBalance getAccountBalancePage();
    
    
    public AccountBalance onClickViewBalanceLink() {
        return getAccountBalancePage();
    .
        .
}

The most interesting change that we have made here is the annotation: @InjectPage. Annotations are a new feature of J2SE 5.0 (Tiger) and are used to provide metadata. Annotations take the form of an at sign (@), followed by the annotation name. You thwn supply data to the annotation--when data is required--in name=value pairs.

Using @InjectPage annotation, we have injected the AccountBalance page into our component. Now this page is available for our listener method to change its value, interact with a backend system or simply display the page named "AccountBalance" as the response after this listener method returns. And this is exactly what we have done!

On the account balance page, the balance is displayed using the tag:

<h2>Your account balance is: $<span jwcid="accountBalance"> </span> </h2>

Needless to say, that the accountBalance is an attribute of the AccountBalance.java class. The same result can be displayed if we include the following tag instead:

<h2>Your account balance is: 
    $<span jwcid="@Insert" value="ognl:accountBalance"> </span>
</h2>

Note that there is an Object Graph Navigational Language (OGNL) expression in the value. OGNL is a powerful open source expression language used to get and set properties of Java objects. When the Tapestry comes across ognl: it knows that what follows it is an OGNL expression and not a string constant.

Validation and Error Reporting

Consider a scenario in our banking application in which the user wants to edit his profile. On the "Edit Profile" page there are certain mandatory fields such as name and date of birth. These validations can be done with minimal coding in Tapestry. In the banking application, we have a HTML file for updating personal details (PersonalDetails.html), an XML template (PersonalDetails.page), and the Page Object class(PersonalDetails.java).

Starting by modifying the Page Object class, we add an ValidationDelegate attribute type. This attribute holds the list of error messages, in case the validation fails. But how does Tapestry know what to validate? Here's how: we update the page specification (PersonalDetails.page) to inform Tapestry that the component name is mandatory. Also, we bind the ValidationDelegate to the Form (personalDetailsForm) to record any validation errors in its components.

<component id="personalDetailsForm" type="Form">
        <binding name="delegate" value="beans.delegate"/>
            .
            .
</component>
    .
    .
        <component id="name" type="TextField">
    .
    .
    <binding name="validators" value="validators:required"/>
    .
    .
</component>

Further, we need to have a component to render the error message, after the validation fails. This object records the error and is stored in the ValidationDelegate object.

<component id="errormsg" type="Delegator">
    <binding name="delegate" value="currentFieldTracking.errorRenderer"/>
</component>

This way, no code needs to be present in the Page object class for this validation; although for customized validations we can introduce that part in the onSubmit() of the Page class.

Lastly, on the HTML page, following changes need to be made to display the recorded error messages:

<span jwcid="errormsgs">
    <span jwcid="errorOccured"><li><span jwcid="errormsg"/></li></span>
</span>

Conclusion

This article shows how simple it is to develop a web application using Tapestry. Tapestry adopts a component approach to web development, making it possible to move all the boring plumbing code out of the application and into the framework. Applications developed using the Tapestry framework naturally adapt to the Tapestry philosophy of simplicity, consistency, efficiency, and feedback.

Resources

Hemangini Kappla currently works for Mphasis, an EDS company in Bombay, India.


Return to ONJava.com.