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