Making a CelebrityCollector with Apache Tapestry: the For Component

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:

<!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.

<!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:

<!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.

{mospagebreak title=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:

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.

{mospagebreak title=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.

{mospagebreak title=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:

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:

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.

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan