001: // Copyright 2007 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.tapestry.corelib.components;
016:
017: import java.util.Locale;
018:
019: import org.apache.tapestry.Binding;
020: import org.apache.tapestry.Block;
021: import org.apache.tapestry.ClientElement;
022: import org.apache.tapestry.ComponentResources;
023: import org.apache.tapestry.Field;
024: import org.apache.tapestry.FieldValidator;
025: import org.apache.tapestry.FormValidationControl;
026: import org.apache.tapestry.Translator;
027: import org.apache.tapestry.annotations.Component;
028: import org.apache.tapestry.annotations.Inject;
029: import org.apache.tapestry.annotations.Parameter;
030: import org.apache.tapestry.annotations.SupportsInformalParameters;
031: import org.apache.tapestry.beaneditor.BeanModel;
032: import org.apache.tapestry.beaneditor.PropertyModel;
033: import org.apache.tapestry.ioc.Messages;
034: import org.apache.tapestry.ioc.internal.util.TapestryException;
035: import org.apache.tapestry.services.BeanBlockSource;
036: import org.apache.tapestry.services.BeanModelSource;
037: import org.apache.tapestry.services.ComponentDefaultProvider;
038: import org.apache.tapestry.services.Environment;
039: import org.apache.tapestry.services.FieldValidatorDefaultSource;
040: import org.apache.tapestry.services.PropertyEditContext;
041: import org.apache.tapestry.services.TranslatorDefaultSource;
042:
043: /**
044: * A component that creates an entire form editting the properties of a particular bean. Inspired by
045: * <a href="http://www.trailsframework.org/">Trails</a> and <a
046: * href="http://beanform.sourceforge.net/">BeanForm</a> (both for Tapestry 4). Generates a simple
047: * UI for editting the properties of a JavaBean, with the flavor of UI for each property (text
048: * field, checkbox, drop down list) determined from the property type, and the order and validation
049: * for the properties determined from annotations on the property's getter and setter methods.
050: * <p>
051: * You may add <t:parameter>s to the component; when the name matches (case insensitive) the
052: * name of a property, then the corresponding Block is renderered, rather than any of the built in
053: * property editor blocks. This allows you to override specific properties with your own customized
054: * UI, for cases where the default UI is insufficient, or no built-in editor type is appropriate.
055: * <p>
056: * This component is likely to change more than any other thing in Tapestry! What's available now is
057: * a very limited preview of its eventual functionality.
058: *
059: * @see BeanModel
060: * @see BeanModelSource
061: */
062: @SupportsInformalParameters
063: public class BeanEditForm implements ClientElement,
064: FormValidationControl {
065: /** The text label for the submit button of the form, by default "Create/Update". */
066: @Parameter(value="message:submit-label",defaultPrefix="literal")
067: private String _submitLabel;
068:
069: /**
070: * The object to be editted by the BeanEditor. This will be read when the component renders and
071: * updated when the form for the component is submitted. Typically, the container will listen
072: * for a "prepare" event, in order to ensure that a non-null value is ready to be read or
073: * updated.
074: */
075: @Parameter(required=true)
076: private Object _object;
077:
078: /** If true, the default, then the embedded Form component will use client-side validation. */
079: @Parameter("true")
080: private boolean _clientValidation;
081:
082: @Inject
083: private ComponentResources _resources;
084:
085: @Inject
086: private BeanModelSource _modelSource;
087:
088: @Inject
089: private TranslatorDefaultSource _translatorDefaultSource;
090:
091: @Inject
092: private FieldValidatorDefaultSource _fieldValidatorDefaultSource;
093:
094: @Component(parameters="clientValidation=clientValidation")
095: private Form _form;
096:
097: @Inject
098: private Messages _messages;
099:
100: @Inject
101: private Locale _locale;
102:
103: /**
104: * The model that identifies the parameters to be editted, their order, and every other aspect.
105: * If not specified, a default bean model will be created from the type of the object bound to
106: * the object parameter.
107: */
108: @Parameter
109: private BeanModel _model;
110:
111: // Values that change with each change to the current property:
112:
113: private String _propertyName;
114:
115: private PropertyModel _propertyEditModel;
116:
117: private Block _blockForProperty;
118:
119: @Inject
120: private Environment _environment;
121:
122: @Inject
123: private BeanBlockSource _beanBlockSource;
124:
125: @Inject
126: private ComponentDefaultProvider _defaultProvider;
127:
128: private boolean _mustPopBeanEditContext;
129:
130: /**
131: * Defaults the object parameter to a property of the container matching the BeanEditForm's id.
132: */
133: Binding defaultObject() {
134: return _defaultProvider.defaultBinding("object", _resources);
135: }
136:
137: public BeanModel getModel() {
138: return _model;
139: }
140:
141: public String getPropertyName() {
142: return _propertyName;
143: }
144:
145: public void setPropertyName(String propertyName) {
146: _propertyName = propertyName;
147:
148: _propertyEditModel = _model.get(propertyName);
149:
150: _blockForProperty = null;
151:
152: Block override = _resources
153: .getBlockParameter(_propertyEditModel.getId());
154:
155: if (override != null) {
156: _blockForProperty = override;
157: return;
158: }
159:
160: String dataType = _propertyEditModel.getDataType();
161:
162: try {
163: _blockForProperty = _beanBlockSource.getEditBlock(dataType);
164: } catch (RuntimeException ex) {
165: String message = _messages.format("block-error",
166: _propertyName, dataType, _object, ex);
167:
168: throw new TapestryException(message, _resources
169: .getLocation(), ex);
170: }
171: }
172:
173: boolean onPrepareFromForm() {
174: PropertyEditContext context = new PropertyEditContext() {
175: public Messages getContainerMessages() {
176: return _resources.getContainerMessages();
177: }
178:
179: public String getLabel() {
180: return _propertyEditModel.getLabel();
181: }
182:
183: public String getPropertyId() {
184: return _propertyEditModel.getId();
185: }
186:
187: public Class getPropertyType() {
188: return _propertyEditModel.getPropertyType();
189: }
190:
191: public Object getPropertyValue() {
192: return _propertyEditModel.getConduit().get(getObject());
193: }
194:
195: public Translator getTranslator() {
196: return _translatorDefaultSource.find(_propertyEditModel
197: .getPropertyType());
198: }
199:
200: public FieldValidator getValidator(Field field) {
201: return _fieldValidatorDefaultSource
202: .createDefaultValidator(field, _propertyName,
203: _resources.getContainerMessages(),
204: _locale, _propertyEditModel
205: .getPropertyType(),
206: _propertyEditModel.getConduit());
207: }
208:
209: public void setPropertyValue(Object value) {
210: _propertyEditModel.getConduit().set(getObject(), value);
211: }
212: };
213:
214: _environment.push(PropertyEditContext.class, context);
215: // Depending on whether we're rendering or processing the form submission we'll have two
216: // different places to clean up the Environment.
217: _mustPopBeanEditContext = true;
218:
219: // Fire a new prepare event to be consumed by the container. This is the container's
220: // chance to ensure that there's an object to edit.
221:
222: _resources.triggerEvent(Form.PREPARE, null, null);
223:
224: // Now check to see if the value is null.
225:
226: // The only problem here is that if the bound property is backed by a persistent field, it
227: // is assigned (and stored to the session, and propogated around the cluster) first,
228: // before values are assigned.
229:
230: if (_object == null)
231: _object = createDefaultObject();
232:
233: assert _object != null;
234:
235: if (_model == null) {
236: Class<? extends Object> beanType = _object.getClass();
237:
238: _model = _modelSource.create(beanType, true, _resources
239: .getContainerResources());
240: }
241:
242: return true; // abort the form's prepare event
243: }
244:
245: private void cleanupBeanEditContext() {
246: if (_mustPopBeanEditContext) {
247: _environment.pop(PropertyEditContext.class);
248: _mustPopBeanEditContext = false;
249: }
250: }
251:
252: void onSubmit() {
253: cleanupBeanEditContext();
254: }
255:
256: void afterRender() {
257: cleanupBeanEditContext();
258: }
259:
260: /** Used for testing. */
261: void inject(ComponentResources resources,
262: BeanModelSource modelSource, Environment environment) {
263: _resources = resources;
264: _modelSource = modelSource;
265: _environment = environment;
266: }
267:
268: Object getObject() {
269: return _object;
270: }
271:
272: private Object createDefaultObject() {
273: Class type = _resources.getBoundType("object");
274:
275: try {
276: return type.newInstance();
277: } catch (Exception ex) {
278: throw new TapestryException(ComponentMessages
279: .failureInstantiatingObject(type, _resources
280: .getCompleteId(), ex), _resources
281: .getLocation(), ex);
282: }
283: }
284:
285: public Block getBlockForProperty() {
286: return _blockForProperty;
287: }
288:
289: /** Returns the client id of the embedded form. */
290: public String getClientId() {
291: return _form.getClientId();
292: }
293:
294: public String getSubmitLabel() {
295: return _submitLabel;
296: }
297:
298: public boolean getClientValidation() {
299: return _clientValidation;
300: }
301:
302: public void clearErrors() {
303: _form.clearErrors();
304: }
305:
306: public boolean getHasErrors() {
307: return _form.getHasErrors();
308: }
309:
310: public boolean isValid() {
311: return _form.isValid();
312: }
313:
314: public void recordError(Field field, String errorMessage) {
315: _form.recordError(field, errorMessage);
316: }
317:
318: public void recordError(String errorMessage) {
319: _form.recordError(errorMessage);
320: }
321:
322: }
|