LSPS documentation logo
LSPS Documentation
Form Components

Form components constitute your form hierarchy: They are implemented as Records with Fields and methods and have the forms::Form Record as its super type with the form widget.

Every component allows a set of common component actions and can define the following of properties:

  • ID: identifier of the component on design time

    ID allows you to reference the component from another component in the form while designing it.

  • Modeling id: identifier of the component on runtime

    Modeling id is generated automatically and serves to identify form components on runtime.

  • Caption: a String expression rendered as label

    If the caption references localization identifiers, these are resolved automatically to the target locale. Note that the caption is rendered by the parent layout component. Hence if you create a form with a sole component with a caption, the caption will not be rendered in the form. This is due to limits of the Vaadin framework.

  • Description: tooltip displayed on hover

Component-specific properties can be set on the Detail tab in the Properties view or on the Init tab. Both become part of the form tree.

You will work with the following types of components:

Note that you can implement a custom form component if required.

Binding of Components

Some form components hold a value: For example, a Text Area component holds a String value with its text. You can get the value with the getValue() call and set it with the setValue() call.

However, if you want to work with the value, you can bind the component to a variable or a variable field if the variable is of a Record type:

  • When the value of the component changes, the value in the variable to the new value;
  • When the component is loaded or refreshed, its value uses the value in its Binding.

The variable must be of the same type as the value in the component, in the case of the Text Area, it must be of type String.

Note: If the user enters a value into a component, which cannot be stored in the target object, the component validation fails: In such a case, the value is not stored in the target object and an error message is displayed on the component.

Setting Component Binding

To set the target object of binding, either call the setBinding() method or set the Binding property in the Properties view.

Note: By default, the binding is set to a default object which takes any value so you don't have to set the binding if not needed.

Depending on whether and when the binding value should be reloaded, you can use one of the following types:

  • Reference: reloaded when the component is initialized or refreshed
  • Value: loaded when the component is initialized (This is convenient if you want to set a fixed value to a component.)
  • Closure: loaded when the component is initialized or refreshed
  • Custom: custom implementation of the Binding interface (This allows you to apply custom logic to the binding before it is loaded.)

Example: Let us consider a Form with three Label Fields (Label Fields are components that simply display the value of the Binding as a read-only value):

  • LabelA uses Reference Binding to the Date variable &varTime with its initial value set to the time returned by now()
  • LabelB uses Value Binding now()
  • LabelC uses Closure Binding now()

All Labels will show the current date and time when the Form is loaded. If we add a Button component that will refresh the Label components on click, then on click:

  • LabelA will display the value stored in the varTime variable.
  • LabelB will keep the same time value.
  • LabelC will always update the time to the current time.

Getting Component Binding

To get the value set in the component target object, call getValue() on the component.

Note: A component value is stored in the target object, only if it meets the formatting requirements, for example, if you enter a character into a Decimal Field, the value is ignored and the component displays an error. To work with such values, use the getUserText() method.

Clearing Component Binding

To clear the component binding, call setBinding() with the reference parameter set to null.

myTextField.setBinding(null as Reference)

Implementing Custom Binding

If you want to process binding data before they are used by a component, implement your custom Binding data type. This allows you, for example, to convert input values to other units.

  1. Create the Binding data type that implements the forms::Binding interface.
    1. Create data type definition
    2. Import the forms::Interface type and extend it.
      bindingrecord.png
      Custom binding record
  2. Implement the interface methods:
    1. Right-click your binding record and select New Methods Definition.
    2. Open the newly created methods definition file and implement the following methods:
      • get() returns the component value
      • set() sets the component value
      • getBinding() returns the property of a RecordAndProperty object; the validations applicable for the property are used to validate the value (refer to the documentation on constraints); usually you want to return null;
FileReader {
  public Object get(){
      def File myfile := getResource("custom_binding", filename);
      fromBinaryToString(myfile.content);
    }
  public void set(Object value) {
      error("Cannot set target file! Readonly!")
  }
  public RecordAndProperty getBinding() {
    null
  }
}

Now you can use your binding implementation as Custom Binding in your form. Consider using and distributing your implementation as part of a custom Library.

custombindingdefinition.png
Custom binding on a Label component

Defining an Initial Value of a Component

To define the initial value for a component where the data comes in later, define a form variable with the initial values; and define the binding as a closure that provides the appropriate data:

For example myFormVariable != null ? myFormVariable : null

