Class EasyForm<T>

Type Parameters:
T - the bean type
All Implemented Interfaces:
AttachNotifier, DetachNotifier, HasElement, HasEnabled, HasSize, HasStyle, Serializable

public class EasyForm<T> extends Composite<VerticalLayout> implements HasSize, HasStyle, HasEnabled
A form component that is automatically generated from a POJO definition.

EasyForm introspects the properties of the given bean type (via getter/setter conventions) and creates an appropriate Vaadin form field for each one, configures validations based on JSR-380 (Bean Validation) annotations, and manages data binding through an internal Binder. Properties without both a getter and a setter, or whose type has no registered component factory, are ignored.

All customization is programmatic through a fluent API:


 EasyForm<Person> form = new EasyForm<>(Person.class);
 form.configureField("email").withLabel("Email Address").asRequired("Email is required");
 form.setSaveAction(person -> personService.save(person));
 add(form);
 

Discovery is flat: only the direct properties of the bean type are used. Nested property paths such as "address.street", which Binder itself supports, are not discovered, and asking for one through configureField(String) fails as for any unknown property.

Subclasses can influence generation by overriding includeProperty(PropertyDescriptor), createComponent(String, Class), createLabel(String) and configureComponent(String, HasValue). These are called while the constructor discovers the properties, so an override must not depend on state initialized in the subclass constructor body.

