Introducing Simple Components in Apache Tapestry

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?

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

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.

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

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

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!

{mospagebreak title=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 simple HTML form with a text field to enter a word and a button to submit it. It might look like this:

And here is the HTML code for the Home page mock up:

<!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="">

          Enter the secret word:

          <input type="text"/>

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

      </form>

  </body>

</html>

The Secret page is going to be very simple. It will either greet the user if the word was guessed correctly or inform him or her of failure and provide a link to the Home page to go and try again. So the same page will contain two views but only one of them will be shown at a time. However, the mock up will display both views as it doesn’t contain any dynamic functionality yet:

Here is the HTML code for the Secret page mock up:

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

<html>

  <head>

    <title>Guess Result</title>

  </head>

  <body>

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

     

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

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

      <p><a href="">Go back and try again.</a></p>

  </body>

</html>

{mospagebreak title=Which components shall we need?}

Now let’s think about which components we are going to need on these two pages.

First of all, the text box for entering a secret word should obviously be a component as the page class will need to know which word was entered by the user. Page class will have a property to store the entered word. Imagine: the HTML page produced by the page class and sent to the user on the one hand, and the server where the page class lives on the other hand can be thousands of miles apart. And still, there is some sort of data connection between Tapestry components on the page and the corresponding properties of the page class.

When the HTML page is rendered for the first time to be shown to the user, Tapestry components will display the initial values of the properties connected to them. When the user has filled in the form and submitted it, the page class will receive the new values and put them into the properties in place of the initial values.

But, yes, to send new values to the server, some form should be submitted. So the text box component, to work properly, should be surrounded by a form component. Right now, we have an HTML form on the page mock up, so we need only to make it a Tapestry component.

All in all, we need two components for the Home page.

The Secret page is very simple; all its text is hard-coded. However, we shall need to define whether the secret word was guessed properly, and if it was, show the greeting. This is one component, a conditional one. If the word was wrong however, we need to display an alternative message. You will see that Tapestry employs for this purpose yet another component.

Finally, the link leading back to the Home page should be a component too. This is because in Tapestry you don’t need to know URLs corresponding to different pages and use them to create links. You only need to tell the framework which page you want to see when the link is clicked, and there is a simple component intended exactly for this purpose.

Having an idea now of what kind of functionality we are going to need on our pages, we can go to the Tapestry 4.1 website (http://tapestry.apache.org/tapestry4.1/) and check which components are available there.

We shall find that, quite conveniently, Tapestry has a TextField component for displaying text boxes. There is also a Form component to contain the TextField. Naturally, the component named If will check if some condition is true and show or hide accordingly anything surrounded by this component. There is also a component named Else that should be placed after an If component. It will show its contents when the condition of the If component evaluates to false. Finally, there is a PageLink component, and its purpose is to provide a link to another page of the Web application.

You see, Tapestry provides everything we need. In the next article, we will configure the components on the pages.

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

chat