001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.form;
017
018 import java.beans.PropertyChangeEvent;
019 import java.beans.PropertyChangeListener;
020 import java.util.ArrayList;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024
025 import javax.swing.JButton;
026 import javax.swing.JComponent;
027 import javax.swing.JRootPane;
028 import javax.swing.SwingUtilities;
029
030 import org.springframework.binding.form.CommitListener;
031 import org.springframework.binding.form.FormModel;
032 import org.springframework.binding.form.HierarchicalFormModel;
033 import org.springframework.binding.form.ValidatingFormModel;
034 import org.springframework.binding.validation.ValidationListener;
035 import org.springframework.binding.value.IndexAdapter;
036 import org.springframework.binding.value.ValueModel;
037 import org.springframework.binding.value.support.ObservableList;
038 import org.springframework.richclient.command.ActionCommand;
039 import org.springframework.richclient.core.Guarded;
040 import org.springframework.richclient.dialog.Messagable;
041 import org.springframework.richclient.factory.AbstractControlFactory;
042 import org.springframework.richclient.form.binding.BindingFactory;
043 import org.springframework.richclient.form.binding.BindingFactoryProvider;
044 import org.springframework.util.Assert;
045 import org.springframework.util.ClassUtils;
046 import org.springframework.util.StringUtils;
047
048 /**
049 * Base implementation of a Form.
050 *
051 * Commands provided:
052 * <ul>
053 * <li><em>CommitCommand</em>: wraps the {@link FormModel#commit()} method.
054 * Writes data to backing bean. Guarded mask {@link FormGuard#ON_NOERRORS},
055 * {@link FormGuard#ON_ISDIRTY} and {@link FormGuard#ON_ENABLED}.</li>
056 * <li><em>RevertCommand</em>: wraps the {@link FormModel#revert()} method.
057 * Fall back to the values of the backing bean. Guarded mask
058 * {@link FormGuard#ON_ISDIRTY} and {@link FormGuard#ON_ENABLED}.</li>
059 * <li><em>NewFormObjectCommand</em>: set a fresh instance on the
060 * {@link FormModel}. Guarded mask {@link FormGuard#ON_ENABLED}</li>
061 * </ul>
062 *
063 * All commands provide a securityControllerId.
064 *
065 * @author Keith Donald
066 */
067 public abstract class AbstractForm extends AbstractControlFactory implements Form, CommitListener {
068
069 private final FormObjectChangeHandler formObjectChangeHandler = new FormObjectChangeHandler();
070
071 private String formId;
072
073 private ValidatingFormModel formModel;
074
075 private HierarchicalFormModel parentFormModel;
076
077 private FormGuard formGuard;
078
079 private JButton lastDefaultButton;
080
081 private PropertyChangeListener formEnabledChangeHandler;
082
083 private ActionCommand newFormObjectCommand;
084
085 private ActionCommand commitCommand;
086
087 private ActionCommand revertCommand;
088
089 private boolean editingNewFormObject;
090
091 private boolean clearFormOnCommit = false;
092
093 private ObservableList editableFormObjects;
094
095 private ValueModel editingFormObjectIndexHolder;
096
097 private PropertyChangeListener editingFormObjectSetter;
098
099 private BindingFactory bindingFactory;
100
101 private Map childForms = new HashMap();
102
103 private List validationResultsReporters = new ArrayList();
104
105 /**
106 * Default constructor will use the uncapitalized simplename of the class to
107 * construct its id.
108 */
109 protected AbstractForm() {
110 setId(StringUtils.uncapitalize(getClass().getSimpleName()));
111 init();
112 }
113
114 /**
115 * Id configurable constructor.
116 */
117 protected AbstractForm(String formId) {
118 setId(formId);
119 init();
120 }
121
122 /**
123 * Convenience constructor which creates a {@link FormModel} by calling
124 * {@link FormModelHelper#createFormModel(Object)}.
125 *
126 * @param formObject object used to create the formModel.
127 * @see #AbstractForm(FormModel)
128 */
129 protected AbstractForm(Object formObject) {
130 this(FormModelHelper.createFormModel(formObject));
131 }
132
133 /**
134 * Create an AbstractForm with the given {@link FormModel}. Use the
135 * formModel's id to configure the Form.
136 *
137 * @see #AbstractForm(FormModel, String)
138 */
139 protected AbstractForm(FormModel formModel) {
140 this(formModel, formModel.getId());
141 }
142
143 /**
144 * Create an AbstractForm.
145 */
146 protected AbstractForm(FormModel formModel, String formId) {
147 setId(formId);
148 if (formModel instanceof ValidatingFormModel) {
149 setFormModel((ValidatingFormModel) formModel);
150 }
151 else {
152 throw new IllegalArgumentException("Unsupported form model implementation " + formModel);
153 }
154 init();
155 }
156
157 /**
158 * Create a Form with a FormModel that has a child-parent relation with the
159 * provided parentFormModel.
160 *
161 * @param parentFormModel the parent formModel.
162 * @param formId id used for this Form's configuration.
163 * @param childFormObjectPropertyPath the path relative to the
164 * parentFormModel's formObject that leads to the child formObject that will
165 * be handled by this Form.
166 * @see FormModelHelper#createChildPageFormModel(HierarchicalFormModel,
167 * String, String)
168 */
169 protected AbstractForm(HierarchicalFormModel parentFormModel, String formId, String childFormObjectPropertyPath) {
170 setId(formId);
171 this.parentFormModel = parentFormModel;
172 setFormModel(FormModelHelper.createChildPageFormModel(parentFormModel, formId, childFormObjectPropertyPath));
173 init();
174 }
175
176 /**
177 * Create a Form with a FormModel that has a child-parent relation with the
178 * provided parentFormModel.
179 *
180 * @param parentFormModel the parent formModel.
181 * @param formId id used for this Form's configuration.
182 * @param childFormObjectHolder the valueModel of the parentFormModel that
183 * holds the child formObject that will be handled by this Form.
184 * @see FormModelHelper#createChildPageFormModel(HierarchicalFormModel,
185 * String, ValueModel)
186 */
187 protected AbstractForm(HierarchicalFormModel parentFormModel, String formId, ValueModel childFormObjectHolder) {
188 setId(formId);
189 this.parentFormModel = parentFormModel;
190 setFormModel(FormModelHelper.createChildPageFormModel(parentFormModel, formId, childFormObjectHolder));
191 init();
192 }
193
194 /**
195 * Hook called when constructing the Form.
196 */
197 protected void init() {
198
199 }
200
201 public String getId() {
202 return formId;
203 }
204
205 /**
206 * Set the id used to configure this Form.
207 */
208 protected void setId(String formId) {
209 this.formId = formId;
210 }
211
212 public ValidatingFormModel getFormModel() {
213 return formModel;
214 }
215
216 /**
217 * Returns a {@link BindingFactory} bound to the inner {@link FormModel} to
218 * provide binding support.
219 */
220 public BindingFactory getBindingFactory() {
221 if (bindingFactory == null) {
222 bindingFactory = ((BindingFactoryProvider) getService(BindingFactoryProvider.class))
223 .getBindingFactory(formModel);
224 }
225 return bindingFactory;
226 }
227
228 /**
229 * Set the {@link FormModel} for this {@link Form}. Normally a Form won't
230 * change it's FormModel as this may lead to an inconsistent state. Only use
231 * this when the formModel isn't set yet.
232 *
233 * TODO check why we do allow setting when no control is created.
234 * ValueModels might exist already leading to an inconsistent state.
235 *
236 * @param formModel
237 */
238 protected void setFormModel(ValidatingFormModel formModel) {
239 Assert.notNull(formModel);
240 if (this.formModel != null && isControlCreated()) {
241 throw new UnsupportedOperationException("Cannot reset form model once form control has been created");
242 }
243 if (this.formModel != null) {
244 this.formModel.removeCommitListener(this);
245 }
246 this.formModel = formModel;
247 this.formGuard = new FormGuard(formModel);
248 this.formModel.addCommitListener(this);
249 setFormModelDefaultEnabledState();
250 }
251
252 /**
253 * Returns the parent of this Form's FormModel or <code>null</code>.
254 */
255 protected HierarchicalFormModel getParent() {
256 return this.parentFormModel;
257 }
258
259 /**
260 * Add a child (or sub) form to this form. Child forms will be tied in to
261 * the same validation results reporter as this form and they will be
262 * configured to control the same guarded object as this form.
263 * <p>
264 * Validation listeners are unique to a form, so calling
265 * {@link #addValidationListener(ValidationListener)} will only add a
266 * listener to this form. If you want to listen to the child forms, you will
267 * need to add a validation listener on each child form of interest.
268 * <p>
269 * <em>Note:</em> It is very important that the child form provided be
270 * created using a form model that is a child model of this form's form
271 * model. If this is not done, then commit and revert operations will not be
272 * properly delegated to the child form models.
273 *
274 * @param childForm to add
275 */
276 public void addChildForm(Form childForm) {
277 childForms.put(childForm.getId(), childForm);
278 getFormModel().addChild(childForm.getFormModel());
279 }
280
281 /**
282 * @inheritDoc
283 */
284 public List getValidationResultsReporters() {
285 return validationResultsReporters;
286 }
287
288 /**
289 * @inheritDoc
290 */
291 public void addValidationResultsReporter(ValidationResultsReporter reporter) {
292 this.validationResultsReporters.add(reporter);
293 }
294
295 /**
296 * @inheritDoc
297 */
298 public void removeValidationResultsReporter(ValidationResultsReporter reporter) {
299 this.validationResultsReporters.remove(reporter);
300 }
301
302 /**
303 * @inheritDoc
304 */
305 public void removeChildForm(Form childForm) {
306 getFormModel().removeChild(childForm.getFormModel());
307 childForms.remove(childForm.getId());
308 }
309
310 /**
311 * Return a child form of this form with the given form id.
312 * @param id of child form
313 * @return child form, null if no child form with the given id has been
314 * registered
315 */
316 protected Form getChildForm(String id) {
317 return (Form) childForms.get(id);
318 }
319
320 protected void setEditableFormObjects(ObservableList editableFormObjects) {
321 this.editableFormObjects = editableFormObjects;
322 }
323
324 protected void setEditingFormObjectIndexHolder(ValueModel valueModel) {
325 this.editingFormObjectIndexHolder = valueModel;
326 this.editingFormObjectSetter = new EditingFormObjectSetter();
327 this.editingFormObjectIndexHolder.addValueChangeListener(editingFormObjectSetter);
328 }
329
330 public boolean isEditingNewFormObject() {
331 return editingNewFormObject;
332 }
333
334 /**
335 * Set the "editing new form object" state as indicated.
336 * @param editingNewFormOject
337 */
338 protected void setEditingNewFormObject(boolean editingNewFormOject) {
339 this.editingNewFormObject = editingNewFormOject;
340 }
341
342 private class EditingFormObjectSetter implements PropertyChangeListener {
343 public void propertyChange(PropertyChangeEvent evt) {
344 int selectionIndex = getEditingFormObjectIndex();
345 if (selectionIndex == -1) {
346 // FIXME: why do we need this
347 // getFormModel().reset();
348 setEnabled(false);
349 }
350 else {
351 if (selectionIndex < editableFormObjects.size()) {
352 // If we were editing a "new" object, we need to clear
353 // that flag since a new object has been selected
354 setEditingNewFormObject(false);
355 setFormObject(getEditableFormObject(selectionIndex));
356 setEnabled(true);
357 }
358 }
359 }
360 }
361
362 protected int getEditingFormObjectIndex() {
363 return ((Integer) editingFormObjectIndexHolder.getValue()).intValue();
364 }
365
366 protected Object getEditableFormObject(int selectionIndex) {
367 return editableFormObjects.get(selectionIndex);
368 }
369
370 public void setClearFormOnCommit(boolean clearFormOnCommit) {
371 this.clearFormOnCommit = clearFormOnCommit;
372 }
373
374 protected JButton getDefaultButton() {
375 if (isControlCreated()) {
376 JRootPane rootPane = SwingUtilities.getRootPane(getControl());
377 return rootPane == null ? null : rootPane.getDefaultButton();
378 }
379 return null;
380 }
381
382 protected void setDefaultButton(JButton button) {
383 JRootPane rootPane = SwingUtilities.getRootPane(getControl());
384 if (rootPane != null) {
385 rootPane.setDefaultButton(button);
386 }
387 }
388
389 protected final JComponent createControl() {
390 Assert
391 .state(getFormModel() != null,
392 "This form's FormModel cannot be null once control creation is triggered!");
393 initStandardLocalFormCommands();
394 JComponent formControl = createFormControl();
395 this.formEnabledChangeHandler = new FormEnabledPropertyChangeHandler();
396 getFormModel().addPropertyChangeListener(FormModel.ENABLED_PROPERTY, formEnabledChangeHandler);
397 addFormObjectChangeListener(formObjectChangeHandler);
398 if (getCommitCommand() != null) {
399 getFormModel().addCommitListener(this);
400 }
401 return formControl;
402 }
403
404 private void initStandardLocalFormCommands() {
405 getNewFormObjectCommand();
406 getCommitCommand();
407 getRevertCommand();
408 }
409
410 /**
411 * Set the form's enabled state based on a default policy--specifically,
412 * disable if the form object is null or the form object is guarded and is
413 * marked as disabled.
414 */
415 protected void setFormModelDefaultEnabledState() {
416 if (getFormObject() == null) {
417 getFormModel().setEnabled(false);
418 }
419 else {
420 if (getFormObject() instanceof Guarded) {
421 setEnabled(((Guarded) getFormObject()).isEnabled());
422 }
423 }
424 }
425
426 protected abstract JComponent createFormControl();
427
428 private class FormObjectChangeHandler implements PropertyChangeListener {
429
430 public void propertyChange(PropertyChangeEvent evt) {
431 setFormModelDefaultEnabledState();
432 }
433 }
434
435 private class FormEnabledPropertyChangeHandler implements PropertyChangeListener {
436 public FormEnabledPropertyChangeHandler() {
437 handleEnabledChange(getFormModel().isEnabled());
438 }
439
440 public void propertyChange(PropertyChangeEvent evt) {
441 handleEnabledChange(getFormModel().isEnabled());
442 }
443 }
444
445 protected void handleEnabledChange(boolean enabled) {
446 if (enabled) {
447 if (getCommitCommand() != null) {
448 if (lastDefaultButton == null) {
449 lastDefaultButton = getDefaultButton();
450 }
451 getCommitCommand().setDefaultButton();
452 }
453 }
454 else {
455 if (getCommitCommand() != null) {
456 getCommitCommand().setEnabled(false);
457 }
458 // set previous default button
459 if (lastDefaultButton != null) {
460 setDefaultButton(lastDefaultButton);
461 }
462 }
463 }
464
465 public ActionCommand getNewFormObjectCommand() {
466 if (this.newFormObjectCommand == null) {
467 this.newFormObjectCommand = createNewFormObjectCommand();
468 }
469 return newFormObjectCommand;
470 }
471
472 public ActionCommand getCommitCommand() {
473 if (this.commitCommand == null) {
474 this.commitCommand = createCommitCommand();
475 }
476 return commitCommand;
477 }
478
479 public ActionCommand getRevertCommand() {
480 if (this.revertCommand == null) {
481 this.revertCommand = createRevertCommand();
482 }
483 return revertCommand;
484 }
485
486 private ActionCommand createNewFormObjectCommand() {
487 String commandId = getNewFormObjectCommandId();
488 if (!StringUtils.hasText(commandId)) {
489 return null;
490 }
491 ActionCommand newFormObjectCmd = new ActionCommand(commandId) {
492 protected void doExecuteCommand() {
493 getFormModel().setFormObject(createNewObject());
494 getFormModel().setEnabled(true);
495 editingNewFormObject = true;
496 if (isEditingFormObjectSelected()) {
497 setEditingFormObjectIndexSilently(-1);
498 }
499 }
500 };
501 newFormObjectCmd.setSecurityControllerId(getNewFormObjectSecurityControllerId());
502 attachFormGuard(newFormObjectCmd, FormGuard.LIKE_NEWFORMOBJCOMMAND);
503 return (ActionCommand) getCommandConfigurer().configure(newFormObjectCmd);
504 }
505
506 /**
507 * Create a new object to install into the form. By default, this simply
508 * returns null. This will cause the form model to instantiate a new copy of
509 * the model object class. Subclasses should override this method if they
510 * need more control over how new objects are constructed.
511 *
512 * @return new object for editing
513 */
514 protected Object createNewObject() {
515 return null;
516 }
517
518 private boolean isEditingFormObjectSelected() {
519 if (editingFormObjectIndexHolder == null) {
520 return false;
521 }
522 int value = ((Integer) editingFormObjectIndexHolder.getValue()).intValue();
523 return value != -1;
524 }
525
526 protected void setEditingFormObjectIndexSilently(int index) {
527 editingFormObjectIndexHolder.removeValueChangeListener(editingFormObjectSetter);
528 editingFormObjectIndexHolder.setValue(new Integer(index));
529 editingFormObjectIndexHolder.addValueChangeListener(editingFormObjectSetter);
530 }
531
532 /**
533 * Returns a command wrapping the commit behavior of the {@link FormModel}.
534 * This command has the guarded and security aspects.
535 */
536 private final ActionCommand createCommitCommand() {
537 String commandId = getCommitCommandFaceDescriptorId();
538 if (!StringUtils.hasText(commandId)) {
539 return null;
540 }
541 ActionCommand commitCmd = new ActionCommand(commandId) {
542 protected void doExecuteCommand() {
543 commit();
544 }
545 };
546 commitCmd.setSecurityControllerId(getCommitSecurityControllerId());
547 attachFormGuard(commitCmd, FormGuard.LIKE_COMMITCOMMAND);
548 return (ActionCommand) getCommandConfigurer().configure(commitCmd);
549 }
550
551 public void preCommit(FormModel formModel) {
552 }
553
554 public void postCommit(FormModel formModel) {
555 if (editableFormObjects != null) {
556 if (editingNewFormObject) {
557 editableFormObjects.add(formModel.getFormObject());
558 setEditingFormObjectIndexSilently(editableFormObjects.size() - 1);
559 }
560 else {
561 int index = getEditingFormObjectIndex();
562 // Avoid updating unless we have actually selected an object for
563 // edit
564 if (index >= 0) {
565 IndexAdapter adapter = editableFormObjects.getIndexAdapter(index);
566 adapter.setValue(formModel.getFormObject());
567 adapter.fireIndexedObjectChanged();
568 }
569 }
570 }
571 if (clearFormOnCommit) {
572 setFormObject(null);
573 }
574 editingNewFormObject = false;
575 }
576
577 private final ActionCommand createRevertCommand() {
578 String commandId = getRevertCommandFaceDescriptorId();
579 if (!StringUtils.hasText(commandId)) {
580 return null;
581 }
582 ActionCommand revertCmd = new ActionCommand(commandId) {
583 protected void doExecuteCommand() {
584 revert();
585 }
586 };
587 attachFormGuard(revertCmd, FormGuard.LIKE_REVERTCOMMAND);
588 return (ActionCommand) getCommandConfigurer().configure(revertCmd);
589 }
590
591 protected final JButton createNewFormObjectButton() {
592 Assert.state(newFormObjectCommand != null, "New form object command has not been created!");
593 return (JButton) newFormObjectCommand.createButton();
594 }
595
596 protected final JButton createCommitButton() {
597 Assert.state(commitCommand != null, "Commit command has not been created!");
598 return (JButton) commitCommand.createButton();
599 }
600
601 protected String getNewFormObjectCommandId() {
602 return "new"
603 + StringUtils
604 .capitalize(ClassUtils.getShortName(getFormModel().getFormObject().getClass() + "Command"));
605 }
606
607 protected String getCommitCommandFaceDescriptorId() {
608 return null;
609 }
610
611 protected String getRevertCommandFaceDescriptorId() {
612 return null;
613 }
614
615 /**
616 * Subclasses may override to return a security controller id to be attached
617 * to the newFormObject command. The default is
618 * <code>[formModel.id] + "." + [getNewFormObjectCommandId()]</code>.
619 * <p>
620 * This id can be mapped to a specific security controller using the
621 * SecurityControllerManager service.
622 *
623 * @return security controller id, may be null if the face id is null
624 * @see org.springframework.richclient.security.SecurityControllerManager
625 */
626 protected String getNewFormObjectSecurityControllerId() {
627 return constructSecurityControllerId(getNewFormObjectCommandId());
628 }
629
630 /**
631 * Subclasses may override to return a security controller id to be attached
632 * to the commit command. The default is The default is
633 * <code>[formModel.id] + "." + [getCommitCommandFaceDescriptorId()]</code>.
634 * <p>
635 * This id can be mapped to a specific security controller using the
636 * SecurityControllerManager service.
637 *
638 * @return security controller id, may be null if the face id is null
639 * @see org.springframework.richclient.security.SecurityControllerManager
640 */
641 protected String getCommitSecurityControllerId() {
642 return constructSecurityControllerId(getCommitCommandFaceDescriptorId());
643 }
644
645 /**
646 * Construct a default security controller Id for a given command face id.
647 * The id will be a combination of the form model id, if any, and the face
648 * id.
649 * <p>
650 * <code>[formModel.id] + "." + [commandFaceId]</code> if the form model
651 * id is not null.
652 * <p>
653 * <code>[commandFaceId]</code> if the form model is null.
654 * <p>
655 * <code>null</code> if the commandFaceId is null.
656 * @param commandFaceId
657 * @return default security controller id
658 */
659 protected String constructSecurityControllerId(String commandFaceId) {
660 String id = null;
661 String formModelId = getFormModel().getId();
662
663 if (commandFaceId != null) {
664 id = (formModelId != null) ? formModelId + "." + commandFaceId : commandFaceId;
665 }
666 return id;
667 }
668
669 protected void attachFormErrorGuard(Guarded guarded) {
670 attachFormGuard(guarded, FormGuard.FORMERROR_GUARDED);
671 }
672
673 protected void attachFormGuard(Guarded guarded, int mask) {
674 this.formGuard.addGuarded(guarded, mask);
675 }
676
677 protected void detachFormGuard(Guarded guarded) {
678 this.formGuard.removeGuarded(guarded);
679 }
680
681 public Object getFormObject() {
682 return formModel.getFormObject();
683 }
684
685 public void setFormObject(Object formObject) {
686 formModel.setFormObject(formObject);
687 }
688
689 public Object getValue(String formProperty) {
690 return formModel.getValueModel(formProperty).getValue();
691 }
692
693 public ValueModel getValueModel(String formProperty) {
694 ValueModel valueModel = formModel.getValueModel(formProperty);
695 if (valueModel == null) {
696 logger.warn("A value model for property '" + formProperty + "' could not be found. Typo?");
697 }
698 return valueModel;
699 }
700
701 public boolean isEnabled() {
702 return this.formModel.isEnabled();
703 }
704
705 public void setEnabled(boolean enabled) {
706 this.formModel.setEnabled(enabled);
707 }
708
709 public void addValidationListener(ValidationListener listener) {
710 formModel.getValidationResults().addValidationListener(listener);
711 }
712
713 public void removeValidationListener(ValidationListener listener) {
714 formModel.getValidationResults().removeValidationListener(listener);
715 }
716
717 /**
718 * Construct the validation results reporter for this form and attach it to
719 * the provided Guarded object. An instance of
720 * {@link SimpleValidationResultsReporter} will be constructed and returned.
721 * All registered child forms will be attached to the same
722 * <code>guarded</code> and <code>messageReceiver</code> as this form.
723 */
724 public ValidationResultsReporter newSingleLineResultsReporter(Messagable messageReceiver) {
725
726 SimpleValidationResultsReporter reporter = new SimpleValidationResultsReporter(
727 formModel.getValidationResults(), messageReceiver);
728
729 return reporter;
730 }
731
732 public void addFormObjectChangeListener(PropertyChangeListener listener) {
733 formModel.getFormObjectHolder().addValueChangeListener(listener);
734 }
735
736 public void removeFormObjectChangeListener(PropertyChangeListener listener) {
737 formModel.getFormObjectHolder().removeValueChangeListener(listener);
738 }
739
740 public void addFormValueChangeListener(String formPropertyPath, PropertyChangeListener listener) {
741 getFormModel().getValueModel(formPropertyPath).addValueChangeListener(listener);
742 }
743
744 public void removeFormValueChangeListener(String formPropertyPath, PropertyChangeListener listener) {
745 getFormModel().getValueModel(formPropertyPath).removeValueChangeListener(listener);
746 }
747
748 public boolean isDirty() {
749 return formModel.isDirty();
750 }
751
752 public boolean hasErrors() {
753 return formModel.getValidationResults().getHasErrors();
754 }
755
756 public void commit() {
757 formModel.commit();
758 }
759
760 public void revert() {
761 formModel.revert();
762 }
763
764 public void reset() {
765 getFormModel().reset();
766 }
767
768 public void addGuarded(Guarded guarded) {
769 formGuard.addGuarded(guarded, FormGuard.FORMERROR_GUARDED);
770 }
771
772 public void addGuarded(Guarded guarded, int mask) {
773 formGuard.addGuarded(guarded, mask);
774 }
775
776 public void removeGuarded(Guarded guarded) {
777 formGuard.removeGuarded(guarded);
778 }
779 }