LSPS documentation logo
LSPS Documentation
Appendix: Creating Custom Form Component with Custom Events

To create a custom form component, you need to do the following:

  1. Create the implementation:
    1. Implement your component as a UIComponent subclass and implements UIComponent
    2. Implement a custom event as a UIEvent subclass.
  2. Create the component support for PDS:
    1. Create the data type model with records of your component, your event and any related data types.
    2. Create the custom component definition with the data type of the component and relevant properties.
  3. Create a form with the listener to test your component.

Implementing a Custom Event

In the vaadin project, create a class for your event:

  • It must implement the UIEvent.
  • The constructor must have as its second parameter the relevant data type.

    The parameter can be based on the record related to the component record.

  • Override the getEventProperties() method so it returns a hashmap of the custom event properties.
package com.whitestein.colorpicker.vaadin.util;
 
import java.util.HashMap;
import java.util.Map;
 
import com.vaadin.shared.ui.colorpicker.Color;
import com.whitestein.lsps.lang.Decimal;
import com.whitestein.lsps.lang.exec.RecordHolder;
import com.whitestein.lsps.vaadin.ui.components.UIComponent;
import com.whitestein.lsps.vaadin.ui.events.UIEvent;
 
public class UIColorPickEvent extends UIEvent {
 
  private final Color newColor;
 
  public UIColorPickEvent(UIComponent component, Color newColor) {
    super(component, colorpicker::ColorPickEvent);
    this.newColor = newColor;
  }
 
  private static RecordHolder toColor(UIComponent context, Color color) {
    final RecordHolder c = context.getComponentData().getScreen().getScreenContext().getNamespace().createRecord("colorpicker::Color");
    c.setProperty("r", new Decimal(color.getRed()));
    c.setProperty("g", new Decimal(color.getGreen()));
    c.setProperty("b", new Decimal(color.getBlue()));
    c.setProperty("a", new Decimal(color.getAlpha()));
    return c;
  }
 
  @Override
  //creates java hashmap -> fieldname to value; then creates the uicolorpickevent recordholder;
  protected Map<String, ?> getEventProperties(UIComponent component) {
    final Map<String, Object> result = new HashMap<String, Object>(super.getEventProperties(component));
    result.put("color", toColor(component, newColor));
    return result;
  }
}

Implementing Custom Component

To create an example custom component, create a class implementing your component:

  • It must implement UIComponent.
  • It must define a constructor with UIComponentData as its input argument.

    Make sure the UIComponentData is defined as a class variable, so you can use it in the getComponentData method.

    • Create the custom listener

      The listener should override the method that creates the event on the component, in the example colorChanged(ColorChangeEvent e), so the event is transformed into our custom event and then fired and enters the event queue when UIComponents.fireAndProcess() method is called.

    • Register the component with Vaadin components.
    • Create listeners and context for the component (UIComponents.afterCreate(this)).
public UIColorPicker(UIComponentData data) {
  this.data = data;
  ColorChangeListener listener = new ColorChangeListener() {
&nbsp
    @Override
    public void colorChanged(ColorChangeEvent event) {
      final Color newColor = event.getColor();
      UIComponents.fireAndProcess(new UIColorPickEvent(UIColorPicker.this, newColor));
    }
&nbsp
  };
  //registered to vaadin's color picker
  addColorChangeListener(listener);
  UIComponents.afterCreate(this);
  • Defines the refresh() method for the component.

    The method is called when the component is refreshed.

@Override
public void refresh() {
  Variant.RecordVariant color = Variant.definitionOf(this).getPropertyValue("color").closure()
      .inScope(this).call().record();
  color.checkType("colorpicker::Color").checkPresent();
  setColor(toColor(color));
}
&nbsp
private static Color toColor(Variant.RecordVariant color) {
  return new Color(color.getPropertyValue("r").decimal().get().intValue(),
      color.getPropertyValue("g").decimal().get().intValue(),
      color.getPropertyValue("b").decimal().get().intValue(),
      color.getPropertyValue("a").decimal().or(new Decimal(255)).intValue());
  }
  • Implement the getComponentData() method.
package com.whitestein.colorpicker.vaadin.util;
&nbsp
import com.vaadin.shared.ui.colorpicker.Color;
import com.vaadin.ui.ColorPicker;
import com.vaadin.ui.components.colorpicker.ColorChangeEvent;
import com.vaadin.ui.components.colorpicker.ColorChangeListener;
import com.whitestein.lsps.lang.Decimal;
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;
&nbsp
public class UIColorPicker extends ColorPicker implements UIComponent {
&nbsp
  private final UIComponentData data;
&nbsp
  public UIColorPicker(UIComponentData data) {
    this.data = data;
    ColorChangeListener listener = new ColorChangeListener() {
&nbsp
      @Override
      public void colorChanged(ColorChangeEvent event) {
        final Color newColor = event.getColor();
        UIComponents.fireAndProcess(new UIColorPickEvent(UIColorPicker.this, newColor));
      }
&nbsp
    };
    //registered to vaadin's color picker
    addColorChangeListener(listener);
    UIComponents.afterCreate(this);
  }
&nbsp
  @Override
  public void refresh() {
    Variant.RecordVariant color = Variant.definitionOf(this).getPropertyValue("color").closure()
        .inScope(this).call().record();
    color.checkType("colorpicker::Color").checkPresent();
    setColor(toColor(color));
  }
&nbsp
  private static Color toColor(Variant.RecordVariant color) {
    return new Color(color.getPropertyValue("r").decimal().get().intValue(),
        color.getPropertyValue("g").decimal().get().intValue(),
        color.getPropertyValue("b").decimal().get().intValue(),
        color.getPropertyValue("a").decimal().or(new Decimal(255)).intValue());
  }
&nbsp
  @Override
  public UIComponentData getComponentData() {
    return data;
  }
}

Registering Component in Component Factory

Modify the LspsAppComponentFactory class: uncomment the createComponent method and add the constructor call for your component that is called when the respective Record is requested.

package org.eko.ekoapp.vaadin.util;
&nbsp
import org.eko.ekoapp.vaadin.components.UIText;
&nbsp
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;
&nbsp
public class MyComponentFactory extends UIComponentFactoryImpl {
    public MyComponentFactory(LspsAppConnector connector)
            throws NullPointerException {
        super(connector);
    }
&nbsp
    @Override
    protected UIComponent createComponent(UIComponentData componentData) {
        String type = componentData.getComponentDefinition().getType()
                .getFullName();
        if (type.equals("customComponentModule::TextComponentRecord")) {
            return new UIText(componentData);
        }
        return super.createComponent(componentData);
    }
}

Creating Custom Component Support for PDS

To create a support for your component in PDS, do the following:

  1. Create a data type model that reflects the components, events, and any related data types defined in your application: make sure their are located in the module and have the name defined in their implementation (in the example, the color picker, color, and color-change listener).

    Note that a data type must have the correct super types:

    • A Listener type must have ui::Listener or its subtype as its super type.
    • A Component type must have ui::UIComponent or its subtype as its super type.
    • An Event type must have ui::Event or its subtype as its super type. It contains the field source that holds the component that produced the event and a field with the data the implementation requires.
  2. Create the custom component definition: Use the component record as the implementation (refer to Application User Interface Forms Guide).
  3. When using the component, define the listener as an expression.
new colorpicker::ColorPickListener(
  refresh -> {a->{PICKER}},
  handle -> {e:ColorPickEvent-> color:=e.color; debugLog({->"Color was picked. " + e})}
)