An often-requested Tapestry feature is an easier, more Trails-ish / Rails-ish / Grails-ish way of editing domain objects that gets rid of a lot of the boilerplate typing required to create an edit form. The BeanForm is a simple solution to this common problem:

<span jwcid="@bf:BeanForm" bean="ognl:pojo" save="listener:save" delete="listener:delete"/>

Translations Welcome!

If you use BeanForm and would like the standard captions and messages translated into your language, feel free to translate this file and this file (a total of 6 strings). Then open a new feature request, attach the translated files, and they'll be included in the next release!

The above line of code gets you a form that:

  • calls your page's save() method when submitted for save
  • calls your page's delete() method when submitted for delete
  • gives you a TextField for each of the bean's string and numerical properties
  • gives you a Checkbox for each of the bean's boolean properties
  • gives you a DatePicker for each of the bean's date properties
  • gives you a PropertySelection for each of the bean's enum properties
  • gives you an Upload component for each of the bean's byte[] properties
  • automatically disables fields for read-only properties

If you are using Java Persistence API (EJB3) annotations and/or Hibernate Validator annotations, the BeanForm component also:

Some other important features:

  • EJB3 and Hibernate Validator support is optional and enabled automatically at runtime only when appropriate
  • you can use a BeanForm inside a Form component, in which case the BeanForm will not emit a second form tag
  • you can explicitly specify which properties to display, as well as their validation strings
  • when specifying properties explicitly, they may be recursive ("", for example)
  • you can override the input components used to edit properties
  • support for canceling and refreshing forms
  • support for localization

See the component reference page for more details, setup instructions and an exhaustive set of examples.

Goodbye RY, Hello DRY

The traditional Tapestry code required to achieve an equivalent form is about 10 lines of code per property. Each edited property needs a label:

<component id="descriptionLabel" type="FieldLabel">
    <binding name="field" value="component:descriptionField"/>

An input component:

<component id="descriptionField" type="TextField">
    <binding name="value" value="ognl:pojo.description"/>
    <binding name="validators" value="validators:required"/>
    <binding name="displayName" value="message:description"/>

And the HTML that glues it all together:

    <td><label jwcid="descriptionLabel">Description</label>:</td>
    <td><span jwcid="descriptionField" class="text"/></td>

Assuming a pretty standard bean with about 6 edited properties, that's probably going to be over 60 lines of boilerplate code.

Especially grating, however, is the duplication of information:

  • component IDs are usually based off the property names (description -> descriptionLabel, descriptionField, etc.)
  • components reference each other so the IDs have to match
  • you have to specify your property constraints two or even three times if you are using EJB3, Hibernate Validator and Tapestry validation

The worst part of this duplication of information is that all of it gets checked at runtime, not at compile time. If you reference an inexistent input component in a FieldLabel component, you don't find out until you try to access the page and Tapestry blows up. If you have a string property whose EJB3 length you change from 2000 to 1000, but you forget to change the Tapestry maxLength validation, you might not find out until users start complaning about truncated data in the database. Basically, the XML and HTML is the worst place possible to ignore the DRY (Don't Repeat Yourself) principle.

So don't repeat yourself, save your wrists the typing, save your brain the mindlessness, and save yourself 30 minutes of coding now and 2 hours of debugging a month from now. Or so we hope ;-)


You need the following in order to use the BeanForm component: