Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Jakarta-tapestry Wiki" 
for change notification.

The following page has been changed by DanielGredler:
http://wiki.apache.org/jakarta-tapestry/BeanForm

------------------------------------------------------------------------------
- == BeanForm ==
- 
  An [http://howardlewisship.com/blog/2006/03/from-fanciful-ideas-category.html 
often-requested feature] is an easier, more Trails-like way of editing domain 
objects that gets rid of a lot of the boilerplate typing required to create an 
edit form. 
[http://planesailing.blogspot.com/2006/03/tapestry-edit-component.html Some 
solutions] are breathtaking in their comprehensiveness but suffer from lack of 
documentation and over-architecting (imho). The BeanForm is a simpler solution 
to this common problem:
  
- {{{<span jwcid="@BeanForm" bean="ognl:pojo" success="ognl:listeners.save"/>}}}
+ {{{<span jwcid="@BeanForm" bean="ognl:pojo" save="ognl:listeners.save" 
delete="ognl:listeners.delete"/>}}}
  
- The above code gets you a form that will call save() when submitted, displays 
a TextField for every string or number property, a Checkbox for every boolean 
property and a DatePicker for every date property, and automatically disables 
fields for read-only properties. Properties are discovered via 
[http://java.sun.com/docs/books/tutorial/javabeans/index.html bean 
introspection].
+ The above code gets you a form that will call save() when submitted for save, 
delete() when submitted for delete, displays a TextField for every string or 
number property, a Checkbox for every boolean property and a DatePicker for 
every date property, and automatically disables fields for read-only 
properties. Properties are discovered via 
[http://java.sun.com/docs/books/tutorial/javabeans/index.html bean 
introspection]. You can use the BeanForm with a single line, as above, or you 
can include it inside one of your existing Forms, in which case the internal 
Form component will not be generated. It's your choice.
  
- You can also specify exactly which properties you want included in the 
BeanForm, as well as cancel and refresh listeners:
+ You can also specify exactly which properties you want included in the 
BeanForm, as well as cancel and refresh listeners. When you explicitly specify 
editable properties, those properties are shown on the page in the order you 
specify:
  
- {{{<span jwcid="@BeanForm" bean="ognl:pojo" properties="ognl:'id,name,email'" 
success="ognl:listeners.save" cancel="ognl:listeners.cancel" 
refresh="ognl:listeners.refresh" />}}}
+ {{{<span jwcid="@BeanForm" bean="ognl:pojo" 
properties="ognl:'name,email,comment'" success="ognl:listeners.save" 
cancel="ognl:listeners.cancel" refresh="ognl:listeners.refresh" />}}}
  
- The generated HTML is a form containing a two-column table. The left column 
contains the field labels and the right column contains the data entry fields. 
The bottom row spans both columns and contains the submit button, the cancel 
button (if the ''cancel'' parameter was specified) and the refresh button (if 
the ''refresh'' parameter was specified). The table can be styled using CSS: 
the table's CSS class is ''beanFormTable'', the left column's CSS class is 
''beanFormLeftColumn'', the right column's CSS class is 
''beanFormRightColumn'', and the bottom column that contains the buttons has 
the CSS class ''beanFormButtonColumn''.
+ The generated HTML is a form containing a two-column table (unless the 
BeanForm is already inside a Form, in which case no Form is generated). The 
left column contains the field labels and the right column contains the data 
entry fields. The bottom row spans both columns and contains the save button, 
the cancel button (if the ''cancel'' parameter was specified), the refresh 
button (if the ''refresh'' parameter was specified) and the delete button (if 
the ''delete'' parameter was specified). The table can be styled using CSS: the 
table's CSS class is ''beanFormTable'', the left column's CSS class is 
''beanFormLeftColumn'', the right column's CSS class is 
''beanFormRightColumn'', and the bottom column that contains the buttons has 
the CSS class ''beanFormButtonColumn''.
  
