LSPS documentation logo
LSPS Documentation
Creating a Forms Component

When creating a custom form component, you will need to create the following:

  • Declaration of your form component as a Record
  • Implementation of your form component
    • in the Expression Language: you will create a form component that will extend an existing form component available in your libraries.
    • in Java as a Vaadin component: you will implement the component as a Vaadin component in your LSPS Application freely and deploy the implementation as part of the LSPS Application. You can implement a custom forms component in Java or in the Expression Language.

In addition to a custom form component, you can also implement a custom Grid renderer: a Grid renderer allows you to define how the data in a cell of a Grid Column is rendered.

Creating a Custom Form Component in the Expression Language

To create a custom form component based on an existing component, use the expression in a custom component definition: Like this, you can, for example, define a custom component that returns a Vertical Layout with a set of components inside.

To create a custom component implemented in the Expression Language, do the following:

  1. In a module, create a Record which has a subtype of the forms::FormComponent record as its parent: Pick a component that is the closest to what you require, and add the additional fields for new properties of the components.
    exampleFormRecord.png
    Example record of a custom form component
  2. Create a custom form component definition file: right-click your Module and go to New > Custom Form Component Definition.
  3. In the Custom Form Components section of the editor, click Add to create a new form component definition.
  4. In Custom Form Component Details define the component properties:
    • Name: name of the form component
    • Icon path: relative workspace path to the icon that should be used in the palette
    • Properties: properties that will be available for editing in the Properties view of the component
      • Property Name: name of the underlying record field
      • Display Name: name displayed in the Properties view
      • Type: data type of the property (needed if the Implementation is defined as an expression)
      • Edit Style: child component edit style
        • EXPRESSION: Property is edited as an expression in the component.
        • DYNAMIC_EXPRESSION: Property is edited as an expression in the component and automatically wrapped as a non-parametric closure (the parameter is processed as { -> <parameter_value> }).
        • COMPONENT: Property is handled as a child component.
        • COMPONENT_LIST: Property can be inserted multiple times as a child component.
      • Mandatory: whether the property value must be specified
      • Displayed in Editor: if set to true, the defined value of the property is displayed in the graphical depiction of the component in the Form editor
  5. In the Expression section enter an expression that returns a custom component.
  6. Close and reopen any forms for the component to appear in the palette.
customComponentasExp.png
A custom component definition and the component record constructor
The component is now available in the palette of form definitions editor. Note that if you want to use it in other modules, make sure to they import your Module.

Creating a Custom Form Component in Java

