Apache Tapestry and DirectLink, IoC and DI

Previously we began building our CelebrityCollector application. But right now it is a hollow shell. In this article we will use several new Apache Tapestry components to help us start filling in the details.

In the previous article of this tutorial we learned to use the For component. It (a) iterates through a number of objects, (b) displays any markup surrounded by itself as many times as there are objects to iterate through and (c) exposes each current object to our code. As a result, we can put into markup a few simple components like Insert and have all the objects in the bunch neatly displayed, precisely the way we want them to be displayed.

Now, it is a common pattern to have one of the listed objects to be displayed in more detail. Normally, some kind of link is used for this purpose. Say, in the CelebrityCollector application we’ve made the surname a link. Click on the surname "Gates" and you will see all the details about Bill Gates. So far, the links do not work like this however, just because they are empty HTML links with empty href attributes and nothing else:

<a href=""> Gates </a>

To achieve the desired result, we should convert these links into Tapestry components.

Meet DirectLink

We are already familiar with one kind of link component — PageLink. Although quite useful in many cases, PageLink will not suit our current purpose as it simply displays another page while we need to pass to the Details page some data to display.

This is exactly where DirectLink will be helpful as the main features of this component are:

  1. It displays itself as a link.
  2. It is associated with a listener method. When the link is clicked, that method will be invoked, and we can have any kind of code executed as a result.
  3. It can pass to its listener a parameter — some additional detail that will specify what exactly we want to get done.

Let’s see how all this works.

In the CelebritiesList.html, make the link surrounding a surname a Tapestry component:

<a href="" jwcid="detailsLink">

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

</a>

And then configure this component in the CelebritiesList page specification:

<component id="detailsLink" type="DirectLink">

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

   <binding name="parameters" value="currentCelebrity.id"/>

</component>

As you can see, we have defined a listener method, onShowDetails(), to be invoked when the link is clicked. Now let’s add this method to CelebritiesList.java:

public IPage onShowDetails() {

   // Do something

   return null;

}

There is also a binding named "parameters." Potentially, there can be several parameters, but we need just one — the ID of the celebrity whose detailed information we want to see. To obtain its value we use an OGNL expression, and I believe there is no need to decipher it for you.

However, it may be useful to review how everything works at the CelebritiesList page.

{mospagebreak title=DirectLink and For working together}

Let’s have a look at this fragment again:

<tr jwcid="eachCelebrity">

   <td>

      <a href="" jwcid="detailsLink">

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

      </a>

   </td>

   <td>

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

   </td>

</tr>

As you know, eachCelebrity is a For component. We gave it a List containing four Celebrity objects filled with data for Jane Fonda, Ernest Gallo, Bill Gates and Angelina Jolie stored in them.

The eachCelebrity component will take the first object, for Jane Fonda, and assign it to the currentCelebrity property defined in the page specification. After that it will render the piece of markup surrounded by it (shown in bold in the code snippet above).

But the markup contains three Tapestry components: detailsLink, lastName and firstName. Each of them is defined in the page specification in such a way that it gets some information from the currentCelebrity property. In the first iteration, this property is set to Jane Fonda’s Celebrity object, so the first chunk of HTML is filled with her data. If in the running application you take a look at the source of the page showing the table of celebrities, you will find this fragment:

<tr id="eachCelebrity">

   <td>

      <a id="detailsLink"

      href="/CelebrityCollector/app? … service=direct&amp;sp=1">

         Fonda

      </a>

   </td>

   <td>

      Jane

   </td>

</tr>

This is exactly the result of the first iteration of the eachCelebrity component — a table row for Jane Fonda. I had to abbreviate the contents of the href attribute, otherwise it would not fit into my page. Anyway, for now we can just say that all this long string needs to do is tell Tapestry what to do when the link is clicked. Note however the highlighted "sp=1" piece, which in the language of Tapestry means "service parameter equals 1." You see here how the ID for Jane Fonda was encoded into the link.

The first iteration is completed, and the eachCelebrity component takes the next Celebrity object, for Ernest Gallo, assigns it to the eachCelebrity property and renders the contained markup for the second time. Here is the result:

<tr id="eachCelebrity_0">

   <td>

      <a id="detailsLink_0"

      href="/CelebrityCollector/app? … direct&amp;sp=2">

         Gallo

      </a>

   </td>

   <td>

      Ernest

   </td>

</tr>

Again, I had to abbreviate the contents of the href attribute of the link, but notice that service parameter this time equals 2, i.e. the ID for Ernest Gallo.

You might also notice how Tapestry takes care of the id attributes of HTML elements. In the first iteration it gave them the names of corresponding components. But id values have to be unique, and in the second iteration Tapestry appended "_0" to the names of the components in attribute values. In the third iteration it appended "_1," and so on.

What is really important to understand however is that whichever link you click, the same listener will be invoked, the onShowDetails() method of the CelebritiesList page class. The only way to know which celebrity was chosen is by using the parameter passed by the link. But how do we get the value of this parameter in the listener method?

{mospagebreak title=Passing a parameter}

As it is usual with Tapestry, it takes care of low level details, so all we need to do is tell that we expect to have some parameter in the listener, like so:

public IPage onShowDetails(int id) {

 

   // Do something

 

   return null;

}

Could it be simpler? Now, let’s go on and complete the application. One minor enhancement: as we are going to use the DataSource class more than once, let’s save its instance so that we don’t have to instantiate it every time. Add the following line of code to CelebritiesList.java:

