Article

Step-by-Step Jakarta Tapestry

Page: 1 2 3 4

Creating the Second Page

Let's name the second page Content, as though it contained something valuable requiring user authentication.

Right-click the Package Explorer, choose New > Other... , and locate and select the Tapestry Page option. Press Next.

In the New Tapestry Page Component dialog which opens, enter Content for the Page Name. Make sure that the Generate an associated HTML file? checkbox is checked. Leave all other values to their default settings and press Next.

Here you can choose which class to use as a page class. We're going to create some custom functionality, so let's make our own class. Select the Create a new class radio button. Specify the package to be com.sitepoint.tapestry.login, and make the class public and abstract (yes, we are going to use those shadowy Tapestry properties again). Your dialog should look like Figure 14.

1508_figure14
Figure 14. Creating a New Page with a Custom Page Class

Press Finish and Spindle will create three new files for you: Content.html, Content.page and Content.java.

The default HTML template for the second page doesn't contain anything useful, so we'll replace its contents with the mockup code from Page 2 in the previous section (titled "Our First Tapestry Web Application"). Modify this mockup into the real HTML template, which looks like this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"    
   "http://www.w3.org/TR/html4/strict.dtd">    
<html>    
<head>    
 <title>You've got it!</title>    
</head>    
<body>    
 <h2>Precious <span jwcid="uname">User</span>!</h2>    
 <h3>Welcome to the wonderful World of Tapestry!</h3>    
</body>    
</html>

You can see that, on the second page, we'll be using just one Tapestry component, which is named uname and disguised as a standard HTML span element. You're probably aware that the span element is often used to apply a special style to text. We won't be specifying any styles here, just our magical jwcid, which is incomprehensible and invisible to a web browser.

A Specification for the Second Page

We're going to need both the username and password in the code for the second page, so let's create the same two properties in the page specification (you can just copy them from Home.page):

<property-specification name="uname" type="java.lang.String"/>    
<property-specification name="password" type="java.lang.String"/>

Let's also specify the uname component mentioned in the HTML template. This time, it will be a standard Tapestry Insert component, which simply inserts a string into the outputted HTML. This is how the specification will look:

<component id="uname" type="Insert">    
 <binding name="value" expression="uname"/>        
</component>

And here's the completed page specification, which goes into the Content.page file:

<?xml version="1.0" encoding="UTF-8"?>    
<!DOCTYPE page-specification PUBLIC    
 "-//Apache Software Foundation//Tapestry Specification 3.0//EN"    
 "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">    
<!-- generated by Spindle, http://spindle.sourceforge.net -->    
   
<page-specification class="com.sitepoint.tapestry.login.Content">    
   
 <description>Content page</description>    
 <property-specification name="uname" type="java.lang.String"/>    
 <property-specification name="password"    
     type="java.lang.String"/>    
       
 <component id="uname" type="Insert">    
   <binding name="value" expression="uname"/>        
 </component>    
       
</page-specification>

All that's left is to fill in our custom page class with some functionality.

Adding Functionality to the Page Class

We want to be able not only to read the uname and password properties of the page, but to set them as well. That's why we'll create two pairs of abstract getters/setters for the page class. Remember from the first page that these methods will be abstract, because we didn't create the properties ourselves, but asked Tapestry to make them for us at run time.

With the addition of getters and setters, our Content.java will look like this:

package com.sitepoint.tapestry.login;    
   
import org.apache.tapestry.html.BasePage;    
   
public abstract class Content extends BasePage {    
 public abstract String getUname();    
 public abstract String getPassword();    
 public abstract void setUname(String uname);    
 public abstract void setPassword(String password);    
}

It's now time to return to the first page, Login, and add functionality to its page class so that when the form is submitted, the second page (that we named Content) is shown.

Navigating From Page to Page

Our listener method onFormSubmit() is still empty. Let's add some code to it to display the second page. This can be done quite simply: we'll just ask Tapestry to show another page.

How do we ask Tapestry to do something for us from our code? We've got its representative, cycle (of type IRequestCycle), which is passed as a parameter to our listener method. So let's just ask this cycle object to do the job for us. This is what our listener method in Home.java looks like:

public void onFormSubmit(IRequestCycle cycle) {    
 cycle.activate("Content");    
}

