Apache Tapestry and Listener Methods, Conditional Components and PageLink

Today we continue to explore some of the most fundamental concepts of Tapestry while building one of the simplest Java Web applications. In the previous part of this tutorial we became familiar with properties of Tapestry pages and different ways to configure them. Now we’ll look into the details of writing listener methods.


A downloadable zip file is available for this article.

The simplest approach

We have already used one listener method in our GuessTheWord application. Let’s remind ourselves how we did that.

In the Home.html template we created a form:

<form action="" jwcid="secretWordForm">

… some content …

</form>

Then in the Home.page specification we told Tapestry what to do when the form is submitted:

<component id="secretWordForm" type="Form">

   <binding name="listener" value="listener:onWordSubmit"/>

</component>

Which basically means that we want the onWordSubmit() listener method of the Home class to be invoked. And here is the method itself:

public String onWordSubmit() {

       return "Secret";

}

Right now, the listener method is very simple. It does not perform any processing and simply returns a String. Tapestry understands this as “Show the page with this name, please!” and displays the Secret page.

But the listener method could be even simpler than this, for example:

public void onWordSubmit() {

   // Do something

}

This version returns nothing – so the same page will be redisplayed. But if we put some code inside of the method, it will be executed, of course.

More often however we want some other page to be shown, and not only that, but we want to somehow prepare the next page before showing it.

{mospagebreak title=Passing information to the next page}

In our GuessTheWord project we need to somehow pass the secret word submitted by the user to the Secret page, where the word will be checked, and depending on the result, an appropriate part of the page will be shown.

The Home page class has a property to store the submitted word. Before passing the word to the Secret page, we need to configure a similar property in that page too.

As you already know, to have a property configured automatically by Tapestry, all we need to provide is an abstract getter method. So let’s add to the Secret class this line of code:

public abstract String getTheWord();

However, as you will see soon, we are going to need a setter method too, as we are going to set the value for this property from the Home page. Nothing can be easier; let’s just add an abstract setter too, so that the Secret class will contain the following code:

public abstract class Secret extends BasePage {

   

   public abstract String getTheWord();

   public abstract void setTheWord();

   

}

Now we can return to our listener method in the Home class. First of all, we need to somehow obtain in it a reference to the Secret page; then we are going to use this reference to pass the value of the secret word. But how can we get a reference to a different page class? IRequestCycle will help us.

To every user, Tapestry assigns a kind of majordomo – an object that carries the burden of the application’s housekeeping. All that we need to know about this object as Tapestry developers is that it implements the IRequestCycle interface with a number of useful methods in it. In particular, it can give us a reference to any page of our application.

To tell Tapestry that we are going to need help from the majordomo, we simply pass an implementation of IRequestCycle as a parameter of our listener method:

… onWordSubmit(IRequestCycle cycle) {

   …

}

And then we use the getPage() method to obtain a reference to the desired page:

Secret nextPage = (Secret) cycle.getPage(“Secret”);

The getPage() method has no idea of the type our Secret page class, so it returns a generic Tapestry page (technically, an implementation of IPage interface, and this interface is always implemented by any Tapestry page). But we know that the page named “Secret” has a class named Secret for its page class, so we can safely cast the returned result to what we expect it to be.

The next step is to pass the value of the secret word to the Secret page:

nextPage.setTheWord(getTheWord());

Or you can write it like this for clarity:

nextPage.setTheWord(this.getTheWord());

Finally, we need to ask Tapestry to show the next page. The simplest way to do this is to return the reference to the page we have just used to set the value. Technically, we return an IPage which simply means “some Tapestry page.” Here is what our completed listener method will look like:

public IPage onWordSubmit(IRequestCycle cycle) {

 

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

   nextPage.setTheWord(getTheWord());

  

   return nextPage;

 

}

Run the application, and it should work exactly as before, i.e. both views are displayed by the Secret page simultaneously: the one for a wrong word and the one for the correct word. Now it is the time to complete the application and to meet a few new components while doing this.

There is yet another kind of listener in Tapestry, the one that receives a user defined parameter, but let me delay its discussion until we come to the DirectLink component in one of the later parts of the tutorial.

{mospagebreak title=Conditional components}

We want to show the hidden truth only if the secret word was guessed properly. Wouldn’t it be natural to use the Tapestry’s If component for this purpose? The If component has one binding, the condition to evaluate, but since our Secret page is very simple in its design, I think it will be okay to define this component implicitly, like so:

<div jwcid=”@If” condition=”ognl:theWord == ‘abrakadabra’”>

   <!—Stuff to show if everything is OK –/>

</div>

Previously, we have used a span HTML element as a placeholder for our Tapestry component. Now we use div for the same purpose, as it is more logical to surround a block of markup with a div. The If component will simply show or not show the contents of the div depending on whether its condition evaluates to true or to false.

We have used the ognl prefix to specify that the value for the condition attribute should be treated as an OGNL expression. If we forget to do this (which I do quite often) the value will be treated verbatim and then anything non-empty will be understood as true.

Then we are comparing the word that was passed from the Home page with the hard-coded word “abrakadabra” (you can use any other word of course!). The word to compare with is put into single quotes as double quotes are already used to surround the value of the condition attribute. Of course, hard-coding anything like this would not be a good idea in any real-life application, but we’ll leave it just like that for now, for simplicity’s sake.

So, when the user enters the magical word “abrakadabra” at the Home page and presses the Submit button, the hidden truth is displayed by the Secret page, but if the user enters anything else… The Tapestry’s Else component will take care of displaying the alternative content.

The Else component is very simple, it doesn’t require any bindings, and so it is natural to define it implicitly, like so:

<div jwcid=”@Else”>

   <!– An alternative content goes here –>

</div>

Of course, to work properly, an Else component has to follow an If component. Now the HTML template for the Secret page should look like this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

 

<html>

  <head>

    <title>Guess Result</title>

  </head>

  <body>

      <div jwcid="@If" condition="ognl:theWord == ‘abrakadabra’">

          <!– This will be shown if the guess was successful –>

          <h2>Congratulations!</h2>

          <p>You have guessed the secret word properly,

          and here is the hidden wisdom:</p>

          <p><i>Lorem ipsum dolor sit amet, consectetur

                  adipisicing elit, sed do eiusmod tempor

          incididunt ut labore et dolore magna aliqua.</i></p>    

      </div>

      <div jwcid="@Else">

          <!– And this will be shown if case of failure –>

          <h2>This was a wrong word</h2>

          <p><a rel="nofollow" target="_blank" href = "">Go back and try again.</a></p>

      </div>

  </body>

</html>

Run the application, and it should work properly: if you submit the correct word, the hidden truth is shown, otherwise you are prompted to try again.

{mospagebreak title=PageLink component}

Notice that if you click the “Go back and try again” link, the application reacts properly: it shows the Home page again. But this is more of a coincidence than a proper solution. We left the href attribute empty; this creates a link to the application’s context, and trying to navigate to the context, we land at the default Home page. But in some other case we might wish to go not to the Home page, but to some other page. In that case such a “bare” link would be worthless; we need to use an appropriate Tapestry component.

Let’s define this component implicitly again:

<a rel="nofollow" target="_blank" href = "" jwcid="@PageLink" page="Home">Go back and try again.</a>

The application will continue to work as it did before, but now we have done everything properly, and we can easily redirect the link to some other page by simply changing the name in the page attribute.

As we have defined all three components of the Secret page implicitly, its page specification remains empty, but that’s okay. Later, we might decide to add more components to this page or change its design so that it will make sense to move some components’ definitions to the page specification.

We can congratulate ourselves as we have completed the GuessTheWord application. I didn’t want to delay this glorious moment anymore, so let me leave the discussion of two ways of handling form submission until one of the upcoming parts of the tutorial.

For now, let’s see how to make our completed application available to the outside world, as we have created it not just to play with it on our own computer.

{mospagebreak title=Deploying the application}

Most Web applications are created to be deployed on the Web, and this is exactly what we are going to do with GuessTheWord.

First of all, we need to find a hosting provider. Fortunately, there are providers who make Java Web hosting available for free. Actually, I know just one of them – Eatj.com (if you know any other Java Web hosting provider who offers a free plan, please leave a note at the discussions page).

It is very easy to open a trial account with Eatj.com. Visit their website, and right on the home page you will see the “CREATE NEW ACCOUNT” form. Enter some username, and an email address where you will want to receive a message with information and choose a password. Read terms and conditions, then press the Register button.

Very soon you will receive an email with a link in it. You need to click that link to confirm your registration. In a minute or so you’ll receive another email with some useful links in it. You will find their “Getting Started” instructions and FAQ, but you can read all that later. Right now, simply go back to Eatj.com and log in with your selected username and password.

You will find yourself at the “My Account” page:

Note the “UPLOAD WAR FILES” header. This is exactly how we are going to deploy our application – as a WAR (which means “Web archive”) file. We don’t need to do anything special to create a WAR file because such a file is created automatically by NetBeans (and then deployed to its bundled Tomcat) every time we run our application in NetBeans. All we need to do is locate where this file is stored in our file system.

In the Files view of NetBeans, expand the GuessTheWord node and the dist subfolder. You will see the GuessTheWord.war file in it:

 

On your computer, this file is located in the folder you have chosen for NetBeans projects, in the GuessTheWord/dist subfolder.

Now, press the Choose File button on the My Account page of Eatj.com. Navigate to the GuessTheWord.war file wherever you have it on your computer and choose it. Then press the Upload button.

The upload itself will take quite some time despite the fact that the application is small. This is because the package contains all Tapestry libraries in it – a few megabytes of goodies. If you had your own server, or at least you had control over which libraries are available to your hosting provider’s Tomcat, you could upload only the application itself, without libraries, but this is not the case with Eatj.com. You can’t have everything for free!

When the upload completes, you should see the message: “Upload successful. Please restart your server.” When you restart Tomcat, it will pick up the WAR file you’ve just uploaded and deploy it properly. Under the “SERVER MAINTENANCE” header you will see the “RESTART” link – click on it and wait.

Finally, you should see a message stating that your server was restarted and giving you a link to your website. I have chosen username tttest, so my message looks like this:

Your server (tttest) has been restarted.

Click the link to see your home page:

http://tttest.s42.eatj.com

However, if you follow the provided link, you will see the default page of the Tomcat Web server with a funny cat picture on it, but not the application we have just uploaded. To see the application itself, we need to add the /GuessTheWord context path to the link. In my case, the URL will be http://tttest.s42.eatj.com/GuessTheWord. Navigate to your application’s URL and – hurray! – you will see it working.

Isn’t it incredible – we have just created a Tapestry application, and it is already working on the Internet! You can invite your friends and relatives to see what a cool Java Web developer you are. However, in a few hours they will tell you that the application doesn’t respond anymore. This is because on trial accounts Eatj shuts down Tomcats four times a day (see their FAQ for details). But you can always log in, restart Tomcat, and everything will be working again.

What comes next

We shall continue our study of Tapestry components, gradually passing from the most simple to slightly more complicated. In the next part, I am going to show you two very useful components: For and DirectLink. We are also going to create a new application to play with them.

[gp-comments width="770" linklove="off" ]

chat