See Also:
  • Constructor Details

    • EasyForm

      public EasyForm(Class<T> beanType)
      Creates a form whose fields are generated from the properties of the given bean type.
      Parameters:
      beanType - the bean type to generate the form for, not null
      Throws:
      NullPointerException - if beanType is null
      IllegalArgumentException - if beanType cannot be introspected
  • Method Details

    • setDefaultComponentFactory

      public static <V, C extends Component & HasValue<?, V>> void setDefaultComponentFactory(Class<V> type, SerializableSupplier<C> factory)
      Registers a global default component factory for the given value type. The factory applies to all EasyForm instances created after this call, unless overridden per form instance or per property.

      The built-in type mappings (e.g. String to TextField) are registered through this same registry and can be replaced by calling this method.

      Type Parameters:
      V - the value type
      C - the component type, which must be a Component and a HasValue of the value type
      Parameters:
      type - the value type to register the factory for, not null
      factory - the factory that creates a component for the type, not null
      Throws:
      NullPointerException - if type or factory is null
    • setDefaultComponentFactory

      public static <V, P, C extends Component & HasValue<?, V>> void setDefaultComponentFactory(Class<P> propertyType, SerializableSupplier<C> factory, Converter<V,P> converter)
      Registers a global default component factory for the given property type, together with a converter that adapts the component presentation type to the property type (e.g. a NumberField whose Double value is converted to a Long property). The factory applies to all EasyForm instances created after this call, unless overridden per form instance or per property.
      Type Parameters:
      V - the presentation value type of the created components
      P - the property type
      C - the component type, which must be a Component and a HasValue of the presentation value type
      Parameters:
      propertyType - the property type to register the factory for, not null
      factory - the factory that creates a component for the type, not null
      converter - the converter from the presentation type to the property type, not null
      Throws:
      NullPointerException - if propertyType, factory or converter is null
    • setComponentFactory

      public <V, C extends Component & HasValue<?, V>> void setComponentFactory(Class<V> type, SerializableSupplier<C> factory)
      Registers a component factory for the given value type in this form instance, overriding the global defaults. Fields already generated for properties of this type are recreated, unless a custom component was set for them via EasyForm.Field.withComponent(C).
      Type Parameters:
      V - the value type
      C - the component type, which must be a Component and a HasValue of the value type
      Parameters:
      type - the value type to register the factory for, not null
      factory - the factory that creates a component for the type, not null
      Throws:
      NullPointerException - if type or factory is null
    • setComponentFactory

      public <V, P, C extends Component & HasValue<?, V>> void setComponentFactory(Class<P> propertyType, SerializableSupplier<C> factory, Converter<V,P> converter)
      Registers a component factory for the given property type in this form instance, together with a converter that adapts the component presentation type to the property type, overriding the global defaults. Fields already generated for properties of this type are recreated, unless a custom component was set for them via EasyForm.Field.withComponent(C).
      Type Parameters:
      V - the presentation value type of the created components
      P - the property type
      C - the component type, which must be a Component and a HasValue of the presentation value type
      Parameters:
      propertyType - the property type to register the factory for, not null
      factory - the factory that creates a component for the type, not null
      converter - the converter from the presentation type to the property type, not null
      Throws:
      NullPointerException - if propertyType, factory or converter is null
    • configureField

      public EasyForm.Field<?> configureField(String propertyName)
      Returns the configuration wrapper for the field generated for the given property.

      The presentation value type of a field cannot be inferred from a property name, so the wrapper is returned with a wildcard type. The state, presentation and layout methods chain as usual, as does EasyForm.Field.withComponent(C), which takes its type from its argument. The two methods that take the presentation value type as a parameter, EasyForm.Field.withValidator(Validator) and EasyForm.Field.withConverter(Converter), are only callable on a wrapper obtained through configureField(String, Class).

      Parameters:
      propertyName - the name of the bean property
      Returns:
      the field wrapper
      Throws:
      IllegalArgumentException - if no property with the given name was discovered
    • configureField

      public <V> EasyForm.Field<V> configureField(String propertyName, Class<V> valueType)
      Returns the configuration wrapper for the field generated for the given property, typed to the given presentation value type. The type is inferred from the argument, so the wrapper can be configured in a fluent chain without an explicit type argument:
      
       form.configureField("email", String.class).withValidator(new EmailValidator("Invalid email"));
       

      The given type is checked against the value type of the component currently generated for the property, which is the type the field's validators and converters see. Note that this is the presentation type and not necessarily the property type: a Long property bound through the built-in NumberField factory has a presentation type of Double. The check is skipped for properties without a component, and for components whose value type cannot be resolved (such as ComboBox).

      Because the check reflects the component in place at the time of the call, replacing the component for a property is done through configureField(String) and EasyForm.Field.withComponent(C), which types the returned wrapper after the new component.

      Type Parameters:
      V - the presentation value type of the field
      Parameters:
      propertyName - the name of the bean property
      valueType - the expected presentation value type, not null
      Returns:
      the field wrapper
      Throws:
      NullPointerException - if valueType is null
      IllegalArgumentException - if no property with the given name was discovered, or if the component of the property does not have the given presentation value type
    • field

      public HasValue<?,?> field(String propertyName)
      Returns the component generated for the given property, which is what configureField(String) wraps. Use this when the component itself is wanted rather than its configuration:
      
       TextField email = (TextField) form.field("email");
       
      Parameters:
      propertyName - the name of the bean property
      Returns:
      the component, or null if the property type has no component factory
      Throws:
      IllegalArgumentException - if no property with the given name was discovered
    • field

      public <V> HasValue<?,V> field(String propertyName, Class<V> valueType)
      Returns the component generated for the given property, typed to the given presentation value type:
      
       HasValue<?, String> email = form.field("email", String.class);
       
      Type Parameters:
      V - the presentation value type of the component
      Parameters:
      propertyName - the name of the bean property
      valueType - the expected presentation value type, not null
      Returns:
      the component, or null if the property type has no component factory
      Throws:
      NullPointerException - if valueType is null
      IllegalArgumentException - if no property with the given name was discovered, or if the component of the property does not have the given presentation value type
      See Also:
    • findField

      public Optional<EasyForm.Field<?>> findField(String propertyName)
      Returns the configuration wrapper for the given property, or an empty optional if the property was not discovered. This is the non-throwing counterpart of configureField(String).
      Parameters:
      propertyName - the name of the bean property
      Returns:
      the field wrapper, or an empty optional
    • getFieldNames

      public List<String> getFieldNames()
      Returns the names of the discovered properties, in the order the fields are displayed.
      Returns:
      an unmodifiable list of property names
    • getFields

      public List<EasyForm.Field<?>> getFields()
      Returns the configuration wrappers of the discovered properties, in the order the fields are displayed. Excluded fields are included in the result.
      Returns:
      an unmodifiable list of field wrappers
    • setFieldOrder

      public EasyForm<T> setFieldOrder(String... propertyNames)
      Sets the display order of the fields. The listed properties are shown first, in the given order, followed by every field that was not listed, in declaration order. Repeated names are ignored after their first occurrence.

      This method only reorders: a field left out of the list is still shown and still bound. Use setVisibleFields(String...) to restrict which fields the form shows.

      Parameters:
      propertyNames - the names of the properties to display first, in order
      Returns:
      this, for method chaining
      Throws:
      NullPointerException - if the array or any of its elements is null
      IllegalArgumentException - if any property name is unknown
    • setVisibleFields

      public EasyForm<T> setVisibleFields(String... propertyNames)
      Restricts the form to the given properties: every field that is not listed is excluded, and every listed field that was excluded is included again. This is the bulk complement of EasyForm.Field.excluded() and operates on the same per-field state, so an individual field can still be brought back afterwards with EasyForm.Field.visible(). A listed field that is read-only stays read-only.

      The display order is unaffected ? use setFieldOrder(String...) for that.

      Parameters:
      propertyNames - the names of the properties to show
      Returns:
      this, for method chaining
      Throws:
      NullPointerException - if the array or any of its elements is null
      IllegalArgumentException - if any property name is unknown
    • setBean

      public EasyForm<T> setBean(T bean)
      Binds the given bean to the form in edit mode: fields are populated from the bean and valid value changes are written through to it.
      Parameters:
      bean - the bean to edit, or null to clear the form
      Returns:
      this, for method chaining
      Throws:
      IllegalStateException - if a field has a component whose value type cannot be written to its property and no converter was set for it
    • readBean

      public EasyForm<T> readBean(T bean)
      Populates the fields with values from the given bean without live binding. Changes are not written to the bean until getValidBean() or the save action runs.
      Parameters:
      bean - the bean to read values from, or null to clear the form
      Returns:
      this, for method chaining
      Throws:
      IllegalStateException - if a field has a component whose value type cannot be written to its property and no converter was set for it
    • getValidBean

      public Optional<T> getValidBean()
      Validates the form and returns the bean with the current field values written to it. When no bean is attached ? because none was set, or because clear() detached it ? the values are written to a new instance, and the bean type must have an accessible no-args constructor.

      Use validate() instead when the reason for a validation failure is needed.

      Returns:
      the populated bean, or an empty optional if validation failed
      Throws:
      IllegalStateException - if no bean has been set and the bean type cannot be instantiated, or if a field has a component whose value type cannot be written to its property and no converter was set for it
    • validate

      public BinderValidationStatus<T> validate()
      Validates every bound field and returns the resulting status, which carries the individual error messages. Nothing is written to a bean, so this can be called to drive the state of the surrounding UI. Follows Binder.validate() semantics.
      Returns:
      the validation status
      Throws:
      IllegalStateException - if a field has a component whose value type cannot be written to its property and no converter was set for it
    • addStatusChangeListener

      public Registration addStatusChangeListener(StatusChangeListener listener)
      Adds a listener notified whenever the validation status of the form changes, which is the supported way to keep the surrounding UI ? a save button, a summary ? in step with the form. The event carries the errors, and its binder answers hasChanges() for dirty state.
      
       form.addStatusChangeListener(
           event -> saveButton.setEnabled(!event.hasValidationErrors() && event.getBinder()
               .hasChanges()));
       
      Parameters:
      listener - the listener to add, not null
      Returns:
      a registration for removing the listener
    • addValueChangeListener

      public Registration addValueChangeListener(HasValue.ValueChangeListener<? super HasValue.ValueChangeEvent<?>> listener)
      Adds a listener notified whenever the value of any bound field changes, whether or not the new value is valid.
      Parameters:
      listener - the listener to add, not null
      Returns:
      a registration for removing the listener
    • reset

      public EasyForm<T> reset()
      Resets the fields to the values of the last bean set through setBean(Object) or readBean(Object), re-attaching it if it was detached by clear(). If no bean was ever set, all fields are cleared.
      Returns:
      this, for method chaining
    • clear

      public EasyForm<T> clear()
      Clears all fields and detaches the current bean, so that the cleared values are never written to it: after this call getBean() returns null and getValidBean() writes to a new instance. The last bean set is remembered and can be restored with reset().
      Returns:
      this, for method chaining
    • addBeanValidator

      public EasyForm<T> addBeanValidator(Validator<? super T> validator)
      Adds a bean-level (cross-field) validator. Bean validators run after all field-level validators have passed.
      Parameters:
      validator - the bean validator to add, not null
      Returns:
      this, for method chaining
      Throws:
      NullPointerException - if validator is null
    • setSaveAction

      public EasyForm<T> setSaveAction(SerializableConsumer<T> saveAction)
      Sets the action invoked with the validated bean when the save button is clicked, and makes the save button visible.
      Parameters:
      saveAction - the save action, or null to remove it
      Returns:
      this, for method chaining
    • setCancelAction

      public EasyForm<T> setCancelAction(SerializableRunnable cancelAction)
      Sets the action invoked when the cancel button is clicked, and makes the cancel button visible.
      Parameters:
      cancelAction - the cancel action, or null to remove it
      Returns:
      this, for method chaining
    • setI18n

      public EasyForm<T> setI18n(EasyForm.EasyFormI18n i18n)
      Sets the texts of the button bar.
      Parameters:
      i18n - the texts to use, not null
      Returns:
      this, for method chaining
      Throws:
      NullPointerException - if i18n is null
    • setLabelGenerator

      public EasyForm<T> setLabelGenerator(SerializableFunction<String,String> labelGenerator)
      Sets the function that generates the label of every field whose label was not set explicitly through EasyForm.Field.withLabel(String), replacing the default derivation from the property name. Pass null to restore the default.

      This is the bulk counterpart of overriding createLabel(String), and applies immediately to the fields already generated.

      Parameters:
      labelGenerator - the generator, taking a property name and returning a label, or
      Returns:
      this, for method chaining null for the default
    • setSaveButtonVisible

      public EasyForm<T> setSaveButtonVisible(boolean visible)
      Overrides the visibility of the save button. By default the button is visible if and only if a save action has been set.
      Parameters:
      visible - whether the save button is visible
      Returns:
      this, for method chaining
    • setCancelButtonVisible

      public EasyForm<T> setCancelButtonVisible(boolean visible)
      Overrides the visibility of the cancel button. By default the button is visible if and only if a cancel action has been set.
      Parameters:
      visible - whether the cancel button is visible
      Returns:
      this, for method chaining
    • addButton

      public Button addButton(String text, ComponentEventListener<ClickEvent<Button>> clickListener, ButtonVariant... variants)
      Adds an extra button to the button bar.
      Parameters:
      text - the button text
      clickListener - the click listener
      variants - the theme variants to apply, if any
      Returns:
      the added button
    • addButton

      public Button addButton(String text, Component icon, ComponentEventListener<ClickEvent<Button>> clickListener, ButtonVariant... variants)
      Adds an extra button with an icon to the button bar.
      Parameters:
      text - the button text
      icon - the button icon
      clickListener - the click listener
      variants - the theme variants to apply, if any
      Returns:
      the added button
    • removeButton

      public EasyForm<T> removeButton(Button button)
      Parameters:
      button - the button to remove, not null
      Returns:
      this, for method chaining
      Throws:
      NullPointerException - if button is null
      IllegalArgumentException - if the button is not in the button bar, or is the save or cancel button ? use setSaveButtonVisible(boolean) or setCancelButtonVisible(boolean) for those
    • setEnabled

      public void setEnabled(boolean enabled)
      Enables or disables the form. In addition to disabling the generated components and the button bar ? which the inherited HasEnabled behaviour already does, since they are all in this component's element tree ? this makes every binding read-only, so that a disabled form cannot be written to the bean even programmatically.

      Re-enabling restores the read-only state each field was configured with, so a field made read-only through EasyForm.Field.readOnly() stays read-only.

      Specified by:
      setEnabled in interface HasEnabled
      Parameters:
      enabled - whether the form is enabled
    • setResponsiveSteps

      public EasyForm<T> setResponsiveSteps(FormLayout.ResponsiveStep... steps)
      Configures the responsive steps of the internal form layout.
      Parameters:
      steps - the responsive steps
      Returns:
      this, for method chaining
      See Also:
    • includeProperty

      protected boolean includeProperty(PropertyDescriptor property)
      Decides whether a discovered property becomes a field. Called during construction for every property that has both a getter and a setter; returning false leaves the property out of the form and the binding entirely. The default implementation accepts every property.
      Parameters:
      property - the property being considered
      Returns:
      whether to generate a field for the property
    • createComponent

      protected HasValue<?,?> createComponent(String propertyName, Class<?> propertyType)
      Creates the component for a property that has no registered component factory. The default implementation returns a ComboBox of the constants for an enum property, and null for anything else, which leaves the property out of the form with a logged warning.

      Components for property types that do have a factory are not created here ? override them with setComponentFactory(Class, SerializableSupplier), which also carries the converter, or per property with EasyForm.Field.withComponent(C). The precedence is withComponent > registered factory > this method.

      Parameters:
      propertyName - the name of the property
      propertyType - the property type, with primitives already wrapped
      Returns:
      the component to use, or null to leave the property out of the form
    • createLabel

      protected String createLabel(String propertyName)
      Returns the label for a property whose label was not set explicitly through EasyForm.Field.withLabel(String). The default implementation applies the function given to setLabelGenerator(SerializableFunction), or derives the label from the property name when there is none.
      Parameters:
      propertyName - the name of the property
      Returns:
      the label to use
    • configureComponent

      protected void configureComponent(String propertyName, HasValue<?,?> component)
      Called after a generated component has been created and its label, placeholder and helper text applied, for decoration that applies to every field ? style names, widths, theme variants. The default implementation does nothing. Not called for components set through EasyForm.Field.withComponent(C).
      Parameters:
      propertyName - the name of the property the component was generated for
      component - the generated component