Apache Tapestry: ASO and More Components

We shall continue working on the CelebrityCollector application. The next step is to create a page for adding a new celebrity. While we create that page, I will introduce you to three more components that will add new capabilities to our application and help us get around certain problems.

There is one inconvenience that hinders our progress: the DataSource object that we use to obtain either the whole list of celebrities or a selected celebrity is currently stored as a property of the CelebritiesList page. This is not convenient as we are planning to access this object from different pages, and  the changes we might make to it on one page should be immediately reflected by another page.

In other words, the DataSource should be stored somewhere, we don’t care where, but it should be readily available for our code whenever we need it. If you remember our discussion of Inversion of Control and Dependency Injection in the previous article, you can probably guess that we are going to use some kind of IoC here. And since we want the DataSource delivered to us (or injected, like those drinks to our mini-bar) this should be a DI indeed.

You might also remember that the system that takes care of IoC issues in Tapestry 4 is HiveMind. All we need to do is create a HiveMind configuration file, hivemodule.xml, and specify in it that we are going to need an instance of the DataSource class. The first time we request this instance at runtime, Tapestry (well, HiveMind, behind the scenes) will create it for us and store it in some kind of memory. Whenever we request it afterward, the same instance will be given to us (you can see an implementation of the so-called Singleton design pattern here).

Sounds good, now let’s actually do all this.

{mospagebreak title=Creating hivemodule.xml}

Under WEB-INF folder, we need to create a new XML file named hivemodule.xml. Right-click WEB-INF in the Projects view, select New > File/Folder…, then select ‘XML’ in Categories and ‘XML’ for file types. Enter ‘hivemodule’ as the file name on the next page of the wizard; leave the selected ‘Well-formed Document’ for Document Type.

Now change the contents of the new file to look like this:

<?xml version="1.0" encoding="UTF-8"?>

 

<module id="com.devshed.tapestry.celebrities" version="1.0.0">

  <contribution

       configuration-id="tapestry.state.ApplicationObjects">

    <state-object name="dataSource" scope="session">

      <create-instance

                class="com.devshed.tapestry.celebrities.DataSource"/>

    </state-object>

  </contribution>

</module>

Let’s see what we have here. The <module> element is the root of hivemodule.xml; we shall have it in every file like this. Note that the version attribute should look exactly as shown, i.e. it must contain three numbers separated by periods, or the application won’t start.

The <contribution> element tells HiveMind that we are going to contribute something of our own to its already existing extensive list of services and objects. The  tapestry.state.ApplicationObjects value for configuration ID explains what we are going to do with this object; it indicates an ASO.

The <state-object> element gives the ASO its name under which we shall be requesting it in the pages. Also we have specified that the scope for this ASO is session. Session is basically a piece of memory associated specifically with the given user; nobody else can access this memory. So in our case there will be a separate instance of DataSource for each user, which makes sense to me, as different collectors will have different collections.

Alternatively, we could use an application scope, and in that case all the users would share the same DataSource.

Finally, the <create-instance> element specifies which class to instantiate to create the desired ASO.

Now that we made available the DataSource as an Application State Object, we don’t need to store its instance in the CelebritiesList page class. Remove the line of code where we’ve instantiated it before:

private DataSource dataSource = new DataSource();

And replace it with the following code:

@InjectState("dataSource")

public abstract DataSource getDataSource();

These two lines of code can be used in any page and any component of the application to obtain a reference to the DataSource. All that remains now is to replace all the references to the removed dataSource property with invocations of the newly created method. Here is the completed code for the DependenciesList class:

public abstract class CelebritiesList extends BasePage {

   

    @InjectState("dataSource")

    public abstract DataSource getDataSource();

   

    @InjectPage("Details")

    public abstract Details getDetailsPage();

   

    public List getCelebrities() {

        return getDataSource().getCelebrities();

    }

   

    public IPage onShowDetails(int id) {

       

        Celebrity celebrity = getDataSource().getCelebrityById(id);

 

        Details nextPage = getDetailsPage();

        nextPage.setCelebrity(celebrity);

       

        return nextPage;

    }

}

Now it is the time to create a page for creating a new Celebrity.

{mospagebreak title=AddCelebrity page}

You have already had substantial experience with creating Tapestry pages, so my instructions will be brief: create all three files, AddCelebrity.html, AddCelebrity.page and AddCelebrity.java.

As we shall certainly need to use the DataSource, create an abstract getter for it with an appropriate annotation, as we did this in the previous section.

Now let’s work on the mock up. Let the page look similar to this:

If you are curious as to why I’ve created these ‘Gender’ radio buttons, the purpose is to decide how to spell the occupation in case the ‘Actor/Actress’ option was selected. If it is a man, he will be recorded as an ‘Actor,’ and vice versa. It’s not the best design, I agree, but it allows me to introduce you to two components: RadioGroup and PropertySelection.

Here is the source for the mock up:

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

 

<html>

  <head>

    <title>Add New Celebrity</title>

  </head>

  <body>

      <h1>Add New Celebrity</h1>

     

      <form>

          <table cellpadding="5">

              <tr>

                  <td>First Name:</td>

                  <td>

                      <input type="text"/>

                  </td>

              </tr>

              <tr>

                  <td>Last Name:</td>

                  <td>

                      <input type="text"/>

                  </td>

              </tr>

              <tr>

                  <td>Date of Birth:</td>

                  <td>

                      <input type="text"/>

                  </td>

              </tr>

              <tr>

                  <td colspan="2">

                      <fieldset>

                          <legend>Gender</legend>

                          <input type="radio" name="gender"

                                  value="M"> Male &nbsp;&nbsp;

                          <input type="radio" name="gender"

                                  value="F"> Female

                      </fieldset>

                  </td>

              </tr>

              <tr>

                  <td>Occupation:</td>

                  <td>

                      <select>

                          <option>Actor/Actress</option>

                          <option>Wine-maker</option>

                          <option>Programmer</option>

                      </select>

                  </td>

              </tr>

              <tr>

                  <td align="center" colspan="2">

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

                  </td>

              </tr>

          </table>

      </form>

  </body>

</html>

{mospagebreak title=Creating the template with new components}

Now let’s convert this mock up into a Tapestry template. Along with the already mentioned RadioGroup and PropertySelection, we are going to use yet another new component, DatePicker. These components are somewhat more complicated than those that you’ve used before, so I am going to introduce and configure them one by one. The RadioGroup is the simplest of the three, so let’s begin from it.

Here is the piece of markup we are going to deal with:

<input type="radio" name="gender" value="M"/> Male &nbsp;&nbsp;

<input type="radio" name="gender" value="F"/> Female

The RadioGroup itself serves as a container for a number of Radio components, so the first task is to convert the existing <input type=”radio”> elements into Radio components. This is very easy to do, and in many cases these components will be defined implicitly:

<input type="radio" name="gender" value="M"

                               jwcid=”@Radio”/> Male &nbsp;&nbsp;

<input type="radio" name="gender" value="F" jwcid=”@Radio”/> Female

The next step is to surround these two with a Tapestry component and then define that component as a RadioGroup. Here is the addition to the template:

<div jwcid="gender">

  <input type="radio" name="gender" value="M"

                 jwcid="@Radio"/> Male &nbsp;&nbsp;

  <input type="radio" name="gender" value="F" jwcid="@Radio"/> Female

</div>

And here is the addition to the page specification:

<component id="gender" type="RadioGroup">

    <binding name="selected" value="gender"/>

</component>

Here we have ensured that when the form is submitted, the value of the selected Radio component is passed to the page class. Finally, declare that you need a property named ‘gender’ in the page class:

@InitialValue("literal:F")

public abstract String getGender();

We’ve provided an initial value so that Tapestry knew which of the radio buttons should be selected by default.

And of course, the form surrounding the components should be declared as a Tapestry Form component:

<form jwcid="@Form">

At this point, we might want to test whether the page runs properly, but we need some link to access it. It will be probably most natural to place such a link at the CelebritiesList page, straight under the table:

<p><a href="" jwcid="@PageLink" page="AddCelebrity">

   Add another celebrity

</a></p>

Run the application, click on the new link, and you should see the AddCelebrity page. Try to change the value for gender and press the Submit button. Nothing exciting happens so far, but the fact that everything works smoothly confirms that all components were configured properly – otherwise Tapestry would complain.

{mospagebreak title=Continuing to configure the application}

Before going further, let’s configure TextField components for the first and last names. This is easy; you have already done this in the previous application, so I will simply show the relevant pieces of code. In the page template:

<input type="text" jwcid="firstName"/>

<input type="text" jwcid="lastName"/>

In the page specification:

<component id="firstName" type="TextField">

    <binding name="value" value="firstName"/>

</component>

<component id="lastName" type="TextField">

    <binding name="value" value="lastName"/>

</component>

And in the page class:

public abstract String getFirstName();

public abstract String getLastName();

If you run the application again, you will notice that whatever you enter for the names and whichever gender you selected, these values are retained after the form was submitted and the page redisplayed. This is very simple, but it might be worth thinking about. The values we have entered were delivered to the page class and put into its properties, and when the page was redisplayed after form submission, Tapestry checked which values are stored in the properties of the page class and put those values into the components connected to those properties.

Now, let’s configure the next component, the one used to select an occupation for the newly added celebrity. Here is its mock up:

<select>

   <option>Actor/Actress</option>

   <option>Wine-maker</option>

   <option>Programmer</option>

</select>

To display a dynamic version of an HTML <select> element in a Tapestry application, we use the PropertySelection component. Mark this <select> with a Tapestry ID:

<select jwcid=”occupation”>

and then configure it in the page specification:

<component id="occupation" type="PropertySelection">

    <binding name="model" value="occupationsModel"/>

    <binding name="value" value="occupation"/>

</component>

As you can see, this component has two bindings, model and value. As for the value, we are familiar with it from the other components. This binding will pass to the page class the value selected by the user, and when the page is rendered for the first time, it will provide the initial value for the component. Let’s configure the appropriate property in the page class:

public abstract String getOccupation();

The model binding is new to us. Basically, it provides to the component the options to display, but the way how it does this might seem a little bit cumbersome at first. Here is the method that the occupation component will expect to find in the page class:

public IPropertySelectionModel getOccupationsModel() {

        // Some code that creates and returns an implementation

        // of IPropertySelectionModel interface

The return value, IPropertySelectionModel, is an interface that has several methods in it, and Tapestry provides a few implementations of this interface. Today we shall use the simplest implementation, but in one of the upcoming parts of the tutorial I am going to show how to unleash the full power of IPropertySelectionModel and explain the logic behind this seemingly cumbersome but potentially very flexible way of providing the options to display. For now, just add to the page class the following method:

public IPropertySelectionModel getOccupationsModel() {

    String[] occupations = {"Actor/Actress",

       "Wine-maker", "Programmer"};

    return new StringPropertySelectionModel(occupations);

}

We have simply created an array of strings and then used it to create an instance of StringPropertySelectionModel, the simplest of IPropertySelectionModel implementations.

Run the application and it should work properly. Well, at the first glance the AddCelebrity page looks exactly as its mock up, but if you select some occupation other than the first, and then submit the form, the selected value will be redisplayed. This this means that the page in your browser and the page class on the server are speaking to each other.

Have a look at the source of the page in your browser, and you will see that the <select> for specifying the occupation was rendered like this:

<select name="occupation" id="occupation">

  <option value="0">Actor/Actress</option>

  <option value="1">Wine-maker</option>

  <option value="2">Programmer</option>

</select>

You can see that each occupation has a label, displayed to the user, and a value associated with that label. This is something we are going to explore soon, but for today we have already covered enough ground.

What comes next

In the next part of the tutorial we are going to complete the AddCelebrity page by adding a DatePicker component to it, and also some code to actually create a new Celebrity object and “record” it into the DataSource.

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

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort