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

New page:
== 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"/>}}}

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

You can also specify exactly which properties you want included in the 
BeanForm, as well as cancel and refresh listeners:

{{{<span jwcid="@BeanForm" bean="ognl:pojo" properties="ognl:'id,name,email'" 
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''.

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

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!

Three files:
 * BeanForm.java
 * BeanForm.jwc
 * BeanForm.html

Gotchas:
 * Change the package name in BeanForm.java!
 * Use your own logging framework in BeanForm.java!

==== BeanForm.html ====

{{{
<form jwcid="beanForm">
        <table class="beanFormTable">
                <span jwcid="beanPropertyRow">
                        <span jwcid="isString">
                                <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Text"/></td>
                                <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Text"/></td>
                        </span>
                        <span jwcid="isBoolean">
                                <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Checkbox"/></td>
                                <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Checkbox"/></td>
                        </span>
                        <span jwcid="isNumber">
                                <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Number"/></td>
                                <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Number"/></td>
                        </span>
                        <span jwcid="isDate">
                                <td class="beanFormLeftColumn"><span 
jwcid="beanPropertyLabel_Date"/></td>
                                <td class="beanFormRightColumn"><span 
jwcid="beanPropertyField_Date"/></td>
                        </span>
                </span>
                <td colspan="2" class="beanFormButtonColumn">
                        <span jwcid="beanFormSubmit"/>
                        <span jwcid="hasCancel"><span jwcid="beanFormCancel" 
onclick="this.form.events.cancel()"/></span>
                        <span jwcid="hasRefresh"><span jwcid="beanFormRefresh" 
onclick="this.form.events.refresh()"/></span>
                </td>
        </table>
</form>
}}}

==== BeanForm.java ====

{{{
package com.diphy.web.components;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.apache.tapestry.BaseComponent;

import com.diphy.util.Logger;

/**
 * <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
 * key <tt>submit</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>
 *
 * <p>The component parameters are as follows:</p>
 *
 * <ul>
 *   <li><tt>bean</tt>: Required, specifies the Java Bean to modify.</li>
 *   <li><tt>success</tt>: 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
 * 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 <tt>cancel</tt> parameter was specified) and the refresh button (if 
the
 * <tt>refresh</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>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'"
 *      success="ognl:listeners.save" cancel="ognl:listeners.cancel" 
refresh="ognl:listeners.refresh" /&gt;</tt></li>
 * </ul>
 *
 * <p>Possible enhancements:</p>
 *
 * <ul>
 *   <li>Expose all the parameters exposed by the <tt>Form</tt> component.</li>
 *   <li>Add validation, perhaps within the <tt>properties</tt> parameter (ie, 
if you have
 *      <tt>properties</tt> set to <tt>"name,email,comment"</tt> and you want 
to make <tt>name</tt>
 *      and <tt>email</tt> required, you might set <tt>properties</tt> to
 *      <tt>"name{required},email{required,email},comment"</tt>).</li>
 * </ul>
 *
 * @author Daniel Gredler
 * @version 1.0
 */
public abstract class BeanForm extends BaseComponent {

        private final static Logger LOG = Logger.getLogger( BeanForm.class );

        public abstract Object getBean();
        public abstract void setBean( Object bean );

        public abstract String getProperties();
        public abstract void setProperties( String properties );

        public List<BeanProperty> getBeanProperties() {
                Object bean = this.getBean();
                BeanInfo info = this.getBeanInfo();
                List<String> names = this.getEditablePropertyNames();
                PropertyDescriptor[] descriptors = 
info.getPropertyDescriptors();
                List<BeanProperty> properties = new ArrayList<BeanProperty>();
                for( PropertyDescriptor descriptor : descriptors ) {
                        BeanProperty property = new BeanProperty( descriptor );
                        String name = property.getName();
                        if( names == null || names.contains( name ) ) {
                                if( property.isEditableType() ) {
                                        properties.add( property );
                                }
                                else if( names != null ){
                                        LOG.warn( "Explicitly included property 
'" + name + "' cannot be edited because it is not a string, boolean, number or 
date." );
                                }
                        }
                }
                if( LOG.isDebugEnabled() ) {
                        LOG.debug( "Found " + properties.size() + " editable 
properties for bean " + bean + ": " + properties );
                }
                return properties;
        }

        private BeanInfo getBeanInfo() {
                Object bean = this.getBean();
                BeanInfo info;
                if( bean != null ) {
                        try {
                                info = Introspector.getBeanInfo( 
bean.getClass() );
                        }
                        catch( IntrospectionException e ) {
                                LOG.error( e );
                                info = new SimpleBeanInfo();
                        }
                }
                else {
                        LOG.warn( "BeanForm's bean is null!" );
                        info = new SimpleBeanInfo();
                }
                return info;
        }

        private List<String> getEditablePropertyNames() {
                String s = this.getProperties();
                List<String> names;
                if( s != null ) {
                        String[] array = s.split( "," );
                        for( int i = 0; i < array.length; i++ ) {
                                array[ i ] = array[ i ].trim();
                        }
                        names = Arrays.asList( array );
                }
                else {
                        names = null;
                }
                return names;
        }

        /**
         * Instances of this class get serialized into the HTML, so keep it 
trim (ie,
         * as few serialized instance variables as possible).
         */
        public static class BeanProperty implements Serializable {

                private static final long serialVersionUID = 
-9064407627959060313L;

                private static final String STRING = String.class.getName();
                private static final String BOOLEAN = Boolean.class.getName();
                private static final String BOOL = boolean.class.getName();
                private static final String INTEGER = Integer.class.getName();
                private static final String INT = int.class.getName();
                private static final String LONG = Long.class.getName();
                private static final String LNG = long.class.getName();
                private static final String FLOAT = Float.class.getName();
                private static final String FLT = float.class.getName();
                private static final String DOUBLE = Double.class.getName();
                private static final String DBL = double.class.getName();
                private static final String DATE = Date.class.getName();

                private String name;
                private boolean readOnly;
                private String typeName;

                public BeanProperty( PropertyDescriptor descriptor ) {
                        this.name = descriptor.getName();
                        this.readOnly = descriptor.getWriteMethod() == null;
                        this.typeName = descriptor.getPropertyType().getName();
                }

                public String getName() {
                        return this.name;
                }

                public boolean isReadOnly() {
                        return this.readOnly;
                }

                public String getTypeName() {
                        return this.typeName;
                }

                public boolean isString() {
                        return STRING.equals( this.typeName );
                }

                public boolean isBoolean() {
                        return BOOLEAN.equals( this.typeName ) || BOOL.equals( 
this.typeName );
                }

                public boolean isNumber() {
                        return INTEGER.equals( this.typeName ) || INT.equals( 
this.typeName ) ||
                                LONG.equals( this.typeName ) || LNG.equals( 
this.typeName ) ||
                                FLOAT.equals( this.typeName ) || FLT.equals( 
this.typeName ) ||
                                DOUBLE.equals( this.typeName ) || DBL.equals( 
this.typeName );
                }

                public boolean isDate() {
                        return DATE.equals( this.typeName );
                }

                public boolean isEditableType() {
                        return this.isString() || this.isBoolean() || 
this.isNumber() || this.isDate();
                }

                @Override
                public String toString() {
                        return this.getName();
                }

        }

}
}}}

==== BeanForm.jwc ====

{{{
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE component-specification PUBLIC
        "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
        "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd";>

<component-specification class="com.diphy.web.components.BeanForm" 
allow-body="no" allow-informal-parameters="yes">

        <description>A form that provides edit capabilities for a Java 
Bean.</description>

        <parameter name="bean" required="true"/>
        <parameter name="success" required="true"/>
        <parameter name="cancel" required="false"/>
        <parameter name="refresh" required="false"/>
        <parameter name="properties" required="false"/>

        <property name="beanProperty"/>

        <component id="beanForm" type="Form">
                <binding name="success" value="success"/>
                <binding name="cancel" value="cancel"/>
                <binding name="refresh" value="refresh"/>
        </component>
        <component id="beanPropertyRow" type="For">
                <binding name="source" value="beanProperties"/>
                <binding name="value" value="beanProperty"/>
                <binding name="element" value="'tr'"/>
        </component>

        <component id="isString" type="If">
                <binding name="condition" value="beanProperty.isString()"/>
        </component>
        <component id="isBoolean" type="If">
                <binding name="condition" value="beanProperty.isBoolean()"/>
        </component>
        <component id="isNumber" type="If">
                <binding name="condition" value="beanProperty.isNumber()"/>
        </component>
        <component id="isDate" type="If">
                <binding name="condition" value="beanProperty.isDate()"/>
        </component>

        <component id="beanPropertyLabel_Text" type="FieldLabel">
                <binding name="field" value="component:beanPropertyField_Text"/>
        </component>
        <component id="beanPropertyField_Text" type="TextField">
                <binding name="value" value="(beanProperty.name)(bean)"/>
                <binding name="disabled" value="beanProperty.readOnly"/>
                <binding name="displayName" 
value="getMessage(beanProperty.name)"/>
        </component>

        <component id="beanPropertyLabel_Checkbox" type="FieldLabel">
                <binding name="field" 
value="component:beanPropertyField_Checkbox"/>
        </component>
        <component id="beanPropertyField_Checkbox" type="Checkbox">
                <binding name="value" value="(beanProperty.name)(bean)"/>
                <binding name="disabled" value="beanProperty.readOnly"/>
                <binding name="displayName" 
value="getMessage(beanProperty.name)"/>
        </component>

        <component id="beanPropertyLabel_Number" type="FieldLabel">
                <binding name="field" 
value="component:beanPropertyField_Number"/>
        </component>
        <component id="beanPropertyField_Number" type="TextField">
                <binding name="value" value="(beanProperty.name)(bean)"/>
                <binding name="disabled" value="beanProperty.readOnly"/>
                <binding name="displayName" 
value="getMessage(beanProperty.name)"/>
        </component>

        <component id="beanPropertyLabel_Date" type="FieldLabel">
                <binding name="field" value="component:beanPropertyField_Date"/>
        </component>
        <component id="beanPropertyField_Date" type="DatePicker">
                <binding name="value" value="(beanProperty.name)(bean)"/>
                <binding name="disabled" value="beanProperty.readOnly"/>
                <binding name="displayName" 
value="getMessage(beanProperty.name)"/>
        </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="beanFormSubmit" type="Submit">
                <binding name="label" value="message:submit"/>
        </component>
        <component id="beanFormCancel" type="Button">
                <binding name="label" value="message:cancel"/>
        </component>
        <component id="beanFormRefresh" type="Button">
                <binding name="label" value="message:refresh"/>
        </component>

</component-specification>
}}}

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

Reply via email to