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 theInput 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 ofSmeupDataCellOption
, 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 theX1_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.
IfSmeupDataCellShapes
is not provided in theField
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
- Initialize the
-
3.8 Create
getInputPanelData
method
Build anInputPanelData
using theInputPanelDataBuilder
.
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 returnInputPanelFieldBuilder
setting theShape
and if the field is not required settingMandatory
to false usingoptional
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 theInputPanelConfigurationBuilder
. -
For each field, call the
getInitialInputPanelFieldBuilder
to set an initialInputPanelFieldBuilder
-
For each field, add
SmeupDataObj
type and the properties of the component declared inwithDataAttribute
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 ofInputPanelFieldBuilder
.
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 theInputPanelLayoutBuilder
. -
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 bywithColStart(1)
) with a width of one column (set bywithColSpan(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 theload
method
SetInputPanelData
andInputPanelConfiguration
in theInputPanelLoadResponseBuilder
using the created methods.@Override
public InputPanelLoadResponse load() {
return InputPanelLoadResponseBuilder.builder()
.withData(getInputPanelData())
.withConfiguration(getInputPanelConfiguration())
.build();
} -
3.13 Set
InputPanelSaveResponse
in thesave
method
When the user clicks the submit button, generate anInputPanelSavePayload
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 eachField
, 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"
}
]
}
}
]
}