KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > form > JForm


1 /*
2  * $Id: JForm.java,v 1.5 2005/02/03 13:33:06 kleopatra Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  */

7
8 package org.jdesktop.swing.form;
9
10 import org.jdesktop.swing.data.DataModel;
11 import org.jdesktop.swing.data.JavaBeanDataModel;
12 import org.jdesktop.swing.data.MetaData;
13 import org.jdesktop.swing.data.DefaultTableModelExt;
14 import org.jdesktop.swing.data.TableModelExtAdapter;
15 import org.jdesktop.swing.data.Validator;
16
17 import org.jdesktop.swing.binding.Binding;
18 import org.jdesktop.swing.binding.BindException;
19
20 import org.jdesktop.swing.UIAction;
21
22 import java.awt.Color JavaDoc;
23 import java.awt.GridBagConstraints JavaDoc;
24 import java.awt.GridBagLayout JavaDoc;
25 import java.awt.Insets JavaDoc;
26
27 import java.awt.event.ActionEvent JavaDoc;
28
29 import java.beans.IntrospectionException JavaDoc;
30
31 import java.util.ArrayList JavaDoc;
32 import java.util.Collections JavaDoc;
33 import java.util.HashMap JavaDoc;
34 import java.util.Iterator JavaDoc;
35 import java.util.List JavaDoc;
36
37 import javax.swing.ActionMap JavaDoc;
38 import javax.swing.JComponent JavaDoc;
39 import javax.swing.JLabel JavaDoc;
40 import javax.swing.JOptionPane JavaDoc;
41 import javax.swing.JPanel JavaDoc;
42
43
44 /**
45  * <p>
46  * Form component class for enabling display, editing, and actions on
47  * a collection of data fields, each of which corresponds to
48  * an identifiable element within an application data model. For
49  * example, a form field may map to a named column on a RowSet,
50  * a property on a JavaBean, or a keyed element in a map, etc.
51  * A single form can be used to display/edit data fields from
52  * multiple data models.</p>
53  * <p>
54  * For each data field in the form, a user-interface component is
55  * created to display and edit (if field is writable) values
56  * for that field in the data model. Each user-interface component
57  * is &quot;bound&quot; to the data model field using a <code>Binding</code>
58  * instance, which handles the following tasks:
59  * <ul>
60  * <li>pull: load the current value from the data model into the
61  * user-interface component, performing any necessary type
62  * conversion from model type to the type required by the
63  * user-interface component, which is often string.</li>
64  * <li>validate: run any validation logic to determine if the value
65  * contained in the user-interface component is valid for
66  * the data model</li>
67  * <li>push: copy the value contained in the user-interface component
68  * to the data model, performing any necessary type conversion
69  * from the user-interface value's type to the model's type.
70  * Note that only valid values may be pushed.</li>
71  * </ul>
72  *
73  * Changed (JW): the Form can handle non-visual data fields - will not
74  * create a component/binding if the metaData customProperty
75  * DataConstants.NON_VISUAL_FIELD is set to Boolean.TRUE.
76  *
77  * To use the Form component, an application need only bind the form to
78  * the desired fields on its data models, and by default the form will handle
79  * creating the necessary user-interface components and bindings, and will
80  * add those components to the form using a standard layout scheme. Applications
81  * generally should not need to interact directly with the component or binding
82  * objects at all.
83  * For example, the following code creates a form which can display/edit each
84  * column in a DefaultTableModelExt (and in the future, a RowSet):
85  * <pre><code>
86  * DefaultTableModelExt data = new DefaultTableModelExt("http://foo.bar/appdata");
87  * JForm form = new JForm();
88  * try {
89  * form.bind(data); // creates components/bindings for all columns
90  * } catch (BindException e) {
91  * }
92  * </code></pre>
93  * Once the form is created and bound, as in the example above, the form
94  * handles execution of the binding operations as the user interacts with the
95  * user-interface components in the form.
96  * </p>
97  * <p>The following is an example which shows how a form is bound to
98  * multiple data models:
99  * <pre><code>
100  * Customer customer = new Customer();
101  * Cart cart = new Cart();
102  *
103  * JForm form = new JForm();
104  * try {
105  * form.bind(customer, "firstName"); // binds to "firstName" property
106  * form.bind(customer, "lastName"); // binds to "lastName" property
107  * form.bind(customer, "address"); // binds to "address" property (nested bean)
108  * form.bind(cart, "items"); // binds to "items" property
109  * } catch (BindException e) {
110  * }
111  * </code></pre>
112  * </p>
113  * @author Amy Fowler
114  * @version 1.0
115  */