Let's see if it works. At the moment, we need to keep restarting Tomcat every time we make a change, in order to see the most recent version of our application. This is definitely not the most convenient thing to do, and there are easier ways to achieve the same result. However, we've had enough distractions already, so I'll leave you to research the alternatives. For the moment, let's press on.

Shut down Tomcat if it was running, then start it up again. Point your browser to http://localhost:8080/Login/app and you'll see our login form. Push the Let me in! button and you should see the second page as shown on Fig. 15.

1508_figure15
Figure 15. The Second Page is Displayed Without a User Name

Notice that no username is output. Well, I didn't ask you to enter a login or password into the login form! But even if I had done so, we didn't tell the second page in our listener method what information we were receiving from the user. Let's correct this.

Passing Parameters to the Second Page

I'll now show you a design pattern that's used often in Tapestry development to pass variables to another page: the "bucket brigade" pattern.

We'll do the following:

  1. Ask Tapestry to give us a reference to the next page that we want to display.

  2. Use setters on that page to assign values that we want to pass to the page's properties.

  3. Ask Tapestry to display the next page.

At this point you should understand one important thing: although the users of our web application think of the pages they're viewing as HTML that's being sent to their browsers, in reality (and as far as Tapestry is concerned), each page is a Java class -- a page class -- such as Home or Content. This class has many talents; the ability to send an HTML response is just one of them.

Our next page is called Content. So, if we wanted to declare a reference to the next page, it would look like this:

Content nextPage;

We can tell Tapestry: "Please give us a reference to an instance of a page class for the page named Content". Translated into Java, it would look like this:

Content nextPage = (Content) cycle.getPage("Content");

After receiving the reference, we can use it to invoke any setters that we have prepared in that next page. For example, if we had a username and password received from a user stored in uname and password variables, we could write this:

nextPage.setUname(uname);    
nextPage.setPassword(password);

We'll then ask Tapestry to show the next page:

cycle.activate(nextPage);

Our completed listener method looks like this:

public void onFormSubmit(IRequestCycle cycle) {    
 Content nextPage = (Content) cycle.getPage("Content");    
 nextPage.setUname(getUname());    
 nextPage.setPassword(getPassword());    
 cycle.activate(nextPage);    
}

Note that in nextPage.setUname(getUname()); we're calling the abstract getter of our Home class and passing the returned value to the abstract setter of Content class. It looks strange: how can we use a method that has yet to be implemented? But it works perfectly well at run time, because Tapestry creates implementations of all the abstract methods for us.

Let's test the new version of our application. Shut down and start up your Tomcat server, and point your browser to http://localhost:8080/Login/app. When the login form appears, enter a user name and any password into it and press the Let me in! button. Depending on which user name you used, the result might look like Figure 16.

1508_figure16
Figure 16. The Second Page Displays as Expected

Limiting Access to a Page

Since we're requesting a user name and password, it would be natural to limit access to our valuable Content page, so that only authorised users can access it. For now, let's hard-code one password for all users.

Normally, we'd check the password in the Login page and, if it was correct, we'd send the visitor the hidden Content. But what if some unwanted visitor managed to request the Content page directly, without logging in? Technically, this is possible.

One of the solutions to this problem is to check the password in the Content page. If the password is correct, we display the contents of the page, otherwise we redirect the user to the Login page.

To be able to do this, our Content page needs to make an agreement with Tapestry. It should ask, "Please notify me when you are going to show me, so that I can check a few things." This kind of agreement can be achieved if the page implements the PageValidateListener interface. To implement this interface, we need to declare it and add a pageValidate() method to our page class. With these additions, our Content.java looks like this:

package com.sitepoint.tapestry.login;    
   
import org.apache.tapestry.html.BasePage;    
import org.apache.tapestry.event.PageValidateListener;    
import org.apache.tapestry.event.PageEvent;    
   
public abstract class Content extends BasePage    
   implements PageValidateListener {    
     
 public void pageValidate(PageEvent event) {    
   // Some code will go here    
 }    
     
 public abstract String getUname();    
 public abstract String getPassword();    
 public abstract void setUname(String uname);    
 public abstract void setPassword(String password);    
}