+ If you need to customize any of the input fields, you can do so by adding a 
Block component to your page (with the id ''[propertyName]BeanFieldBlock'') 
that contains any IFormComponent (with the id ''[propertyName]BeanField''). For 
example, if you wanted to edit the ''comment'' property in a text area instead 
of the default textfield, you could add the following to your page:
+ 
+ {{{<div jwcid="[EMAIL PROTECTED]"><input jwcid="[EMAIL PROTECTED]" 
value="ognl:pojo.comment" displayName="message:comment"/></div>}}}
+ 
- Internationalization is supported out of the box: field labels are messages 
keyed on the property name. The submit button is labeled with the message 
corresponding to the key ''submit''. The cancel button (if displayed) is 
labeled with the message corresponding to the key ''cancel''. The refresh 
button (if displayed) is labeled with the message corresponding to the key 
''refresh''.
+ Internationalization is supported out of the box: field labels are messages 
keyed on the property name. The save button (if displayed) is labeled with the 
message corresponding to the key ''save''. The cancel button (if displayed) is 
labeled with the message corresponding to the key ''cancel''. The refresh 
button (if displayed) is labeled with the message corresponding to the key 
''refresh''.  The delete button (if displayed) is labeled with the message 
corresponding to the key ''delete''.
  
- All of this is done in ~300 lines and is simple enough that when you need 
that one extra feature, you can just enhance this code rather than going back 
to boilerplateland. All constructive criticism is welcome. Enjoy!
+ All of this is done in ~400 lines of (Java + XML + HTML) and is simple enough 
that when you need that one extra feature, you can just enhance this code 
rather than going back to boilerplateland. All constructive criticism is 
welcome. Enjoy!
  
  Three files:
   * BeanForm.java
@@ -30, +32 @@

  ==== BeanForm.html ====
  
  {{{
+ <!-- If this component is already inside a form, don't render another form. 
-->
+ <span jwcid="isInsideAForm">
+       <span jwcid="renderBlock1"/>
+ </span>
+ <span jwcid="isNotInsideAForm">
- <form jwcid="beanForm">
+       <form jwcid="beanForm">
+               <span jwcid="renderBlock2"/>
+       </form>
+ </span>
+ 
+ <!-- The main content block. -->
+ <span jwcid="block">
        <table class="beanFormTable">
                <span jwcid="beanPropertyRow">
-                       <span jwcid="isString">
+                       <span jwcid="hasCustomField">
-                               <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Text"/></td>
+                               <td class="beanFormLeftColumn"><span 
jwcid="customFieldLabel"/></td>
-                               <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Text"/></td>
+                               <td class="beanFormRightColumn"><span 
jwcid="customFieldBlock"/></td>
                        </span>
+                       <span jwcid="noCustomField">
+                               <span jwcid="isString">
+                                       <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Text"/></td>
+                                       <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Text"/></td>
+                               </span>
-                       <span jwcid="isBoolean">
+                               <span jwcid="isBoolean">
-                               <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Checkbox"/></td>
+                                       <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Checkbox"/></td>
-                               <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Checkbox"/></td>
+                                       <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Checkbox"/></td>
-                       </span>
+                               </span>
-                       <span jwcid="isNumber">
+                               <span jwcid="isNumber">
-                               <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Number"/></td>
+                                       <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Number"/></td>
-                               <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Number"/></td>
+                                       <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Number"/></td>
-                       </span>
+                               </span>
-                       <span jwcid="isDate">
+                               <span jwcid="isDate">
-                               <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Date"/></td>
+                                       <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Date"/></td>
-                               <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Date"/></td>
+                                       <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Date"/></td>
+                               </span>
                        </span>
                </span>
                <td colspan="2" class="beanFormButtonColumn">
-                       <span jwcid="beanFormSubmit"/>
+                       <span jwcid="hasSave"><span 
jwcid="beanFormSave"/></span>
                        <span jwcid="hasCancel"><span jwcid="beanFormCancel" 
onclick="this.form.events.cancel()"/></span>
                        <span jwcid="hasRefresh"><span jwcid="beanFormRefresh" 
onclick="this.form.events.refresh()"/></span>
+                       <span jwcid="hasDelete"><span 
jwcid="beanFormDelete"/></span>
                </td>
        </table>
- </form>
+ </span>
  }}}
  
  ==== BeanForm.java ====
