Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
listat
Participant
In this blog post you will learn how to implement Fiori List Report/Object Page application using RAP Unmanaged scenario with OData V4.

Blog concentrates on the following features:

  • Popup implementation,

  • Function with parameters,

  • Generated Fiori app extensions.


System version: SAP S4HANA ON PREMISE 2021.

Business requirements


Create Fiori app where user could overview and filter list of records, display single record in detail.

On initial screen with list of records user shall be able to select single record, press custom button on Toolbar and get Popup window for data input. Few values displayed in Popup window should be from selected record (fields closed for input), the rest - open for input, with value validation.

After user confirms input in Popup window, new record(s) should be created in backend, email sent and list with records refreshed.

Design


For implementation it was decided to use RAP with Unmanaged scenario with binding type OData V4, later generating Fiori elements app based on List Report Page template.

Few features were implemented in SAP BAS with a help of extensions in generated Fiori app, as it was not possible to achieve the same fully on backend side.


Figure 1. Initial screen design



Figure 2. Popup window for data input



Figure 3. Confirmation popup



Implementation


Following objects were created in SAP S/4 HANA system:

  • DB table ZDISPATCH_DB,

  • CDS view (root view entity) Z_DISPATCH,

  • CDS view (projection) Z_DISPATCH_PROJ,

  • CDS view (abstract entity) Z_DISPATCH_POPUP,

  • Metadata extension Z_DISPATCH_ME,

  • Behavior definition Z_DISPATCH,

  • Behavior definition Z_DISPATCH_PROJ,

  • Class Z_CL_DISPATCH_BEHAVIOR,

  • Service Definition Z_DISPATCH_PROJ_SRV,

  • Service Binding Z_DISPATCH_PROJ_V4UI.


Additionally for Direction and Quantity fields CDS view were created to provide Value Help and calculated quantity.

DB table ZDISPATCH_DB from which data will be selected for representation in Fiori app has following structure:





































































































































































Field Name Key Data Element Data Type Length Decimal Places Check Table
MANDT X MANDT CLNT 3 0 T000
EXIDV X EXIDV CHAR 20 0
DIRECTION X Z_DIRECTION CHAR 1 0
COUNTER X Z_COUNTER CHAR 2 0
DISPATCH_ID X Z_DISPATCH_ID CHAR 10 0
TRANSFER_ID X Z_TRANSFER_ID CHAR 10 0
WERKS WERKS_D CHAR 4 0 T001W
LGORT LGORT_D CHAR 4 0 T001L
TRANS_DATE Z_TRANS_DATE DATS 8 0
TRANS_TIME Z_TRANS_TIME TIMS 6 0
MATNR MATNR CHAR 40 0 MARA
LIFNR LIFNR CHAR 10 0 LFA1
BNAME XUBNAME CHAR 12 0 USR02
VEHICLE_ID Z_VEHICLE_ID CHAR 20 0
QUANTITY Z_QUANTITY QUAN 3 0
MEINS MEINS UNIT 3 0 T006
LASTCHANGEDAT ABP_LASTCHANGE_TSTMPL DEC 21 7

CDS view (root view entity) Z_DISPATCH
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Dispatch, Root Entity'
define root view entity Z_DISPATCH
as select from zdispatch_db as Record_DB
association [0..1] to Z_DIRECTION as _Direction on $projection.Direction = _Direction.Code
association [0..1] to I_Plant as _Plant on $projection.Plant = _Plant.Plant
association [0..1] to I_StorageLocation as _StorageLocation on $projection.Plant = _StorageLocation.Plant
and $projection.StorageLocation = _StorageLocation.StorageLocation
association [0..*] to I_MaterialText as _MaterialText on $projection.Material = _MaterialText.Material
association [0..1] to I_Supplier as _Supplier on $projection.Supplier = _Supplier.Supplier
association [0..1] to I_UserContactCard as _User on $projection.UserID = _User.ContactCardID
association [0..1] to I_UnitOfMeasure as _UnitOfMeasure on $projection.UnitOfMeasure = _UnitOfMeasure.UnitOfMeasure
association [0..1] to I_UserContactCard as _DispatchUser on _DispatchUser.ContactCardID = $session.user
association [0..1] to Z_DISPATCH_QUANTITY as _VendorQuantity on $projection.Plant = _VendorQuantity.Plant
and $projection.StorageLocation = _VendorQuantity.StorageLocation
and $projection.Supplier = _VendorQuantity.Supplier
{
key Record_DB.exidv as HandlingUnit,
key Record_DB.direction as Direction,
key Record_DB.counter as Counter,
key Record_DB.dispatch_id as DispatchID,
key Record_DB.transfer_id as TransferID,
Record_DB.werks as Plant,
Record_DB.lgort as StorageLocation,
@Semantics.dateTime: true
Record_DB.trans_date as TransactionDate,
@Semantics.dateTime: true
Record_DB.trans_time as TransactionTime,
Record_DB.matnr as Material,
Record_DB.lifnr as Supplier,
Record_DB.bname as UserID,
Record_DB.vehicle_id as VehicleID,
@Semantics.quantity.unitOfMeasure: 'UnitOfMeasure'
Record_DB.quantity as Quantity,
Record_DB.meins as UnitOfMeasure,

Record_DB.lastchangedat as Lastchangedat,

// Associations
_Direction,
_Plant,
_StorageLocation,
_MaterialText,
_Supplier,
_User,
_UnitOfMeasure,
_DispatchUser,
_VendorQuantity
}

