Provides an HTML form allowing the viewing, editing, saving and deleting of standard Java beans.
Name | Type | Direction | Required | Default | Description |
---|---|---|---|---|---|
bean | Object | in | yes | - | The Java bean to be displayed by the component; any old POJO. The bean specified must be marked persistent in the page specification. |
properties | String | in | no | - | A comma-delimited list of the names of the properties which you would like the form to display. The property names may be recursive, for example "owner.address.city.name". If this parameter is omitted the form will show all bean properties. You can also override the default input types used for each property by placing an equals sign (=) and an input type name after each property name. For example, "name,age,email" might become "name=TextArea,age=Insert,email". The available input types are TextField, TextArea, Checkbox, DatePicker, Upload and Insert. You can also specify validation strings here between squiggly brackets, so for example "name,age,email" might become "name{required},age,email{required,email}", and "name=TextArea,age=Insert,email" might become "name=TextArea{required},age=Insert,email{required,email}". In most cases you will want to either omit this parameter, or specify a list of properties but let the BeanForm figure out the appropriate input types and validation. This is especially true if you are using EJB3 and/or Hibernate Validator annotations on your beans. |
exclude | String | in | no | - | A comma-delimited list of the names of the properties to exclude from the form. Use this parameter rather than the properties parameter when you want to quickly show all properties except a few -- like an ID or an optimistic locking version number. |
cacheProperties | boolean | in | no | true | Whether or not you want to cache bean properties that have been discovered via bean introspection. If set to false, the bean properties will be rediscovered each time the containing page is rendered. The only reason to set this parameter to false is if you are using a single BeanForm to edit many different types of objects and/or the properties parameter can change between requests. |
element | String | in | no | table | The element to render. |
save | IActionListener | in | no | - | The page listener to invoke to save the Java bean being edited. If omitted, the user is not given the save option. |
delete | IActionListener | in | no | - | The page listener to invoke to delete the Java bean being edited. If omitted, the user is not given the delete option. |
method | String | in | no | post | Same functionality as the Form component's method parameter. |
success | IActionListener | in | no | - | Same functionality as the Form component's success parameter. |
cancel | IActionListener | in | no | - | Same functionality as the Form component's cancel parameter. If omitted, the user is not given the cancel option. |
refresh | IActionListener | in | no | - | Same functionality as the Form component's refresh parameter. If omitted, the user is not given the refresh option. |
listener | IActionListener | in | no | - | Same functionality as the Form component's listener parameter. |
stateful | boolean | in | no | true | Same functionality as the Form component's stateful parameter. |
delegate | IValidationDelegate | in | no | default instance | Same functionality as the Form component's delegate parameter. |
clientValidationEnabled | boolean | in | no | false | Same functionality as the Form component's clientValidationEnabled parameter. |
focus | boolean | in | no | true | Same functionality as the Form component's focus parameter. |
scheme | String | in | no | - | Same functionality as the Form component's scheme parameter. |
port | Integer | in | no | - | Same functionality as the Form component's port parameter. |
updateComponents | String[], Collection | in | no | - | Same functionality as the Form component's updateComponents parameter. Only available in Tapestry 4.1. |
json | boolean | in | no | false | Same functionality as the Form component's json parameter. Only available in Tapestry 4.1. |
async | boolean | in | no | false | Same functionality as the Form component's async parameter. Only available in Tapestry 4.1. |
Body: allowed
Informal Parameters: allowed
Download the BeanForm .jar file and add it to your container's classpath.
Download the Tapestry-Prop .jar file and add it to your container's classpath.
Be sure to include the following line in your .application file:
<library id="bf" specification-path="/net/sf/beanform/BeanForm.library"/>
Also please make sure that any beans you pass to the BeanForm component are marked persistent in the page specification:
<page-specification> ... <property name="pojo" persist="session"/> ... </page-specification>
Finally, you will need to provide message catalogs with mappings keyed on the beans' property names:
name=Name description=Description dateSold=Date Sold ...
name=Nombre description=Descripción dateSold=Fecha de Venta ...
Working examples of all the usage scenarios described below are available in the example webapp.
First an example of some basic usage. The code below assumes you have defined a validation bean called validationDelegate and that your page has save() and delete() methods. Since we haven't specified the properties parameter, the BeanForm component will discover our bean's properties dynamically via bean introspection. Properties that are read-only (no setter method) will have their inputs disabled. The generated form will have two buttons: a Save button and a Delete button. If we had specified refresh and delete listeners (see the parameter table above) the generated form would also contain buttons for these actions.
If the bean properties are annotated with the EJB3 Column or Basic annotations or any of the Hibernate Validator annotations, the BeanForm will automatically add Tapestry validation strings where appropriate, as well as using TextArea components (instead of TextField components) for strings whose length is over 255.
<form jwcid="@bf:BeanForm" bean="ognl:pojo" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete"/>
Our second example builds off the first one. The only difference is that we no longer want the BeanForm to dynamically discover the properties that need to be displayed: maybe we don't want all the properties to be shown, or maybe we want to control their order of appearance. Whatever the reason, we just add the properties parameter to the BeanForm. The below code assumes your Java bean has a name property, a description property, and a notes property.
<form jwcid="@bf:BeanForm"
bean="ognl:pojo"
properties="literal:name{required,maxLength=255},description{maxLength=255},notes"
delegate="bean:validationDelegate"
clientValidationEnabled="ognl:true"
focus="ognl:true"
save="listener:save"
delete="listener:delete"/>
If you want to display all properties except two or three, you can use the exclude parameter instead of the properties parameter. The exclude parameter allows you to specify properties that you don't want the BeanForm component to display. This feature is most useful when you want to show all bean properties except a few -- like an ID or an optimistic locking version number.
In the following example we demonstrate the two ways in which you can override the type of input component used to edit a property.
The easiest way is to specify the component type in the properties parameter. We use this method below to use a TextArea to edit the name property, rather than the TextField that it would usually receive as a result of being a short string. The input types available when using this method are TextField, TextArea, Checkbox, DatePicker and Insert.
The second way to override the type of input component used to edit a property is via the use of carefully named components in the page template. This method is used below with the notes property. Usually the BeanForm would use a TextArea component for the notes property because it is a long string. The code below allows us to edit this property using a custom component called MyCustomComponent. This override method should only be used when the first method will not suffice (i.e. you want to use a non-standard input component).
<form jwcid="myBeanForm@bf:BeanForm" bean="ognl:pojo" properties="literal:name=TextArea,description,notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete"/> <div jwcid="myBeanForm_notes_BeanFieldBlock@Block"> <input jwcid="myBeanForm_notes_BeanField@MyCustomComponent" value="ognl:pojo.notes" displayName="message:notes"/> </div>
One common customization is the mixing of fields bound to bean properties with custom fields bound to page properties. In this example, we want to add a row with a file upload component to the form. This file upload component is bound to a page property, not a bean property.
There are two ways to achieve this. The easiest and most flexible method is to use pseudo properties, which are fake properties that you can add to the properties parameter. Pseudo property names are annotated with an initial # character and must have corresponding fields defined in the template or page specification, using the same format as the input override mechanism we saw in the previous example. The following example assumes that your page has a setUploadFile(IUploadFile) method:
<form jwcid="myBeanForm@bf:BeanForm" bean="ognl:pojo" properties="literal:name{required,maxLength=255},#file,description{maxLength=255},notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete"> <div jwcid="myBeanForm_file_BeanFieldBlock@Block"> <input jwcid="myBeanForm_file_BeanField@Upload" file="ognl:uploadFile" displayName="message:file"/> </div>
Another way to add our extra field is to give the BeanForm component a body, which will be rendered below the fields for the bean properties, but above the save / delete / cancel / refresh buttons:
<form jwcid="@bf:BeanForm" bean="ognl:pojo" properties="literal:name{required,maxLength=255},description{maxLength=255},notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete"> <tr> <td class="bf_Left">File:</td> <td class="bf_Right"><input jwcid="file@Upload" file="ognl:uploadFile" displayName="message:file"/></td> </tr> </form>
You may override bindings on the individual input fields by specifying informal parameters on the BeanForm component whose name is of the format "[property-name]_[binding-name]", for example "comments_disabled" to set the disabled binding of the input field for the comments property. You can omit the property name in order to apply the binding to all property input fields -- setting "_disabled" to true would disable all input fields.
A special case of binding override is when you specify an override named [property-name]_model which is bound to an IPropertySelectionModel instance. In this case the BeanForm component will guess that you want to use a PropertySelection component to edit the property, and will use this informal parameter as the PropertySelection's model. You might want to do this either to limit choices for a particular property, or because you have a x-to-1 relationship in your domain objects.
In the example below we specify models for the owner and manufacturer properties. If the page class has getOwnerPSM() and getManufacturerPSM() methods that return IPropertySelectionModel instances, these two properties will receive dropdown select boxes whose data is supplied by the models returned by these methods. In addition, we have disabled the input field for the name property and given the price property's input field a title attribute. Finally, we have applied a (hideous) CSS style to all the input fields: a dashed gray line.
<form jwcid="@bf:BeanForm" bean="ognl:pojo" properties="literal:name,description,notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete" owner_model="ognl:ownerPSM" manufacturer_model="ognl:manufacturerPSM" name_disabled="ognl:true" price_title="literal:this is a title" _style="literal: border: 2px dashed gray"/>
More advanced use cases might call for binding overrides that vary according to the property. For example, instead of setting the title for just one property like we do above, we might want to set the titles for all the properties. If the keys in our message catalog are of the format [property-name]_title and our BeanForm component's ID is bf, we can accomplish this with the following binding:
_title="ognl: messages.getMessage( components.bf.property.name + '_title' )"
The property variable highlighted above is an instance of BeanProperty.
You can also use binding overrides on the save, cancel, refresh and delete buttons by using the identifiers save, cancel, refresh and delete:
<form jwcid="@bf:BeanForm" bean="ognl:pojo" properties="literal:name,description,notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" cancel="listener:cancel" refresh="listener:refresh" delete="listener:delete" save_value="message:save" cancel_value="message:cancel" refresh_disabled="ognl:true" delete_disabled="ognl:true"/>
Note that all four buttons are instances of the Submit component. Consult its documentation for information on the available bindings.
Sometimes the binding override method of button customization described above will not be sufficient for your needs: you may want to add extra buttons like "Print" or "Clear", change the order of the buttons, add advanced logic, etc. In these cases you may use carefully named components, similar to the input override functionality described in the second half of section 3. This method consists of adding a Block component whose id is of the format "[beanform-id]_Buttons" to your page template. This custom block will be used as a replacement for the standard buttons. The following example assumes that you have defined the buttons approveButton, rejectButton and printButton in your page specification, and that you want to use them rather than the standard BeanForm buttons:
<form jwcid="myBeanForm@bf:BeanForm" bean="ognl:pojo" properties="literal:name,description,notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true"/> <div jwcid="myBeanForm_Buttons@Block"> <input jwcid="approveButton"/> <input jwcid="rejectButton"/> <input jwcid="printButton"/> </div>
What if we want to add a BeanForm to an existing Form component? We can do this, in which case the Form component inside the BeanForm will not be rendered:
<form jwcid="@Form" ... > ... <table jwcid="@bf:BeanForm" bean="ognl:pojo" properties="literal:name{required,maxLength=255},description{maxLength=255},notes" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete"> </table> ... </form>
The simplest way to customize the look of the BeanForm-generated HTML is to define CSS rules. The BeanForm component generates an HTML form (unless the BeanForm is already wrapped in a Form component), which in turn contains a table with a tr for each property. The CSS classes to watch for are bf_Left, bf_Right and bf_Buttons. In addition, textfields are given the CSS class text, buttons are given the CSS class button, checkboxes are given the CSS class checkbox, and file upload inputs are given the CSS class file, so that they can be differentiated in browsers that do not support attribute selectors. Also note that the CSS class of the table is set by specifying an informal class parameter on the BeanForm itself. Here is an example of the HTML generated by the BeanForm component:
<form ... > ... [Tapestry hidden fields] ... <table class="[informal-parameter]"> <tr> <td class="bf_Left"> ... [label] ... </td> <td class="bf_Right"> ... [field] ... </td> </tr> <tr> <td class="bf_Left"> ... [label] ... </td> <td class="bf_Right"> ... [field] ... </td> </tr> <tr> <td colspan="2" class="bf_Buttons"> ... [buttons] ... </td> </tr> </table> </form>
And here is some basic CSS that you can use to style the generated HTML, assuming you have specified class="bf" as an informal parameter on the BeanForm component:
table.bf { width: 400px; padding: 10px; margin-left: auto; margin-right: auto; border: 1px solid #eeeeee; background-color: #eeffee; } table.bf td.bf_Left { text-align: right; white-space: nowrap; } table.bf td.bf_Right { width: 100%; white-space: nowrap; } table.bf td.bf_Buttons { text-align: center; padding-top: 10px; } table.bf input.checkbox { margin: 0px; padding: 0px; } table.bf input.text, table.bf textarea { width: 100%; }
Finally, let's assume that the CSS customization capabilities built into BeanForm aren't going to be sufficient for your needs. Say, for example, that your name is Matt Raible and that you hate table-based forms ;-) What's a developer to do? The following code snippet forces the BeanForm component to generate an unordered list (ul) with a list item (li) for each field, instead of the default table:
<form jwcid="@bf:BeanForm" bean="ognl:pojo" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete" class="customUL" element="ul"> <li jwcid="@bf:BeanFormRows" element="li"> <div jwcid="@bf:BeanFormLabel" element="div" class="customLeft"/> <div jwcid="@bf:BeanFormField" element="div" class="customRight"/> </li> <li class="customButtons"> <span jwcid="@bf:BeanFormButtons"/> </li> </form>
If you develop a custom layout and find that you need to use it on multiple pages, you can move the custom body (the two li elements in the example above) into a separate component, which will then be the only thing you have to place in the BeanForm's body:
<form jwcid="@bf:BeanForm" bean="ognl:pojo" delegate="bean:validationDelegate" clientValidationEnabled="ognl:true" focus="ognl:true" save="listener:save" delete="listener:delete" class="customUL" element="ul"> <span jwcid="@MyBeanFormLayout"/> </form>