Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: fixed language param of code macro

...

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
languagejava
titlesrc/main/java/com/example/tutorial/entities/Address.java

package com.example.tutorial.entities;

import com.example.tutorial.data.Honorific;

public class Address
{
  public Honorific honorific;

  public String firstName;

  public String lastName;

  public String street1;

  public String street2;

  public String city;

  public String state;

  public String zip;

  public String email;

  public String phone;
}

We also need to define the enum type, Honorific:

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

package com.example.tutorial.data;

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

...

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

Code Block
languagexmlXML
titlesrc/main/resources/com/example/tutorial/pages/Index.tml (partial)XML

    <h1>Address Book</h1>

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

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

Code Block
languagexmlXML
titlesrc/main/resources/com/example/tutorial/pages/address/CreateAddress.tmlXML

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

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

</html>

And the corresponding class:

Code Block
languagejava
titlesrc/main/java/com/example/tutorial/pages/address/CreateAddress.java

package com.example.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 (com.example.tutorial.pages.address.CreateAddress) and just stripped out the redundant suffix

Wiki Markup
{footnote}Tapestry also checks for redundant prefixes. In addition, the long name, "address/CreateAddress" is also valid.{footnote}

.

 

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. 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.

...

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

Code Block
languageXMLxml
titleCreateAddress.tml (partial)XML

  <t:beaneditform object="address"/>

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

Code Block
languagejava
titleCreateAddress.java (partial)java

  @Property
  private Address address;

...

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

Wiki Markup
{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).{footnote}

.

 

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

...

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
languagexml
titleCreateAddress.tml (partial)xml

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

...

No Format
titlesrc/main/resources/com/example/tutorial/pages/address/CreateAddress.properties

street1-label=Street 1
street2-label=Street 2
email-label=E-Mail
zip-label=Zip Code
phone-label=Phone Number

...

We can also customize the options in the drop down list. All we have to do is add some more entries to the message catalog matching the enum names to the desired labels. Update CreateAddress.properties and add:

No Format

MR=Mr.
MRS=Mrs.
DR=Dr.

Notice that we don't have to include an option for MISS, because that is converted to "Miss" anyway. You might just want to include it for sake of consistency ... the point is, each option label is searched for separately.

...

That button is a component within the BeanEditForm component. It's not a property, so we can't just put a message into the message catalog, the way we can with the fields. Fortunately, the BeanEditForm component includes a parameter expressly for re-labeling the button. Simply change the CreateAddress component template:

Code Block
XML
languageXMLxml

  <t:beaneditform submitlabel="Create Address" object="address"/>

...

Here we want to reference a message from the catalog, so we use the "message:" prefix:

Code Block
XML
languageXMLxml

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

And then define the submit-label key in the message catalog:

No Format

submit-label=Create Address

...

Edit the Address entity, and update the lastName, firstName, street1, city, state and zip fields, adding a @Validate annotation to each:

Code Block
languagejava

  @Validate("required")
  public String firstName;

...

So ... how about some more interesting validation than just "required or not". Tapestry has built in support for validating based on field length and several variations of field value, including regular expressions. Zip codes are pretty easy to express as a regular expression.

Code Block
languagejava

  @Validate("required,regexp=^\\d{5}(-\\d{4})?$")
  public String zip;

...

Fortunately, it's easy to customize validation messages. All we need to know is the name of the property ("zip") and the name of the validator ("regexp"). We can then put an entry into the CreateAddress message catalog:

No Format

zip-regexp-message=Zip Codes are five or nine digits.  Example: 02134 or 90125-1655.

...

Let's go one step further. Turns out, we can move the regexp pattern to the message catalog as well. If you only provide the name of the validator in the @Validate annotation, Tapestry will search the containing page's message catalog of the constraint value, as well as the validation message. The constraint value for the regexp validator is the regular expression to match against.

Code Block
languagejava

  @Validate("required,regexp")
  public String zip;

Now, just put the regular expression into the CreateAddress message catalog:

No Format

zip-regexp=^\\d{5}(-\\d{4})?$
zip-regexp-message=Zip Codes are five or nine digits.  Example: 02134 or 90125-1655.

...

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.

...

 

Wiki Markup
{display-footnotes}
Wiki Markup
{scrollbar}