CDS view (projection) Z_DISPATCH_PROJ
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Dispatch, Projection View'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
@Search.searchable: true
@ObjectModel.usageType:{
serviceQuality: #X,
sizeCategory: #S,
dataClass: #MIXED
}

@UI:{ headerInfo: {
typeName: 'Record',
typeNamePlural: 'Records',
title: {
type: #STANDARD,
value: 'HandlingUnit'
}
} }
define root view entity Z_DISPATCH_PROJ
provider contract transactional_query
as projection on Z_DISPATCH
{
key HandlingUnit,
@ObjectModel.text.element: ['DirectionName']
key Direction,
key Counter,
key DispatchID,
key TransferID,
_Direction._Text[1: Language = $session.system_language ].Description as DirectionName,

//Filter field
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['PlantName']
@Consumption.valueHelpDefinition: [{
entity: {
name: 'I_Plant',
element: 'Plant'
}}]
Plant,
_Plant.PlantName,

//Filter field
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['StorageLocationName']
@Consumption.valueHelpDefinition: [{
entity: {
name: 'I_StorageLocation',
element: 'StorageLocation'
},
additionalBinding : [{ localElement : 'Plant',
element : 'Plant'
}]
}]
StorageLocation,
_StorageLocation.StorageLocationName,

@Semantics.dateTime: true
TransactionDate,
@Semantics.dateTime: true
TransactionTime,

//Filter field
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['MaterialName']
Material,
_MaterialText[1: Language = $session.system_language ].MaterialName as MaterialName,

//Filter field
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['SupplierName']
@Consumption.valueHelpDefinition: [{
entity: {
name: 'I_Supplier',
element: 'Supplier'
}}]
Supplier,
_Supplier.OrganizationBPName1 as SupplierName,
_Supplier._StandardAddress._DefaultEmailAddress.EmailAddress as SupplierEmail,

@ObjectModel.text.element: ['FullName']
UserID,
_User.FullName,
VehicleID,

@Semantics.quantity.unitOfMeasure: 'UnitOfMeasure'
Quantity,
@Semantics.unitOfMeasure: true
@ObjectModel.text.element: ['UnitOfMeasureName']
UnitOfMeasure,
_UnitOfMeasure._Text[1: Language = $session.system_language ].UnitOfMeasureName,

@ObjectModel.text.element: ['DispatchUserFullName']
_DispatchUser.ContactCardID as DispatchUser,
_DispatchUser.FullName as DispatchUserFullName,

_DispatchUser.EmailAddress as DispatchUserEmail,

@Semantics.quantity.unitOfMeasure: 'VendorTotalUoM'
_VendorQuantity.TotalQuantity as VendorTotalQuantity,
@ObjectModel.text.element: ['VendorTotalUoMName']
_VendorQuantity.UnitOfMeasure as VendorTotalUoM,
_VendorQuantity._UnitOfMeasure._Text[1: Language = $session.system_language ].UnitOfMeasureName as VendorTotalUoMName,

Lastchangedat,

// Associations
_Direction,
_Plant,
_StorageLocation,
_MaterialText,
_Supplier,
_User,
_UnitOfMeasure,
_DispatchUser,
_VendorQuantity
}