private DataSource dataSource = new DataSource();

And then amend the getCelebrities() method:

public List getCelebrities() {

   return dataSource.getCelebrities();

}

Back in the listener method, we can obtain the Celebrity object, as selected by the user, by its ID, from the DataSource:

public IPage onShowDetails(int id) {

 

   Celebrity celebrity = dataSource.getCelebrityById(id);

   // Pass the celebrity to the Details page

 

return null;

}

Now we need to get a reference to the Details page. In one of the previous issues you have seen one way to do this, through IRequestCycle. There is, however, a nicer way to do the same. Add these two lines of code to CelebrityList.java:

@InjectPage("Details")

public abstract Details getDetailsPage();

This is an example of so-called injection, and we’ll be using a lot of them in Tapestry. We are just saying here that we are going to need a reference to the Details page at some point, so we ask the framework to provide such a reference at runtime.

As you might guess, this is an example of the super-popular "Dependency Injection." You will find my clarifications on this term, as well as on the other very popular one, "Inversion of Control." at the end of this article. Right now, let’s just complete the practical part. Make the code for the CelebritiesList class look like this:

package com.devshed.tapestry.celebrities;

 

import java.util.List;

import org.apache.tapestry.IPage;

import org.apache.tapestry.annotations.InjectPage;

import org.apache.tapestry.html.BasePage;

 

public abstract class CelebritiesList extends BasePage {

 

   private DataSource dataSource = new DataSource();

 

 

   @InjectPage("Details")

   public abstract Details getDetailsPage();

 

 

   public List getCelebrities() {

      return dataSource.getCelebrities();

   }

 

 

   public IPage onShowDetails(int id) {

 

      Celebrity celebrity = dataSource.getCelebrityById(id);

 

      Details nextPage = getDetailsPage();

      nextPage.setCelebrity(celebrity);

 

      return nextPage;

   }

}

Run the application, go to the list of celebrities and click on someone’s surname. You should see something like this:

The application works okay, but isn’t the format used to display the birth date a bit weird? You probably remember that I have promised you in a previous article that we’ll take care of how this date is displayed. Actually, the Insert component can do this for us.

Go to the Details page specification and change the declaration of the birthday component to look like this:

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

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

   <binding name="format" value="dateFormat"/>

</component>

Finally, add the getDateFormat() method to the Details.java code:

package com.devshed.tapestry.celebrities;

 

import java.text.Format;

import java.text.SimpleDateFormat;

import org.apache.tapestry.html.BasePage;

 

public abstract class Details extends BasePage {

 

 

   public abstract Celebrity getCelebrity();

   public abstract void setCelebrity(Celebrity c);

 

 

   public Format getDateFormat() {

      return new SimpleDateFormat("MMM d, yyyy");

   }

}

Now if you run the application everything should appear properly:

Before going further, we need to become familiar with a few theoretical concepts.

{mospagebreak title=Inversion of Control and Dependency Injection}

Inversion of Control (IoC) and Dependency Injection (DI) are some of the most popular concepts in software development these days, at least in the world of Java. However when I decided to find out what these words mean, I found that most of the available definitions are incomprehensible, while the concepts themselves are quite simple. So let me make yet another attempt to explain what IoC and DI actually mean.

Imagine you are staying in a five star hotel. Are you going to cook your breakfast or wash your shirts there? Probably not. You will trust the hotel to do all the low level work while you are doing something much more important like sunbathing or visiting local attractions. That’s quite a natural thing to do, but from the computer science point of view this approach is labeled "Inversion of Control."

In general, IoC means that you don’t want to deal with some low level basic functionality in your code (you don’t want to control that stuff yourself as you have more interesting things to do), so you leave it to a container (like that hotel) in which your code runs.

We have already dealt with IoC in Tapestry when we used just an abstract getter to tell the framework that we need to have a fully configured and properly maintained page property. Tapestry itself can be considered a container, although it would be more proper to say that Tapestry 4 is built on top of an IoC container named HiveMind.

HiveMind is a separate Apache project, also created by Howard Lewis Ship, the creator of Tapestry. We are going to deal with HiveMind configuration in the upcoming articles, but for now it is enough just to know that it exists. Arguably, HiveMind is more sophisticated than the much better known IoC container of Spring framework. By the way, we can easily use Spring beans in a Tapestry application. Strangely enough, it might be easier to get a Spring bean reference into Tapestry than into Spring itself! But I digress; this is a topic for a later article.

Now what about Dependency Injection? It is actually a variety of IoC, a more specialized version of it. Back in your five star hotel, you were very thirsty and consumed all the contents of your mini-bar. Does this mean that you will die from thirst the next evening? Not at all, the hotel (your container) will put (inject!) more drinks into your mini-bar. Similar to this, when we need some resource (dependency) in our Tapestry application, as we needed a reference to the Details page recently, we only provide a way to inject that dependency for us (perhaps using an annotation and an abstract getter), so that it is ready to use when we need it.

I hope the only question that still bothers you now is why such weird words are used: injection, inversion… See, some software developers like to think of themselves as not mere programmers but computer scientists. And what typically distinguishes any scientist from ordinary people? It is perhaps their tendency to use incomprehensible weird terms to label simple things. So here we are.

What comes next

We are going to create a page for adding a new celebrity. But before doing this we’ll want to make a DataSource stored in the application’s memory and easily accessible for all pages. In other words, we are going to make it an Application State Object (ASO).

See you in the next article.

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