116
117 public class JForm extends JPanel JavaDoc {
118
119     private FormFactory formFactory;
120     private ArrayList JavaDoc bindings;
121     private DataModel model;
122
123     private boolean autoLayout = true;
124
125     private HashMap JavaDoc models; // leak? should use weak ref?
126

127     /**
128      * Creates a new form component.
129      */

130     public JForm() {
131         initActions();
132     }
133
134     /**
135      * Creates a new form component and binds it to the specified data model.
136      * @param model data model whose fields should be bound in this form
137      * @throws BindException if there were errors when binding to the data model
138      */

139     public JForm(DataModel model) throws BindException {
140         initActions();
141         bind(model);
142         pull();
143     }
144
145     protected void initActions() {
146         // Register the actions that this class can handle.
147
ActionMap JavaDoc map = getActionMap();
148         map.put("submit", new Actions("submit"));
149         map.put("reset", new Actions("reset"));
150     }
151
152     /**
153      * A small class which dispatches actions.
154      * TODO: Is there a way that we can make this static?
155      */

156     private class Actions extends UIAction {
157         Actions(String JavaDoc name) {
158             super(name);
159         }
160
161         public void actionPerformed(ActionEvent JavaDoc evt) {
162             if ("submit".equals(getName())) {
163                 doSubmit();
164             }
165             else if ("reset".equals(getName())) {
166                 doReset();
167             }
168         }
169     }
170
171     /**
172      * Sets the &quot;autoLayout&quot; property. The default is <code>true</code>.
173      * If set to <code>false</code>, then the application must take responsibility
174      * for adding the bound user-interface components to the form.
175      * @see #getAutoLayout
176      * @param autoLayout boolean value indicating whether or
177      * not the components created and bound in this form are
178      * automatically added to and layed out in the form
179      */

180     public void setAutoLayout(boolean autoLayout) {
181         this.autoLayout = autoLayout;
182     }
183
184     /**
185      * @see #setAutoLayout
186      * @return boolean value indicating whether or
187      * not the components created and bound in this form are
188      * automatically added to and layed out in the form
189      */

190     public boolean getAutoLayout() {
191         return autoLayout;
192     }
193
194     /**
195      * Sets the form factory for this form.
196      * @param factory FormFactory instance used to create the components and
197      * bindings for data model fields bound to the form
198      */

199     public void setFormFactory(FormFactory factory) {
200         /**@todo aim: FormFactory could be replace by FormUI? */
201         this.formFactory = factory;
202     }
203
204     /**
205      * If the form factory was never explicitly set, this method will
206      * return the default FormFactory instance obtained from
207      * <code>FormFactory:getDefaultFormFactory()</code>.
208      * @return FormFactory instance used to create the components and
209      * bindings for data model fields bound to the form
210      */

211     public FormFactory getFormFactory() {
212         if (formFactory == null) {
213             return FormFactory.getDefaultFormFactory();
214         }
215         return formFactory;
216     }
217
218     //public void bind(RowSet rowset)
219
//public void bind(RowSet rowset, String columnName)
220

221     /**
222      * Binds the form to each column in the specified DefaultTableModelExt object.
223      * The bind operation will create the best user-interface components to
224      * display/edit the data model values based on each column's <code>MetaData</code>
225      * object. It will also create the bindings from those user-interface components
226      * to the data model. And finally, if &quot;autoLayout&quot; is <code>true</code>,
227      * it will also add and layout those components within the form according
228      * to the mechanism defined by the form factory.
229      * @see MetaData
230      * @param tabularData DefaultTableModelExt being bound to the form
231      * @return array of Binding instances created from bind operation
232      * @throws BindException if there were errors when binding to the data model
233      */

234     public Binding[] bind(DefaultTableModelExt tabularData) throws BindException {
235         return bind(getDataModelWrapper(tabularData));
236     }
237
238     /**
239      * Binds the form to the specified column in the DefaultTableModelExt object.
240      * The bind operation will create the best user-interface component to
241      * display/edit the data model value based on the column's <code>MetaData</code>
242      * object. It will also create the binding from the user-interface component
243      * to the data model. And finally, if &quot;autoLayout&quot; is <code>true</code>,
244      * it will also add and layout the component within the form according
245      * to the mechanism defined by the form factory.
246      * @param tabularData DefaultTableModelExt being bound to the form
247      * @param columnName String containing the name of the column
248      * @return Binding instance created from bind operation
249          * @throws BindException if there were errors when binding to the data model
250      */

251     public Binding bind(DefaultTableModelExt tabularData, String JavaDoc columnName) throws BindException {
252         return bind(getDataModelWrapper(tabularData), columnName);
253     }
254
255     /**
256      * Binds the form to each property in the specified JavaBean object.
257      * The bind operation will create the best user-interface components to
258      * display/edit the data model values based on each property's description.
259      * It will also create the bindings from those user-interface components
260      * to the data model. And finally, if &quot;autoLayout&quot; is <code>true</code>,
261      * it will also add and layout those components within the form according
262      * to the mechanism defined by the form factory.
263      * @param bean JavaBean object being bound to the form
264      * @return array of Binding instances created from bind operation
265          * @throws BindException if there were errors when binding to the data model
266      */

267     public Binding[] bind(Object JavaDoc bean) throws BindException {
268         return bind(getDataModelWrapper(bean));
269     }
270
271     /**
272      * Binds the form to the specified property in the JavaBean object.
273      * The bind operation will create the best user-interface component to
274      * display/edit the data model value based on the property's description.
275      * It will also create the binding from the user-interface component
276      * to the data model. And finally, if &quot;autoLayout&quot; is <code>true</code>,
277      * it will also add and layout the component within the form according
278      * to the mechanism defined by the form factory.
279      * @param bean JavaBean object being bound to the form
280      * @param propertyName String containing the name of the property
281      * @return Binding instance created from bind operation
282      * @throws BindException if there were errors when binding to the data model
283      */

284     public Binding bind(Object JavaDoc bean, String JavaDoc propertyName) throws BindException {
285         return bind(getDataModelWrapper(bean), propertyName);
286     }
287
288     /**
289      * Binds the form to each visual field in the specified DataModel object.
290      * The bind operation will create the best user-interface components to
291      * display/edit the data model values based on each field's <code>MetaData</code>
292      * object. It will also create the bindings from those user-interface components
293      * to the data model. And finally, if &quot;autoLayout&quot; is <code>true</code>,
294      * it will also add and layout those components within the form according
295      * to the mechanism defined by the form factory.
296      * @param model DataModel object being bound to the form
297      * @return array of Binding instances created from bind operation
298          * @throws BindException if there were errors when binding to the data model
299      */

300     public Binding[] bind(DataModel model) throws BindException {
301         String JavaDoc fieldNames[] = model.getFieldNames();
302         List JavaDoc bindings = new ArrayList JavaDoc();
303         for(int i = 0; i < fieldNames.length; i++) {
304             Binding binding = bind(model, fieldNames[i]);
305             if (binding != null) {
306                 bindings.add(binding);
307             }
308         }
309         return (Binding[]) bindings.toArray(new Binding[bindings.size()]);
310     }
311
312     /**
313      * Binds the form to the specified field in the DataModel object.
314      * The bind operation will create the best user-interface component to
315      * display/edit the data model value based on the field's <code>MetaData</code>
316      * object. It will also create the binding from the user-interface component
317      * to the data model. And finally, if &quot;autoLayout&quot; is <code>true</code>,
318      * it will also add and layout the component within the form according
319      * to the mechanism defined by the form factory.
320      * @param model DataModel object being bound to the form
321      * @param fieldName String containing the name of the field
322      * @return Binding instance created from bind operation. Will be null for
323      * non-visual fields.
324      * @throws BindException if there were errors when binding to the data model
325      */

326     public Binding bind(DataModel model, String JavaDoc fieldName) throws BindException {
327         if (getFormFactory().isNonVisual(model.getMetaData(fieldName))) return null;
328         JComponent JavaDoc component = getFormFactory().
329             createComponent(model.getMetaData(fieldName));
330
331         if (component instanceof JForm) {
332             DataModel nestedModel = (DataModel)model.getValue(fieldName);
333             if (nestedModel != null) {
334                 JForm nestedForm = (JForm) component;
335                 nestedForm.bind(nestedModel);
336                 return bind(model, fieldName, nestedForm);
337             } else {
338                 throw new BindException(model, fieldName);
339             }
340         } else {
341             return bind(model, fieldName, component);
342         }
343     }
344
345     /**
346      * Binds the specified component to the field in the DataModel object.
347      * This bind operation will attempt to create a Binding instance appropriate
348      * for the specified component. If it is unable to do so, a BindException
349      * will be thrown.
350      * This method can be used when the application wishes to control the
351      * type of user-interface component used to display/edit the data model field.
352      * @param model DataModel object being bound to the form
353      * @param fieldName String containing the name of the field
354      * @param component user-interface component being bound to the data model
355      * @return Binding instance created from bind operation
356      * @throws BindException if there were errors when binding to the data model
357      */

358     public Binding bind(DataModel model, String JavaDoc fieldName, JComponent JavaDoc component)
359         throws BindException {
360         Binding binding = getFormFactory().createBinding(model, fieldName, component);
361
362         if (binding != null) {
363             return bind(binding, component);
364         } else {
365             throw new BindException("could not create binding for component "+
366                                     component.getClass().getName());
367         }
368     }
369
370     /**
371      * Adds the specified binding to this form. The user-interface
372      * component is the one which should be added to the form if the &quot;autoLayout&quot;
373      * property is <code>true</code>. Note that this component may be different
374      * from the component which is contained in the Binding instance.
375      * This method is invoked by the other <code>bind</code> methods and is
376      * typically not invoked directly by applications unless they require the
377      * ability to create their own Binding objects.
378      * @param binding Binding instance being added to this form
379      * @param component user-interface component which should be added to the form
380      * @return Binding instance created from bind operation
381      * @throws BindException if there were errors when binding to the data model
382      */

383     public Binding bind(Binding binding, JComponent JavaDoc component) throws BindException {
384         addBinding(binding);
385         if (autoLayout) {
386             DataModel model = binding.getDataModel();
387             MetaData metaData = model.getMetaData(binding.getFieldName());
388             getFormFactory().addComponent(this, component, metaData);
389         }
390         return binding;
391     }
392
393     /**
394      * Added to ease subclassing without actually
395      * exposing the private bindings-list.
396      *
397      * @param binding
398      */

399     protected void addBinding(Binding binding) {
400         if (binding == null) return;
401         if (bindings == null) {
402             bindings = new ArrayList JavaDoc();
403         }
404         bindings.add(binding);
405     }
406
407     /**
408      * Removes the specified binding from this form.
409      * @param binding Binding instance being removed
410      */

411     public void unbind(Binding binding) {
412         if (bindings != null) {
413             bindings.remove(binding);
414         }
415     }
416
417     /**
418      *
419      * @return array containing all Binding objects currently in this form
420      */

421     public Binding[] getBindings() {
422         if (bindings != null) {
423             return (Binding[]) bindings.toArray(new Binding[0]);
424         }
425         return new Binding[0];
426     }
427
428     /**
429      * For each binding defined in this form, pull the value from the data model
430      * and load it into the user-interface component.
431      * @return boolean indicating whether or not the data model values were
432      * successfully pulled into the user-interface components
433      */

434     public boolean pull() {
435         boolean result = true;
436         Binding bindings[] = getBindings();
437         for (int i = 0; i < bindings.length; i++) {
438             if (!bindings[i].pull()) {
439                 result = false;
440             }
441         }
442         return result;
443     }
444
445     /**
446      *
447      * @return boolean value indicating whether or not all fields in this form
448      * have a valid state
449      */

450     public boolean isFormValid() {
451         boolean result = true;
452         Binding bindings[] = getBindings();
453         ArrayList JavaDoc models = new ArrayList JavaDoc();
454         for (int i = 0; i < bindings.length; i++) {
455             DataModel bindingModel = bindings[i].getDataModel();
456             if (!models.contains(bindingModel)) {
457                 models.add(bindingModel);
458             }
459             if (!bindings[i].isValid()) {
460                 result = false;
461             }
462         }
463         if (result) {
464             for(int i = 0; i < models.size(); i++) {
465                 DataModel model = (DataModel)models.get(i);
466                 Validator validators[] = model.getValidators();
467                 for(int j = 0; j < validators.length; j++) {
468                     String JavaDoc error[] = new String JavaDoc[1];
469                     /**@todo aim: where to put error? */
470                     if (!validators[j].validate(model, getLocale(), error)) {
471                         result = false;
472                     }
473                 }
474             }
475
476         }
477         return result;
478     }
479
480     /**
481      *
482      * @return boolean value indicating whether or not any form field values
483      * have pending edits in the user-interface components since the
484      * last time values were pulled or pushed.
485      */

486     public boolean isModified() {
487         boolean result = false;
488         Binding bindings[] = getBindings();
489         for (int i = 0; i < bindings.length; i++) {
490             if (bindings[i].isModified()) {
491                 result = true;
492             }
493         }
494         return result;
495     }
496
497     /**
498      * Public callback for submit action.
499      */

500     public void doSubmit() {
501         if (push()) {
502             executeSubmit();
503         }
504         else {
505             JOptionPane.showMessageDialog(JForm.this,
506                 "Form contains invalid values.\nPlease correct values before submitting.",
507                 "Form Submission Error", JOptionPane.ERROR_MESSAGE);
508         }
509     }
510
511     /**
512      * Public callback for reset action.
513      */

514     public void doReset() {
515         pull();
516     }
517
518     /**
519      * If all form fields are in a valid state, push each
520      * value contained in a user-interface component its associated data model.
521      * @see #isFormValid
522      * @return boolean indicating whether or not the form field values were
523      * successfully pushed to the data models
524      */

525     public boolean push() {
526         if (!isFormValid()) {
527             return false;
528         }
529         boolean result = true;
530         Binding bindings[] = getBindings();
531         for (int i = 0; i < bindings.length; i++) {
532             if (!bindings[i].push()) {
533                 result = false;
534             }
535         }
536         return result;
537     }
538
539     /**
540      * Invoked from the submit action if and only if
541      * all form component values were successfully pushed to the
542      * form's data model.
543      */

544     protected void executeSubmit() {
545         JOptionPane.showMessageDialog(this,
546                                       "Form submitted.\nThanks",
547                                       "Form Submission", JOptionPane.PLAIN_MESSAGE);
548     }
549
550     private DataModel getDataModelWrapper(Object JavaDoc model) throws BindException {
551         if (models == null) {
552             models = new HashMap JavaDoc();
553         }
554         DataModel wrapper = (DataModel)models.get(model);
555         if (wrapper == null) {
556             if (model instanceof DefaultTableModelExt) {
557                 wrapper = new TableModelExtAdapter((DefaultTableModelExt)model);
558             } else {
559                 try {
560                     wrapper = new JavaBeanDataModel(model.getClass(), model);
561                 } catch (IntrospectionException JavaDoc e) {
562                     throw new BindException(model, e);
563                 }
564             }
565             models.put(model, wrapper);
566         }
567         return wrapper;
568     }
569 }
570
Popular Tags