Class FieldBuilder<T>
- Type Parameters:
T- backing object type
- All Implemented Interfaces:
Serializable
FieldBuilder annotations allow for the automatic construction and configuration of fields for editing a bean.
Annotations on "getter" methods specify how the fields that edit the corresponding bean property should be constructed,
configured, and bound to a Binder. This allows all information about how to edit a Java type
to be specified declaratively. Annotations are also provided to configure how to add the generated fields to a
FormLayout.
The primary method is bindFields(), which automatically creates, configures, and binds fields
into a given Binder based on information gleaned from scanned annotations.
@FieldBuilder.Foo vs. @ProvidesField
FieldBuilder supports two types of annotations from which it infers how to construct and configure a field
for editing an associated bean property.
The various @FieldBuilder.Foo annotations are the purely declarative way to specify how to construct a field.
Each annotation corresponds to a specific field class (e.g., @FieldBuilder.TextField
configures a TextField). The annotation's properties parallel the
properties of the field and specify how to construct, configure, and bind an instance of the field.
The annotation annotates the bean property's "getter" method.
@ProvidesField provides a more general approach, but it requires writing code.
Use @ProvidesField on a method that itself knows how to build a field suitable
for editing the named property. The method should return a component for editing the property. Both instance and static
methods are supported; instance methods require that the Binder being used has a bound bean.
In all cases, an annotation on a subclass method will override the same annotation on the corresponding superclass method.
Fields defined from these annotations are created, configured, and bound via bindFields().
Configuring the Binding
In addition to constructing and configuring the fields associated with each bean property into the Binder,
you may also want to configure the bindings themselves, for example, to specify a Converter or Validator.
The @FieldBuilder.Binding annotation allows you to configure the
binding using properties corresponding to methods in Binder.BindingBuilder.
Adding Fields to a FormLayout
The @FieldBuilder.FormLayout annotation allows you to configure field labels,
column span, and ordering of fields in a FormLayout.
Fields are added to a FormLayout via
addFieldComponents().
Example
A simple example shows how these annotations are used:
@FieldBuilder.TextField(placeholder = "Enter your name...", maxLength = 64)
@FieldBuilder.Binding(required = "Name is mandatory", validators = MyValidator.class)
@FieldBuilder.FormLayout(label = "Name:", colspan = 2, order = 1)
@NotNull
public String getName() { ... }
@FieldBuilder.ComboBox(items = EnumDataProvider.class)
@FieldBuilder.Binding(required = "Status is mandatory")
@FieldBuilder.FormLayout(label = "Status:", order = 2)
@NotNull
public Status getStatus() { ... }
// A property that can't be edited with existing fields
public Foobar getFoobar() { ... }
// Instead, use my own custom field to edit "foobar"
@FieldBuilder.ProvidesField("foobar")
@FieldBuilder.FormLayout(label = "Your Foobar:", order = 3)
private static CustomField<Foobar> createFoobarField() { ... }
All of the declarative @FieldBuilder.Foo annotations have an implementation() property that allows you
to specify a custom implementation. So a more consistent way to customize the "foobar" component would be:
// Use my own custom FoobarField to edit "foobar"
@FieldBuilder.CustomField(label = "Your Foobar:", implementation = FoobarField.class)
public Foobar getFoobar() { ... }
Building the Form
First, use bindFields() to create a new set of fields, configure them, and bind them into a Binder:
// Create a FieldBuilder
FieldBuilder<Person> fieldBuilder = new FieldBuilder<>(Person.class);
// Create a Binder and bind fields
Binder<Person> binder = new Binder<>(Person.class);
fieldBuilder.bindFields(binder);
Then (optionally) use addFieldComponents() to add and configure those fields into a
FormLayout:
// Create form and add fields to it
FormLayout form = new FormLayout();
fieldBuilder.addFieldComponents(form);
You can also access the fields directly via getFieldComponents().
Efficient Reuse
A FieldBuilder can be used multiple times. Each time bindFields() is invoked any previously
created fields are forgotten and a new set of fields is created and bound. This avoids the relatively expensive process
of scanning the class hierarchy for annotations that occurs during construction.
FieldBuilder also has a copy constructor, which accomplishes the same thing.
Alternate Defaults
You can override the default value for specific field properties on a per-property-name basis by annotating static methods
in the edited model class with @FieldBuilder.FieldDefault.
For example:
public class Person {
public String getFirstName() { ... }
public String getLastName() { ... }
@FieldBuilder.FieldDefault("itemLabelGenerator")
private static ItemLabelGenerator<Person> buildPersonILG() {
return person -> person.getLastName() + ", " + person.getFirstName();
}
}
These defaults can be accessed via getScannedFieldDefaults().
See @FieldBuilder.FieldDefault for details.
Providing Context via FieldBuilderContext
All classes instantiated by FieldBuilder (fields, data providers, converters, validatiors, etc.) are instantiated
using the default constructor, unless a constructor taking a FieldBuilderContext exists, in which case it will
be used instead. In this latter case, the new object can do further introspection of the method and/or annotation
for the purpose of doing additional automated self-configuration.
For example, a @FieldBuilder.ComboBox annotation might specify a general purpose
DataProvider implementation that determines which object type to provide from
the method's return type. This is how EnumDataProvider automatically infers the Enum type to use.
Override newFieldBuilderContext() if you wish to pass a custom context; the constructor
chosen will be the one with the narrowest type compatible with the actual FieldBuilderContext in use.
Automatic Validator Registration
This class automatically registers validators with the Binder as follows:
- If a field implements
ValidatingField, a corresponding field validator will be registered - If the target bean class implements
ValidatingBean, a corresponding bean-level validator will be registered
This allows for more modularity, especially when nested types having sub-fields are in use; for example,
FieldBuilderCustomField relies on this mechanism.
Production Bundle Caveat
If you build a Vaadin production bundle then you may run into an issue where a field does not appear and Vaadin logs a warning like this:
The component class com.vaadin.flow.component.datepicker.DatePicker includes '@vaadin/date-picker/src/vaadin-date-picker.js' but this file was not included when creating the production bundle. The component will not work properly. Check that you have a reference to the component and that you are not using it only through reflection. If needed add a @Uses(DatePicker.class) where it is used.This happens because Vaadin thinks your application never uses
FieldBuilder.DatePicker because it doesn't see your code
directly instantiating one anywhere. In other words, the indirect instantiation by this class goes undetected.
To fix this, either add the @Uses annotation to one of your view classes as described in the warning,
or else configure your Vaadin Maven plugin with <optimizeBundle>false</optimizeBundle>.
Homebrew Your Own
You can create your own version of this class containing auto-generated annotations for whatever classes you want
simply by subclassing AbstractFieldBuilder and applying a Maven plugin. See source code for details.
- See Also:
-
FieldBuilder.CheckboxFieldBuilder.CheckboxGroupFieldBuilder.ComboBoxFieldBuilder.MultiSelectComboBoxFieldBuilder.CustomFieldFieldBuilder.DatePickerFieldBuilder.DateTimePickerFieldBuilder.InputFieldBuilder.RangeInputFieldBuilder.ListBoxFieldBuilder.MultiSelectListBoxFieldBuilder.RadioButtonGroupFieldBuilder.SelectFieldBuilder.BigDecimalFieldFieldBuilder.EmailFieldFieldBuilder.IntegerFieldFieldBuilder.NumberFieldFieldBuilder.PasswordFieldFieldBuilder.TextAreaFieldBuilder.TextFieldFieldBuilder.TimePicker- Serialized Form
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic @interfaceSpecifies how a Java bean property should be edited using aBigDecimalField.static @interfaceSpecifies how a Java bean property should be edited using aCheckbox.static @interfaceSpecifies how a Java bean property should be edited using aCheckboxGroup.static @interfaceSpecifies how a Java bean property should be edited using aComboBox.static @interfaceSpecifies how a Java bean property should be edited using aCustomField.static @interfaceSpecifies how a Java bean property should be edited using aDatePicker.static @interfaceSpecifies how a Java bean property should be edited using aDateTimePicker.static @interfaceSpecifies how a Java bean property should be edited using anEmailField.static @interfaceSpecifies how a Java bean property should be edited using anInput.static @interfaceSpecifies how a Java bean property should be edited using anIntegerField.static @interfaceSpecifies how a Java bean property should be edited using aListBox.static @interfaceSpecifies how a Java bean property should be edited using aMultiSelectComboBox.static @interfaceSpecifies how a Java bean property should be edited using aMultiSelectListBox.static @interfaceSpecifies how a Java bean property should be edited using aNumberField.static @interfaceSpecifies how a Java bean property should be edited using aPasswordField.static @interfaceSpecifies how a Java bean property should be edited using aRadioButtonGroup.static @interfaceSpecifies how a Java bean property should be edited using aRangeInput.static @interfaceSpecifies how a Java bean property should be edited using aSelect.static @interfaceSpecifies how a Java bean property should be edited using aTextArea.static @interfaceSpecifies how a Java bean property should be edited using aTextField.static @interfaceSpecifies how a Java bean property should be edited using aTimePicker.Nested classes/interfaces inherited from class org.dellroad.stuff.vaadin24.field.AbstractGridFieldBuilder
AbstractGridFieldBuilder.GridMultiSelect, AbstractGridFieldBuilder.GridSingleSelectNested classes/interfaces inherited from class org.dellroad.stuff.vaadin24.field.AbstractFieldBuilder
AbstractFieldBuilder.Binding, AbstractFieldBuilder.BindingInfo, AbstractFieldBuilder.DefaultInfo, AbstractFieldBuilder.EnabledBy, AbstractFieldBuilder.FieldDefault, AbstractFieldBuilder.FormLayout, AbstractFieldBuilder.NullifyCheckbox, AbstractFieldBuilder.ProvidesField -
Field Summary
Fields inherited from class org.dellroad.stuff.vaadin24.field.AbstractFieldBuilder
DEFAULT_ANNOTATION_DEFAULTS_METHOD_NAME, DEFAULT_IMPLEMENTATION_PROPERTY_NAME -
Constructor Summary
ConstructorsConstructorDescriptionFieldBuilder(Class<T> type) Constructor.FieldBuilder(FieldBuilder<T> original) Static information copy constructor. -
Method Summary
Methods inherited from class org.dellroad.stuff.vaadin24.field.AbstractGridFieldBuilder
buildDeclarativeField, buildGrid, getDeclarativeAnnotationTypesMethods inherited from class org.dellroad.stuff.vaadin24.field.AbstractFieldBuilder
addFieldComponents, applyFieldDefaultAnnotations, bindFields, buildProvidedField, createBindingInfo, getAnnotationDefaultsMethod, getAnnotationDefaultsMethodName, getDefaultsFor, getDefaultsFor, getFieldComponents, getImplementationPropertyName, getScannedFieldDefaults, getScannedProperties, getType, instantiate, instantiate, newFieldBuilderContext, scanForAnnotations, scanForFieldDefaultAnnotations
-
Constructor Details
-
FieldBuilder
Constructor.- Parameters:
type- backing object type
-
FieldBuilder
Static information copy constructor.Using this constructor is more efficient than repeatedly scanning the same classes for the same annotations.
Only the static information gathered by this instance by scanning for annotations is copied. Any previously bound fields are not copied.
- Parameters:
original- original instance- Throws:
IllegalArgumentException- iforiginalis null
-