Fiori app using RAP Unmanaged scenario OData V4 with Popup
2024-1-6 01:3:55 Author: blogs.sap.com(查看原文) 阅读量:71 收藏

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%201.%20Initial%20screen%20design

Figure 1. Initial screen design

Figure%202.%20Popup%20window

Figure 2. Popup window for data input

Figure%203.%20Confirmation%20popup

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%204%2C%20Service%20preview

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%205%2C%20Controller%20Extension

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%207%2C%20Action%20button

Figure 8, Action button

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

Figure%207.%20New%20data%20source

Figure 9. New data source

Figure%208%2C%20New%20models

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%2011%2C%20Function%20Register_Dispatch%20in%20metadata

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/execute

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!


文章来源: https://blogs.sap.com/2024/01/05/fiori-app-using-rap-unmanaged-scenario-odata-v4-with-popup/
如有侵权请联系:admin#unsafe.sh