@@ -74, +94 @@

  import java.util.Arrays;
  import java.util.Date;
  import java.util.List;
+ import java.util.Map;
  
+ import org.apache.hivemind.ApplicationRuntimeException;
  import org.apache.tapestry.BaseComponent;
+ import org.apache.tapestry.IComponent;
+ import org.apache.tapestry.IRequestCycle;
+ import org.apache.tapestry.TapestryUtils;
+ import org.apache.tapestry.annotations.InjectObject;
+ import org.apache.tapestry.components.Block;
+ import org.apache.tapestry.form.IFormComponent;
  
  import com.diphy.util.Logger;
  
@@ -83, +111 @@

   * <p>A form that provides edit capabilities for a Java Bean. Only properties 
that are
   * strings, booleans, numbers or dates are considered editable. Fields for 
read-only
   * bean properties are automatically disabled. Field labels are messages 
keyed on the
-  * property name. The submit button is labeled with the message corresponding 
to the
+  * property name. The save button (if displayed) is labeled with the message 
corresponding to
-  * key <tt>submit</tt>. The cancel button (if displayed) is labeled with the 
message
+  * the key <tt>save</tt>. The cancel button (if displayed) is labeled with 
the message
   * corresponding to the key <tt>cancel</tt>. The refresh button (if 
displayed) is labeled
-  * with the message corresponding to the key <tt>refresh</tt>.</p>
+  * with the message corresponding to the key <tt>refresh</tt>. The delete 
button (if
+  * displayed) is labeled with the message corresponding to the key 
<tt>delete</tt>.</p>
   *
   * <p>The component parameters are as follows:</p>
   *
   * <ul>
   *   <li><tt>bean</tt>: Required, specifies the Java Bean to modify.</li>
+  *   <li><tt>properties</tt>: Not required, specifies the bean properties to 
be edited; if omitted,
+  *      all eligible bean properties (strings, booleans, numbers and dates) 
are considered
+  *      editable; if specified, properties are displayed in the specified 
order.</li>
+  *   <li><tt>save</tt>: Not required, specifies the listener to invoke on 
save.</li>
+  *   <li><tt>delete</tt>: Not required, specifies the listener to invoke on 
delete.</li>
-  *   <li><tt>success</tt>: Required, specifies the listener to invoke on form 
submission.</li>
+  *   <li><tt>success</tt>: Not required, specifies the listener to invoke on 
form submission.</li>
   *   <li><tt>cancel</tt>: Not required, specifies the listener to invoke on 
form cancellation.</li>
   *   <li><tt>refresh</tt>: Not required, specifies the listener to invoke on 
form refresh.</li>
-  *   <li><tt>properties</tt>: Not required, specifies the bean properties to 
be edited; if omitted,
-  *      all eligible bean properties (strings, booleans, numbers and dates) 
are considered
-  *      editable.</li>
   * </ul>
   *
-  * <p>The generated HTML is a form containing a two-column table. The left 
column
+  * <p>The generated HTML is a form containing a two-column table, unless this 
component
+  * is inside an external Form component, in which case no extraneous form is 
generated. The table's
-  * contains the field labels and the right column contains the data entry 
fields.
+  * left column contains the field labels and the right column contains the 
data entry fields.
-  * The bottom row spans both columns and contains the submit button, the 
cancel button
+  * The bottom row spans both columns and contains the save button (if the 
<tt>save</tt> parameter
+  * was specified), the cancel button (if the <tt>cancel</tt> parameter was 
specified), the
-  * (if the <tt>cancel</tt> parameter was specified) and the refresh button 
(if the
+  * refresh button (if the <tt>refresh</tt> parameter was specified) and the 
delete button (if
-  * <tt>refresh</tt> parameter was specified).</p>
+  * the <tt>delete</tt> parameter was specified).</p>
   *
   * <p>The table can be styled using CSS: the table's CSS class is 
<tt>beanFormTable</tt>,
   * the left column's CSS class is <tt>beanFormLeftColumn</tt>, the right 
