    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.corelib.components;
    014
    015import org.apache.tapestry5.*;
    016import org.apache.tapestry5.annotations.*;
    017import org.apache.tapestry5.beaneditor.BeanModel;
    018import org.apache.tapestry5.internal.beaneditor.BeanModelUtils;
    019import org.apache.tapestry5.ioc.annotations.Inject;
    020import org.apache.tapestry5.services.BeanModelSource;
    021
    022/**
    023 * A component that creates an entire form editing the properties of a particular bean. Inspired by <a
    024 * href="http://www.trailsframework.org/">Trails</a> and <a href="http://beanform.sourceforge.net/">BeanForm</a> (both
    025 * for Tapestry 4). Generates a simple UI for editing the properties of a JavaBean, with the flavor of UI for each
    026 * property (text field, checkbox, drop down list) determined from the property type (or by other means, such as an
    027 * annotation), and the order and validation for the properties determined from annotations on the property's getter and
    028 * setter methods.
    029 *
    030 * You may add block parameters to the component; when the name matches (case insensitive) the name of a property, then
    031 * the corresponding Block is renderered, rather than any of the built in property editor blocks. This allows you to
    032 * override specific properties with your own customized UI, for cases where the default UI is insufficient, or no
    033 * built-in editor type is appropriate.
    034 *
    035 * BeanEditForm contains a {@link org.apache.tapestry5.corelib.components.Form} component and will trigger all the
    036 * events of a Form.
    037 *
    038 * @tapestrydoc
    039 * @see org.apache.tapestry5.beaneditor.BeanModel
    040 * @see org.apache.tapestry5.services.BeanModelSource
    041 * @see org.apache.tapestry5.corelib.components.PropertyEditor
    042 * @see org.apache.tapestry5.beaneditor.DataType
    043 * @see Form
    044 * @see Errors
    045 * @see BeanEditor
    046 */
    047@SupportsInformalParameters
    048@Events(EventConstants.PREPARE)
    049public class BeanEditForm implements ClientElement, FormValidationControl
    050{
    051
    052    /**
    053     * The text label for the submit button of the form, by default "Create/Update".
    054     */
    055    @Parameter(value = "message:core-submit-label", defaultPrefix = BindingConstants.LITERAL)
    056    @Property
    057    private String submitLabel;
    058
    059    /**
    060     * The object to be edited. This will be read when the component renders and updated when the form for the component
    061     * is submitted. Typically, the container will listen for a "prepare" event, in order to ensure that a non-null
    062     * value is ready to be read or updated. Often, the BeanEditForm can create the object as needed (assuming a public,
    063     * no arguments constructor). The object property defaults to a property with the same name as the component id.
    064     */
    065    @Parameter(required = true, autoconnect = true)
    066    @Property
    067    private Object object;
    068
    069    /**
    070     * A comma-separated list of property names to be retained from the
    071     * {@link org.apache.tapestry5.beaneditor.BeanModel} (only used
    072     * when a default model is created automatically).
    073     * Only these properties will be retained, and the properties will also be reordered. The names are
    074     * case-insensitive.
    075     */
    076    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    077    private String include;
    078
    079    /**
    080     * A comma-separated list of property names to be removed from the {@link org.apache.tapestry5.beaneditor.BeanModel}
    081     * (only used
    082     * when a default model is created automatically).
    083     * The names are case-insensitive.
    084     */
    085    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    086    private String exclude;
    087
    088    /**
    089     * A comma-separated list of property names indicating the order in which the properties should be presented. The
    090     * names are case insensitive. Any properties not indicated in the list will be appended to the end of the display
    091     * orde. Only used
    092     * when a default model is created automatically.
    093     */
    094    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    095    private String reorder;
    096
    097    /**
    098     * A comma-separated list of property names to be added to the {@link org.apache.tapestry5.beaneditor.BeanModel}
    099     * (only used
    100     * when a default model is created automatically).
    101     */
    102    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    103    private String add;
    104
    105    /**
    106     * Specifies the CSS class attribute for the form; the factory default is "well".
    107     */
    108    @Property
    109    @Parameter(name = "class", defaultPrefix = BindingConstants.LITERAL, value = "message:private-core-components.beaneditform.class")
    110    private String className;
    111
    112    @Component(parameters = "validationId=componentResources.id", publishParameters = "clientValidation,autofocus,zone")
    113    private Form form;
    114
    115    /**
    116     * If set to true, then the form will include an additional button after the submit button labeled "Cancel".
    117     * The cancel button will submit the form, bypassing client-side validation. The BeanEditForm will fire a
    118     * {@link EventConstants#CANCELED} event (before the form's {@link EventConstants#VALIDATE} event).
    119     *
    120     * @since 5.2.0
    121     */
    122    @Property
    123    @Parameter
    124    private boolean cancel;
    125
    126    /**
    127     * The model that identifies the parameters to be edited, their order, and every other aspect. If not specified, a
    128     * default bean model will be created from the type of the object bound to the object parameter. The add, include,
    129     * exclude and reorder parameters are <em>only</em> applied to a default model, not an explicitly provided one.
    130     */
    131    @SuppressWarnings("unused")
    132    @Parameter
    133    @Property
    134    private BeanModel model;
    135
    136    @Inject
    137    private ComponentResources resources;
    138
    139    @Inject
    140    private BeanModelSource beanModelSource;
    141
    142    boolean onPrepareFromForm()
    143    {
    144        resources.triggerEvent(EventConstants.PREPARE, null, null);
    145
    146        if (model == null)
    147        {
    148            Class beanType = resources.getBoundType("object");
    149
    150            model = beanModelSource.createEditModel(beanType, resources.getContainerMessages());
    151
    152            BeanModelUtils.modify(model, add, include, exclude, reorder);
    153        }
    154
    155        return true;
    156    }
    157
    158    /**
    159     * Returns the client id of the embedded form.
    160     */
    161    public String getClientId()
    162    {
    163        return form.getClientId();
    164    }
    165
    166    public void clearErrors()
    167    {
    168        form.clearErrors();
    169    }
    170
    171    public boolean getHasErrors()
    172    {
    173        return form.getHasErrors();
    174    }
    175
    176    public boolean isValid()
    177    {
    178        return form.isValid();
    179    }
    180
    181    public void recordError(Field field, String errorMessage)
    182    {
    183        form.recordError(field, errorMessage);
    184    }
    185
    186    public void recordError(String errorMessage)
    187    {
    188        form.recordError(errorMessage);
    189    }
    190}