If an existing form component doesn't cut it, you can either import an existing Vaadin component or implement your custom Vaadin component from scratch and import your implementation:

  1. Switch to Java perspective.
  2. Create the Vaadin implementation in a package in the <YOUR_APP>-vaadin project.

    The implementation might be provided by Vaadin already. This is the scenario used in the example: creating the custom component for the Vaadin's Colorpicker class.

  3. In a new package in <YOUR_APP>-vaadin, create a Java class that will connect the LSPS component to its Vaadin implementation:

    • If the component makes use of LSPS mechanisms, the class must extend the FormComponent class or its subclass.

      You can either inherit from LSPS components, typically prefixed with W: For the forms::TextArea record, the implementation is com.whitestein.lsps.vaadin.forms.WTextArea class; for generic input components, it is WInputComponentWithValue, etc.

      Important: Do not use components of Vaadin 7 located in the com.vaadin.v7.ui package. These are included for compatibility purposes and will be removed.

    • The class must implement the createWidget() method, which returns the Vaadin implementation.
    • Optionally, the class can implement the getWidget() method, which calls the getWidget() of the extended LSPS class and casts the returned component to the custom Vaadin component.

    Example connector LSPS class

    import com.vaadin.shared.ui.colorpicker.Color;
    import com.vaadin.ui.ColorPicker;
    import com.whitestein.lsps.lang.Decimal;
    import com.whitestein.lsps.lang.exception.ValidationException;
    import com.whitestein.lsps.lang.exec.RecordHolder;
    import com.whitestein.lsps.vaadin.forms.FormComponent;
     
    public class WColorPicker extends FormComponent {
     
      @Override
      protected ColorPicker createWidget() {
     
        return new ColorPicker();
      }
     
      @Override
      public ColorPicker getWidget() {
        return (ColorPicker) super.getWidget();
      }
     
      public void setColor(RecordHolder colorHolder) {
        //Decimal since it is mapped to lsps integer (red property in color)
        Decimal red = (Decimal) colorHolder.getProperty("red");
        Decimal green = (Decimal) colorHolder.getProperty("green");
        Decimal blue = (Decimal) colorHolder.getProperty("blue");
     
        Color color = new Color(red.intValue(), green.intValue(), blue.intValue());
        getWidget().setValue(color);
      }
     
      public RecordHolder getColor() {
        Color color = getWidget().getValue();
        RecordHolder colorHolder = getNamespace().createRecord("forms::Color");
        colorHolder.setProperty("red", new Decimal(color.getRed()));
        colorHolder.setProperty("green", new Decimal(color.getGreen()));
        colorHolder.setProperty("blue", new Decimal(color.getBlue()));
        return colorHolder;
      }
    }
    
  4. Once the Vaadin implementation is ready, create the definition of the custom form component so you can use it in forms definitions, do the following:
    1. In a GO-BPMN module, create a record that will represent your Vaadin component:
      • The supertype of the record must be the forms::FormComponent record or its child record: this will typically be the component returned by your createWidget() method. If your Vaadin component extends the com.vaadin.ui.CustomComponent class as it is the case of composite components, your record should have forms::FormComponent as its super type.

        It is not recommended to add record fields to the record since the record serves to reflect a Vaadin component in LSPS and Vaadin components are intended for presentation, not for business logic.

      • The record must implement the methods of its interfaces, and override inherited methods as applicable, and define methods that call their Vaadin implementation: Such methods will use call() to call the method on its Vaadin implementation. When implementing a layout component, that is, components that can hold child components, your record should implement the HasChildren interface.
        formscustomcomponentdatamodel.png
        Custom component record with FormComponent supertype; when you enter the super type, refresh the record to display the inherited fields
           ColorPicker {
         
            public void setColor(Color color){
              //calls the setColor(color) method on the connecting class:
               call("setColor", [color]);
            }
         
            public Color getColor(){
              //calls the getColor(color) method on the connecting class:
                call("getColor",[]) as Color;
            }
         
            public void refresh() {
              getColor();  // make sure that at least ObjectReference is set as a property
              call(#"refresh", null)
          }
        }
    2. To include the component in the palette of the form editor, create a custom component definition in a Custom Form Component definition file in your module.
      creatingcustomformcomponentfile.png
      Creating custom form component definition file
    3. In the definition file, create a new custom component declaration:
      1. Set the Component Type to the component record.
      2. Define properties that will be edited in the Properties view of your custom component in the Properties area:
        • Property Name: name of the underlying record field
        • Display Name: name displayed in the Properties view
        • Type: data type of the property (needed if the Implementation is defined as an expression)
        • Edit Style: child component edit style
          • EXPRESSION: Property is edited as an expression in the component.
          • COMPONENT: Property is handled as a child component.
          • COMPONENT_LIST: Property can be inserted multiple times as a child component.
        • Mandatory: whether the property value must be specified
        • Property displayed in editor: if set to true, the value of the property is displayed in the Form editor (only one property can be displayed in the component graphical depiction).
      3. In the Expression field, define an expression that will return the instance of the record, typically the constructor of the record that takes the defined properties, such as new MyLabel(text). Implement handling of the properties in the Expression.
        definingcustomformcomponent.png
        Custom form component definition with a property
  5. In the LspsFormComponentFactory class of your LSPS Application (connectors package), connect the LSPS record to its Vaadin implementation: Modify the create() method that returns FormComponent to return your implementation when the Record of your component is requested.
      @Override
      public FormComponent create(Variant.RecordVariant def) {
        final String type = def.getTypeFullName();
        //modified code (returns the WColorPicker for the record ColorPicker):
        if (type.equals("colorpicker::ColorPicker")) {
          return new WColorPicker();
        }
        return super.create(def);
      }
    
  6. Rebuild and deploy your application.

You can now use the custom components in your forms and distribute it to other users as part of a library.

customFormsInFormDefinition.png
Custom component in a form definition

Saving of a Custom Form Component

In the LSPS Application, the user can save a to-do or document with a ui form for later editing. To include data of your custom component when a to-do or a document is saved, you need to include the data on save and recover it when required:

  1. To save presentation data which is not part of the component record, do the following:
    1. In your connector class, in the example, WColorPicker, create constant-property variables for each property values to be saved.
      private static final String STATE_CURRENT_COLOR = "ColorPicker_currentColor";
    2. Override writeInternalState() so it saves these properties in the internal state of the component record.
      @Override
      protected void writeInternalState(Map<String, Object> state) {
        super.writeInternalState(state);
        state.put(STATE_CURRENT_COLOR, getWidget().getValue());
      }
      
    3. Override restoreInternalState() so it applies the properties on restore:
      @Override
      protected void restoreInternalState(Map<String, Object> state) {
        super.restoreInternalState(state);
        Color color = (Color) state.get(STATE_CURRENT_COLOR);
        if (color != null) {
          getWidget().setColor(color);
        }
      }
      
  2. If applicable, to save the child components of the custom component, do the following:
    1. Add a list of the components to writeInternalState():
      @Override
      protected void writeInternalState(Map<String, Object> state) {
        super.writeInternalState(state);
       
        writeChildComponents(state);
      }
      protected void writeChildComponents(Map<String, Object> state) {
        state.put(STATE_COMPONENTS, getComponents());
      }
      public ListHolder getComponents() {
        final List<Object> children = new ArrayList<>(getWidget().getComponentCount());
        for (FormComponent child : getChildren()) {
          children.add(form.getDef(child).get());
        }
        return form.getContext().getExecutionContext().getNamespace().createList(children);
      }
    2. Create empty instances of the child components in restoreInternalState() so the saved data has a component which it can populate.
      protected void restoreInternalState(Map<String, Object> state) {
        super.restoreInternalState(state);
       
        restoreChildComponents(state);
       
      }
      protected void restoreChildComponents(Map<String, Object> state) {
        final LspsContextHolder context = form.getContext();
       
        ListHolder children = (ListHolder) state.get(STATE_COMPONENTS);
       
        for (Object child : children) {
          RecordHolder recordHolder = (RecordHolder) child;
          Variant.RecordVariant record = Variant.wrap(recordHolder, context).record();
          form.createComponent(record);
       
          addComponent(recordHolder);
        }
      }

Creating a Custom Grid Renderer

Columns of the Grid component define a renderer which is used to render the data in each row of the Column. While a variety of renderers are available by default, you can define your own renderer if necessary.

To implement your custom Grid renderer, do the following:

  1. In a GO-BPMN module, create a record that will represent your renderer: The supertype of the record must be the forms::Renderer record or its child record.

    customrendererdatamodel.png
    Custom renderer record
  2. Switch to Java perspective to work with the Java part of the implementation:

    1. Create the Vaadin component implementation in <YOURAPP>.vaadin.util.<RENDERER> in the <YOUR_APP>-vaadin project. In the example, we are using the already available ProgressBarRenderer.
    2. Create a Java class that will connect the LSPS renderer to its Vaadin renderer. The class must extend your Vaadin renderer.

    Example renderer class:

    public class WProgressBarRenderer extends ProgressBarRenderer {
     
      private final WGrid grid;
     
      public WProgressBarRenderer(WGrid owner, RecordVariant rendererDef) {
        grid = owner;
        rendererDef.checkSubtypeOf("gridModule::ProgressBarRenderer");
      }
    }
  3. In your Application User Interface, modify the createRenderer() method of MyFormComponentFactory class to return the LSPS implementation when the renderer record is requested:
    public Renderer<?> createRenderer(WGrid owner, Variant.RecordVariant rendererDef) {
      final String type = rendererDef.getTypeFullName();
      //return the renderer in the if block for the record that reflects the renderer:
      if (type.equals("gridModule::ProgressBarRenderer")) {
      //add the if with the renderer record and the call to return the progress bar via the connecting renderer class:
        return createProgressBarRenderer(owner, rendererDef);
      } else {
        return super.createRenderer(owner, rendererDef);
      }
    }
     
    protected Renderer<?> createProgressBarRenderer(WGrid owner, Variant.RecordVariant rendererDef) {
      return new WProgressBarRenderer(owner, rendererDef);
    }
    
  4. If your renderer passes parameters to its Vaadin counterpart, make sure the data types of the parameters are compatible. You can check the compatibility of data types in Data Type Mapping in the Expression Language documentation. If the data types of the parameters passed from LSPS to the renderer implementation and vice versa are not compatible, do the following:
    1. Implement the converter as a class that implements the Vaadin Converter interface. for your renderer.
      //This is example implementation of a converter of 
      public class DoubleToDecimalConverter implements Converter<Double, Decimal> {
       
        /**
         * serialVersionUID
         */
        private static final long serialVersionUID = 1L;
       
        @Override
        public Decimal convertToModel(Double value, Class<? extends Decimal> targetType, Locale locale) throws com.vaadin.data.util.converter.Converter.ConversionException {
          return value == null ? null : new Decimal(value);
        }
       
        @Override
        public Double convertToPresentation(Decimal value, Class<? extends Double> targetType, Locale locale) throws com.vaadin.data.util.converter.Converter.ConversionException {
          return value == null ? null : new Double(value.toString());
        }
       
        @Override
        public Class<Decimal> getModelType() {
          return Decimal.class;
        }
       
        @Override
        public Class<Double> getPresentationType() {
          return Double.class;
        }
      }
      
    2. Implement the createConverterForRenderer() method in the LspsFormComponentFactory() class.
      @Override
      public Converter<?, ?> createConverterForRenderer(WGrid owner, Variant.RecordVariant rendererDef) {
        if (type.equals("gridModule::ProgressBarRenderer")) {
          //return new StringToDecimalConverter();
          return new DoubleToDecimalConverter();
        } else {
          return super.createConverterForRenderer(owner, rendererDef);
        }
      }
      
  5. Rebuild and deploy your application as required.

You can now use the custom renderer in your grid columns. Consider distributing the renderer as part of a Library.

customGridColumnRenderer.png