Technical Articles
Part 1 – Custom Dynamic Form control based on OData Metadata Annotations
Overview
In this blog I am showcasing a Dynamic Custom Control which can be used to render a form in any UI Application with minimal code. It uses the OData metadata annotation to render control in form field. It supports message handling and perform validation on correctness of values based on data type of that field.
There is a similar kind of Smart Form control available but this Form Control offers far more flexibility and developer have to write much less code to declare it.
So lets start with the looking at Form control in Display and in Edit Mode.
Figure 1: Sales Order Form in Display Mode
Figure 2: Sales Order Form in Edit Mode
Control Features
Details of Control Type Supported in the control
This control supports varied control types. The usage of these control type and their configuration are based on annotation provided in the metadata of the property. By default this control uses 8 control types which are used commonly in any form. These are listed below along with what annotation to be used.
Additionally this control also supports loading of control type different from default one or overriding of default behavior of these control types (Refer control property propertyMap for more details regarding this).
Date Picker
Assign Edm.Type = Edm.DateTime or Edm.DateTimeoffset in the entity to that. an
Note:- Also Provide a custom annotation sap:display-format="Date"
![]()
![]()
Time Picker
Assign Edm.Type = Edm.Time in the entity.
Note:- Do not provide sap:display-format="Date" annotation![]()
![]()
Date Time Picker
Assign Edm.Type = Edm.DateTime or Edm.DateTimeoffset in the entity.
Note:- Do not provide sap:display-format="Date" annotation
Text Area
MaxLength in the entity should be more than 50 to render a TextArea control.
Amount-Currency Type Control
Assign sap-unit=’Name of the Currency property’ annotation to to Amount property as it is done in this example – sap-unit=”CurrencyCode” is assigned to NetAmount.
Yes-No Option Control (CheckBox like)
Assign sap-isCheckBox=’true’ annotation to the property for which a ComboBox with option yes/no is required. It can be interpreted as a Checkbox.
ValueHelps
Assign sap:value-list=”fixed-values“ annotation to the property for which ValueHelp is to be rendered. Also assign the name of the property as a reference to the ValueHelp Entity to load data in the ValueHelp.
Input
By default Input control will be loaded
![]()
Field Control
This control uses Field Control feature of OData to dynamically update the visual configuration of Control type. These 4 types of Configuration are as follows:-
1. Mandatory
Pass 7 to Field Control value (here Uxfc01) at the time of fetching data.
2. Read only
Pass 1 to Field Control value (here Uxfc01) at the time of fetching data.
Additionally it also uses sap:creatable and sap:updatable annotation in entity to make a field as read only.
Note: Later will take preference over the former in case of conflict.
3. Hiding fields
Pass 0 to Field Control value (here Uxfc01) at the time of fetching data.
Additionally it also uses sap:visible annotation in entity to hide a field.
Note: Later will take preference over the former in case of conflict.
4. Optional
Pass 3 to Field Control value (here Uxfc01) at the time of fetching data.
In this case the default behavior of the control will be used.
Validations & Checks
Control itself validates whether any field in the form is invalid or not.
It verifies that all the mandatory fields are having values, fields are having valid values or not (based on Edm.Type)
Properties available in this control
Name | Type | Default Value | Description |
entityTypeName
|
string | Specifies Name of the OData entity | |
title
|
string | null | Title of the form |
mode
|
string | null | Specifies mode in which control needs to be Rendered. “E” for Edit mode and “D” for display Mode |
includeOnlyFields
|
string[]
|
[] |
Specifies Explicit list of fields to display. All other fields will be ignored. Example of Usage <Form includeOnlyFields=”F1,F2,F3″ /> |
useSplitLayout
|
boolean | true | Specifies whether to use split form layout (with two form containers) |
required
|
boolean | false | If set to true then all the fields in the Form becomes mandatory |
odataModelName
|
string |
Specifies name of the OData Model. Note:- Use this property only when odata model has some name |
|
dataPath
|
string | Specifies the path in the binded model(Name of the model is stored in the property modelName) where Form data is stored | |
modelName
|
string | Name of the Binded Model where data of the Form is stored | |
mandatoryFields
|
string[] | [] |
Specifies name of the field which have to be marked as mandatory and those are not configurable by the user. Example of Usage <Form mandatoryFields=”F1,F2,F3″ /> |
propertyMap
|
object |
Using this property a user can handle:- 1. Custom Behavior of default types* Using the user can override the the default behavior of the control type and provide their own custom behavior. 2. Custom Control type factory** Using this user can even user their own control factory if the default one does not suites to their requirement. |
* Let say there is requirement to override enabled property of LifeCycleStatus field then it can be done in following manner
{
LifeCycleStatus:{
enabled: // Handling of enable property
}
}
** Let say there is requirement to use a checkbox type control instead of default Yes/No ComboBox for field then it can be done in following way
{
CreatedByBp: {
editFactory: // bind method name which returns checkBox control factroy which // will be rendered in edit mode,
displayFactory: // bind method name which returns checkBox control factroy // which will be rendered in display mode
}
}
Control Events
changeFieldValue – The event is fired when value in any form field control is changed
Visibility: public
Param | Type | Description |
oControlEvent | sap.ui.base.Event | |
getSource
|
sap.ui.base.EventProvider | |
getParameters | object | |
event
|
sap.ui.base.Event |
event of the control which is fired by particular form fied
|
field
|
string |
The name of an updated field
|
value
|
string |
New field value
|
entityType
|
string |
Name in the Entity type passed in the property entityTypeName
|
Now lets create a Sales Order App which is using this form control.
OData project created for this example
Soheaderdata Entity
Fields to be shown in the control is determined by the properties defined in the Entity(here Soheaderdata). Please note that properties shown in Figure 1 & 2 are same as defined in the Entity.
Value defined in the label section of the entity is used as Label of form field.
Code to fetch data
Download source code of the Form Control
Source Code Snippet for Sales Order App
SalesOrder.view.xml
<mvc:View controllerName="sa.sap.sample.reuse.form.controller.SalesOrder"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true"
xmlns="sap.m"
xmlns:c="sap.custom.controls.form">
<App id="idAppControl">
<pages>
<Page title="Custom Form Sample">
<content>
<c:Form id="idSalesOrderDetails"
entityTypeName="Soheaderdata"
title="Sales Order Details"
editable="true"
visible="true"
mode="{FormDataModel>/displayMode}"
modelName="FormDataModel"
dataPath="/data" />
</content>
<footer>
<Bar>
<contentRight>
<Button text="Save" visible="{=${FormDataModel>/displayMode} !== 'D'}" press="onSalesOrderSave" />
<Button text="Edit" visible="{=${FormDataModel>/displayMode} === 'D'}" press="onSalesOrderEdit" />
<Button text="Display" visible="{=${FormDataModel>/displayMode} !== 'D'}" press="onSalesOrderDisplay"/>
</contentRight>
</Bar>
</footer>
</Page>
</pages>
</App>
</mvc:View>
Control declaration
SalesOrder.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageToast"
], function (Controller, JSONModel, MessageToast) {
"use strict";
return Controller.extend("sa.sap.sample.reuse.form.controller.SalesOrder", {
onInit: function () {
var oJsonModel = new JSONModel({
data: {},
displayMode: "D"
});
this.getView().setModel(oJsonModel, "FormDataModel");
var oDataModel = this.getOwnerComponent().getModel();
var sPath = "/SoheaderdataSet('500000000')";
oDataModel.read(sPath, {
success: this._onSalesOrderReadSuccess.bind(this),
error: this._onSalesOrderReadFail.bind(this)
});
},
onSalesOrderEdit: function () {
this.getView().getModel("FormDataModel").setProperty("/displayMode", "E");
this.getView().byId("idSalesOrderDetails").refresh();
},
onSalesOrderDisplay: function () {
this.getView().getModel("FormDataModel").setProperty("/displayMode", "D");
this.getView().byId("idSalesOrderDetails").refresh();
},
onSalesOrderSave: function(){
this.byId("idSalesOrderDetails").validate();
},
_onSalesOrderReadSuccess: function (oResponse) {
this.getView().getModel("FormDataModel").setProperty("/data", oResponse);
},
_onSalesOrderReadFail: function () {
MessageToast.show("Error in Reading Sales Order Details");
}
});
});
Note:- In the above code snippet refresh method defined in the control needs to be called on change of the mode of the screen.
As you can see in the above code snippet, with such a minimal code we have rendered a full fledged form control which differentiate it with Smart Form Control.
Download source code of the example used in this blog from this git repo
Note:- This control renders TypeAhead, SingleSelect ValueHelp, MultiSelect ValueHelp controls(Custom) which I will be showcasing in the next blog .
Nice blog and the link is not working(maybe it's an internal SAP link). Could you please share the public repo?
Mahesh, Project Git Links has been updated with public repo. Please try to download the code now
Hi,
Your Git repository is on the internal SAP GitHub Enterprise so it cannot be accessed from the outside. If you want to share your code with the community, you should put it on https://github.com/
Cheers,
Pierre
Hi Pierre,
Project Git Links has been updated with github.com details. Please try to download the code now.