PropertySelection and IPropertySelectionModel in Apache Tapestry

We already encountered PropertySelection in one of the previous articles so we know that it is a Tapestry component used to display a drop-down list, allowing the user to choose one of multiple options. You might think that the way in which options are provided to this component (through its model binding) is somewhat cumbersome when all you need to do is select one of a few strings. However, PropertySelection was designed with a great deal of power and flexibility in mind, and I hope that today you will appreciate this.


A downloadable zip file is available for this article.

Let’s add one more piece of functionality to the CelebrityCollector application. Say we want to select a "celebrity of the week." This will cause one of the celebrities in our collection to have his or her name displayed on the home page of the application. 

One of the ways we can do this is by having a drop-down list with celebrities’ names somewhere in the application, and a button that will assign a selected celebrity to be the "celebrity of the week," something like this:

And here is a mock up for this piece of the user interface:

<h4>Select a Celebrity of the Week:</h4>

  <form action="">

    <select>

      <option value="1">Angelina Jolie</option>

      <option value="2">Bill Gates</option>

    </select>

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

  </form>

I suggest placing this bit at the CelebritiesList page, just under the table. At a later stage we might add a special Admin page and move it there.

Let me briefly remind you how the HTML <select> element works. It has a number of <option> child elements that provide the options available in the drop-down list. The text inside an option, such as "Bill Gates" serves as a label for that option; this is exactly what the user sees.

The value attribute is optional. If it is not provided, the label will be used as the value for that option.

When the form containing the <select> is submitted, it is the value of the selected option which gets reported to the server. For instance, in the example above, if the user selected Angelina Jolie, what gets submitted to the server is the value "1."

On the server side we’ll have a property of the page class, and we’re going to store the celebrity, selected by the user, into that property. So on one hand we have the value "1" arriving from the user, and on the other hand we have a page property of the Celebrity type in which we are going to put the result of the selection. You can see a significant gap which we’ll need to fill in with some code converting the "1" into a Celebrity object filled with information on Angelina Jolie.

There is also another task at hand. We have a List filled with Celebrity objects on the server side, but on the client side we want to have a <select> with values and labels of its options properly filled in. That’s another piece of code to write. And every time we’ll want the user to select a Celebrity, we’ll have to write some code to enable this functionality, more or less the same code again and again. Wouldn’t it be great to delegate this work to a component?

{mospagebreak title=Configuring PropertySelection} 

As you already know, the component used to do this kind of job in Tapestry is PropertySelection. Let’s configure it in the CelebritiesList page. Here is the addition to the template:

<form action="" jwcid="@Form">

  <select jwcid="selectCelebrity">

    <option value="1">Angelina Jolie</option>

    <option value="2">Bill Gates</option>

  </select>

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

</form>

Let’s add a property to store the selected celebrity in the page class. We want some other page to be able to display the selected celebrity afterward, so it will be an ASO:

@InjectState("celebrityOfTheWeek")

public abstract Celebrity getCelebrityOfTheWeek();

You already know how to configure an ASO in hivemodule.xml, but here is the addition, just to help you:

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

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

    <create-instance

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

  </state-object>

</contribution>

Finally, we need to configure the component in the page specification:

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

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

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

</component>

The last piece of configuration is to provide a model, which should be an implementation of the IPropertySelectionModel interface. We don’t have a proper model yet, so let’s just return a null:

public IPropertySelectionModel getCelebrityModel() {

  return null;

}

This will not work however as we need to return a real model. To summarize what we have already done, you can see that the PropertySelection component per se is quite simple, it doesn’t take much effort to configure. It is the model that requires the most thought this collaboration, and we do need to write some code to implement it.

{mospagebreak title=Working on the Model}

Let’s begin by creating a new class, CelebrityModel, and let it implement the IPropertySelection interface:

public class CelebrityModel implements IPropertySelectionModel {

 

  /** Creates a new instance of CelebrityModel */

  public CelebrityModel() {

  }

}

At first, NetBeans will not recognize the interface, but press Alt-Shift-F, and it will add an appropriate import statement automatically. Now NetBeans will complain that the class doesn’t implement the methods of the interface, and there will be a little bulb to the left of the underlined line of code. Click that bulb, and the IDE will suggest adding the required methods. Let it do so, and you will see that five methods were added to the new class. We need to slightly modify the code to get rid of red underlining. Make it look like so:

public class CelebrityModel implements IPropertySelectionModel {

 

 

  public CelebrityModel() {

  }

 

  public int getOptionCount() {

    return 0;

  }

 

  public Object getOption(int i) {

    return null;

  }

 

  public String getLabel(int i) {

    return null;

  }

 

  public String getValue(int i) {

    return null;

  }

 

  public Object translateValue(String value) {

    return null;

  }

 

}

Now let’s modify this code to make it work with our celebrities, and learn what different methods do along the way.

First of all, we need to pass our list of celebrities to the model. This is convenient to do in the constructor. Add a private List property and modify the constructor to look like this:

private List celebrities;

 

public CelebrityModel(List celebrities) {

  this.celebrities = celebrities;

}

Now let me tell you about the getOptionCount(() method. It serves to tell the PropertySelection how many options it is going to display. All we need to do is return the size of the list of celebrities:

public int getOptionCount() {

  return celebrities.size();

}

The next method, getOption(int i), takes the index of an option and returns the object corresponding to this index. Say we’ve told PropertySelection in the previous method that there are four options to display. It will number them, starting from zero: 0, 1, 2, 3. Now it asks the model: which Celebrity object corresponds to, say, option 2? We can answer this question easily:

public Object getOption(int i) {

  return celebrities.get(i);

}

The next method, getLabel(int i), is equally simple. It answers a PropertySelection’s question like "What should be the label for option number 0?" Say we want to display the full names of celebrities as labels, in which case the answer will be:

public String getLabel(int i) {

  Celebrity c = (Celebrity) celebrities.get(i);

  return c.getFirstName() + " " + c.getLastName();

}

The next method, getValue(), answers a question like "What should contain the value attribute for option number 3?" The simplest possible approach is to use the index itself as a value — just convert it into String:

public String getValue(int i) {

  return Integer.toString(i);

}

The final method, translateValue(), will work when the user has made some selection and submitted the form. His or her choice will arrive at the server as the contents of the value attribute of the selected option. As we have specified in the previous method, this is going to be a string representation of the option’s index, like "0," or "1," or "2." The translateValue() method should define how to find the appropriate Celebrity object using the submitted value, which is quite simple in our case:

public Object translateValue(String value) {

  int index = Integer.parseInt(value);

  return celebrities.get(index);

}

Finally, we need to change the getCelebrityModel() method of CelebritiesList page so that it returns an instance of the just created CelebrityModel class:

public IPropertySelectionModel getCelebrityModel() {

  return new CelebrityModel(getCelebrities());

}

You can run the application and see that it works, and the drop-down list is filled with celebrities from the DataSource. When we press the Save button, the selected celebrity (i.e. a Celebrity object filled with his or here data) is stored as an Application State Object, but at the moment we can’t see that and can’t be sure of that. To complete what we’ve planned to do, we need to display the Celebrity of the Week at the Home page, and perhaps display that page after the selection was made.

Add the following piece of HTML to the contents of the Home.html file, wherever you see as appropriate:

<p><strong>Celebrity of the Week</strong>:

      <span jwcid="celebrityOfTheWeek">John Smith</span></p>

The component we are using here is an Insert. Let’s configure it in the Home.page specification:

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

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

</component>

And then add to the Home class a method returning the full name:

public String getFullName() {

  Celebrity c = getTheCelebrity();

  return c.getFirstName() + " " + c.getLastName();

}

As you see, we also need a method in the Home class which will give us a reference to the celebrityOfTheWeek ASO:

@InjectState("celebrityOfTheWeek")

public abstract Celebrity getCelebrityOfTheWeek();

Finally, after the Celebrity of the Week was selected at the CelebritiesList page, we want to show the Home page, and this turns our attention back to the CelebritiesList class. This class should have some listener method invoked when the user presses the Save button. We don’t have a listener yet, so let’s add it. Here is the addition to the CelebritiesList.html template:

<form action="" jwcid="@Form"

        listener="listener:onSaveCelebrityOfTheWeek">

  <select jwcid="selectCelebrity">

    <option value="1">Angelina Jolie</option>

    <option value="2">Bill Gates</option>

  </select>

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

</form>

And here is the listener method itself:

public String onSaveCelebrityOfTheWeek() {

  return "Home";

}

{mospagebreak title=One More Detail}

Is everything ready now? Not yet. Try to run the application, and when the Home page appears for the first time, you will see something that is not very nice:

AS soon as the application started, the Home page requested an instance of the Celebrity class to be injected into it as a celebrityOfTheWeek ASO. Tapestry created such an instance and gave it to the Home page. However, this instance is empty; all its properties, including firstName and lastName have their default values, null. Hence the result.

What we actually want is to show the celebrity of the week only if it was already chosen. This sounds like we need an If component. But what will be the criterion of whether the ASO was filled in with proper values or not? Let’s say the first name should have some value stored in it. All we need to do is surround the fragment of the Home.html template displaying the celebrity of the week with an If component, like so:

<div jwcid="@If" condition="ognl:theCelebrity.firstName != null">

  <p><strong>Celebrity of the Week</strong>:

    <span jwcid="celebrityOfTheWeek">John Smith</span></p>

</div>

Note that I am intentionally mixing together implicit and declared components. This might be not the best approach in a real life application, but here it gives me an opportunity to show you both ways. Also, you have a chance to develop a feeling for where your personal threshold between implicit and declared components lies.

If you run the application now, it should work properly. When the Home page is displayed for the first time, it has no information about the Celebrity of the Week on it. Go to the list of Celebrities, grab the glorious title attached to one of them, and you’ll see his or her name appearing on the Home page.

If you now navigate to the CelebritiesList page again, you’ll see that the celebrity you’ve just selected appears as a default choice in the drop-down list. This means that the connection between the PropertySelection component on the page and an ASO in the session on the server remains active.

{mospagebreak title=So what have we done?}

To summarize, after we have created a model (an implementation of IPropertySelectionModel), working with drop-down lists in a Tapestry application becomes very simple. All we need to display the appropriate options is to provide the created model to a PropertySelection component through its model binding.

And in order to put the result of the user’s selection into a page property or even straight into an ASO stored in the session, all we need to do is provide the familiar value binding. There is no code to write at all, even if the selection covers a very complicated object, perhaps retrieved from a database. All the code is written just once and encapsulated into the model; it then can be reused numerous times across multiple applications.

Writing an implementation of IPropertySelectionModel isn’t a big deal either. We just need to remember which question each of its five methods answers and provide an appropriate answer.

What comes next?

My initial plan for the next issue was to start a new big topic, namely custom components development. However, I have suddenly realized that there is another Tapestry component which resembles PropertySelection; that one is named Autocompleter. It is also one of the AJAX-enabled components that come with Tapestry 4.1, and I know that many of you are waiting impatiently for something about AJAX.

So in the next article we are going to touch on the AJAX side of Tapestry and maybe, if space will allow, cover one of the minor outstanding topics that has accumulated over time. And then, in the article after the next, we’ll unleash our creativity developing custom components.

Until the next article then.

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

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort