Skip to main content

Second Service - Add Activity

The second service implements an Input Panel that allows the user to add a new activity, which is then saved in the local database. The Input Panel is a form, but underneath, it still constructs and returns a SmeupDataTable.

  • Required fields:

    • Date: The date when the activity takes place
    • Start at: The time the activity begins
    • End at: The time the activity ends
  • Optional fields:

    • Description: A brief description of the activity, with a maximum length of 30 characters
  • Buttons:

    • Clear: reset all fields to their inital values
    • Submit: it's displayed by default in the Input Panel

Steps

1. Inside the com.smeup.kokos.service create new class X1_X01_02

package com.smeup.kokos.service;

import com.smeup.kokos.sdk.annotations.DocsService;
import com.smeup.kokos.sdk.inputpanel.KokosInputPanelService;
import com.smeup.kokos.sdk.inputpanel.strategy.KokosInputPanelStrategy;

@DocsService(name = "X1_X01_02", description = "Return an Input Panel to add new Activity")
public class X1_X01_02 extends KokosInputPanelService {

public X1_X01_02() {
super(new X1_X01_02_InputPanelStrategy());
}
}

To implement the Input Panel, unlike the first service where the Data Table was explicitly defined, the class must extend KokosInputPanelService instead of KokosService, which requires a constructor with an InputPanelStrategy as a parameter.

2. In the same file create a new class InputPanelStrategy

class X1_X01_02_InputPanelStrategy extends KokosInputPanelStrategy {
@Override
public InputPanelLoadResponse load() {
return InputPanelLoadResponseBuilder.builder()
.build();
}

@Override
public InputPanelSaveResponse save(InputPanelSavePayload payload) {
return InputPanelSaveResponseBuilder.builder()
.build();
}
}

This class extends KokosInputPanelStrategy, requiring the implementation of load and save methods.

  • load:
    It is invoked when the Input Panel is initialized through the *INIT service method. It sets up the structure of the initial input panel.

  • save: It is invoked when the submit button is clicked. It handles form validation and defines what to do after it.