column's CSS class
   * is <tt>beanFormRightColumn</tt>, and the bottom column that contains the 
buttons has
   * the CSS class <tt>beanFormButtonColumn</tt>.</p>
   *
+  * <p>If you need to customize any of the input fields, you can do so by 
adding a <tt>Block</tt>
+  * component to your page (with the id <tt>[propertyName]BeanFieldBlock</tt>) 
that contains any
+  * <tt>IFormComponent</tt> (with the id <tt>[propertyName]BeanField</tt>).</p>
+  *
   * <p>Usage examples:</p>
   * <ul>
   *   <li>Minimal: <tt>&lt;span jwcid="@BeanForm" bean="ognl:pojo" 
success="ognl:listeners.save"/&gt;</tt></li>
-  *   <li>Normal: <tt>&lt;span jwcid="@BeanForm" bean="ognl:pojo" 
properties="ognl:'id,name,email'"
+  *   <li>Normal: <tt>&lt;span jwcid="@BeanForm" bean="ognl:pojo" 
properties="ognl:'name,email,comment'"
   *      success="ognl:listeners.save" cancel="ognl:listeners.cancel" 
refresh="ognl:listeners.refresh" /&gt;</tt></li>
+  *   <li>To edit the <tt>comment</tt> property in a text area you would add 
the following to your page:<br>
+  *      <tt>&lt;div jwcid="[EMAIL PROTECTED]"&gt;</tt><br>
+  *      <tt>&lt;input jwcid="[EMAIL PROTECTED]" value="ognl:pojo.comment" 
displayName="message:comment"/&gt;</tt><br>
+  *      <tt>&lt;/div&gt;</tt><br>
+  *   </tt></li>
   * </ul>
   *
   * <p>Possible enhancements:</p>
@@ -129, +171 @@

   * </ul>
   *
   * @author Daniel Gredler
-  * @version 1.0
+  * @version 1.1
   */
  public abstract class BeanForm extends BaseComponent {
  
        private final static Logger LOG = Logger.getLogger( BeanForm.class );
+       private final static String CUSTOM_FIELD_BLOCK_SUFFIX = 
"BeanFieldBlock";
+       private final static String CUSTOM_FIELD_SUFFIX = "BeanField";
+ 
+       @InjectObject( "infrastructure:requestCycle" )
+       public abstract IRequestCycle getCycle();
  
        public abstract Object getBean();
        public abstract void setBean( Object bean );
@@ -159, +206 @@

                                }
                        }
                }
+               properties = this.reorderProperties( properties, names );
                if( LOG.isDebugEnabled() ) {
                        LOG.debug( "Found " + properties.size() + " editable 
properties for bean " + bean + ": " + properties );
                }
                return properties;
+       }
+ 
+       public boolean isInsideAForm() {
+               try {
+                       TapestryUtils.getForm( this.getCycle(), this );
+                       return true;
+               }
+               catch( ApplicationRuntimeException e ) {
+                       return false;
+               }
+       }
+ 
+       public boolean hasCustomField( BeanProperty property ) {
+               boolean customBlock = this.getCustomFieldBlock( property ) != 
null;
+               boolean customField = this.getCustomField( property ) != null;
+               return customBlock && customField;
+       }
+ 
+       public Block getCustomFieldBlock( BeanProperty property ) {
+               return (Block) this.getComponent( property, 
CUSTOM_FIELD_BLOCK_SUFFIX, Block.class );
+       }
+ 
+       public IFormComponent getCustomField( BeanProperty property ) {
+               return (IFormComponent) this.getComponent( property, 
CUSTOM_FIELD_SUFFIX, IFormComponent.class );
+       }
+ 
+       @SuppressWarnings( "unchecked" )
+       private IComponent getComponent( BeanProperty property, String suffix, 
Class clazz ) {
+               IComponent component = null;
+               String name = property.getName() + suffix;
+               Map components = this.getPage().getComponents();
+               if( components.containsKey( name ) ) {
+                       IComponent candidate = (IComponent) components.get( 
name );
+                       if( clazz.isAssignableFrom( candidate.getClass() ) ) {
+                               component = candidate;
+                       }
+                       else {
+                               LOG.error( "Component '" + candidate.getId() + 
"' should be of type " + clazz.getName() + "!" );
+                       }
+               }
+               return component;
        }
  
        private BeanInfo getBeanInfo() {
@@ -198, +287 @@

                        names = null;
                }
                return names;
