Class FormBinder<T>

java.lang.Object
org.vaadin.firitin.form.FormBinder<T>
Type Parameters:
T - The class/record type bound by this binder.
All Implemented Interfaces:
com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>, Serializable

public class FormBinder<T> extends Object implements com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
A new start for the Binder. Note, that this is still a fairly new class, so there might be API changes to it.

Design principles:

  • Only support "non-buffered mode" so that validation logic can use the bean/record + simplicity of the implementation
  • Validation is "just validation", and not concern of this class. BUT, API must support binding external validation logic, like Bean Validation API
  • Must support Records and immutable objects as well
  • No requirements for BeanValidation or Spring DataBinding stuff, but optional support (or extensible for those)
Non-goals:
  • Aiming for binding anything without property names (for good solution this needs to be resolved at language level and supported with thing like Bean Validation first)
  • Being a field in another form. A binder binds the fields of a form; it is not one. When a property is itself a value worth editing as a unit, put its fields in a CustomField and bind that — a CustomField is a component, it can show a violation of its own, and it can use a FormBinder of its own inside. See FormBinderKnownIssuesTest#aCompositionIsBoundThroughACustomField for the whole of it.
See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
     

    Nested classes/interfaces inherited from interface com.vaadin.flow.component.HasValue

    com.vaadin.flow.component.HasValue.ValueChangeEvent<V>, com.vaadin.flow.component.HasValue.ValueChangeListener<E>
  • Constructor Summary

    Constructors
    Constructor
    Description
    FormBinder(Class<T> tClass, com.vaadin.flow.component.Component... containerComponents)
    Constructs a new binder.
    FormBinder(Class<T> tClass, Object editorObject)
    Deprecated.
    not sure yet if this is a good idea, added for backwards compatibility
    FormBinder(Class<T> tClass, Map<String, com.vaadin.flow.component.HasValue> propertyNameToEditor)
    Binds type to given property editors
    FormBinder(tools.jackson.databind.introspect.BasicBeanDescription bdd)
    Constructs a new binder for semi-manual wiring.
    FormBinder(T dto, com.vaadin.flow.component.Component... containerComponents)
    Binds given dto to the UI fields found from given component(s).
  • Method Summary

    Modifier and Type
    Method
    Description
    com.vaadin.flow.shared.Registration
    addValidValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener<? super FormBinderValueChangeEvent<T>> listener)
    Adds a listener for changes the reader made that left the form in a usable state.
    com.vaadin.flow.shared.Registration
    addValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener<? super FormBinderValueChangeEvent<T>> listener)
     
    protected void
    applyConstraintsToEditor(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.component.HasValue<?,?> hasValue)
    Hands the constraints a field can enforce itself over to the field.
    void
    bindProperty(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.component.HasValue hasValue)
    Binds given property to the given editor field.
    void
    Removes all validation errors from bound fields and getClassLevelViolationDisplay().
    protected void
    configureEditor(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.component.HasValue hasValue)
     
    protected T
     
    protected T
     
     
    com.vaadin.flow.component.HasComponents
    Gets the class level violation display.
    com.vaadin.flow.component.HasValue
    getEditor(String property)
     
    com.vaadin.flow.component.HasValue
    getEditor(PropertyRef<T,?> property)
    Returns the field editing the given property, naming it with a method reference to its getter instead of a string.
    Returns a map containing input conversion errors (propertyname-error).
    Class<?>[]
     
    The value the form currently holds.
    protected void
    handleClassLevelValidations(Set<jakarta.validation.ConstraintViolation<T>> violations)
     
    protected Object
    handleInputConversionError(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.data.binder.ValueContext ctx, String conversionErrorMsg)
    Handles input conversion error.
    boolean
    Checks if there have recently been errors to convert value from the UI to the domain object.
    protected boolean
    ignoreRequiredConstraintForField(jakarta.validation.ConstraintViolation<T> cv, com.vaadin.flow.component.HasValue hasValue)
    By default, if a validation is "required" validation for a field that user has not touched, it is not reported in the UI, but is taken otherwise into accound.
    boolean
    Whether every bound editor is empty.
    protected boolean
     
    boolean
     
    protected boolean
    isReadOnly(tools.jackson.databind.introspect.BeanPropertyDefinition property)
     
    protected boolean
    isRequired(tools.jackson.databind.introspect.BeanPropertyDefinition property)
     
    protected static boolean
    isRequired(tools.jackson.databind.introspect.BeanPropertyDefinition property, Class<?>[] activeGroups)
     
    boolean
     
    boolean
     
    void
    setClassLevelValidationViolationComponentProvider(com.vaadin.flow.function.SerializableFunction<String, com.vaadin.flow.component.Component> classLevelValidationViolationComponentProvider)
     
    void
    setClassLevelViolationDisplay(com.vaadin.flow.component.HasComponents display)
    Sets the container component where "class level" constraint violations are displayed.
    void
    setConstraintViolations(Set<jakarta.validation.ConstraintViolation<T>> violations)
    Set the constraint violations found during validation.
    void
    setConverter(String property, com.vaadin.flow.data.converter.Converter<?,?> strToDt)
    Sets a converter to use between the domain model property and the corresponding UI component editing it.
    void
    setConverter(PropertyRef<T,?> property, com.vaadin.flow.data.converter.Converter<?,?> strToDt)
    Sets a converter to use between the domain model property and the corresponding UI component editing it, naming the property with a method reference to its getter instead of a string.
    void
    A flag to control whether server originated value change events should be ignored.
    void
    Reports a single constraint violation for the given property.
    void
    Reports a single constraint violation, naming the property with a method reference to its getter instead of a string:
    void
    An alternative API to report constraint violations without BeanValidation API on the classpath.
    void
    setReadOnly(boolean readOnly)
    Not supported: a binder binds the fields of a form rather than being one.
    void
    setRequiredIndicatorVisible(boolean requiredIndicatorVisible)
    Not supported: a binder has no place to show a required indicator, and marking every bound field required would say something else entirely.
    void
    setValidationGroups(Class<?>... groups)
    Sets the active validation groups.
    void
    setValue(T valueObject)
    Sets the value object bound to this form.
    void
    Clears bindings, might be needed to clean up references if e.g. re-using fields
    withValue(T value)
     

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface com.vaadin.flow.component.HasValue

    bindReadOnly, bindRequiredIndicatorVisible, bindValue, clear, getEmptyValue, getOptionalValue
  • Constructor Details

    • FormBinder

      public FormBinder(Class<T> tClass, com.vaadin.flow.component.Component... containerComponents)
      Constructs a new binder.
      Parameters:
      tClass - the class of the bound entity/bean, set later with setValue(Object)
      containerComponents - the components whose class contains the fields to bound
    • FormBinder

      @Deprecated public FormBinder(Class<T> tClass, Object editorObject)
      Deprecated.
      not sure yet if this is a good idea, added for backwards compatibility
      Constructs a new binder.
      Parameters:
      tClass - the class of the bound entity/bean, set later with setValue(Object)
      editorObject - the editor object that contains the fields to bound, does not need to be a component
    • FormBinder

      public FormBinder(Class<T> tClass, Map<String, com.vaadin.flow.component.HasValue> propertyNameToEditor)
      Binds type to given property editors
      Parameters:
      tClass - the type to bind
      propertyNameToEditor - pre-instantiated editors to bind
    • FormBinder

      public FormBinder(tools.jackson.databind.introspect.BasicBeanDescription bdd)
      Constructs a new binder for semi-manual wiring. Not designed for general use, but can be handy for some special cases.
    • FormBinder

      public FormBinder(T dto, com.vaadin.flow.component.Component... containerComponents)
      Binds given dto to the UI fields found from given component(s).
      Parameters:
      dto - the object to bind. The type of the FormBinder will be taken from this object.
      containerComponents - the components whose class contains the fields to bound
  • Method Details

    • setValidationGroups

      public void setValidationGroups(Class<?>... groups)
      Sets the active validation groups. This affects which fields show the required indicator based on their constraint annotations' group membership.
      Parameters:
      groups - the validation groups to activate
    • getValidationGroups

      public Class<?>[] getValidationGroups()
    • isRequired

      protected boolean isRequired(tools.jackson.databind.introspect.BeanPropertyDefinition property)
    • isRequired

      protected static boolean isRequired(tools.jackson.databind.introspect.BeanPropertyDefinition property, Class<?>[] activeGroups)
    • applyConstraintsToEditor

      protected void applyConstraintsToEditor(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.component.HasValue<?,?> hasValue)
      Hands the constraints a field can enforce itself over to the field.

      @NotNull has always reached the widget as a required indicator, and that one mapping does real work: a required field refuses to be emptied, so the constraint is met in the browser rather than reported after the fact. The same is available for the limits — @Size(max = 64) is a maxlength, and the developer should not have to write it twice.

      Only bounds that mean exactly what the field means:

      • a limit the developer already set is kept. They knew something; the constraint is still checked where it always was.
      • strict bounds are left out. @Positive is "greater than zero" while a field's minimum is inclusive, and there is no next double after zero to use instead. A field that allowed zero while the constraint refused it would be a worse lie than no limit at all.
      • @Pattern is left out too: the expression would be handed to the browser, and a Java regular expression is not a JavaScript one.
      • constraints belonging to a validation group are left out. Which groups are active is decided after binding, and a limit cannot be un-set per group.
      Everything skipped here is still validated where it always was.
      Parameters:
      property - the property being bound
      hasValue - the editor bound to it
    • isReadOnly

      protected boolean isReadOnly(tools.jackson.databind.introspect.BeanPropertyDefinition property)
    • bindProperty

      public void bindProperty(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.component.HasValue hasValue)
      Binds given property to the given editor field.
      Parameters:
      property - the property to bind
      hasValue - the editor field to bind
    • configureEditor

      protected void configureEditor(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.component.HasValue hasValue)
    • handleInputConversionError

      protected Object handleInputConversionError(tools.jackson.databind.introspect.BeanPropertyDefinition property, com.vaadin.flow.data.binder.ValueContext ctx, String conversionErrorMsg)
      Handles input conversion error. By default, the error message saved and set to the field.
      Parameters:
      property -
      ctx -
      conversionErrorMsg -
      Returns:
      the value to be set to the edited object, null by default
    • getValue

      public T getValue()
      The value the form currently holds.

      For a record a new instance is built from the editors on every call: that is what immutability leaves available, and record equality makes it invisible.

      For a mutable bean the object that was set is returned, and it is the same object every time — the binder writes the changes into it as they are made. When nothing has been set, one is built from the editors on the first ask and kept from then on, so that both cases behave alike: a caller may hold on to what it was given and write into it without the next call handing back something else. Building a fresh bean per call, as this used to, made such a write disappear without a word.

      Emptying the form with setValue(null) lets go of that bean as well; the next call builds another.

      Specified by:
      getValue in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
      Returns:
      the value, never null
    • setValue

      public void setValue(T valueObject)
      Sets the value object bound to this form.

      A null value is accepted and empties the form: every bound editor is cleared to its own empty value and validation errors are removed. This is the empty value of a binder, so HasValue.clear(), which the HasValue contract defines as setValue(getEmptyValue()), does the same thing. It also matches Binder.setBean(null) in Vaadin's own binder.

      Note that getValue() does not return null afterwards: with no value object bound it constructs one from the current editor values, which is what makes a binder usable for creating a new object as well as for editing one.

      Specified by:
      setValue in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
      Parameters:
      valueObject - the new value, or null to empty the form
    • isEmpty

      public boolean isEmpty()
      Whether every bound editor is empty.

      HasValue defines this as the value being equal to HasValue.getEmptyValue(), and that comparison cannot answer it here. The empty value of a form would have to be an object built from empty editors, and such an object cannot always be built — a record with a primitive component has none — nor recognised, since a mutable bean is equal to nothing but itself unless someone wrote it an equals. So the question goes to the editors, where "empty" is already defined exactly for each of them: an empty text field, a number field with nothing in it, an unticked checkbox.

      This is therefore about the form rather than about the value. A component of the value that no editor edits — an identifier carried over from the object that was set — does not make the form non-empty. "Nothing has been typed here" is the question a form can answer.

      A binder with no editors bound at all is empty, for the same reason.

      Specified by:
      isEmpty in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
      Returns:
      true if no bound editor holds a value
    • withValue

      public FormBinder<T> withValue(T value)
    • addValueChangeListener

      public com.vaadin.flow.shared.Registration addValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener<? super FormBinderValueChangeEvent<T>> listener)
      Specified by:
      addValueChangeListener in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
    • addValidValueChangeListener

      public com.vaadin.flow.shared.Registration addValidValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener<? super FormBinderValueChangeEvent<T>> listener)
      Adds a listener for changes the reader made that left the form in a usable state.

      The two conditions a "save as you type" form checks, in one place: the change came from the client rather than from the application filling the fields, and nothing is currently reported as wrong.

      "Wrong" means what this binder has been told, since a binder shows violations rather than producing them — a conversion error it noticed itself, and the violations last handed to setConstraintViolations(Set). If you feed those from a listener of your own, add that listener first, or this one will answer about the change before last. BeanValidationForm has no such order to get right: it registers its own validating listener when the binder is created.

      Parameters:
      listener - the listener
      Returns:
      a registration for removing it
    • isReadOnly

      public boolean isReadOnly()
      Specified by:
      isReadOnly in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
    • setReadOnly

      public void setReadOnly(boolean readOnly)
      Not supported: a binder binds the fields of a form rather than being one.
      Specified by:
      setReadOnly in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
      Throws:
      UnsupportedOperationException - always
    • isRequiredIndicatorVisible

      public boolean isRequiredIndicatorVisible()
      Specified by:
      isRequiredIndicatorVisible in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
    • setRequiredIndicatorVisible

      public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible)
      Not supported: a binder has no place to show a required indicator, and marking every bound field required would say something else entirely.
      Specified by:
      setRequiredIndicatorVisible in interface com.vaadin.flow.component.HasValue<FormBinderValueChangeEvent<T>, T>
      Throws:
      UnsupportedOperationException - always
    • isImmutable

      protected boolean isImmutable()
    • constructRecord

      protected T constructRecord()
    • constructPojo

      protected T constructPojo()
    • setConstraintViolations

      public void setConstraintViolations(Set<jakarta.validation.ConstraintViolation<T>> violations)
      Set the constraint violations found during validation. If violation is bound to a bound property, it is shown next to the field, otherwise shown at "form level", see setClassLevelViolationDisplay(HasComponents).
      Parameters:
      violations - the constraint violations that should be shown in the UI
    • ignoreRequiredConstraintForField

      protected boolean ignoreRequiredConstraintForField(jakarta.validation.ConstraintViolation<T> cv, com.vaadin.flow.component.HasValue hasValue)
      By default, if a validation is "required" validation for a field that user has not touched, it is not reported in the UI, but is taken otherwise into accound.
      Parameters:
      cv - validation constraing
      hasValue - the field to be tested
      Returns:
      true if should be ignored based on emptry validity
    • getClassLevelViolationDisplay

      public com.vaadin.flow.component.HasComponents getClassLevelViolationDisplay()
      Gets the class level violation display. If not set, the first container component is used if of appropriate type.
      Returns:
      the component where constraint violations will be displayed
    • setClassLevelViolationDisplay

      public void setClassLevelViolationDisplay(com.vaadin.flow.component.HasComponents display)
      Sets the container component where "class level" constraint violations are displayed.
      Parameters:
      display - the component where the "class level" constraint violations will be displayed.
    • handleClassLevelValidations

      protected void handleClassLevelValidations(Set<jakarta.validation.ConstraintViolation<T>> violations)
    • setClassLevelValidationViolationComponentProvider

      public void setClassLevelValidationViolationComponentProvider(com.vaadin.flow.function.SerializableFunction<String, com.vaadin.flow.component.Component> classLevelValidationViolationComponentProvider)
    • setRawConstraintViolations

      public void setRawConstraintViolations(Map<String,String> propertyToViolation)
      An alternative API to report constraint violations without BeanValidation API on the classpath.
      Parameters:
      propertyToViolation -
    • setRawConstraintViolation

      public void setRawConstraintViolation(PropertyRef<T,?> property, String message)
      Reports a single constraint violation, naming the property with a method reference to its getter instead of a string:
      binder.setRawConstraintViolation(Account::getPasswordVerification, "Passwords do not match!");
      
      Like setRawConstraintViolations(Map), of which this is the single property form, this replaces the violations currently shown. Use the map version to report several at once.
      Parameters:
      property - a method reference to the getter of the property the violation is about
      message - the message to show
    • setRawConstraintViolation

      public void setRawConstraintViolation(String property, String message)
      Reports a single constraint violation for the given property.
      Parameters:
      property - the property the violation is about, or an empty string for a violation that belongs to no single field
      message - the message to show
      See Also:
    • clearValidationErrors

      public void clearValidationErrors()
      Removes all validation errors from bound fields and getClassLevelViolationDisplay().
    • setConverter

      public void setConverter(String property, com.vaadin.flow.data.converter.Converter<?,?> strToDt)
      Sets a converter to use between the domain model property and the corresponding UI component editing it.
      Parameters:
      property - the property
      strToDt - the converter
    • setConverter

      public void setConverter(PropertyRef<T,?> property, com.vaadin.flow.data.converter.Converter<?,?> strToDt)
      Sets a converter to use between the domain model property and the corresponding UI component editing it, naming the property with a method reference to its getter instead of a string.
      Parameters:
      property - a method reference to the getter of the property
      strToDt - the converter
    • hasInputConversionErrors

      public boolean hasInputConversionErrors()
      Checks if there have recently been errors to convert value from the UI to the domain object.
      Returns:
      true if there are active conversion errors
    • getInputConversionErrors

      public Map<String,String> getInputConversionErrors()
      Returns a map containing input conversion errors (propertyname-error).
      Returns:
      input conversion errors
    • isValid

      public boolean isValid()
      Returns:
      true if the binging looks valid for the user: no displayed constraint violations nor input conversion errors.
    • setIgnoreServerOriginatedChanges

      public void setIgnoreServerOriginatedChanges(boolean ignore)
      A flag to control whether server originated value change events should be ignored. Currently only known to be needed for testing, might be removed in the future.
      Parameters:
      ignore - true if non-client originated events should be ignored
    • unBind

      public void unBind()
      Clears bindings, might be needed to clean up references if e.g. re-using fields
    • getBoundProperties

      public List<String> getBoundProperties()
    • getEditor

      public com.vaadin.flow.component.HasValue getEditor(String property)
    • getEditor

      public com.vaadin.flow.component.HasValue getEditor(PropertyRef<T,?> property)
      Returns the field editing the given property, naming it with a method reference to its getter instead of a string.
      Parameters:
      property - a method reference to the getter of the property
      Returns:
      the field bound to the property, or null if none is bound