Forms Support
Forms are used to accept input from the user.
Creating a Form
Subclass AbstractForm.
public class CustomerForm extends AbstractForm {
public CustomerForm(FormModel formModel) {
super(formModel, "customerForm");
}
protected JComponent createFormControl() {
FormLayout layout = new FormLayout("left:pref, 5dlu, pref:grow");
BeanFormBuilder formBuilder = new JGoodiesBeanFormBuilder(
getFormModel(), layout);
formBuilder.add("number");
formBuilder.add("name");
return formBuilder.getForm();
}
}
Compound Forms
The CompoundForm class is used to combine several "formpages" into one logical form. For example when you have a wizard with multiple pages, and each page contains one form, you can bind them together using CompoundForm.
FormBuilders
To create an advanced layout, you can use a FormBuilder. There are several implementations.
TableFormBuilder
Example:
TableFormBuilder formBuilder = new TableFormBuilder(formModel);
formBuilder.addSeparator("Name");
formBuilder.row();
formBuilder.add("title");
formBuilder.add("firstName");
formBuilder.add("lastName");
formBuilder.row();
formBuilder.addSeparator("Address");
formBuilder.row();
formBuilder.add("address1");
formBuilder.row();
formBuilder.add("address2");
formBuilder.row();
formBuilder.add("city");
formBuilder.add("postcode", "colSpec=2cm");
formBuilder.row();
formBuilder.add("state");
formBuilder.row();
formBuilder.add("country");
return formBuilder.getForm();
GridBagLayoutFormBuilder
Uses GridBagLayout.
Using the Form
In a Wizard
Create a FormBackedWizardPage and add it to the wizard.
In a Dialog
Customer customer = ...;
FormModel formModel = SwingFormModel.createFormModel(customer);
CustomerForm form = new CustomerForm(formModel);
FormBackedDialogPage page = new FormBackedDialogPage(form);
TitledPageApplicationDialog dialog = new TitledPageApplicationDialog(page, getParentWindowControl()) {
protected void onAboutToShow() {
setEnabled(compositePage.isPageComplete());
}
protected boolean onFinish() {
ownerFormModel.commit();
clinic.storeOwner(owner);
ownersTreeModel.nodeChanged(getSelectedOwnerNode());
return true;
}
};
dialog.showDialog();
Validation
Currently there are two ways: you can register validation rules by configuring a RulesSource instance, like Petclinic's "ValidationRulesSource" class, or your form object can implement the RulesProvider PropertyConstraintProvider interface, like the Login SessionDetails class does.
Approach 1
You need to have a RulesSource implementation (see the PetClinicValidationRulesSource class for an example).
Here are the rules for the Owner class defined (firstName and lastName attributes are required, max 25 long and must be alphabetic, and the address attribute is required)
public class PetClinicValidationRulesSource extends DefaultRulesSource {
public PetClinicValidationRulesSource() {
addRules(createOwnerRules());
}
private Rules createOwnerRules() {
Rules rules = Rules.createRules(Owner.class);
rules.add("firstName", getNamePropertyConstraint());
rules.add("lastName", getNamePropertyConstraint());
rules.add("address", required());
return rules;
}
private Constraint getNamePropertyConstraint() {
return all(new Constraint[] { required(), maxLength(25),
regexp("[a-zA-Z]*", "alphabetic") });
}
}
The RulesSource is registered in the rich-clientappication-context.xml file:
This will cause all forms that use the Owner class use these validation rules.
Approach 2
TODO - update to show use of PropertyConstraintProvider since RulesProvider is no longer used.
Make your form model object implement the RulesProvider interface. The RulesProvider defines one method: public PropertyConstraint getRules(String property). This example uses the DefaultRulesSource to implement this method.
Form model object:
public class TestFormBean implements RulesProvider {
RulesSource rulesSource = new DefaultRulesSource();
private String inputString;
public TestFormBean() {
rulesSource.addRules(getRules());
}
private Rules getRules() {
Rules rules = Rules.createRules(getClass());
Constraints c = Constraints.instance();
rules.add("inputString", c.required());
return rules;
}
public PropertyConstraint getRules(String property) {
return rulesSource.getRules(getClass(), property);
}
}
Form instance
public class TestForm extends AbstractForm {
private TestFormBean testFormBean = new TestFormBean();
public LoginForm() {
super("general");
setFormModel(SwingFormModel.createFormModel(this.testFormBean));
}
protected JComponent createFormControl() {
FormLayout layout = new FormLayout("left:pref, 5dlu, pref:grow");
BeanFormBuilder formBuilder = new JGoodiesBeanFormBuilder(
getFormModel(), layout);
formBuilder.add("inputString");
return formBuilder.getForm();
}
public void commit() throws AuthenticationException {
}
}
Constraints
How to implement your own Constraint.
I18n the error message
Make your constraint implement TypeResolvable. There is a TypeResolvableConstraint helper class you can subclass. TypeResolvables automatically get resolved in Spring message sources.
public class MyCustomConstraint extends TypeResolvableConstraint {
public MyCustomConstraint() {
super("customConstraint");
}
public boolean test(Object argument) {
}
}
Binding
Binding the value in the control with the backing domain object is done by way of a Binding object, which is typically created by a Binder. For example, when you ask the framework to create a control for you for the "bar" property of Foo, where "bar" is a String, a TextComponentBinder returns back a TextComponentBinding, which handles keeping everything in sync.
If you did all of this explicitly, it would look like:
BindingFactory bindingFactory = new SwingBindingFactory(formModel);
Binding fooBinding = bindingFactory.createBoundTextField("bar");
JComponent fooControl = fooBinding.getControl();
That may look like a lot of work just to create a text field, but there's an enormous amount of work that's happening there automaticly, including registering both the object's property and the control for validation support, automaticly keeping in sync, and much more. It also means that the kind of control to use is automaticly determined and created for you, so you can make uniform changes across your application in one place. (For example, changing from a standard JTextArea to a component that supports full rich text capabilities across the entire application.) The scope of which Binder to use can be determined by the property class, associated with just a specific property, and more.
Binder
An example of a Binder:
/**
* This provides a {@link Binder} for {@link Long} that only allows
* valid "Long" values to be entered into the control.
*/
public class LongBinder extends AbstractBinder {
public LongBinder() {
super(Long.class);
}
protected JComponent createControl(Map context) {
return new LongTextField();
}
protected Binding doBind(JComponent control, FormModel formModel, String formPropertyPath, Map context) {
return new TextComponentBinding((JTextComponent)control, formModel, formPropertyPath);
}
private static class LongTextField extends JFormattedTextField {
public boolean isEditValid() {
final String text = getText();
if (text == null) return true;
try {
Long.parseLong(text);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
}
}
BinderSelectionStrategy
If you want to change or add to the standard set of automatic bindings, simply implement BinderSelectionStrategy. An example BinderSelectionStrategy would be:
public class MyBinderSelectionStrategy extends SwingBinderSelectionStrategy {
protected void registerDefaultBinders() {
super.registerDefaultBinders();
registerBinderForPropertyType(Long.class, new LongBinder());
registerBinderForPropertyType(Date.class, new DateBinder());
registerBinderForPropertyType(Foo[].class, new FooArrayBinder());
}
}
To get the Application object to automaticly use your custom BinderSelectionStrategy, add this line to your application-context.xml:
<bean id="binderSelectionStrategy" class="bar.foo.MyBinderSelectionStrategy"/>
From the forum - http://forum.springframework.org/viewtopic.php?t=2482
When building components with a BeanFormBuilder (at least; likely others), a Label is automatically applied. The framework searches for labels in this order:
1. label.property
2. property
3. formId.label.property
So, if you have two pojos (foo, bar), each with the same property (id), then the following entries in your <messages>.properties:
label.id=FooBar
bar.label.id=Bar
would result in 'Bar' as the label for bar.id and 'FooBar' as the label for foo.id.