+       }
+ 
+       private List<BeanProperty> reorderProperties( List<BeanProperty> 
properties, List<String> orderedNames ) {
+               if( orderedNames == null ) return properties;
+               List<BeanProperty> orderedProperties = new 
ArrayList<BeanProperty>( properties.size() );
+               for( String name : orderedNames ) {
+                       for( BeanProperty property : properties ) {
+                               if( property.getName().equals( name ) ) {
+                                       orderedProperties.add( property );
+                               }
+                       }
+               }
+               return orderedProperties;
        }
  
        /**
@@ -289, +391 @@

        <description>A form that provides edit capabilities for a Java 
Bean.</description>
  
        <parameter name="bean" required="true"/>
+       <parameter name="properties" required="false"/>
+       <parameter name="save" required="false"/>
+       <parameter name="delete" required="false"/>
+ 
-       <parameter name="success" required="true"/>
+       <parameter name="success" required="false"/>
        <parameter name="cancel" required="false"/>
        <parameter name="refresh" required="false"/>
-       <parameter name="properties" required="false"/>
  
        <property name="beanProperty"/>
+ 
+       <component id="isInsideAForm" type="If">
+               <binding name="condition" value="isInsideAForm()"/>
+       </component>
+       <component id="isNotInsideAForm" type="Else"/>
+ 
+       <component id="block" type="Block"/>
+       <component id="renderBlock1" type="RenderBlock">
+               <binding name="block" value="component:block"/>
+       </component>
+       <component id="renderBlock2" type="RenderBlock">
+               <binding name="block" value="component:block"/>
+       </component>
  
        <component id="beanForm" type="Form">
                <binding name="success" value="success"/>
@@ -307, +425 @@

                <binding name="element" value="'tr'"/>
        </component>
  
+       <component id="hasCustomField" type="If">
+               <binding name="condition" value="hasCustomField(beanProperty)"/>
+       </component>
+       <component id="noCustomField" type="Else"/>
+ 
+       <component id="customFieldLabel" type="FieldLabel">
+               <binding name="field" value="getCustomField(beanProperty)"/>
+       </component>
+       <component id="customFieldBlock" type="RenderBlock">
+               <binding name="block" 
value="getCustomFieldBlock(beanProperty)"/>
+       </component>
+ 
        <component id="isString" type="If">
                <binding name="condition" value="beanProperty.isString()"/>
        </component>
@@ -356, +486 @@

                <binding name="displayName" 
value="getMessage(beanProperty.name)"/>
        </component>
  
+       <component id="hasSave" type="If">
+               <binding name="condition" value="save!=null"/>
+       </component>
        <component id="hasCancel" type="If">
                <binding name="condition" value="cancel!=null"/>
        </component>
        <component id="hasRefresh" type="If">
                <binding name="condition" value="refresh!=null"/>
        </component>
+       <component id="hasDelete" type="If">
+               <binding name="condition" value="delete!=null"/>
+       </component>
  
-       <component id="beanFormSubmit" type="Submit">
+       <component id="beanFormSave" type="Submit">
-               <binding name="label" value="message:submit"/>
+               <binding name="label" value="message:save"/>
+               <binding name="listener" value="save"/>
        </component>
        <component id="beanFormCancel" type="Button">
                <binding name="label" value="message:cancel"/>
@@ -372, +509 @@

        <component id="beanFormRefresh" type="Button">
                <binding name="label" value="message:refresh"/>
        </component>
+       <component id="beanFormDelete" type="Submit">
+               <binding name="label" value="message:delete"/>
+               <binding name="listener" value="delete"/>
+       </component>
  
  </component-specification>
  }}}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to