CDS view (abstract entity) Z_DISPATCH_POPUP
@EndUserText.label: 'Popup'
@Metadata.allowExtensions: true
define abstract entity Z_DISPATCH_POPUP
{
@EndUserText.label: 'Vendor'
Imv_Supplier : lifnr;
@EndUserText.label: 'Material'
Imv_Material : matnr;
@EndUserText.label: 'Quantity'
@Semantics.quantity.unitOfMeasure: 'Imv_UnitOfMeasure'
Imv_Quantity : z_quantity;
@EndUserText.label: 'Unit of Measure'
Imv_UnitOfMeasure : meins;
@EndUserText.label: 'Vehicle ID'
Imv_VehicleID : z_vehicle_id;

}

In Popup default values can be set and Value Help defined in following way:
...
@Consumption.defaultValue: '0900'
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_Plant', element: 'Plant' }}]
@EndUserText.label: 'Supplying Plant (Mandatory)'
Imv_Supl_Plant : werks_d;
...
@Consumption.valueHelpDefinition: [{
entity : {
name : 'I_StorageLocation',
element: 'StorageLocation'
},
additionalBinding : [{ localElement : 'Imv_Supl_Plant',
element : 'Plant'
}]
}]
@EndUserText.label: 'Supplying Sloc (Mandatory)'
Imv_Supl_Sloc : lgort_d;
...

Metadata extension Z_DISPATCH_ME with button definition for Action 'Register_Dispatch':
@Metadata.layer: #CUSTOMER
@UI:{ headerInfo: {
typeName: 'Record',
typeNamePlural: 'Records',
title: { type: #STANDARD, value: 'Supplier' },
description: { type: #STANDARD, value: 'SupplierName' }
} }

@UI.presentationVariant: [{
sortOrder: [
{ by: 'TransactionDate', direction: #ASC },
{ by: 'Supplier', direction: #ASC } ,
{ by: 'Material', direction: #ASC } ,
{ by: 'Direction', direction: #ASC } ],

visualizations: [{ type: #AS_LINEITEM }],
requestAtLeast: [ 'TransactionDate', 'Supplier', 'Material', 'Quantity', 'UnitOfMeasure', 'Direction' ]
}]
annotate entity Z_DISPATCH_PROJ with
{
@UI.facet: [
{label: 'Record Details',
id: 'RecordInfo',
type: #COLLECTION,
position: 10 },

{id: 'General',
purpose: #STANDARD,
position: 10,
type: #IDENTIFICATION_REFERENCE,
parentId: 'RecordInfo' },

{label: 'Quantity',
id: 'QuantityInfo',
type: #COLLECTION,
position: 20 },

{id: 'SupplierData',
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
parentId: 'RecordInfo',
label: 'Supplier Info',
position: 10,
targetQualifier: 'SupplierGroup' },

{id: 'TransactionData',
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
parentId: 'RecordInfo',
label: 'Transaction Info',
position: 20,
targetQualifier: 'TransactionGroup' },

{id: 'Quantity',
purpose: #STANDARD,
targetQualifier: 'Quantity',
position: 10,
label: 'Transaction Quantity',
type: #FIELDGROUP_REFERENCE,
parentId: 'QuantityInfo' },

{id: 'AllowedQuantity',
purpose: #STANDARD,
targetQualifier: 'AllowedQuantity',
position: 20,
label: 'Allowed Quantity',
type: #FIELDGROUP_REFERENCE,
parentId: 'QuantityInfo' }
]

@UI:{ lineItem: [ {position: 10, importance: #LOW },
{ type: #FOR_ACTION,
dataAction: 'Register_Dispatch' ,
label: 'Func Itm' }
],
identification: [{position: 10 }]
}
HandlingUnit;
@UI:{ lineItem: [{position: 20,importance: #HIGH }],
identification: [{position: 20 }]}
Direction;
@UI:{ lineItem: [{position: 30,importance: #LOW }],
identification: [{position: 30 }] }
Counter;
@UI:{ lineItem: [{position: 50,importance: #LOW }],
identification: [{position: 50 }],
selectionField: [{position: 10 }] }
@Consumption.filter:{ mandatory:true,
selectionType : #SINGLE,
multipleSelections : false,
defaultValue : '0900' }
Plant;
@UI:{ lineItem: [{position: 60,importance: #LOW }],
identification: [{position: 60 }],
selectionField: [{position: 20 }] }
@Consumption.filter:{ mandatory:true,
selectionType : #SINGLE,
multipleSelections : false,
defaultValue : '0990' }
StorageLocation;
@UI:{ lineItem: [{position: 70,importance: #LOW }],
fieldGroup: [{ type: #STANDARD, qualifier: 'TransactionGroup', position: 10, importance: #HIGH }]}
TransactionDate;
@UI:{ lineItem: [{position: 80,importance: #LOW }],
fieldGroup: [{ type: #STANDARD, qualifier: 'TransactionGroup', position: 20, importance: #HIGH }] }
TransactionTime;
@UI:{ lineItem: [{position: 90,importance: #HIGH }],
selectionField: [{position: 40 }],
fieldGroup: [{ type: #STANDARD, qualifier: 'Quantity', position: 10, importance: #HIGH }] }
@Consumption.filter:{ mandatory:false }
Material;

@UI:{ lineItem: [{position: 100,importance: #HIGH }],
selectionField: [{position: 30 }],
fieldGroup: [{ type: #STANDARD, qualifier: 'SupplierGroup', position: 10, importance: #HIGH }] }
@Consumption.filter:{ mandatory:true,
selectionType : #SINGLE,
multipleSelections : false }
Supplier;
@UI:{ lineItem: [{position: 110,importance: #LOW }]}
SupplierName;

@UI:{ lineItem: [{position: 120,importance: #LOW }],
fieldGroup: [{ type: #STANDARD, qualifier: 'SupplierGroup', position: 20, importance: #HIGH }]}
SupplierEmail;
@UI:{ lineItem: [{position: 130,importance: #LOW }],
fieldGroup: [{ type: #STANDARD, qualifier: 'TransactionGroup', position: 10, importance: #HIGH }] }
UserID;
@UI:{ lineItem: [{position: 140,importance: #LOW }],
identification: [{position: 130 }] }
DispatchID;
@UI:{ lineItem: [{position: 150,importance: #LOW }],
identification: [{position: 140 }] }
TransferID;
@UI:{ lineItem: [{position: 160,importance: #LOW }],
identification: [{position: 150 }] }
VehicleID;
@UI:{ lineItem: [{position: 170,importance: #HIGH, label: 'Quantity' }],
fieldGroup: [{ type: #STANDARD, qualifier: 'Quantity', position :30, importance: #HIGH }] }
@EndUserText.label: 'Quantity'
Quantity;
@UI:{ lineItem: [{position: 180,importance: #HIGH, label: 'UoM' }],
fieldGroup: [{ type: #STANDARD, qualifier: 'Quantity', position :40, importance: #HIGH }] }
@EndUserText.label: 'UoM'
UnitOfMeasure;

@UI.hidden: true
DispatchUser;

@UI:{ lineItem: [{position: 190,importance: #LOW }],
identification: [{position: 160 }] }
DispatchUserEmail;

@UI:{ lineItem: [{position: 200,importance: #HIGH, label: 'Total' }],
fieldGroup: [{ type: #STANDARD, qualifier: 'AllowedQuantity', position :50, importance: #HIGH }] }
@EndUserText.label: 'Total'
VendorTotalQuantity;
}


Behavior definition Z_DISPATCH, here function 'Register_Dispatch' has as input CDS view for abstract Entity.




Selection of Row with check box in List Report is available if Action is implemented.
unmanaged implementation in class Z_CL_DISPATCH_BEHAVIOR unique;
strict;

define behavior for Z_DISPATCH alias DispatchRecord
lock master
authorization master ( global )
{
delete;
function Register_Dispatch parameter Z_DISPATCH_POPUP result [1] $self;
}

Behavior definition Z_DISPATCH_PROJ
projection;
strict;

define behavior for Z_DISPATCH_PROJ
{
use delete;
use function Register_Dispatch;
}

Class Z_CL_DISPATCH_BEHAVIOR, extracts from local implementation provided below. Detailed logic for table update and email sending not provided as it is not main topic of this blog.
Method Definition:
METHODS register_dispatch FOR READ
IMPORTING keys FOR FUNCTION dispatchrecord~register_dispatch RESULT result.

Method Implementation:
METHOD register_dispatch.
*--- read record from CDS
READ ENTITY z_dispatch_proj
FIELDS ( plant )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_dispatch_rec).

LOOP AT keys ASSIGNING FIELD-SYMBOL(<lfs_keys>).
TRY.

READ TABLE lt_dispatch_rec REFERENCE INTO DATA(lr_dispatch_rec)
WITH KEY %key = <lfs_keys>-%key.
IF sy-subrc <> 0.
CONTINUE.
ENDIF.

* posting of record(s) in DB
* sending email

CATCH zcx_msg INTO lr_exception.
APPEND VALUE #( handlingunit = <lfs_keys>-handlingunit
direction = <lfs_keys>-direction
counter = <lfs_keys>-counter
dispatchid = <lfs_keys>-dispatchid
transferid = <lfs_keys>-transferid
) TO failed-dispatchrecord.

APPEND VALUE #( %msg = new_message( id = lr_exception->if_t100_message~t100key-msgid
number = lr_exception->if_t100_message~t100key-msgno
v1 = lr_exception->v_msgv1
v2 = lr_exception->v_msgv2
v3 = lr_exception->v_msgv3
v4 = lr_exception->v_msgv4
severity = if_abap_behv_message=>severity-error )
%key-handlingunit = <lfs_keys>-handlingunit
%key-direction = <lfs_keys>-direction
%key-counter = <lfs_keys>-counter
%key-dispatchid = <lfs_keys>-dispatchid
%key-transferid = <lfs_keys>-transferid
handlingunit = <lfs_keys>-handlingunit
direction = <lfs_keys>-direction
counter = <lfs_keys>-counter
dispatchid = <lfs_keys>-dispatchid
transferid = <lfs_keys>-transferid
) TO reported-dispatchrecord.
ENDTRY.

ENDLOOP.

In implementation of Function 'Register Dispatch' should be no COMMIT nor ROLLBACK commands. If you need COMMIT you can use it, for example, inside FM called in separate task:


CALL FUNCTION 'Z_EMAIL_IN_TASK' STARTING NEW TASK imv_task_name


DESTINATION 'NONE' ...





Service Definition Z_DISPATCH_PROJ_SRV. Here all associations used in CDS view (root view entity) Z_DISPATCH are exposed for usage.
@EndUserText.label: 'Dispatch, Service Definition'
define service Z_DISPATCH_PROJ_SRV {
expose Z_DISPATCH_PROJ as DispatchRecord;
expose Z_DIRECTION as Direction;
expose I_Plant as Plant;
expose I_StorageLocation as StorageLocation;
expose I_MaterialText as MaterialText;
expose I_Supplier as Supplier;
expose I_UserContactCard as User;
expose I_UnitOfMeasure as UnitOfMeasure;
expose Z_DISPATCH_QUANTITY as VendorQuantity;
}

Service Binding Z_DISPATCH_PROJ_V4UI.

After Service Binding was created for service Z_DISPATCH_PROJ_SRV, OData V4 - UI binding type, Service Group is available in transaction /IWBEP/V4_ADMIN.

In transaction /IWFND/V4_ADMIN service group Z_DISPATCH_PROJ_V4UI was published.

Now preview link is available in Service Binding.


Figure 4. Service preview


Button on Toolbar is present and Popup for input is displayed. Values from selected Item not passed.


Figure 5, Service preview


In SAP BAS Fiori Elements app was generated using List Report Page template and service Z_DISPATCH_PROJ_V4UI.


Figure 6, Fiori application file paths


In Fiori project several changed made to file manifest.json.

Section for Controller extension added with Controller Name as Application ID followed by path to controller file in webapp folder:


Figure 7, Controller Extension


In targets find "DispatchRecordList" and in Settings add LineItem "Control Configuration":

  • "press" is pointing at file "Action_Dispatch" with function onOpenPopUp with event processing logic,

  • "enabled" is pointing at file "Dispatch_Enabled" with function onPopUpEnabled with logic to control state of button enabled/disabled.



Figure 8, Action button


For custom local JSON model and OData model entries done in madifest.json file "dataSources" and "models" parts accordingly:


Figure 9. New data source



Figure 10, New models


LocalJSON is used as variable to memorize selected Item key fields. OData model "CustomModel" is used for OData V4 call to backend.

File Component.js has code to set new JSON and OData models. [Application ID] was placed instead of application id value from manifest.json file.
sap.ui.define(
[
"sap/fe/core/AppComponent",
],
function (Component) {
"use strict";

return Component.extend("[Application ID].Component", {
metadata: {
manifest: "json"
},
init: function () {
// call the base component's init function
Component.prototype.init.apply(this, arguments);
// create the views based on the url/hash
this.getRouter().initialize();

// setting the model to the core
// so that it’s available in the whole application
sap.ui.getCore().setModel(this.getModel("CustomModel"), "ODataModel");
sap.ui.getCore().setModel(this.getModel("LocalJSON"), "LocalJSONModel");
}
});
}
);

Fragment was added in file InputPopUp.fragment.xml in folder view. Here 5 fields added with 2 push buttons at the end. Look and feel similar to popup generated with CDS view. Only Quantity and Vehicle ID fields are enabled for input.

Pay attention that [Application ID] was placed instead of application id value from manifest.json file.
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
>
<Dialog
id="PopUpDialog"
>
<VBox
width="100%"
direction="Column"
id="vbox0"
alignContent="Center"
alignItems="Center"
>
<items>
<Text
id="SupplierLabel"
xmlns="sap.m"
width="300px"
text="{i18n>SupplierLabelText}"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Input
id="SupplierInput"
width="300px"
valueLiveUpdate="true"
enabled="false"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Text
id="MaterialLabel"
xmlns="sap.m"
width="300px"
text="{i18n>MaterialLabelText}"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Input
id="MaterialInput"
width="300px"
valueLiveUpdate="true"
enabled="false"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Text
id="QuantityLabel"
xmlns="sap.m"
width="300px"
text="{i18n>QuantityLabelText}"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Input
id="QuantityInput"
width="300px"
valueLiveUpdate="true"
enabled="true"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Text
id="UnitOfMeasureLabel"
xmlns="sap.m"
width="300px"
text="{i18n>UnitOfMeasureLabelText}"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Input
id="UnitOfMeasureInput"
width="300px"
valueLiveUpdate="true"
enabled="false"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Text
id="VehicleIDLabel"
xmlns="sap.m"
width="300px"
text="{i18n>VehicleIDLabelText}"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>

<Input
id="VehicleIDInput"
width="300px"
valueLiveUpdate="true"
enabled="true"
class="sapUiSmallMarginBegin sapUiSmallMarginEnd"
/>
</items>
</VBox>
<beginButton>
<Button
id="ExecuteBtn"
text="{i18n>OKBtnText}"
press=".extension.[Application ID].ext.controller.LRExtend.onPopUpOK"
/>
</beginButton>
<endButton>
<Button
icon="sap-icon://undo"
id="PopUpCloseBtn"
text="{i18n>CloseBtnText}"
press=".extension.[Application ID].ext.controller.LRExtend.onPopUpClose"
/>
</endButton>
</Dialog>
</core:FragmentDefinition>

File Dispatch_Enabled.js contains function onPopUpEnabled for custom button "Dispatch" status change depending on quantity of selected Line Items.
sap.ui.define(
[],
function () {
"use strict";
return {
onPopUpEnabled: function (oContext, aSelectedContexts) {
// oContext : is the binding context of the current entity
// aSelectedContexts : contains an array of binding contexts corresponding to
// selected items in case of table action (or)

if (aSelectedContexts && aSelectedContexts.length === 1) {
return true;
}
return false;
},
};
}
);

File Action_Dispatch.js contains function onOpenPopUp which will read values of selected Item and open Popup window. Local JSON model is used to memorize selected item key fields.

Again [Application ID] was placed instead of application id value from manifest.json file.
sap.ui.define(
[ ],
function (oFunction) {
"use strict";
return {
onOpenPopUp: function (oContext, aSelectedContexts, oLContext = this) {
// oContext : is the binding context of the current entity
// aSelectedContexts : contains an array of binding contexts corresponding to
// selected items in case of table action (or)

// create dialog
if (aSelectedContexts && aSelectedContexts.length === 1) {
// read values from selected context line
for (let index = 0; index < aSelectedContexts.length; index++) {
var oSelContext = aSelectedContexts[index];

// show dialog
this.oInputDialog ??= this.loadFragment({
name: "[Application ID].view.InputPopUp"
});
if (this.oInputDialog) {
this.oInputDialog.then(function (oDialog) {

//set key of selected record to local model
var sHandlingUnit = oSelContext.getProperty("HandlingUnit");
var sDirection = oSelContext.getProperty("Direction");
var sCounter = oSelContext.getProperty("Counter");
var sDispatchID = oSelContext.getProperty("DispatchID");
var sTransferID = oSelContext.getProperty("TransferID");

var oLocalJSON = sap.ui.getCore().getModel("LocalJSONModel");
if (oLocalJSON) {
oLocalJSON.setData(
{
"HandlingUnit": sHandlingUnit,
"Direction": sDirection,
"Counter": sCounter,
"DispatchID": sDispatchID,
"TransferID": sTransferID
}
);
sap.ui.getCore().setModel(oLocalJSON);
}

// pass values of selected record to Pop-up
var sSupplier = oSelContext.getProperty("Supplier");
var sMaterial = oSelContext.getProperty("Material");
var sUnitOfMeasure = oSelContext.getProperty("UnitOfMeasure");

var oSupplierInput = sap.ui.getCore().byId("SupplierInput");
if (oSupplierInput) {
oSupplierInput.setValue(sSupplier);
oSupplierInput.fireChange();
}
var oMaterialInput = sap.ui.getCore().byId("MaterialInput");
if (oMaterialInput) {
oMaterialInput.setValue(sMaterial);
oMaterialInput.fireChange();
}
var oUnitOfMeasure = sap.ui.getCore().byId("UnitOfMeasureInput");
if (oUnitOfMeasure) {
oUnitOfMeasure.setValue(sUnitOfMeasure);
oUnitOfMeasure.fireChange();
}

oDialog.open();
});
}
}; //end for
return true;
}
return false;
}, //onOpenPopUp

};
}
);


Finally file LRExtend.controller.js contains controller extension described in manifest.json.

  • Function onInit makes button defined in RAP not visible. Button ID you can find using browser developer tools: F12->Elements section. In example below instead of real id label [RAP button ID] provided.

  • Function onPopUpClose was marked in fragment file for press event of PopUpCloseBtn button. In implementation input fields are cleared and list is refreshed (same as "Go" button press) with a help of commant: this.base.getExtensionAPI().refresh();

  • Function onPopUpOpen was marked in fragment file for press event of ExecuteBtn button. In implementation fields values are read from popup window, label read from i18n* files, confirmation dialog prepared (second popup). If user chooses Yes, call to backed is performed. For that context binding is done, parameters set.


sap.ui.define(
[
"sap/ui/core/mvc/ControllerExtension",
"sap/m/Dialog",
"sap/m/DialogType",
"sap/m/Text",
"sap/m/Button",
"sap/m/ButtonType",
"sap/m/MessageToast",
"sap/m/MessageBox"
],

function (ControllerExtension, Dialog, DialogType, Text, Button, ButtonType, MessageToast, MessageBox) {
"use strict";

return ControllerExtension.extend("[Application ID].ext.controller.LRExtend", {
override: {
onInit: function (oEvent) {
//disable button from RAP
var oFuncButton = sap.ui.getCore().byId("[RAP button ID]");
if (oFuncButton) {
oFuncButton.setVisible(false);
}
},
onAfterRendering: function (oEvent) { }
},
onPopUpClose: function (oContext) {
// close Pop-up dialog window
var oInputDialog = sap.ui.getCore().byId("PopUpDialog");
if (oInputDialog) {
// clean input fields
var oQuantityInput = sap.ui.getCore().byId("QuantityInput");
if (oQuantityInput) {
var iQuantityInput = oQuantityInput.getValue();
iQuantityInput = "";
oQuantityInput.setValue(iQuantityInput);
oQuantityInput.fireChange();
}
var oVehicleIDInput = sap.ui.getCore().byId("VehicleIDInput");
if (oVehicleIDInput) {
var sVehicleIDInput = oVehicleIDInput.getValue();
sVehicleIDInput = "";
oVehicleIDInput.setValue(sVehicleIDInput);
oVehicleIDInput.fireChange();
}
oInputDialog.close();
//Refresh in any case
this.base.getExtensionAPI().refresh();
}
},
onPopUpOK: function (oContext) {
// get values from Dialog input fields
var oSupplierInput = sap.ui.getCore().byId("SupplierInput");
var oMaterialInput = sap.ui.getCore().byId("MaterialInput");
var oQuantityInput = sap.ui.getCore().byId("QuantityInput");
var oUnitOfMeasureInput = sap.ui.getCore().byId("UnitOfMeasureInput");
var oVehicleIDInput = sap.ui.getCore().byId("VehicleIDInput");

if (oSupplierInput) {
var sSupplierInput = oSupplierInput.getValue();
}
if (oMaterialInput) {
var sMaterialInput = oMaterialInput.getValue();
}
if (oQuantityInput) {
var iQuantityInput = oQuantityInput.getValue();
}
if (oUnitOfMeasureInput) {
var sUnitOfMeasureInput = oUnitOfMeasureInput.getValue();
}
if (oVehicleIDInput) {
var sVehicleIDInput = oVehicleIDInput.getValue();
}

var oi18nModel = sap.ui.getCore().getModel("i18n");
var oi18nBundle = oi18nModel.getResourceBundle();
var sMsgTitleConfirm = oi18nBundle.getText("MsgTitleConfirm");
var sMsgTextConfirm = oi18nBundle.getText("MsgTextConfirm", [sMaterialInput, iQuantityInput, sUnitOfMeasureInput]);
var sBtnConfirmYes = oi18nBundle.getText("BtnConfirmYes");
var sBtnConfirmNo = oi18nBundle.getText("BtnConfirmNo");
var sMsgDispatchSuccess = oi18nBundle.getText("MsgDispatchSuccess");

// Confirmation dialog
var oConfirmationDialog = new Dialog({
title: sMsgTitleConfirm,
type: 'Message',
content: new Text({ text: sMsgTextConfirm }),
beginButton: new Button({
text: sBtnConfirmYes, press: function () {
oConfirmationDialog.close();
// User confirmed => sent request to backend
var oGlobalBusyDialog = new sap.m.BusyDialog();

//Models assigned in Component.js init
var oDataModel = sap.ui.getCore().getModel("ODataModel");

if (oDataModel) {
oGlobalBusyDialog.open();
}

// get selected entry key
var oLocalJSONModel = sap.ui.getCore().getModel("LocalJSONModel");
if (oLocalJSONModel) {
var sHandlingUnit = oLocalJSONModel.getProperty("/HandlingUnit");
var sDirection = oLocalJSONModel.getProperty("/Direction");
var sCounter = oLocalJSONModel.getProperty("/Counter");
var sDispatchID = oLocalJSONModel.getProperty("/DispatchID");
var sTransferID = oLocalJSONModel.getProperty("/TransferID");
}

//"/DispatchRecord(HandlingUnit='9',Direction='T',Counter='',DispatchID='',TransferID='')/[namespace with service].Register_Dispatch(...)"
var oDispatchFunction = oDataModel.bindContext("/DispatchRecord(HandlingUnit='" + sHandlingUnit + "',Direction='" + sDirection + "',Counter='" + sCounter + "',DispatchID='" + sDispatchID + "',TransferID='" + sTransferID + "')/[namespace with service].Register_Dispatch(...)");
oDispatchFunction.setParameter("Imv_Supplier", sSupplierInput);
oDispatchFunction.setParameter("Imv_Material", sMaterialInput);
oDispatchFunction.setParameter("Imv_Quantity", iQuantityInput);
oDispatchFunction.setParameter("Imv_UnitOfMeasure", sUnitOfMeasureInput);
oDispatchFunction.setParameter("Imv_VehicleID", sVehicleIDInput);
oDispatchFunction.execute().then(function () {
oGlobalBusyDialog.close();
oDataModel.refresh();
MessageToast.show(sMsgDispatchSuccess);
}.bind(this), function (oError) {
oGlobalBusyDialog.close();
MessageBox.error(oError.message);
}
);
}
}),
endButton: new Button({
text: sBtnConfirmNo, press: function () {
oConfirmationDialog.close();
}
}),
afterClose: function () { oConfirmationDialog.destroy(); }
});
oConfirmationDialog.open();
}
})
}
);

[namespace with service] is the same value which is present in metadata file, for example "_it" parameter Type (before last dot).

In Gateway service metadata file accessible in Browser function


Figure 11, Function Register_Dispatch in metadata


OData V4 call to backend will have following details:

HTTP method: GET

URI:../sap/opu/odata4/sap/z_dispatch_proj_v4ui/srvd/sap/z_dispatch_proj_srv/0001/DispatchRecord(HandlingUnit='9',Direction='T',Counter='',DispatchID='',TransferID='')/com.sap.gateway.srvd.z_dispatch_proj_srv.v0001.Register_Dispatch(Imv_Supplier='',Imv_Material='',Imv_Quantity=4,Imv_UnitOfMeasure='',Imv_VehicleID='')

DispatchRecord(parameters list) - is selected Line Item with it's key fields values inside round brackets,

Register_Dispatch(importing parameters) - function name prefixed with namespace with it's importing parameters inside round brackets.

For RAP Operations implementation you can refer to documentation:

https://help.sap.com/docs/abap-cloud/abap-rap/operations

OData V4 calls from SAP UI5:

https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.model.odata.v4.ODataContextBinding%23methods/execu...

As you can see RAP and Fiori elements app generation gives most of the functionality, whereas additional extensions in BAS provide required flexibility: more validations, popups and so on.

Good luck all!
Labels in this area