Validation of Components

When you insert a value into a form component, the form automatically checks if the value meets the basic format requirements, such as, whether you entered a decimal value in a Decimal Field

If you want to introduce further validation rules to a forms::form, you have the following options:

Adding a Validator

To add validation rules and validate the values in an input form component when the value might have changed, add validators to the components.

The value is validated against its validators on every validation run:

  • when the validator is added;
  • when the value might have changed, for example, the component lost focus, its setValue() method is called, isValid() is called, the component is rendered, etc.

To set a validator, call the addValidator() method on the component. The method takes a closure as its input argument:

  • The input of the closure is the value in the input component: on a Text Box, the input is its text; On a Decimal Field, the input is the decimal value. Mind that this value must be in the correct format.
  • The closure returns the error message that is displayed on the component. If the closure returns null, the component is valid.

If you want to validate only after some action takes place, name your input component and add the validator in the appropriate listener expression.

Example expression that adds a validator to a Date Field

MyDateField.addValidator (
  { dateValue:Date ->
    if
      dateValue > date(2018, 3, 3)
    then
         null
    else
        "The date must occur later than March 3, 2018."
    end
  }
)

You can get your validation messages and messages caused by incorrect input format with the getValidatorsMessages() or getValidatorsMessagesRecursive() methods.

Validating Record Constraints in Forms

When acquiring data for a record from a form, generally the input components in your form are bound to a record or record field: the binding is set to type Reference and its value to patient.ssn. For such form components, you can check whether the input values meet the record constraints and display the constraint messages, called data-error messages.

To validate the value and display data-error messages in a form, do one of the following:

  • To validate the value automatically, call inferValidator(): The method adds a validator to a component, which keeps checking the constraints of the bound Record Property and displays any returned constraint messages on the component.
    c.inferValidator(null)

    Note: The inferValidator() creates a validator object for the component: The input value is assigned to the validator object only if it is in the correct format: If the users enters a string into a Date Field, the string does not make it into the validator object. Therefore, if you call inferValidator() after the form component has been assigned a value, then any subsequent invalid values are ignored and the first value is always restored.

  • To validate at a particular moment, call validate() and showDataErrorMessages(): The validate() call returns a list of data-error messages and showDataErrorMessages() erases the currently displayed data-error messages and then displays the received data-error messages on the subtree of the second argument.
    { e -> showDataErrorMessages(validate(currentPersonVariable, null, null, null), null)}
    Example on-click validation of the newUnit record instance in a Listener expression:
    { click ->
      def List<ConstraintViolation> cv := validate(newUnit, null, null, null);
      if cv.isEmpty()
        then
          //merge without optimistic-lock check:
          myProxySet.merge(false, newUnit);
          vocabTable.refresh();
        else
         def List<String> messages := collect(cv, {cvi:ConstraintViolation -> cvi.message});
         //The showDataErrorMessages() function displays violation messages
         //on the subtree of the component defined by the second argument.
         //The validation messages are displayed on the components that have the Binding
         //to the data that produced the constraint violation:
         showDataErrorMessages(cv, MyButtonComponent);
      end
    }

To get the data error messages of a form component, use the getDataErrorMessages() or getDataErrorMessagesRecursive() methods.

To clear the data error messages, use the clearConstraintViolations() function.

More information on constraints is available in the Modeling Guide.

Checking Component Validity

To check if a component and all its children are valid, use call the isValid() method. The method searches for any validators, including inferred validators, that return a error message. If at least one error message is found, the method returns false.

Custom error messages are ignored by the isValid() method.

Setting a Custom Error Message

To have full control over when an value in an is validated, use the custom error message of components: With the setCustomErrorMessage() method, you can set and display one arbitrary error message on any component.

To get the error messages of a form component, call the getCustomErrorMessage() method and to clear the error messages, call setCustomErrorMessage(null) on your component.

Since error messages are not produced by validators or constraint mechanism, they do not represent a validation breach: consequently, the isValid() call IGNORES error messages. They are simply messages attached to components.

{ e ->
  def String val := text.getValue() as String;
  if val.isBlank() or val.length() < 3  then
    //setting error message on a component:
    text.setCustomErrorMessage("Must be at least 3 characters.");
  else
    Forms.submit()
  end
}

Getting All Error Messages

To collect the custom error, data error, and validator messages on a form component, use the getAllErrorMessages() or getAllErrorMessagesRecursive() methods.