Quantcast

Making a CelebrityCollector with Apache Tapestry: the For Component

(No Ratings Yet)
Loading ... Loading ...

If you're new here, you may want to subscribe to my RSS feed. So that you can read the latest updates about Web2.0 tools, Making Money Online, Tips in SEO, Ajax and many more. Thanks for visiting ProgramimiCOM!

We are going to start a new project today, named CelebrityCollector. At first it will be very simple, but in the following articles we’ll be adding more and more functionality to it. Of course, the purpose at this stage of study is not to build a real-world application but to meet different Tapestry components and to learn various important concepts.Creating the project

Create a new project as explained in the article “Creating Your First Tapestry Project” and name it CelebrityCollector. Don’t forget to add the Tapestry41 library and configure the Tapestry servlet properly.

Add to the project three empty pages: Home, CelebritiesList and Details (see the “Introducing Simple Components in Apache Tapestry” article for an explanation on how to add empty pages; this operation will be simplified significantly when we’ll adopt the NBTapestry module in one of the upcoming articles). Let the package containing page classes be com.devshed.tapestry.celebrities.

Let’s envision what we are going to have in the project so far. The Home page will only have one link (already familiar to you as the PageLink component). Upon clicking this link the user will see the CelebritiesList page.

The CelebritiesList page will display a table with a number of celebrities’ names in it. Each surname will be a link, and if the user clicks on it, the Details page with information on the selected celebrity will open.

As usual, let’s start from mock ups. Here is the Home page mock up and its HTML code:

image0011.gif

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

 

<html>

  <head>

    <title>Celebrity Collector: Home</title>

  </head>

  <body>

      <h1>Celebrity Collector</h1>

 

      <p><a href=”">List Celebrities</a></p>

 

  </body>

</html>

The CelebritiesList page mock up and its HTML code are shown below. The designer who worked on the mock up didn’t know which celebrities we are going to list, so he put some arbitrary names into the table, just to see how everything might look in the working application.

image0021.gif

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

 

<html>

  <head>

    <title>Celebrity Collector: List</title>

  </head>

  <body>

 

  <h2>Celebrities in Collection:</h2>

 

  <table width=”300″ cellpadding=”5″ border=”1″>

      <tr>

          <th>Last Name</th>

          <th>First Name</th>

      </tr>

      <tr>

          <td>

              <a href=”">Smith</a>

          </td>

          <td>John</td>

      </tr>

      <tr>

          <td>

              <a href=”">Smithson</a>

          </td>

          <td>Jane</td>

      </tr>

      <tr>

          <td>

              <a href=”">Swedenborg</a>

          </td>

          <td>Emmanuel</td>

      </tr>

  </table>

 

  </body>

</html>

Finally, here is some content for the Details page mock up:

 image0031.gif

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

 

<html>

  <head>

    <title>Celebrity Collector: John Smith</title>

  </head>

  <body>

 

  <h2>John Smith</h2>

 

  <table>

      <tr>

          <td><b>Birthday:</b></td>

          <td>01/01/1950</td>

      </tr>

      <tr>

          <td><b>Occupation:</b></td>

          <td>Actor</td>

      </tr>

  </table>

  <p><a href=”">Back to the list</a></p>

 

  </body>

</html>

Now, before going into the specifics of Tapestry development, we need to think about which Java classes we might need in order to manipulate information in our application – store it, transfer, etc. In other words, we need to design an object model. This is not Tapestry specific work but something we would do in any Java Web application.

Creating an object model

First of all, we need some class to represent a celebrity; it would be natural to name it Celebrity. For the first iteration of the project, this class will have only five properties:

  • A numeric ID (an int);

  • First name (a String);

  • Last name (a String);

  • Date of birth (a Date);

  • Occupation (a String).

We need to have this numeric ID because, well, theoretically speaking it is not against the laws of nature to have two celebrities named John Smith born on the same day who became famous in the same occupation. But even if that happened, we would have this ID as a way to distinguish between them. In a real life application, this could be a primary key in a database.

Create a new class named Celebrity (right-click the earlier created com.devshed.tapestry.celebrities package, New -> Java Class…) and add to it five private members:

private int id;

private String firstName;

private String lastName;

private Date dateOfBirth;

private String occupation;

Again, make use of this handy shortcut: after typing “Date” press Ctrl-Space and select java.util.Date class.

Then allow NetBeans to create getter and setter methods: right-click somewhere in your code and choose Refactor -> Encapsulate Fields… Make sure that everything is selected as the picture shows:

image004.jpg

Press Next, and NetBeans will create an overview of the changes it is going to make and show it in the Refactoring view below the code editor. Just press Do Refactoring there, and you will see getters and setters added to the Celebrity class.

Now, we need to have a source of data that will give us a number of Celebrity objects. In a real life application, this would be a database, but for now let’s leave everything as simple as possible and use a mock data source – a simple Java class that creates a List of Celebrity objects and provides methods to retrieve either the whole list or just one specified object. Later, we might replace the mock data source with a real one.

Let’s add a DataSource class to the existing Java package. There is nothing Tapestry-specific or terribly clever in this class, so you can simply copy the code:

package com.devshed.tapestry.celebrities;

 

import java.util.ArrayList;

import java.util.Calendar;

import java.util.Iterator;

import java.util.List;

 

public class DataSource {

 

    List list = new ArrayList();

 

    /** Creates a new instance of DataSource */