Notice the two new import statements. You don't have to know where to find the classes or interfaces that you need, and you don't need to write these statements by hand. Eclipse will do all that for you. For example, when you type PageEvent, Eclipse will underline it with red, because it doesn't know what you mean. Place your cursor somewhere on this word and choose Source > Add Import, and this smart IDE will create the necessary import statement for you!

Tapestry will check that the page implements the PageValidateListener interface, so it should have a pageValidate() method. Tapestry calls this method just before showing the page to a user, and it is up to us to define which checks should be done in the method.

We're going to check if the password specified by the user is the same as the one we've hard-coded. If not, we'll tell Tapestry: "Stop! Don't show this page! Redirect the user to the Home page instead." This is what it looks like in Java:

if (!"mellon".equals(getPassword())) {    
 throw new PageRedirectException("Home");    
}

If the password entered by the user is "mellon" (the Elvish word for "friend"), everything is fine: they'll see the Content page. If not, we switch on an alarm and tell Tapestry: "Forget whatever you were doing, just show our login form to this user."

This is how the completed Content.java should look:

package com.sitepoint.tapestry.login;    
   
import org.apache.tapestry.PageRedirectException;    
import org.apache.tapestry.html.BasePage;    
import org.apache.tapestry.event.PageValidateListener;    
import org.apache.tapestry.event.PageEvent;    
   
public abstract class Content extends BasePage implements PageValidateListener {    
     
 public void pageValidate(PageEvent event) {    
   if (!"mellon".equals(getPassword())) {    
     throw new PageRedirectException("Home");    
   }    
 }    
     
 public abstract String getUname();    
 public abstract String getPassword();    
 public abstract void setUname(String uname);    
 public abstract void setPassword(String password);    
   
}

Now let's test our completed login web application.

Start or restart Tomcat and navigate, as usual, to http://localhost:8080/Login/app. Enter any user name and a wrong password, and press the Let me in! button. Nothing should happen -- you'll just see the same login form. Now type the secret word for the password that we hard-coded above. You should be able to view the Content page!

What Have we Learnt?

First, we saw that Tapestry HTML templates can be displayed perfectly in any web browser, because Tapestry's server-side hooks are limited to the attributes of standard HTML tags, so they remain virtually invisible.

This is very important because the presentation (HTML) remains absolutely independent of the logic (Java code), and either of them can be edited without any changes to the other. This is the cleanest possible separation between presentation and logic -- something that other frameworks struggle with and often fail to achieve.

We also saw how convenient and useful the Eclipse IDE (with the Spindle plug-in) is when it comes to Tapestry development.

We got our hands dirty and created a couple of pages, and saw that every page has three parts to it: an HTML template, a page specification and a page class.

We learned that Tapestry components are declared in the page specification, and saw how the binding is established between a component's jwcid attribute in the HTML template and either an event listener or a property.

We also saw one of the trickier but more powerful aspects of Tapestry: how page properties can be declared in the page specification, and how we can work with them as if they really existed, even though they're only created by Tapestry at run time.

We learned how to write code to navigate from one page to another, and how to pass parameters between pages (the "bucket brigade" pattern).

Finally, we saw how a Tapestry page can protect itself from being accessed by unauthorised visitors, and how little needs to be done to implement this functionality.

Whew! We've mastered quite a lot for this introduction, but there is still much more to the world of Tapestry!

Further Reading

Currently there are only two books on Jakarta Tapestry available in English:

  1. "Tapestry in Action" by Howard Lewis Ship, the creator of Tapestry. This book is useful for gaining a deeper understanding of the ideas behind Tapestry, and details of the inner workings of Tapestry 3. However, it's not very useful as a tutorial for beginners.
  2. "Enjoying Web Development with Tapestry" by Ka lok 'Kent' Tong. I would call this book an advanced tutorial, and it is a must-have title for every Tapestry developer. It's packed with examples and advice starting from the basics and going on to some advanced topics. I found it invaluable when working on my own commercial Tapestry application. The book is available in two editions: for Tapestry 3 and for Tapestry 4.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links

Rate This Article

  • 1
    Poor
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
    Great

Comment on This Article

Have something to say?

Post A Comment

You need to be a member of the SitePoint Forums to comment on this post. Sign Up

Already a member? Post using your SitePoint Forums account: