Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
{scrollbar}

Chapter 4: Forms in Tapestry

In the previous chapters, we saw how Tapestry can handle simple links, even links that pass information in the URL. In this chapter, we'll see how Tapestry can do the same, and quite a bit more, for HTML forms.

...

We'll start with the entity data, a simple object to store the information we'll need. These classes go in an entities sub-package. Unlike the use of the pages sub-package (for page component classes), this is not enforced by Tapestry; it's just a convention (but as we'll see shortly, a handy one).

Tapestry treats public fields as if they were JavaBeans properties; since the Address object is just "dumb data", there's no need to get carried away writing getters and setters. Instead, we'll define an entity that is all public fields:

Code Block
titlesrc/main/java/orgcom/apache/tapestry5example/tutorial/entities/Address.java
package orgcom.apache.tapestry5example.tutorial.entities;

import orgcom.apache.tapestry5example.tutorial.data.Honorific;

public class Address
{
  privatepublic Honorific honorific;

  privatepublic String firstName;

  privatepublic String lastName;

  privatepublic String street1;

  privatepublic String street2;

  privatepublic String city;

  privatepublic String state;

  privatepublic String zip;

  privatepublic String email;

  privatepublic String phone;

  public String getCity()
  {
    return city;
  }

  public String getEmail()
  }

We also need to define the enum type, Honorific:

Code Block
titlesrc/main/java/com/example/tutorial/data/Honorific.java

package com.example.tutorial.data;

public enum Honorific
{
  MR, MRS, returnMISS, email;
  }

  public String getFirstName()
  {
    return firstName;
  }

  public Honorific getHonorific()
  {
    return honorific;
  }

  public String getLastName()
  {
    return lastName;
  }

  public String getPhone()
  {
    return phone;
  }

  public String getState()
  {
    return state;
  }

  public String getStreet1()
  {
    return street1;
  }

  public String getStreet2()
  {
    return street2;
  }

  public String getZip()
  {
    return zip;
  }

  public void setCity(String city)
  {
    this.city = city;
  }

  public void setEmail(String email)
  {
    this.email = email;
  }

  public void setFirstName(String firstName)
  {
    this.firstName = firstName;
  }

  public void setHonorific(Honorific honorific)
  {
    this.honorific = honorific;
  }

  public void setLastName(String lastName)
  {
    this.lastName = lastName;
  }

  public void setPhone(String phone)
  {
    this.phone = phone;
  }

  public void setState(String state)
  {
    this.state = state;
  }

  public void setStreet1(String street1)
  {
    this.street1 = street1;
  }

  public void setStreet2(String street2)
  {
    this.street2 = street2;
  }

  public void setZip(String zip)
  {
    this.zip = zip;
  }
}

It's just a collection of getter and setter methods. We also need to define the enum type, Honorific:

Code Block
titlesrc/main/java/org/apache/tapestry5/tutorial/data/Honorific.java

package org.apache.tapestry5.tutorial.data;

public enum Honorific
{
  MR, MRS, MISS, DR
}DR
}

Address Pages

We're probably going to create a few pages related to addresses: pages for creating them, for editing them, for searching and listing them. We'll create a sub-folder, address, to hold them. Let's get started on the first of these pages, "address/Create" (that's the real name, including the slash — we'll see in a minute how that maps to classes and templates).

First, we'll update the Index.tml template, to create a link for creating a to the new page:

Code Block
XML
XML
titlesrc/main/webapp/resources/com/example/tutorial/pages/Index.tml (partial)
    <h1>Address Book</h1>

    <ul>
      <li><t:pagelink page="address/create">Create new address</t:pagelink></li>
    </ul>

Now we need the page, let's address/Create page; lets start with an empty shell, just to test our navigation.

Code Block
XML
XML
titlesrc/main/webapp/address/CreateAddress.tml/resources/com/example/tutorial/pages/address/CreateAddress.tml

<html t:type="layout" title="Create New Address"
 
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
  <head>
    <title>Create New Address</title>
  </head>
  <body>

    <h1>Create New Address</h1>

    <em>coming soon ...</em>

  </body>
</html>

And the corresponding class:

Code Block
titlesrc/main/java/orgcom/apacheexample/tapestry5tutorial/tutorial1tutorial/pages/address/CreateAddress.java
package orgcom.apache.tapestry5example.tutorial.pages.address;

public class CreateAddress
{

}

So ... why is the class named "CreateAddress" and not simply "Create"? Actually, we could have named it "Create", and the application would still work, but the longer class name is equally valid. Tapestry noticed the redundancy in the class name : org.apache.tapestry5(com.example.tutorial.pages.address.CreateAddress) and just stripped it outout the redundant suffix

Footnote

Tapestry also checks for redundant prefixes. In addition, the long name, "address/CreateAddress" is also valid.

.

Eventually, your application will probably have more entities: perhaps you'll have a "user/Create" page and a "payment/Create" page and an "account/Create" page. Now, you You could have a bunch of different classes all named Create spread across a number of different packages. That's legal Java, but it isn't ideal. You may find yourself accidentally editing the Java code for creating an Account when your really want to be editing the code for creating a Payment.

Tapestry is encouraging you to use a more descriptive name: CreateAddress, not just Create, but it isn't making you pay the cost (in terms of longer, uglier URLs). The URL to access the page will still be http://localhost:8080/tutorial1/address/create.

Info

...

Index pages work in folders as well. A class named

...

com.

...

example.tutorial.pages.address.AddressIndex would be given the name "address/Index". However, Tapestry has special rules for pages named "Index" and the

...

rendered URL would be http://localhost:8080/tutorial1/address/. In other words, you can place Index pages in any folder and Tapestry will build a short URL for that page ... and you don't have to keep naming the classes Index (it's confusing to have many classes with the same name, even across multiple packages); instead, you can name each index page after the package that contains it. Tapestry users a smart convention to keep it all straight and generate short, to the point URLs.

Using the BeanEditForm

...

Component

Time to start putting together the logic for this form. Tapestry has a specific component for client-side Forms: the Form component, as well as components for form . In fact, let's use a magic trick ... the BeanEditForm component. This component can analyze a class and create an editor UI for it all in one go. Let's give it a trycontrols, such as Checkbox and TextField. We'll cover those in a bit more detail later .. instead, we're again going to let Tapestry do the heavy lifting for us, via the BeanEditForm component.

Add the following to the CreateAddress template (replacing the "coming soon ..." message):

Code Block
XML
XML
titleCreateAddress.tml (partial)
  <t:beaneditform object="address"/>

And match that up with a property in the CreateAddress class:

Code Block
java
java
titleCreateAddress.java (partial)
  @Property
  private Address address;

When you refresh the page, you'll see the following:

Image Removed

Initial version of the create address form

you refresh the page, you'll see the following:

Image AddedThere have been minor changes to the default CSS since this screenshot was taken; for example, the labels are a bit wider now. In addition, the Honorific field (being optional) would include a blank option, rather than the first real selection, "Mr".

Tapestry's done quite a bit of work here. It has created a form that includes a field for each property. Further, its seen that the honorific property is an enumerated type, and presented that as a drop-down list.

In addition, Tapestry has converted the property names ("city", "email", "firstName") to user presentable labels ("City", "Email", "First Name"). In fact, these are <label> elements, so clicking a label with the mouse will move the input cursor into the corresponding field.

This is an awesome start; it's a presentable interface, quite nice in fact for a few minute's work. But it's far from perfect; let's get started with some customizations.

Changing field order

It looks like the fields are being displayed in alphabetical order, ("city" first, "zip" last). That's not quite the reality, however: If you check the listing for the Address class, you'll see that the getter and setter methods are in alphabetical order (care of Eclipse, which generated all those methods from the fields).

The BeanEditForm works in the order in which the getter methods are defined in the class. Let's reorder them into a more reasonable order:

  • honorific
  • firstName
  • lastName
  • street1
  • street2
  • city
  • state
  • zip
  • email
  • phone
    (This is also the order of in which the fields are defined.)

Because Address is not a component class, it is necessary to restart Jetty to see the effects of these changes.

Once Jetty is restarted, hit the browser's refresh button to see the fields in the correct order:

Changing Field Order

The BeanEditForm must guess at the right order to present the fields; for public fields, they end up in alphabetical order

Footnote

For standard JavaBeans properties, the BeanEditForm default is in the order in which the getter methods are defined in the class (it uses line number information, if available).

.

A better order for these fields is the order in which they are defined in the Address class:

  • honorific
  • firstName
  • lastName
  • street1
  • street2
  • city
  • state
  • zip
  • email
  • phone

We can accomplish this by using the reorder parameter of the BeanEditForm component, which is a comma separated list of property (or public field) names:

Code Block
xml
xml
titleCreateAddress.tml (partial)

  <t:beaneditform object="address"
    reorder="honorific,firstName,lastName,street1,street2,city,state,zip,email,phone" />

Image Added

Ultimately, however we will be using the BeanEditForm with the Address entity on several pages, and don't want to have to reiterate that list of properties every time. Image Removed
Create address form with fields in proper order

Customizing labels

Tapestry makes it pretty easy to customize the labels used on the fields. It's just a matter of creating a message catalog for the page.

...

By now you are likely curious about what happens after the form submits successfully (without validation errors), so that's what we'll focus on next.

...

Footnotes Display

Wiki Markup
{scrollbar}