    public DataSource() {

 

        Calendar calendar = Calendar.getInstance();

 

 

        Celebrity fonda = new Celebrity();

        fonda.setFirstName(”Jane”);

        fonda.setLastName(”Fonda”);

        fonda.setOccupation(”Actress”);

        calendar.set(1937, 11, 21);

        fonda.setDateOfBirth(calendar.getTime());

        fonda.setId(1);

 

        list.add(fonda);

 

        Celebrity gallo = new Celebrity();

        gallo.setFirstName(”Ernest”);

        gallo.setLastName(”Gallo”);

        gallo.setOccupation(”Wine-maker”);

        calendar.set(1909, 2, 18); // Months are 0-based (2 is March)

        gallo.setDateOfBirth(calendar.getTime());

        gallo.setId(2);

 

        list.add(gallo);

 

        Celebrity gates = new Celebrity();

        gates.setFirstName(”Bill”);

        gates.setLastName(”Gates”);

        gates.setOccupation(”Programmer ;)”);

        calendar.set(1955, 9, 28);

        gates.setDateOfBirth(calendar.getTime());

        gates.setId(3);

 

        list.add(gates);

 

        Celebrity jolie = new Celebrity();

        jolie.setFirstName(”Angelina”);

        jolie.setLastName(”Jolie”);

        jolie.setOccupation(”Actress”);

        calendar.set(1975, 5, 4);

        jolie.setDateOfBirth(calendar.getTime());

        jolie.setId(4);

 

        list.add(jolie);

 

    }

 

    public List getCelebrities() {

        return list;

    }

 

    public Celebrity getCelebrityById(int id) {

 

        for (Iterator i = list.iterator(); i.hasNext();) {

            Celebrity celebrity = (Celebrity)i.next();

            if (celebrity.getId() == id) return celebrity;

        }

 

        return null;

    }

}

This class simply creates a List of a few celebrities in its constructor using some hard-coded data. We can obtain the whole lot by using the getCelebrities() method, or retrieve a single celebrity by specifying his or her ID and using the getCelebrityById() method.

Now that we have all supporting code ready, we can begin converting page mock ups into Tapestry templates.

Creating pages

Making Home.html a Tapestry template is easy. All we need to do is convert the existing HTML link into an already familiar PageLink component, and it is okay to do this implicitly:

<p><a href=”" jwcid=”@PageLink” page=”CelebritiesList”>

    List Celebrities</a></p>

You might want to run the project at this point and see if clicking on the link produces the desired result. It should.

As the Home page is so simple and its page specification remains empty, we don’t actually need any Home page class for it. In fact, you could simply remove the class attribute from the page specification:

<?xml version=”1.0″?>

<!DOCTYPE page-specification PUBLIC

    “-//Apache Software Foundation//Tapestry Specification 4.0//EN”

    “http://tapestry.apache.org/dtd/Tapestry_4_0.dtd”>

<page-specification>

</page-specification>

In this case Tapestry will use its BasePage class as a Home page class. However with time, as the CelebrityCollector application becomes more complex, we might decide to add something to the Home page. So I suggest leaving in place both the Home.page specification and the Home.java class; it doesn’t matter that they are empty so far.

Converting Details.html is also straightforward. All we need is a number of Insert components and another PageLink. Let’s suppose for now that we have a celebrity property already available in the Details class. We shall add it in a few minutes. Here is the Details page template:

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

 

<html>

  <head>

      <title>Celebrity Collector: <span jwcid=”fullName”>

          John Smith</span></title>

  </head>

  <body>

 

  <h2><span jwcid=”fullName2″>John Smith</span></h2>

 

  <table>

      <tr>

          <td><b>Birthday:</b></td>

          <td><span jwcid=”birthday”>01/01/1950</span></td>

      </tr>

      <tr>

          <td><b>Occupation:</b></td>

          <td><span jwcid=”occupation”>Actor</span></td>

      </tr>

  </table>

  <p><a href=”" jwcid=”@PageLink” page=”CelebritiesList”>

       Back to the list</a></p>

 

  </body>

</html>

And here is the corresponding Details page specification (note that all Insert components are configured in the specification while PageLink is defined implicitly):

<?xml version=”1.0″?>

<!DOCTYPE page-specification PUBLIC

    “-//Apache Software Foundation//Tapestry Specification 4.0//EN”

    “http://tapestry.apache.org/dtd/Tapestry_4_0.dtd”>

<page-specification class=”com.devshed.tapestry.celebrities.Details”>

 

    <component id=”fullName” type=”Insert”>

        <binding name=”value”

          value=”celebrity.firstName + ‘ ‘ + celebrity.lastName”/>

    </component>

 

    <component id=”fullName2″ copy-of=”fullName”/>

 

    <component id=”birthday” type=”Insert”>

        <binding name=”value” value=”celebrity.dateOfBirth”/>

    </component>

 

    <component id=”occupation” type=”Insert”>

        <binding name=”value” value=”celebrity.occupation”/>

    </component>

 

</page-specification>

Before discussing how components were configured, let’s add a celebrity property to the Details page by providing abstract public getter and setter methods:

public abstract class Details extends BasePage {

 

    public abstract Celebrity getCelebrity();

    public abstract void setCelebrity(Celebrity c);

 

}

At some point, we are going to use the setter to assign some Celebrity object to this property, but for now we don’t care when and where that will be done. Let’s concentrate on how different Insert components were configured in the page specification or, most importantly, on their bindings.

The component named occupation is the simplest one. It is bound to the OGNL expression

celebrity.occupation

Which means that to obtain a value to display, Tapestry will first invoke the getCelebrity() method of the Details class. We have provided this method, and although we made it abstract, Tapestry will take care of creating a concrete implementation of it at runtime. So we’ll get a Celebrity object as a return value from this method.

Then, according to the rules of OGNL, Tapestry will try to invoke the getOccupation() method on the Celebrity object. Sure enough, we have provided such method, and it returns a String. That same string will be displayed by the component then.

The birthday component is different in only one respect: its binding returns a Date. To display it, Tapestry will invoke the Date’s toString() method which will produce… well, some result. In fact, we can have complete control of how the value is displayed by an Insert component by using its format binding, but let me delay the explanation until the next article.

In two cases we want to display the full name of the celebrity: first, in the title element of the resulting HTML page (this is the piece of information displayed by a browser’s title bar), and second, in the header of the page. The fullName component does the job in the title element. Have a look at its binding expression:

celebrity.firstName + ‘ ‘ + celebrity.lastName

According to this OGNL expression, Tapestry will obtain the first and the last names of the celebrity by invoking appropriate methods of the Details and Celebrity classes and then glue both names together leaving a blank space between them. So you see, OGNL can be quite useful.

In order to insert the full name into the page header, we use the fullName2 component. As we need exactly the same result, we could use exactly the same binding – but there is a more rational way to achieve the desired result. We can simply state that fullName2 component is a copy of the fullName component, and this is exactly what we are doing here.

Finally, we are coming to the CelebritiesList page, and here we are going to need the new component named For. Let’s see what it can do for us.

The For component

The For component does three crucially important things:

  1. It takes a bunch of objects and iterates through them one by one. Technically, this bunch can be an Array or any representatives of the Collection interface, such as an ArrayList or a HashSet, to name a few.

  2. Each time it picks an object from the bunch it makes that object available for our code.

  3. It displays any markup and any components surrounded by itself as many times as there are objects in the bunch.

Let’s begin from configuring a For component in the List page specification:

<component id=”eachCelebrity” type=”For”>

   <binding name=”source” value=”celebrities”/>

</component>

We’ve specified a For component and given it some name (eachCelebrity). We have also provided its required and most important binding, source. This is where the bunch of objects will come from. Let’s define the getCelebrities() method in the CelebritiesList page then:

public abstract class CelebritiesList extends BasePage {

 

    public List getCelebrities() {

        return new DataSource().getCelebrities();

    }

 

}

We can place a For component in HTML markup in the same way as we did this before, using a div or a span. However, in this specific case it is more reasonable to convert a table row (tr element) into a For component. Let’s see how we can do this:

<table width=”300″ cellpadding=”5″ border=”1″>

    <tr>

        <th>Last Name</th>

        <th>First Name</th>

    </tr>

    <tr jwcid=”eachCelebrity”>

        <td>

            <a href=”">Smith</a>

        </td>

        <td>John</td>

    </tr>

    <tr jwcid=”$remove$”>

        <td>

            <a href=”">Smithson</a>

        </td>

        <td>Jane</td>

    </tr>

    <tr jwcid=”$remove$”>

        <td>

            <a href=”">Swedenborg</a>

        </td>

        <td>Emmanuel</td>

    </tr>

</table>

We have marked one of the table rows as an “eachCelebrity” component which we have just defined in the page specification. It should be repeated as many times as there are Celebrity objects in the bunch provided to this component. But now we have two table rows which we don’t want to see at runtime; they were written by the designer for preview purposes only.

Quite conveniently, Tapestry has a special thing which looks like a component ID but in fact just marks an HTML element for removal at runtime: jwcid=”$remove$”. So we have marked the two remaining table rows for removal.

Run the application, and this is what you should see:

image0051.jpg

It works! The rows for Jane Smithson and Emmanuel Swedenborg were removed, as required, and the row for John Smith was repeated four times – exactly the number of celebrities in our mock data source. Now we need to display the real celebrities instead of John Smith.

First of all, we shall ask the For component to expose to our code the Celebrity object it is currently iterating through. We use another binding for this purpose:

<component id=”eachCelebrity” type=”For”>

    <binding name=”source” value=”celebrities”/>

    <binding name=”value” value=”currentCelebrity”/>

</component>

Each time our For component picks a Celebrity object from the List provided to it, it will pass this object to the page class by invoking the page class’ setCurrentCelebrity() method. We don’t have such a method at the moment and we don’t need to write it by hand. All we need to do is tell Tapestry to create a currentCelebrity property.

Normally, we would simply provide an abstract getter method, and Tapestry would do the rest. This time however we are not going to access the new property from our Java code. As you will see in a moment, it will be used only by components on the page. So it makes sense to specify the property in the page specification:

<page-specification
class=”com.devshed.tapestry.celebrities.CelebritiesList”>

 

    <property name=”currentCelebrity”/>

 

    <component id=”eachCelebrity” type=”For”>

        <binding name=”source” value=”celebrities”/>

        <binding name=”value” value=”currentCelebrity”/>

    </component>

 

</page-specification>

Finally, we are going to use a couple of Insert components to display the first and last name of the current celebrity in the corresponding table row:

 <tr jwcid=”eachCelebrity”>

      <td>

          <a href=”">

              <span jwcid=”lastName”>Smith</span>

          </a>

      </td>

      <td>

          <span jwcid=”firstName”>John</span>

      </td>

  </tr>