3.Inside X1_X01_02_InputPanelStrategy

  • 3.1 Define the variables for the fields IDs

    public static final String DATE_ID = "X1DATE";
    public static final String START_ID = "X1STIM";
    public static final String END_ID = "X1ETIM";
    public static final String CATEGORY_ID = "X1CTGR";
    public static final String DESCRIPTION_ID = "X1DESC";
    public static final String BUTTON_CLEAR_ID = "X1CLER";
  • 3.2 Define the Data Objects

    private final SmeupDataObj dateObj = new SmeupDataObj("D8", "", "");
    private final SmeupDataObj timeObj = new SmeupDataObj("I1", "2", "");
    private final SmeupDataObj clearButtonObj = new SmeupDataObj("", "", BUTTON_CLEAR_ID);
  • 3.3 Define the category options list
    This list represents the different categories for the activities. Each option is in the format of SmeupDataCellOption, with an ID and a label. In this case, the ID represents the style code of the associated category.

    private final List<SmeupDataCellOption> categoryOptions = List.of(
    new SmeupDataCellOption("00E20", "Health"),
    new SmeupDataCellOption("00D01", "Sport"),
    new SmeupDataCellOption("53200", "Work"),
    new SmeupDataCellOption("53400", "Free time"));
  • 3.4 Define the String messages

    private final String INPUT_PANEL_TITLE = "Add new activity";
    private final String INVALID_FORM_ERROR_MESSAGE = "Unable to submit! Please fill all required fields and try again";
    private final String SUBMIT_ERROR_MESSAGE = "Unable to create new activity! Server Error";
    private final String SUBMIT_SUCCESS_MESSAGE = "New activity has been added!";
  • 3.5 Define the activities controller to interact with database

    private Controller<Activity> activitiesController;
  • 3.6 Define a map of IDs and Fields

    private Map<String, Field> fields;

    private class Field {
    private String value;
    private boolean isRequired;
    private SmeupDataCellShapes shape;

    public Field(String value, boolean isRequired) {
    this(value, SmeupDataCellShapes.TEXT_FIELD, isRequired);
    }

    public Field(String value, SmeupDataCellShapes shape) {
    this(value, shape, true);
    }

    public Field(String value, SmeupDataCellShapes shape, boolean isRequired) {
    this.value = value;
    this.shape = shape;
    this.isRequired = isRequired;
    }
    }

    This class helps to keep track of field values and to return the Input Panel with the data entered by the user before clicking submit. It will be useful also for the validation.

  • 3.7 Initialize activitiesController and the map of fields
    In the X1_X01_02_InputPanelStrategy constructor:

    • Initialize the activitiesController to interact with MongoDB (or inject your DAO to interact with another database)
    • Create the map that associates the previously declared field IDs with the corresponding Field objects
    • Each Field is initialized with an initial value, data cell shape and its isRequired status.

    public X1_X01_02_InputPanelStrategy() {
    this.activitiesController = new Controller<Activity>(new ActivitiesMongoDAO());

    this.fields = new LinkedHashMap<>(Map.of(
    DATE_ID, new Field("", SmeupDataCellShapes.DATE),
    START_ID, new Field("", SmeupDataCellShapes.TIME),
    END_ID, new Field("", SmeupDataCellShapes.TIME),
    CATEGORY_ID, new Field("", SmeupDataCellShapes.COMBOBOX),
    DESCRIPTION_ID, new Field("", false),
    BUTTON_CLEAR_ID, new Field("Clear", SmeupDataCellShapes.BUTTON)));
    }

    The shapes define which component will be used to render the field.
    If SmeupDataCellShapes is not provided in the Field constructor, it uses SmeupDataCellShapes.TEXT_FIELD by default.

    • SmeupDataCellShapes.DATE: component for selecting a date
    • SmeupDataCellShapes.TIME: component for selecting a time
    • SmeupDataCellShapes.COMBOBOX: component for selecting an option from a list
    • SmeupDataCellShapes.BUTTON: component for rendering a button
    • SmeupDataCellShapes.TEXT_FIELD: component for rendering a standard input field
  • 3.8 Create getInputPanelData method
    Build an InputPanelData using the InputPanelDataBuilder.
    The Input Panel fields are defined using field IDs and their associated values.

    private InputPanelData getInputPanelData() {

    // Fields definition
    return InputPanelDataBuilder.builder()
    // 1
    .withField(DATE_ID,
    InputPanelFieldDataBuilder.builder()
    .withValue(this.fields.get(DATE_ID).value)
    .build())
    // 2
    .withField(START_ID,
    InputPanelFieldDataBuilder.builder()
    .withValue(this.fields.get(START_ID).value)
    .build())
    // 3
    .withField(END_ID,
    InputPanelFieldDataBuilder.builder()
    .withValue(this.fields.get(END_ID).value)
    .build())
    // 4
    .withField(CATEGORY_ID,
    InputPanelFieldDataBuilder.builder()
    .withValue(this.fields.get(CATEGORY_ID).value)
    .build())
    // 5
    .withField(DESCRIPTION_ID,
    InputPanelFieldDataBuilder.builder()
    .withValue(this.fields.get(DESCRIPTION_ID).value)
    .build())
    // 6
    .withField(BUTTON_CLEAR_ID,
    InputPanelFieldDataBuilder.builder()
    .withValue(this.fields.get(BUTTON_CLEAR_ID).value)
    .build())
    .build();
    }
  • 3.9 Create getInitialInputPanelFieldBuilder method
    Create and return InputPanelFieldBuilder setting the Shape and if the field is not required setting Mandatory to false using optional

    private InputPanelFieldBuilder getInitialInputPanelFieldBuilder(String fieldID) {
    Field field = this.fields.get(fieldID);
    InputPanelFieldBuilder inputPanelFieldBuilder = InputPanelFieldBuilder.builder(fieldID).withShape(field.shape);
    if (!field.isRequired) {
    inputPanelFieldBuilder.optional();
    }
    return inputPanelFieldBuilder;
    }
  • 3.10 Create getInputPanelConfiguration method

    • Build an InputPanelConfiguration using the InputPanelConfigurationBuilder.

    • For each field, call the getInitialInputPanelFieldBuilder to set an initial InputPanelFieldBuilder

    • For each field, add SmeupDataObj type and the properties of the component declared in withDataAttribute as key/value pairs (to see which properties can be set for the component, refer to Ketchup Showcase).

    • To set the list, use withOptions method of InputPanelFieldBuilder.

    private InputPanelConfiguration getInputPanelConfiguration() {

    // Fields configuration
    return InputPanelConfigurationBuilder.builder()
    // 1
    .withField(DATE_ID, getInitialInputPanelFieldBuilder(DATE_ID)
    .withTitle("Date*")
    .withObj(dateObj)
    .build())
    // 2
    .withField(START_ID, getInitialInputPanelFieldBuilder(START_ID)
    .withTitle("Start at*")
    .withObj(timeObj)
    .build())
    // 3
    .withField(END_ID, getInitialInputPanelFieldBuilder(END_ID)
    .withTitle("End at*")
    .withObj(timeObj)
    .build())
    // 4
    .withField(CATEGORY_ID, getInitialInputPanelFieldBuilder(CATEGORY_ID)
    .withTitle("Category*")
    .withOptions(categoryOptions)
    .withDataAttribute("isSelect", true)
    .build())
    // 5
    .withField(DESCRIPTION_ID, getInitialInputPanelFieldBuilder(DESCRIPTION_ID)
    .withTitle("Description")
    .withDataAttribute("outlined", true)
    .withDataAttribute("maxLength", "30")
    .build())
    // 6
    .withField(BUTTON_CLEAR_ID, getInitialInputPanelFieldBuilder(BUTTON_CLEAR_ID)
    .withObj(clearButtonObj)
    .withDataAttribute("styling", "outlined")
    .build())
    // Layout
    .withLayout(getInputPanelLayout())
    .build();
    }

    Here is defined also the layout of the Input Panel (returned by the following method getInputPanelLayout).

  • 3.11 Create getInputPanelLayout method

    • The InputPanelLayout sets the position for displaying the fields and is built using the InputPanelLayoutBuilder.

    • In this case, a section is defined with one column and six rows.
      Each row displays the corresponding field (based on its ID), starting in the first column (set by withColStart(1)) with a width of one column (set by withColSpan(1)).

    private InputPanelLayout getInputPanelLayout() {
    return InputPanelLayoutBuilder.builder()
    .withSection(InputPanelLayoutSectionBuilder.builder()
    // Sections
    // 1
    .withId("SECTION_1")
    .withTitle(INPUT_PANEL_TITLE)
    .withGridCols(1)
    .withGridRows(6)
    .withGap(1)
    // Show contents of section 1
    // 1
    .withContent(InputPanelLayoutFieldBuilder.builder(DATE_ID)
    .withColStart(1)
    .withColSpan(1)
    .withRowStart(1)
    .build())
    // 2
    .withContent(InputPanelLayoutFieldBuilder.builder(START_ID)
    .withColStart(1)
    .withColSpan(1)
    .withRowStart(2)
    .build())
    // 3
    .withContent(InputPanelLayoutFieldBuilder.builder(END_ID)
    .withColStart(1)
    .withColSpan(1)
    .withRowStart(3)
    .build())
    // 4
    .withContent(InputPanelLayoutFieldBuilder.builder(CATEGORY_ID)
    .withColStart(1)
    .withColSpan(1)
    .withRowStart(4)
    .build())
    // 5
    .withContent(InputPanelLayoutFieldBuilder.builder(DESCRIPTION_ID)
    .withColStart(1)
    .withColSpan(1)
    .withRowStart(5)
    .build())
    // 6
    .withContent(InputPanelLayoutFieldBuilder.builder(BUTTON_CLEAR_ID)
    .withColStart(1)
    .withColSpan(1)
    .withRowStart(6)
    .build())
    .build())
    .build();
    }
  • 3.12 Set InputPanelLoadResponse in the load method
    Set InputPanelData and InputPanelConfiguration in the InputPanelLoadResponseBuilder using the created methods.

    @Override
    public InputPanelLoadResponse load() {

    return InputPanelLoadResponseBuilder.builder()
    .withData(getInputPanelData())
    .withConfiguration(getInputPanelConfiguration())
    .build();
    }
  • 3.13 Set InputPanelSaveResponse in the save method
    When the user clicks the submit button, generate an InputPanelSavePayload containing both the before and after values. In this case, only the after values are needed.
    If the submission is successful, the field values will be reset and a success message will be displayed.

    @Override
    public InputPanelSaveResponse save(InputPanelSavePayload payload) {
    final LinkedHashMap<String, InputPanelFieldData> payloadAfterFields = payload.getAfter().getFields();

    /* If payloadAfterFields are not valid,
    re-render the input panel with the same values and INVALID_FORM_ERROR_MESSAGE.*/

    /* If they are valid, try to add the activity to the database using activitiesController.
    If error, re-render the input panel with the same values and SUBMIT_ERROR_MESSAGE.*/


    // If submit is successful
    // reset values
    return InputPanelSaveResponseBuilder.builder()
    .withMessage(new InputPanelMessage(SUBMIT_SUCCESS_MESSAGE))
    .withData(getInputPanelData())
    .withConfiguration(getInputPanelConfiguration())
    .build();
    }
  • 3.14 Create the method for the validation form checkSubmit
    For each Field, assign the new value from the provided payload and, if it's a required field, verify that it is not empty.
    If some required field is empty, return false.

    private boolean checkSubmit(LinkedHashMap<String, InputPanelFieldData> payloadFields) {
    boolean isValid = true;
    for (Map.Entry<String, Field> mapField : this.fields.entrySet()) {
    String id = mapField.getKey();
    Field field = mapField.getValue();
    String newValue = payloadFields.get(id).getValue().toString().trim();
    field.value = newValue;
    if (field.isRequired && field.value.isEmpty()) {
    isValid = false;
    }
    }

    return isValid;
    }
  • 3.15 Create addActivity method
    Try to add the new activity to the database using the activitiesController initialized in the constructor.
    If createdActivity is null, an error has occurred and the operation has failed. In this case, return false.

      private boolean addActivity(LinkedHashMap<String, InputPanelFieldData> payloadAfterFields) {
    String date = payloadAfterFields.get(DATE_ID).getValue().toString();
    String start = payloadAfterFields.get(START_ID).getValue().toString();
    String end = payloadAfterFields.get(END_ID).getValue().toString();
    String styleCategory = payloadAfterFields.get(CATEGORY_ID).getValue().toString();
    String category = categoryOptions.stream()
    .filter(categoryOption -> categoryOption.getId().equals(styleCategory))
    .map(categoryOption -> categoryOption.getLabel())
    .findFirst()
    .orElse(null);
    String description = payloadAfterFields.get(DESCRIPTION_ID).getValue().toString();

    Activity createdActivity = activitiesController.create(ActivityBuilder.builder()
    .withDate(TimeUtil.parseToLocalDate(date))
    .withStart(TimeUtil.parseToLocalTime(start))
    .withEnd(TimeUtil.parseToLocalTime(end))
    .withStyleCategory(styleCategory)
    .withCategory(category)
    .withDescription(description)
    .build());
    return createdActivity != null;
    }
  • 3.16 Create resetValues method
    It resets the values of the fields, except for the buttons.

    private void resetValues(Collection<Field> fields) {
    fields.stream()
    .filter(field -> field.shape != SmeupDataCellShapes.BUTTON)
    .forEach(field -> field.value = "");
    }
  • 3.17 In the save method, integrate validation and new activity creation logic

    @Override
    public InputPanelSaveResponse save(InputPanelSavePayload payload) {
    final LinkedHashMap<String, InputPanelFieldData> payloadAfterFields = payload.getAfter().getFields();

    // Form validation
    boolean isValidSubmt = checkSubmit(payloadAfterFields);
    if (!isValidSubmt) {
    return InputPanelSaveResponseBuilder.builder()
    .withMessage(new InputPanelMessage(INVALID_FORM_ERROR_MESSAGE, SmeupMessageGravity.ERROR))
    .withData(getInputPanelData(fields))
    .withConfiguration(getInputPanelConfiguration(fields))
    .build();
    }

    // Try to add activity to database
    boolean isCreatedActivity = addActivity(payloadAfterFields);
    if (!isCreatedActivity) {
    return InputPanelSaveResponseBuilder.builder()
    .withMessage(new InputPanelMessage(SUBMIT_ERROR_MESSAGE, SmeupMessageGravity.ERROR))
    .withData(getInputPanelData(fields))
    .withConfiguration(getInputPanelConfiguration(fields))
    .build();
    }

    // Success
    resetValues(fields.values());
    return InputPanelSaveResponseBuilder.builder()
    .withMessage(new InputPanelMessage(SUBMIT_SUCCESS_MESSAGE))
    .withData(getInputPanelData(fields))
    .withConfiguration(getInputPanelConfiguration(fields))
    .build();
    }
  • 4 In the main class X1_X01_02, create the service method to handle the click of clear button

    @DocsService(name = "X1_X01_02", description = "Return an Input Panel to add new Activity")
    public class X1_X01_02 extends KokosInputPanelService {

    public X1_X01_02() {
    super(new X1_X01_02_InputPanelStrategy());
    }

    @DocsServiceMethod(component = "EXB", function = X1_X01_02_InputPanelStrategy.BUTTON_CLEAR_ID, description = "Clear all fields of the Input Panel")
    @ServiceMethod(X1_X01_02_InputPanelStrategy.BUTTON_CLEAR_ID)
    public void clear(final Fun fun, final ExecutionContext context) throws Exception {
    this.init(fun, context);
    }
    }

Final result

X1_X01_02
package com.smeup.kokos.service;

import java.util.List;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

import com.smeup.kokos.controller.Controller;
import com.smeup.kokos.entity.activity.Activity;
import com.smeup.kokos.entity.activity.ActivityBuilder;
import com.smeup.kokos.repository.mongo.ActivitiesMongoDAO;
import com.smeup.kokos.sdk.annotations.DocsService;
import com.smeup.kokos.sdk.annotations.DocsServiceMethod;
import com.smeup.kokos.sdk.fun.Fun;
import com.smeup.kokos.sdk.inputpanel.KokosInputPanelService;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelConfiguration;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelConfigurationBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelData;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelDataBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelFieldBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelFieldData;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelFieldDataBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelLayout;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelLayoutBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelLayoutFieldBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelLayoutSectionBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelLoadResponse;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelLoadResponseBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelMessage;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelSavePayload;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelSaveResponse;
import com.smeup.kokos.sdk.inputpanel.strategy.InputPanelSaveResponseBuilder;
import com.smeup.kokos.sdk.inputpanel.strategy.KokosInputPanelStrategy;
import com.smeup.kokos.sdk.model.ExecutionContext;
import com.smeup.kokos.sdk.model.ServiceMethod;
import com.smeup.kokos.sdk.model.data.SmeupDataCellOption;
import com.smeup.kokos.sdk.model.data.SmeupDataCellShapes;
import com.smeup.kokos.sdk.model.data.SmeupDataObj;
import com.smeup.kokos.sdk.model.data.SmeupMessage.SmeupMessageGravity;
import com.smeup.kokos.util.TimeUtil;

