LSPS documentation logo
LSPS Documentation
Custom Form Components

You can implement a custom component in Java or in the Expression Language.

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

  • Implementation of your form component for the LSPS server

    You will need to create it in the Expression Language or as a Java class. When implemented in Java, the custom component class must implement the UIComponent interface to make sure it behaves like standard components. All classes implementing custom components must be registered in the LspsAppComponentFactory class.

  • Definition of your form component for PDS

    You will need to create a custom component definition and in case of Java implementation also the respective Record.

Creating Custom Component with Java Implementation

These instructions describe how to implement a custom form component, in this case, a custom slider with binding.

However, they do not contain information on how to implement a custom event for a custom component. For instructions on how to create a custom component with a custom event, refer to Creating Custom Forms with Custom Events.

To implement a custom component, do the following:

  1. Create the custom component class in your application (in the default application, it is recommended to implement custom form components in the <YOUR_APP>.vaadin.util package in the <YOUR_APP>-vaadin project).

    The implementation must meet the following:

    • Implement the com.whitestein.lsps.vaadin.ui.components.UIComponent interface.

      Typically, it will extend a class that implements com.whitestein.lsps.vaadin.ui.components.UIComponent. Note that it is not recommended to extend any of the form components of the Standard Library since their methods might change.

    • Define a constructor with UIComponentData as its input parameter.

      The constructor should set the UIComponentData values. Component data are a wrapper of the component Record that also holds values of the record fields.

    • Implement the getComponentData() method, which returns the component data.
    • Implement the getWidget() method, which returns the Vaadin component to be rendered on the client.

      This method is useful if you want to use multiple Vaadin components to render a single LSPS custom component. Generally, you want the method to return this.

    • It implements the refresh() method.

      An example Slider Component implementation

      public class SliderComponent extends Slider implements UIComponent {
       
          private final UIComponentData componentData;
       
          public SliderComponent(UIComponentData componentData) {
              super();
              this.componentData = componentData;
          }
       
          @Override
          public UIComponentData getComponentData() {
              return componentData;
          }
       
          @Override
          public AbstractComponent getWidget() {
              return this;
          }
       
          @Override
          public void refresh() {
              final LspsProperty property = new LspsProperty(this);
              property.setLocalizeBindingValue(true);
              setPropertyDataSource(property);
              getComponentData().getComponentFactory().applyCommonProperties(this);
          }
      }
      

      An example Label Component implementation

      import com.vaadin.ui.Label;
      import com.whitestein.lsps.vaadin.ui.UIComponentData;
      import com.whitestein.lsps.vaadin.ui.components.UIComponent;
      import com.whitestein.lsps.vaadin.ui.events.UIEvent;
      import com.whitestein.lsps.vaadin.util.UIComponents;
      import com.whitestein.lsps.vaadin.util.Variant;
       
      public class UIText extends Label implements UIComponent {
       
          private final UIComponentData uic;
       
          public UIText(UIComponentData uic) {
              this.uic = uic;
          }
       
          @Override
          public UIComponentData getComponentData() {
              return uic;
          }
       
          @Override
          public void refresh() {
       
              String text = Variant.definitionOf(this).getPropertyValue("text").closure()
          .inScope(this).call().string().valueOrNull();
              text = uic.getScreen().getContextHolder().getAppConnector()
          .getLocalizer().getLocalizedString(text, this);
              setCaption(text);
       
          }
      }
      
  2. If you need additional Vaadin components, add the respective Vaadin add-on to the generated application:
    1. Create a GWT XML in <YOUR_APP>-vaadin-war/src/main/resources/com/whitestein/lsps/vaadin/webapp directory.
    2. Edit the file and append appropriate <inherits> XML element.
    3. Enable automatic compilation of the your widget sets: open the <YOUR_APP>-vaadin-war/pom.xml file and configure the maven Vaadin plugin.
    4. In the pom file, uncomment the vaadin-client-compiler dependency.
    5. Open the LspsUI Java file and modify the @Widgetset annotation, to reference your widget set, for example @Widgetset("com.whitestein.lsps.vaadin.webapp.MyWidgetSet")
    6. Open the <YOUR_APP>-vaadin-war/pom.xml and add maven dependency to the Vaadin component jar file.
  3. Modify the LspsAppComponentFactory class: uncomment the createComponent method and modify it to return your component when the respective Record is requested.
    import com.whitestein.lsps.vaadin.LspsAppConnector;
    import com.whitestein.lsps.vaadin.ui.UIComponentData;
    import com.whitestein.lsps.vaadin.ui.UIComponentFactoryImpl;
    import com.whitestein.lsps.vaadin.ui.components.UIComponent;
     
    public class LspsAppComponentFactory extends UIComponentFactoryImpl {
     
        public LspsAppComponentFactory(LspsAppConnector connector) throws NullPointerException {
            super(connector);
        }
     
        @Override
        protected UIComponent createComponent(UIComponentData componentData) {
            final String type = componentData.getDefinition().getTypeFullName();
            switch (type) {
              case "CustomComponentModule::SliderRecord":
                return new SliderComponent(componentData);
              case "CustomComponentModule::TextComponentRecord":
                return new UIText(componentData);
              }
            return super.createComponent(componentData);
        }
    }
    
  4. Deploy your application.
  5. In the referenced module, CustomComponentModule in the example, create the component Records which extend the respective ui::UIComponent record. Add any additional fields which the user needs to populate when they will use the component (make sure these are handled in your implementation properly).
    customcomponentdatamodel.png
    Custom component records
  6. In your model, create a custom component definition in a custom component definition file.

    Set the Implementation property of the custom component definition to Data Type and enter the component record (in the example, CustomComponentModule::TextComponentRecord and CustomComponentModule::SliderRecord).

  7. In the Properties area, define the component properties that will be available for editing in the Properties view:
    • 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

      Note that only one property can be displayed in the component graphical depiction.

      If the custom component extends a non-abstract UIComponent, it is rendered as its UIComponent parent and the Displayed in Editor setting is ignored.

customComponentDefinition.png
A custom component definition with implementation defined as data type
You can now use the custom components in your forms. Consider distributing the components as part of a Library.

Creating Custom Component with Expression Language Implementation

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

  1. In your module, create a Record which extends a ui::UIComponent record. Add any additional fields which the user needs to populate when they will use the component.
  2. Create a custom component definition in a custom component definition file.
  3. In the Properties area, define the component properties that will be available for editing in the Properties view:

    • 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

      Note that only one property can be displayed in the component graphical depiction.

      If the custom component extends a non-abstract UIComponent, it is rendered as its UIComponent parent and the Displayed in Editor setting is ignored.

    customComponentasExp.png
    A custom component with a parameter
  4. Set the Implementation property of the custom component definition to Expression and enter an expression that returns a custom component.
    //create checkbox:
    def ui::CheckBox cb := new ui::CheckBox();
    def Boolean checked;
    //bind checkbox to variable checked:
    cb.binding := &checked;
    //activate immediate mode for checkbox:
    cb.triggerProcessingOnChange := true;
    //handle value change of checkbox: refresh the checkbox list to have all boxes unchecked:
    cb.listeners := {
      new ValueChangeListener(
        refresh -> { a->
          {checkBoxList } 
        },
        handle -> { a ->
          if not checked then
            *(checkBoxList.binding) := {};
          end;
        }
      )
    };
    //return checkbox:
    cb;