Since we decided to use declared components here, we need to define them in the page specification:

<component id=”firstName” type=”Insert”>

    <binding name=”value” value=”currentCelebrity.firstName”/>

</component>

<component id=”lastName” type=”Insert”>

    <binding name=”value” value=”currentCelebrity.lastName”/>

</component>

Run the application, click on the “List Celebrities” link, and you should see the correct result:

image0062.jpg

However, if you click on a link in the table, nothing will happen, while we would like to see the Details page for the selected celebrity. To implement this functionality, we need a DirectLink component, and this is exactly what we are going to deal with in the next article.

by Alexander Kolesnikov 

Apache Tapestry and Listener Methods, Conditional Components and PageLink

(No Ratings Yet)
Loading ... Loading ...

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.

 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.

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.

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.

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:

tapestry_07_01.gif

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:

tapestry_07_02.gif

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.

by Alexander Kolesnikov 

 

The Properties of Tapestry Pages

(No Ratings Yet)
Loading ... Loading ...

In the previous part of this tutorial we started to build a new project, GuessTheWord. The project is very simple, but we are going to spend a lot of time working on it and experimenting with it. This is because the main aim is to learn a lot about the most basic concepts of Tapestry. For every important concept, I want to show you a number of options as to how it can be implemented and explain which option is good for what.

Today we are going to deal with the properties of Tapestry pages. Properties are very important because they facilitate the exchange of information between different parts of a Tapestry application. Components on the page obtain their values from page properties and then report the changed values back to the page. We also normally use properties to pass information from one page to another and to allow different components to speak to each other.

But before discussing properties, we need to be familiar with one important feature of Tapestry’s architecture.

Tapestry pages and pooling

It is important to remember that Tapestry pages are pooled.