@DocsService(name = "X1_X01_02", description = "Return an Input Panel to add new Activity")
public class X1_X01_02 extends KokosInputPanelService {

public X1_X01_02() {
super(new X1_X01_02_InputPanelStrategy());
}

@DocsServiceMethod(component = "EXB", function = X1_X01_02_InputPanelStrategy.BUTTON_CLEAR_ID, description = "Clear all fields of the Input Panel")
@ServiceMethod(X1_X01_02_InputPanelStrategy.BUTTON_CLEAR_ID)
public void clear(final Fun fun, final ExecutionContext context) throws Exception {
this.init(fun, context);
}
}

class X1_X01_02_InputPanelStrategy extends KokosInputPanelStrategy {

public static final String DATE_ID = "X1DATE";
public static final String START_ID = "X1STIM";
public static final String END_ID = "X1ETIM";
public static final String CATEGORY_ID = "X1CTGR";
public static final String DESCRIPTION_ID = "X1DESC";
public static final String BUTTON_CLEAR_ID = "X1CLER";

private final SmeupDataObj dateObj = new SmeupDataObj("D8", "", "");
private final SmeupDataObj timeObj = new SmeupDataObj("I1", "2", "");
private final SmeupDataObj clearButtonObj = new SmeupDataObj("", "", BUTTON_CLEAR_ID);

private final List<SmeupDataCellOption> categoryOptions = List.of(
new SmeupDataCellOption("00E20", "Health"),
new SmeupDataCellOption("00D01", "Sport"),
new SmeupDataCellOption("53200", "Work"),
new SmeupDataCellOption("53400", "Free time"));

private final String INPUT_PANEL_TITLE = "Add new activity";
private final String INVALID_FORM_ERROR_MESSAGE = "Unable to submit! Please fill all required fields and try again";
private final String SUBMIT_ERROR_MESSAGE = "Unable to create new activity! Server Error";
private final String SUBMIT_SUCCESS_MESSAGE = "New activity has been added!";

private Controller<Activity> activitiesController;
private Map<String, Field> fields;

private class Field {
private String value;
private boolean isRequired;
private SmeupDataCellShapes shape;

public Field(String value, boolean isRequired) {
this(value, SmeupDataCellShapes.TEXT_FIELD, isRequired);
}

public Field(String value, SmeupDataCellShapes shape) {
this(value, shape, true);
}

public Field(String value, SmeupDataCellShapes shape, boolean isRequired) {
this.value = value;
this.shape = shape;
this.isRequired = isRequired;
}
}

public X1_X01_02_InputPanelStrategy() {
this.activitiesController = new Controller<Activity>(new ActivitiesMongoDAO());

this.fields = new LinkedHashMap<>(Map.of(
DATE_ID, new Field("", SmeupDataCellShapes.DATE),
START_ID, new Field("", SmeupDataCellShapes.TIME),
END_ID, new Field("", SmeupDataCellShapes.TIME),
CATEGORY_ID, new Field("", SmeupDataCellShapes.COMBOBOX),
DESCRIPTION_ID, new Field("", false),
BUTTON_CLEAR_ID, new Field("Clear", SmeupDataCellShapes.BUTTON)));
}

@Override
public InputPanelLoadResponse load() {

return InputPanelLoadResponseBuilder.builder()
.withData(getInputPanelData())
.withConfiguration(getInputPanelConfiguration())
.build();
}

@Override
public InputPanelSaveResponse save(InputPanelSavePayload payload) {
final LinkedHashMap<String, InputPanelFieldData> payloadAfterFields = payload.getAfter().getFields();

// Form validation
boolean isValidSubmt = checkSubmit(payloadAfterFields);
if (!isValidSubmt) {
return InputPanelSaveResponseBuilder.builder()
.withMessage(new InputPanelMessage(INVALID_FORM_ERROR_MESSAGE, SmeupMessageGravity.ERROR))
.withData(getInputPanelData())
.withConfiguration(getInputPanelConfiguration())
.build();
}

// Try to add activity to database
boolean isCreatedActivity = addActivity(payloadAfterFields);
if (!isCreatedActivity) {
return InputPanelSaveResponseBuilder.builder()
.withMessage(new InputPanelMessage(SUBMIT_ERROR_MESSAGE, SmeupMessageGravity.ERROR))
.withData(getInputPanelData())
.withConfiguration(getInputPanelConfiguration())
.build();
}

// Success
resetValues(this.fields.values());
return InputPanelSaveResponseBuilder.builder()
.withMessage(new InputPanelMessage(SUBMIT_SUCCESS_MESSAGE))
.withData(getInputPanelData())
.withConfiguration(getInputPanelConfiguration())
.build();
}

private InputPanelData getInputPanelData() {

// Fields definition
return InputPanelDataBuilder.builder()
// 1
.withField(DATE_ID,
InputPanelFieldDataBuilder.builder()
.withValue(this.fields.get(DATE_ID).value)
.build())
// 2
.withField(START_ID,
InputPanelFieldDataBuilder.builder()
.withValue(this.fields.get(START_ID).value)
.build())
// 3
.withField(END_ID,
InputPanelFieldDataBuilder.builder()
.withValue(this.fields.get(END_ID).value)
.build())
// 4
.withField(CATEGORY_ID,
InputPanelFieldDataBuilder.builder()
.withValue(this.fields.get(CATEGORY_ID).value)
.build())
// 5
.withField(DESCRIPTION_ID,
InputPanelFieldDataBuilder.builder()
.withValue(this.fields.get(DESCRIPTION_ID).value)
.build())
// 6
.withField(BUTTON_CLEAR_ID,
InputPanelFieldDataBuilder.builder()
.withValue(this.fields.get(BUTTON_CLEAR_ID).value)
.build())
.build();
}

private InputPanelConfiguration getInputPanelConfiguration() {

// Fields configuration
return InputPanelConfigurationBuilder.builder()
// 1
.withField(DATE_ID, getInitialInputPanelFieldBuilder(DATE_ID)
.withTitle("Date*")
.withObj(dateObj)
.build())
// 2
.withField(START_ID, getInitialInputPanelFieldBuilder(START_ID)
.withTitle("Start at*")
.withObj(timeObj)
.build())
// 3
.withField(END_ID, getInitialInputPanelFieldBuilder(END_ID)
.withTitle("End at*")
.withObj(timeObj)
.build())
// 4
.withField(CATEGORY_ID, getInitialInputPanelFieldBuilder(CATEGORY_ID)
.withTitle("Category*")
.withOptions(categoryOptions)
.withDataAttribute("isSelect", true)
.build())
// 5
.withField(DESCRIPTION_ID, getInitialInputPanelFieldBuilder(DESCRIPTION_ID)
.withTitle("Description")
.withDataAttribute("outlined", true)
.withDataAttribute("maxLength", "30")
.build())
// 6
.withField(BUTTON_CLEAR_ID, getInitialInputPanelFieldBuilder(BUTTON_CLEAR_ID)
.withObj(clearButtonObj)
.withDataAttribute("styling", "outlined")
.build())
// Layout
.withLayout(getInputPanelLayout())
.build();
}

private InputPanelFieldBuilder getInitialInputPanelFieldBuilder(String fieldID) {
Field field = this.fields.get(fieldID);
InputPanelFieldBuilder inputPanelFieldBuilder = InputPanelFieldBuilder.builder(fieldID).withShape(field.shape);
if (!field.isRequired) {
inputPanelFieldBuilder.optional();
}
return inputPanelFieldBuilder;
}

private InputPanelLayout getInputPanelLayout() {
return InputPanelLayoutBuilder.builder()
.withSection(InputPanelLayoutSectionBuilder.builder()
// Sections
// 1
.withId("SECTION_1")
.withTitle(INPUT_PANEL_TITLE)
.withGridCols(1)
.withGridRows(6)
.withGap(1)
// Show contents of section 1
// 1
.withContent(InputPanelLayoutFieldBuilder.builder(DATE_ID)
.withColStart(1)
.withColSpan(1)
.withRowStart(1)
.build())
// 2
.withContent(InputPanelLayoutFieldBuilder.builder(START_ID)
.withColStart(1)
.withColSpan(1)
.withRowStart(2)
.build())
// 3
.withContent(InputPanelLayoutFieldBuilder.builder(END_ID)
.withColStart(1)
.withColSpan(1)
.withRowStart(3)
.build())
// 4
.withContent(InputPanelLayoutFieldBuilder.builder(CATEGORY_ID)
.withColStart(1)
.withColSpan(1)
.withRowStart(4)
.build())
// 5
.withContent(InputPanelLayoutFieldBuilder.builder(DESCRIPTION_ID)
.withColStart(1)
.withColSpan(1)
.withRowStart(5)
.build())
// 6
.withContent(InputPanelLayoutFieldBuilder.builder(BUTTON_CLEAR_ID)
.withColStart(1)
.withColSpan(1)
.withRowStart(6)
.build())
.build())
.build();
}

private boolean checkSubmit(LinkedHashMap<String, InputPanelFieldData> payloadFields) {
boolean isValid = true;
for (Map.Entry<String, Field> mapField : this.fields.entrySet()) {
String id = mapField.getKey();
Field field = mapField.getValue();
String newValue = payloadFields.get(id).getValue().toString().trim();
field.value = newValue;
if (field.isRequired && field.value.isEmpty()) {
isValid = false;
}
}

return isValid;
}

private boolean addActivity(LinkedHashMap<String, InputPanelFieldData> payloadAfterFields) {
String date = payloadAfterFields.get(DATE_ID).getValue().toString();
String start = payloadAfterFields.get(START_ID).getValue().toString();
String end = payloadAfterFields.get(END_ID).getValue().toString();
String styleCategory = payloadAfterFields.get(CATEGORY_ID).getValue().toString();
String category = categoryOptions.stream()
.filter(categoryOption -> categoryOption.getId().equals(styleCategory))
.map(categoryOption -> categoryOption.getLabel())
.findFirst()
.orElse(null);
String description = payloadAfterFields.get(DESCRIPTION_ID).getValue().toString();

Activity createdActivity = this.activitiesController.create(ActivityBuilder.builder()
.withDate(TimeUtil.parseToLocalDate(date))
.withStart(TimeUtil.parseToLocalTime(start))
.withEnd(TimeUtil.parseToLocalTime(end))
.withStyleCategory(styleCategory)
.withCategory(category)
.withDescription(description)
.build());
return createdActivity != null;
}

private void resetValues(Collection<Field> fields) {
fields.stream()
.filter(field -> field.shape != SmeupDataCellShapes.BUTTON)
.forEach(field -> field.value = "");
}
}

Test it with FUN

Check if the JSON response is the same

FUN payload:

{
"fun": {
"component": "EXB",
"service": "X1_X01_02",
"function": "*INIT"
},
"context": {
"user": {
"environment": "standard"
}
}
}

JSON response:

{
"type": "SmeupDataTable",
"serviceInfo": {
"fun": "F(EXB;X1_X01_02;*INIT)",
"serviceName": "X1_X01_02"
},
"columns": [
{
"name": "X1DATE",
"title": "Date*",
"visible": true,
"isEditable": false,
"tooltip": false
},
{
"name": "X1STIM",
"title": "Start at*",
"visible": true,
"isEditable": false,
"tooltip": false
},
{
"name": "X1ETIM",
"title": "End at*",
"visible": true,
"isEditable": false,
"tooltip": false
},
{
"name": "X1CTGR",
"title": "Category*",
"visible": true,
"isEditable": false,
"tooltip": false
},
{
"name": "X1DESC",
"title": "Description",
"visible": true,
"isEditable": false,
"tooltip": false
},
{
"name": "X1CLER",
"visible": true,
"isEditable": false,
"tooltip": false
}
],
"rows": [
{
"cells": {
"X1DATE": {
"value": "",
"obj": {
"t": "D8",
"p": "",
"k": ""
},
"options": [],
"editable": true,
"mandatory": true,
"shape": "DAT",
"tooltip": false,
"data": {},
"disabled": false
},
"X1STIM": {
"value": "",
"obj": {
"t": "I1",
"p": "2",
"k": ""
},
"options": [],
"editable": true,
"mandatory": true,
"shape": "TIM",
"tooltip": false,
"data": {},
"disabled": false
},
"X1ETIM": {
"value": "",
"obj": {
"t": "I1",
"p": "2",
"k": ""
},
"options": [],
"editable": true,
"mandatory": true,
"shape": "TIM",
"tooltip": false,
"data": {},
"disabled": false
},
"X1CTGR": {
"value": "",
"options": [
{
"id": "00E20",
"label": "Health"
},
{
"id": "00D01",
"label": "Sport"
},
{
"id": "53200",
"label": "Work"
},
{
"id": "53400",
"label": "Free time"
}
],
"editable": true,
"mandatory": true,
"shape": "CMB",
"tooltip": false,
"data": {
"isSelect": true
},
"disabled": false
},
"X1DESC": {
"value": "",
"options": [],
"editable": true,
"mandatory": false,
"shape": "ITX",
"tooltip": false,
"data": {
"outlined": true,
"maxLength": "30"
},
"disabled": false
},
"X1CLER": {
"value": "Clear",
"obj": {
"t": "",
"p": "",
"k": "X1CLER"
},
"options": [],
"editable": true,
"mandatory": true,
"shape": "BTN",
"tooltip": false,
"data": {
"styling": "outlined"
},
"disabled": false
}
},
"layout": {
"horizontal": false,
"absolute": false,
"sections": [
{
"id": "SECTION_1",
"content": [
{
"options": [],
"editable": false,
"mandatory": false,
"tooltip": false,
"disabled": false,
"id": "X1DATE",
"colSpan": 1,
"colStart": 1,
"rowStart": 1
},
{
"options": [],
"editable": false,
"mandatory": false,
"tooltip": false,
"disabled": false,
"id": "X1STIM",
"colSpan": 1,
"colStart": 1,
"rowStart": 2
},
{
"options": [],
"editable": false,
"mandatory": false,
"tooltip": false,
"disabled": false,
"id": "X1ETIM",
"colSpan": 1,
"colStart": 1,
"rowStart": 3
},
{
"options": [],
"editable": false,
"mandatory": false,
"tooltip": false,
"disabled": false,
"id": "X1CTGR",
"colSpan": 1,
"colStart": 1,
"rowStart": 4
},
{
"options": [],
"editable": false,
"mandatory": false,
"tooltip": false,
"disabled": false,
"id": "X1DESC",
"colSpan": 1,
"colStart": 1,
"rowStart": 5
},
{
"options": [],
"editable": false,
"mandatory": false,
"tooltip": false,
"disabled": false,
"id": "X1CLER",
"colSpan": 1,
"colStart": 1,
"rowStart": 6
}
],
"sections": [],
"horizontal": false,
"gridCols": 1,
"gridRows": 6,
"gap": 1,
"title": "Add new activity"
}
]
}
}
]
}