To create a page, we usually extend our new page class from Tapestry’s BasePage, and that class has already plenty of functionality for us to reuse, with quite a number of different properties and methods (if you are curious, have a look at BasePage class in Tapestry API: http://tapestry.apache.org/tapestry4.1/apidocs/index.html). As a result, creating an instance of a page class is an expensive operation, in terms of computer resources.

To avoid creating a page object every time when yet another of your numerous users requests the page, Tapestry has a pool for every kind of page and stores in that pool some number of instances of the given page class. So there is always a pool for the Home page instances in every application, but our application also has a pool for the Secret page instances.

When a user requests a page, Tapestry takes an instance of that page from the corresponding pool and uses that instance to produce appropriate HTML code and send it to the user’s browser. At some point after that, the page class instance can be sent back to the pool and then reused to serve another client. And when the first client in a few minutes submits a form with their input, some completely different page instance can be used to handle the submission.

It is important to know this detail of Tapestry’s architecture because it will influence the way we shall write our code in a few cases. In particular, this is important for managing page properties properly.

Different ways to manage page properties

Let’s imagine that we have requested the Home page while working with the already created part of our GuessTheWord application. Tapestry took an instance of the Home page class from the pool and used it to produce an appropriate HTML to be sent to our browser. In this process, the page class has checked whether there is some initial value to display in the secretWord text box (implemented as a TextField component). For this, the getTheWord() method of the page class was invoked.

We have trusted Tapestry to create and maintain theWord property by writing an abstract getter method like this:

public abstract String getTheWord();

Because of this, theWord property will be initially always set to null, so the empty text box will be displayed, or exactly what the creators of the Web application wanted.

But let’s imagine that we have created the same property in the traditional Java way, by writing a private class member and accessor methods for it ourselves:

private String theWord;

 

public String getTheWord() {

return theWord;

}

 

public void setTheWord(String theWord) {

this.theWord = theWord;

}

Say we have played with the application and tried to guess the secret word. Every time we submitted the form with a version of the secret word entered into the secretWord component, an instance of the Home page class on the server side was used to serve our submission. The setTheWord() method of that page class was invoked to store our word in its private property. Then some code (yet to be written) was used to define whether the word is correct or not and to act accordingly.

After a few hundred attempts we have finally managed to guess the secret word and left the application, to do something else equally exciting. The Home page instance we dealt with the last time still stores the word we have guessed in its private theWord member. It goes to its pool to rest and wait until another user comes try and guess the secret word.

Say Tapestry chooses to serve the new user that same Home page instance we just left, with the secret word stored in it. As usual, when producing HTML to be sent to the user, the page class will check whether components have any initial values. For the secretWord component, it will invoke the getTheWord() method, and the method will happily return that same secret word we have spent so much effort to guess.

So the next user will see the Home page with the secret word already entered in the text box. It will be masked, yes, because we have set the hidden binding for the secretWord component to true, but for the new user it will be enough to press the Submit button to see the sacred wisdom of the Secret page. Is that fair? I don’t think so.

What we actually need to do is to set theWord property to its default value, null, every time an instance of the Home page is sent to the pool. This is exactly the additional functionality Tapestry takes care of when we trust it to create a property for us (by providing only an abstract getter method). When Tapestry sees the abstract getter, it automatically creates an appropriate private member of the page class, the default concrete getter and setter methods for it and some other code needed to set the property back to its default value every time the page class instance is sent to its pool.

So you can see that it makes a lot of sense to trust Tapestry with creating page properties for us. There can be, however, cases when we have to do all the work ourselves.

Resetting page properties by hand

When Tapestry creates a page property automatically, it creates basic getter and setter methods that do nothing but retrieve or set the value of the property. But sometimes we might want to put some functionality into the getter and/or setter methods. In the GuessTheWord application, for example, we might want to do something depending on the length of the word submitted by the user and write a setter like this:

public void setTheWord(String theWord) {

 

if (theWord != null) {

int length = theWord.length();

 

// Do something…

}

 

this.theWord = theWord;

}

This might be not be a very convincing example, but believe me, sometimes you just cannot avoid writing some code in a getter or a setter. And as soon as you have provided a getter or a setter of your own, you have to write all the remaining code yourself. So Tapestry will not take care of setting theWord to null; you’ll need to do that yourself too.

In Tapestry 3, page classes have a very convenient method for this purpose, initialize(). In Tapestry 4, this method still exists but it is deprecated. So it is not recommended that we use it, and I should not be mentioning it here. But still, this method is more convenient than the alternative solution and I have to admit that I use the deprecated initialize() method in my work.

It looks like this:

protected void initialize() {

theWord = null;

}

As soon as we have provided this method in our page class, it will be called every time the page instance is sent to the pool, so we have achieved what we wanted.

The alternative approach, recommended for use in Tapestry 4, is to implement the PageDetachListener interface and its only method pageDetached(PageEvent event). So if you do everything properly, your Home class will look like this:

public abstract class Home extends BasePage implements PageDetachListener {

 

private String theWord;

 

private void setTheWord(String theWord) {

this.theWord = theWord;

}

 

public String getTheWord() {

return theWord;

}

 

public void pageDetached(PageEvent event) {

theWord = null;

}

 

public String onWordSubmit() {

return “Secret”;

}

}

For me, it’s just the same result with more coding, but of course the creators of Tapestry know better.

Configuring a property in page specification

There is yet another way to configure a page property that you might find useful in some cases. Say you want to have a property, but you are not going to read or set the value of that property in your Java code, in the page class. This can happen when the property is going to be used exclusively by some components on the page, so it can be wired to those components within the page specification without writing any Java code at all.

All you need to do is define the property in the page specification:

<property name=”theWord”/>

And then you can reference this property in the bindings of your components, exactly as we do now in the secretWord component binding.

Having found this <property> element in the page specification, Tapestry will act exactly as it does when we’ve provided an abstract getter method: it will create a private property, public getter and setter and will also reset the property to its default value before sending the page to its pool.

All right, but what exactly is this “default value”? According to the rules of Java, any object, including String, will be set to null, while any numeric variable will be sent to zero. But what if you want to have an initial value different from the default one? Say in the GuessTheWord application you want the text box to be not empty, but to display some prompt when the Home page is initially shown to a user.

Setting an initial value

Let’s experiment with our current project. First of all, comment out the hidden binding of the secretWord component, so that we are able to see the initial value, perhaps like this:

<component id=”secretWord” type=”TextField”>

<binding name=”value” value=”theWord”/>

<!–binding name=”hidden” value=”true”/–>

</component>

If you made any changes to how theWord property is configured, please revert now to the most convenient (and most often used) approach, an abstract getter. Now, if we want to give this property an initial value, we can use a relatively new feature of Java – an annotation. The result will look like this:

@InitialValue(”literal:enter a word”)

public abstract String getTheWord();

Simple and elegant, isn’t it? Run the project, and you should see something similar to the following screen shot:

tapestry_04_01.gif

Note that by default, the value inside of the double quotes inside the annotation is an OGNL expression. This means you can have a method providing an initial value for your property. Try something like this:

@InitialValue(”initialValue”)

public abstract String getTheWord();

 

public String getInitialValue() {

return “enter a word”;

}

Run the project, and it should work exactly as it did before.

But what if we wanted to configure this property in the page specification only? Let’s try this. Comment out the above abstract getter, its annotation and the getInitialValue() method (if you created it). Instead, add the following line of XML to the Home page specification:

<property name=”theWord” initial-value=”literal:enter a word”/>

Again, run the project, and it should work exactly as before.

Finally, if you had to configure a property by hand, here is one possible approach to assigning it an initial value:

public String getTheWord() {

 

if (theWord == null) theWord = “enter a word”;

 

return theWord;

}

 

However, the more fundamental way to initialize a property is to override the finishLoad() method of the BasePage class:

protected void finishLoad() {

theWord = “enter a word”;

}

This method will be invoked only once, when the page is loaded for the first time.

All right, this seems to be everything I wanted to tell you about the properties of Tapestry pages for now. We have made some changes to the GuessTHeWord project, but that was for learning purposes only. You might want to revert the code to its initial state, undoing any changes we have made in this part of tutorial.

What comes next

In the next part, we are going to take a more detailed look at listener methods. They can be written in a few different ways and we are going to decide when each of the approaches can be useful for us, thus gradually expanding our Tapestry toolbox.

If the space will allow, we might also try different ways of submitting a form, but on the other hand, this might be delayed until a later part.

See you in the next part.

by Alexander Kolesnikov

A Closer Look at Simple Components in Apache Tapestry

(No Ratings Yet)
Loading ... Loading ...

In the previous article, we took a look at some of the components that are used most often in Tapestry applications, and learned some important concepts related to them. At the end, we found that we needed three components for our example application. In this article, we will configure those components. We will also learn how to disable caching.TextField component

Adding a component to the Home page template is very easy. Just mark the already existing text box with a jwcid attribute and name it accordingly, perhaps like this:

<input type=”text” jwcid=”secretWord”/>

The most important part is to define the component in the page specification. Insert into the Home.page file, between <page-specification …> and </page-specification> tags the following piece of XML:

<component id=”secretWord” type=”TextField”>

<binding name=”value” value=”theWord”/>

</component>

The id attribute mentioned here must match the name we’ve used for the corresponding jwcid attribute in the page template, ‘secretWord’. The type attribute defines that this is a TextField component, exactly as we wanted.

The <binding> element is exactly what creates that global communication channel between the component and the page class. Its name=”value” attribute states that the piece of information that is communicated along the channel should be understood as a value for the component, while value=”theWord” tells us which methods on the page class to invoke in order to get or set the value associated with the component.

You have probably already guessed that ‘theWord’ is treated as an OGNL expression. It tells Tapestry that to get the value for the component it should use the getTheWord() method on the page class, while to set the value after it was received from the user, the setTheWord() method should be used.

Obviously, we don’t have any of these methods yet. In fact, we are also going to need a variable to store the value itself. The traditional path to follow to provide all this would be to create a private class member in the Home class:

private String theWord;

and then two public methods to access it:

public String getTheWord() {

return theWord;

}

public void setTheWord(String theWord) {

this.theWord = theWord;

}

However, this traditional approach has two down sides. First of all, it is quite boring to write all the predefined, truly “boilerplate” code, while it is in the spirit of Tapestry to avoid any boredom. And second, we would have to provide some additional maintenance for this private class property, which means even more code. Let me delay the detailed explanation until the next article – we have already had enough theory for today.

Right now, I will just show you the preferred way of defining a property of a page class in Tapestry. All we need in our case is this single line of code:

public abstract String getTheWord();

See, Tapestry is clever enough. When it notices a line of code like this one, it understands that we need a property named theWord. The type of that property is String, as can be guessed from the return type, and since the getter method we have provided is abstract, we trust Tapestry to create anything it will need to maintain the property.

You will see this happening quite often as we learn Tapestry: instead of hard coding something boring or verbose we shall just tell Tapestry what exactly we need, and that thing will be provided for us. For now, just add the above abstract getter to the Home page class.

Before going on to the Form component, let me show you another binding that can be used with the TextField. It is not required, but I have two reasons for doing it. First, I will be able to show you that there can be more than one binding. Second, you’ll see that some bindings are used to simply configure the component. Let’s modify the specification for the secretWord component to look like this:

<component id=”secretWord” type=”TextField”>

<binding name=”value” value=”secretWord”/>

<binding name=”hidden” value=”true”/>

</component>

This new binding simply specifies that the text box should be displayed as a password field, i.e. any input should not be displayed but masked. This is a secret word, after all!

Now, let’s take care of the Form component, to enclose the TextField.

Form component

To define this component in the Home page template, simply mark the existing HTML form with a jwcid attribute and give the component a descriptive name, like so:

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

The next step is to configure the Form component in page specification:

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

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

</component>

Let’s concentrate on the <binding> element as everything else should be clear for you now.

The name=”listener” attribute says that what we are specifying in this binding is a listener. Listener can be understood as a special method of the page class, which is invoked when an event associated with this listener happens. Here the event is the form submission.

The value=”listener:onWordSubmit” attribute provides the name for the listener method. Note the ‘listener’ prefix. You are already familiar with two prefixes, ‘ognl’ and ‘literal’, so here is the third one for your collection. It is used to tell Tapestry that what follows is not an OGNL expression and not a literal value, it is rather the name for the listener method to be invoked when the form is submitted.

All that is left is to provide the listener method in the Home page class. There are a few ways in which a listener can be written, but again, let me delay the complete explanation until the next article. Right now, we shall create a very simple listener like this:

public String onWordSubmit() {

return “Secret”;

}

This is an ordinary public method, and its only “special feature” is that it returns a String containing the name of the next page to show. Normally, before returning this name we would do something, say, manipulate some data, but for now let’s just leave everything very simple.

We can already run the new application and see how it works. Hit the F6 key, and NetBeans will compile, package and deploy everything for you. The Home page will appear, looking exactly like its mock up, but if you try to enter a word into the text box, it will be masked, like a password. Finally, press the Submit button, and you will see the Secret page – i.e. its mockup with both views shown as we didn’t add any functionality for that page yet.

Phew… I feel like this article is becoming too lengthy, and there are still a lot of things to discuss and play with. [In fact, we had to break it into two parts. –Ed.] Let me take a break here and continue the general discussion in the next article.

Source Code

To wrap up what was already done, here is the source code for the template, specification and class for the Home page:

Home.html:

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

<html>

<head>

<title>Guess the Word</title>

</head>

<body>

<h2>Guess the Word</h2>

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

Enter the secret word:

<input type=”text” jwcid=”secretWord”/>

<input type=”submit” value=”Submit”>

</form>

</body>

</html>

Home.page:

<?xml version=”1.0″?>

<!DOCTYPE page-specification PUBLIC

“-//Apache Software Foundation//Tapestry Specification 4.0//EN”

“http://tapestry.apache.org/dtd/Tapestry_4_0.dtd”>

<page-specification class=”com.devshed.tapestry.guesstheword.Home”>

<component id=”secretWord” type=”TextField”>

<binding name=”value” value=”theWord”/>

<binding name=”hidden” value=”true”/>

</component>

 

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

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

</component>

 

</page-specification>

Home.java:

package com.devshed.tapestry.guesstheword;

import org.apache.tapestry.html.BasePage;

public abstract class Home extends BasePage {

 

public Home() {

}

 

public abstract String getTheWord();

 

public String onWordSubmit() {

return “Secret”;

}

 

}

However, there is one technical issue we need to care of before leaving.

How to disable caching

Tapestry strives to be as efficient as possible, so it caches page templates and specifications in order not to reload them every time when they are needed. This is a very useful behavior in a production application; however, during development it can become annoying, as we might not immediately see the changes we have just made to the page.

Thankfully, there is a way to ask Tapestry not to cache any pages in our development environment.

In NetBeans, click on the Tools menu and then choose Server Manager. In the Server Manager dialog, choose the Platform tab and then enter into the VM Options text box the following option:

-Dorg.apache.tapestry.disable-caching=true

The result should look like this:

image005.jpg

Close the dialog, and that’s it. If however Tomcat was running when you applied the setting, you will need to restart Tomcat.

To do this, click on the Runtime tab on the left side of NetBeans IDE and expand the Servers node to see the Bundled Tomcat node:

image0061.jpg

Right click on the Bundled Tomcat node and choose Restart. As simple as that.

What comes next

In the next part of this tutorial I will explain in more detail how properties of the page class can be defined in Tapestry and when one way is more preferable than the other.

Next we are going to have a more detailed look at the listener methods as there are three ways to write them.

It might also be useful for the future to get familiar with the two ways of submitting the form.

Finally, we shall complete the GuessTheWord project and become familiar along the way with If, Else and PageLink components.

See you in the next article then.

by Alexander Kolesnikov

Introducing Simple Components in Apache Tapestry

(No Ratings Yet)
Loading ... Loading ...

In the previous article, you witnessed the interplay between a Tapestry page and its components (granted, we had only one simple component there, but you get my point). Page class, when rendering its page, finds any components mentioned in the template and asks those components to display themselves, as they know better how to do that. Components, in their turn, might need some information to display themselves, and they ask the page class to provide the necessary information by calling some of its methods.

How to configure components, their type, which methods of the page class to call to provide them information – all these details are provided in the page specification.

Today you are going to see more components of those most often used in Tapestry application, and you are going to learn some important concepts related to them. First of all, we shall experiment with that very simple Tapestry application created in the previous article, but then we shall start working on another project, which is slightly more complex.

Implicit components

Launch your NetBeans and open the FirstTapestry project we’ve created in the previous article. Let’s concentrate on how we have configured the Insert component used in this project.

First of all, we used a standard <span> HTML element and marked it as a Tapestry component:

<span jwcid=”now”>…</span>

We have also given a name to this component, now. If you don’t like this name, use any other name; there’s no problem with that.

And then, to define what this component actually is, we have configured it in the page specification:

<component id=”now” type=”Insert”>

<binding name=”value” value=”currentDate”/>

</component>

This is what we did in the previous article. There is, however, an alternative way to achieve the same goal. Let’s change the Home.html template to look like this (I am showing only the line that should be changed):

<p>Now is <span jwcid=”@Insert” value=”ognl:currentDate”>8:27, the 1st of April 2007</span>.</p>

Then remove the Insert component configuration from Home.page, and leave the page specification empty:

<?xml version=”1.0″?>

<!DOCTYPE page-specification PUBLIC

“-//Apache Software Foundation//Tapestry Specification 4.0//EN”

“http://tapestry.apache.org/dtd/Tapestry_4_0.dtd”>

<page-specification class=”com.devshed.tapestry.first.Home”>

 

</page-specification>

Press F6 to run the project and make sure that it works exactly as before.

 

In some cases, after making a change in an HTML template, you will not see your change immediately reflected in the running application. This is because, by default, Tapestry caches page templates for higher efficiency. See the How to disable caching section in the next article for an explanation of how to change settings and make your application more responsive during development.

Let’s see what we have done here. In the page template, instead of giving a name to our one and only component, we have directly specified its type: Insert. The ‘@’ symbol in front of the component type tells Tapestry that this is a type of component and not just a name that we have chosen for it.

Now the page class will know immediately what kind of component we have used. But which method should it invoke to provide data to this component? The piece of information which was previously contained in the <binding> element of page specification:

<binding name=”value” value=”currentDate”/>

has turned into an additional attribute of <span> element:

value=”ognl:currentDate”

Clearly, there is no need to provide any details in the Home.page file anymore, so we have left the page specification empty.

This way of configuring Tapestry components (everything moves to the page template, nothing is left for the page specification) is termed “an implicit component” as opposed to the “declared component” we’ve dealt with before.

However, I hear you asking the question: why in one case did we use “curentDate”, while in the other case we used “ognl:currentDate”? And what exactly does this ‘ognl:’ prefix mean?

What is OGNL?

“OGNL stands for Object-Graph Navigation Language; it is an expression language for getting and setting properties of Java objects” – this is a quotation from the OGNL project website (http://www.ognl.org/).

Different software projects, including Tapestry, use OGNL to simplify the basic operations of accessing properties of Java classes.

When in a page specification we write something like value=”currentDate”, Tapestry automatically treats whatever it finds inside of double quotes as an OGNL expression and asks OGNL to handle it. According to the rules of OGNL, ‘currentDate’ means that the getCurrentDate() method should exist somewhere in the page class – so Tapestry goes and invokes this method. Or it complains, if you forgot to write it. This is how it works in page specifications.

In page templates however, if you write value=”currentDate”, the contents between the double quotes are treated verbatim, as a literal value. Try to make this change and see what happens. Instead of going to the page class and invoking an appropriate method, Tapestry will simply use the ‘currentValue’ string as a value for the Insert component, so instead of the current date and time you will see:

Now is currentDate.

That’s not very illuminating. So we have to tell Tapestry that ‘currentDate’ should be treated as an OGNL expression, and we use the ‘ognl:’ prefix to do this.

By the way, you are free to use the ‘ognl:’ prefix in page specification too:

<binding name=”value” value=”ognl:currentDate”/>

This will work perfectly well, however it is not required as OGNL is the default option here. If, however, you wanted to provide a literal value in a page specification, you would have to use either the ‘literal:’ prefix:

<binding name=”value” value=”literal:my birthday”/>

or single quotes inside of double quotes:

<binding name=”value” value=”’my birthday’”/>

This was a very simple example of an OGNL expression. However, imagine that a method called on the page class returned some Person object. Say, this object had an Address object as its property, and what we actually wanted to display is the town property of the Address object. In Java code, to obtain the desired value, we would write something like:

person.getAddress().getTown()

In OGNL this looks simpler:

person.address.town

This is again a basic example, but as we advance to the more involved parts of this tutorial, you will see some more complicated and powerful examples of OGNL expressions. For now, to complete our first acquaintance with OGNL, let’s do a simple experiment.

Add another method to the Home.page class:

public String getSomeMessage() {

return “. Welcome to Tapestry!”;

}

Then change the OGNL expression we use to obtain a value for our Insert component to be:

“ognl:currentDate + someMessage”

Run the application and you should see something like this:

image0011.jpg

To resolve the expression we have passed to it, OGNL invoked two methods on the page class, getCurrentDate() and getSomeMessage(), and put the results returned from them together. The resulting string was used as a value for the insert component. Here you can already see that OGNL can be quite helpful.

But let’s return to the two ways of defining a component: explicit component and declared component. Which approach is better? Well, it depends, first of all, on the component itself, but also on your personal preferences.

Implicit vs. declared components

You have seen just one Tapestry component so far, so this discussion will be unavoidably abstract, but I still think it will be useful to prepare you to understand what will come later.

The obvious advantage of the implicit components (fully described in the HTML template) is that you can see everything related to this component in one place: how and where it is used on the page, what its type is and what it is bound to in the page class. You don’t have to look into another file or page specification to find out any additional information.

The obvious disadvantage of this approach is that if a component has several bindings (in some cases there can be three or even more bindings), the page’s HTML code gets cluttered with weird nonstandard attributes and maybe also complex OGNL expressions.

The advantage of the declared components is that you have them listed all neat and orderly in the page specification. Having multiple bindings is not a problem and complex OGNL expressions are welcome here.

The disadvantage of the declared components is obvious when you have a component without any bindings (you will see one such component soon), or with only one simple binding. The proper specification for such a component will be more verbose than its implicit declaration.

In practice, I find myself combining the two approaches. When a component has no bindings, I define it implicitly. When it has more than one binding, I declare it in page specification. In the case of simple components, like Insert, I might define them either way depending on the complexity of the page design. If the page is large and its design is complex, I will prefer declared components over implicit ones.

It is the time now to have more practice and to meet more components. Let’s create another project, this time having two Tapestry pages.

GuessTheWord project

Create a new Web Application project in exactly the same way as we have created the first project, but give it a different name – say, GuessTheWord.

Add to the project the tapestry41 library that we have created in the previous article (right-click on the Libraries folder, choose AddLibrary…).

Configure the servlet in the deployment descriptor. Again, all settings will be the same; only the name for the servlet will be different. Here is what the Servlets page of your web.xml editing tool should look like:

image002.jpg

Also, change welcome file to app, as we did before, and delete the index.jsp file as it is not needed.

Now, create the skeletons for two Tapestry pages, i.e. all the necessary files, but without any contents. The first page is the default one, Home. As for the second, let it be named Secret.

So our task is to, first, create two HTML files, Home.html and Secret.html. Both should be located in the WEB-INF directory of the new project, and both should have the same default content created by NetBeans.

Next, create two page specification files, Home.page and Secret.page (right-click WEB-INF, New > Empty File…). As for the contents for these files, the Home.page in the previous project is empty right now (after we made that Insert an explicit component), so you can just copy its contents to both the new Home.page and the Secret.page files.

We need, however, to change the class attribute of the <page-specification> element in both files. Let it be com.devshed.tapestry.guesstheword.Home for the Home.page and com.devshed.tapestry.guesstheword.Secret for the Secret.page.

Finally, we need to create the com.devshed.tapestry.guesstheword package and two classes in it, both abstract and extending the Tapestry’s BasePage: Home and Secret. See the previous article for the details on how to create a package and a class in NetBeans. Here is the code for the Secret class; the Home one should be different only in its name (comments are not shown):

package com.devshed.tapestry.guesstheword;

import org.apache.tapestry.html.BasePage;

public abstract class Secret extends BasePage {

 

public Secret() {

}

}

Remember a useful shortcut: after typing in “extends BasePage”, press Ctrl-Space, and NetBeans will find the required class in the available libraries and write an import statement automatically. These new IDEs are so helpful!

Creating mock ups

Let me repeat this: it makes sense to start a Tapestry project from a series of mock ups, as they can be converted into Tapestry pages very easily. So let’s visualize first what we want to do in the new project.

The default page should display a very sim