Enhance the Business Object Behavior With Instance Action
- How to define actions
- How to implement actions
- How to preview and test enhanced travel app
Prerequisites
- You need to have access to an SAP BTP, ABAP environment, or SAP S/4HANA Cloud, ABAP environment or SAP S/4HANA (release 2021 or higher) system.
For example, you can create free trial user on SAP BTP, ABAP environment. - You have downloaded and installed the latest ABAP Development Tools (ADT) on the latest Eclipse© platform.
In the previous exercise, you’ve defined and implemented two validations.
In the present exercise, you will learn how to add different instance-bound actions (aka instance actions) to your application: instance non-factory actions w/o input parameters, instance non-factory action with input parameters, and instance-bound factory actions. For simplification reasons, _non-factory actions_
will be called _actions_
in the exercise description.
Please note: The purpose of the different exercises is to show you how to implement the different instance action types - and less on having the perfect business scenario.
Reminder: Do not forget to replace the suffix placeholder ### with your chosen or assigned group ID in the exercise steps below.
About: Actions
In the RAP context, an action is a non-standard operation that change the data of a BO instance. They are self-implemented operations.
Two main categories of actions can be implemented in RAP:
Non-factory actions: Defines a RAP action which offers non-standard behavior. The custom logic must be implemented in the RAP handler method
FOR MODIFY
. An action per default relates to a RAP BO entity instance and changes the state of the instance. An action is related to an instance by default. If the optional keywordstatic
is used, the action is defined as static action. Static actions are not bound to any instance of a RAP BO entity but relate to the complete entity.Factory actions: Factory actions are used to create RAP BO entity instances. Factory actions can be instance-bound (default) or static. Instance-bound factory actions can copy specific values of an instance. Static factory actions can be used to create instances with
prefilled
default values.Further reading: Actions | CDS BDL - non-standard operations | Implicit Response Parameters | ABAP EML -
response_param
- Step 1
You will now define, implement, and expose the action
deductDiscount
, a non-factory instance-bound action returning itself. The action offers the possibility to deduct a certain percentage from the booking fee (BookingFee
) of a travel instance.The discount percentage can either be fix (30% in the present exercise) in the action implementation or be freely specified by the end-user or the calling APIs by offering an action with input parameters.
You will get familiar with both action implementations, i.e. action without and action with input parameters in the present exercise.
Hint: First, define the non-factory, instance action
deductDiscount
in the behavior definition of the Travel entity. This action has no input parameter.-
Go to your behavior definition
ZRAP100_R_TRAVELTP_###
define the instance action without input parameter. For that, insert the following code snippet after the defined validations as shown on the screenshot below.ABAPCopyaction deductDiscount result [1] $self;
Short explanation:
- The name of the instance action is specified after the keyword action-
The keyword result defines the output parameter of the action.
-
Its cardinality is specified between the square brackets (
[cardinality]
). It is a mandatory addition. -
$self
specifies that the type of the result parameter is the same type as the entity for which the action or function is defined - i.e. the Travel entity type in the present exercise. The return type of the result parameter can be an entity or a structure.
-
-
Note: The output parameter result can be used to store the result of an action or function in an internal table. However, it does not affect the result of an action or function that is committed to the database.
Further reading: Action Definition
-
-
Save
and activatethe changes. -
Now, declare the required method in behavior implementation class with the ADT Quick Fix. Set the cursor on the
deductDiscount
, and pressCtrl+1
to open the Quick Assist view.Select the entry Add method for action
deductDiscount
of entityzrap100_r_traveltp_###
… in the view to add the required method to the local handler class.The result should look like this:
-
Save
the changes. -
Set the cursor on the method name,
deductDiscount
, press F3 to navigate to the declaration part of the local handler class of the behavior poolZRAP100_BP_TRAVELTP_###
. -
In the declaration part set the cursor on the method name
deductDiscount
, press F2, and examine the full method interface.Short explanation:
The addition FOR MODIFY after the method name, together with the addition FOR ACTION after the importing parameter, indicates that this method provides the implementation of an action. Method signature for the non-factory instance action
deductDiscount
:- IMPORTING parameter keys - a table containing the keys of the instances on which the action must be executed
- Implicit CHANGING parameters (aka implicit response parameters):
- result - used to store the result of the performed action.
- mapped - table providing the consumer with ID mapping information.
- failed - table with information for identifying the data set where an error occurred.
- reported - table with data for instance-specific messages.
Please note:
An action is implemented in a FOR MODIFY method with the addition FOR ACTION. The signature of an action method always depends on the type of action: factory or non-factory, and instance or static. The rules for implementing an action operation in a RAP business object are explained in the respective Implementation Contract.Hint: Further reading: Action Implementation| Implementation Contract: Action
Go ahead with the implementation of the action method.
Log in to complete tutorial -
- Step 2
Now implement the required action methods in the behavior pool of the Travel entity.
Implement the action method
deductDiscount
.The main steps of the implemented business logic:
- Implement the custom logic to determine the new values: The discounted booking fee must be calculated for each instance.
- The discount percentage is fix at the current stage: 30%.
- Modify the relevant fields of the instances with the EML statement
MODIFY
: Here only the fieldBookingFee
must be updated. - Read the data from the buffer with the EML statement READ to fill the action result parameter result.
- The implicit response parameters are filled where necessary:
- failed - with information for identifying the data set where an error occurred.
- mapped - table providing the consumer with ID mapping information.
- reported - with data for instance-specific messages in case of failure.
Replace the current method implementation with the code snippet provided below and replace all occurrences of the placeholder ### with your group ID.
You can use the ABAP Pretty Printer (Ctrl+F1) to format your source code.
ABAPCopy************************************************************************** * Instance-bound non-factory action: * Deduct the specified discount from the booking fee (BookingFee) ************************************************************************** METHOD deductDiscount. DATA travels_for_update TYPE TABLE FOR UPDATE ZRAP100_R_TravelTP_###. DATA(keys_with_valid_discount) = keys. " read relevant travel instance data (only booking fee) READ ENTITIES OF ZRAP100_R_TravelTP_### IN LOCAL MODE ENTITY Travel FIELDS ( BookingFee ) WITH CORRESPONDING #( keys_with_valid_discount ) RESULT DATA(travels). LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>). DATA(reduced_fee) = <travel>-BookingFee * ( 1 - 3 / 10 ) . APPEND VALUE #( %tky = <travel>-%tky BookingFee = reduced_fee ) TO travels_for_update. ENDLOOP. " update data with reduced fee MODIFY ENTITIES OF ZRAP100_R_TravelTP_### IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( BookingFee ) WITH travels_for_update. " read changed data for action result READ ENTITIES OF ZRAP100_R_TravelTP_### IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( travels ) RESULT DATA(travels_with_discount). " set action result result = VALUE #( FOR travel IN travels_with_discount ( %tky = travel-%tky %param = travel ) ). ENDMETHOD.
The result should look like this:
Short explanation:
- The provided implementation is mass-enabled. This is recommended.
- The EML statement MODIFY ENTITIES … UPDATE FIELDS is used to update specific fields of the instances.
- The internal tables are filled inline using the constructor operator VALUE which made the need for explicit declaration obsolete.
- The EML statement READ ENTITIES … ALL FIELDS WITH CORRESPONDING is used to read all fields of the updated instances from the buffer to fill the input paramter result.
Save
and activatethe changes.
Log in to complete tutorial - Step 3
In contrary to determinations and validations which are automatically called by the RAP runtime at the specified trigger time, actions must be explicitly exposed on the BO projection layer and called by a consumer, e.g. on the UI or directly via EML statements.
Now, you will expose the actions in the BO behavior projection and enrich the UI semantics in the CDS metadata extension to add appropriate button to the travel App.
Expose the actions in the BO behavior projection.
Go to your behavior projection
ZRAP100_C_TRAVELTP_###
and insert the following code snippet as shown on the screenshot below. The keyworduse action
indicates that a behavior of the base BO is used on the projection layer.ABAPCopyuse action deductDiscount;
Your source code should look like this:
Save
and activatethe changes.The actions are yet ready to be consumed on the UI, but they also need to be explicitly placed on the UI.
Enhance UI semantics of the UI service to make the action
deductDiscount
only visible on the object page with the labelDeduct Discount
. with the labels Accept Travel and Reject Travel specified.For that, go to your CDS metadata extension
ZRAP100_C_TRAVELTP_###
and replace the existing all@UI
annotations placed before the elementOverallStatus
with the code snippet provided below as shown on the screenshot below. The semantic of the annotation@UI.identification
will be enhanced for the purpose.Please note: Some lines in the provided code snippet are commented out using
//
. DO NOT remove them. You will uncomment these lines in the following exercise steps.ABAPCopy@UI: { lineItem: [ { position: 100, importance: #HIGH } //,{ type: #FOR_ACTION, dataAction: 'copyTravel', label: 'Copy Travel' } //,{ type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept Travel' } //,{ type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject Travel' } ], identification: [ { position: 100 } ,{ type: #FOR_ACTION, dataAction: 'deductDiscount', label: 'Deduct Discount' } //,{ type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept Travel' } //,{ type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject Travel' } ], textArrangement: #TEXT_ONLY }
Your source code should look like this:
Save
and activatethe changes.Preview and test the enhanced SAP Fiori elements App.
For example, select a travel entry, navigate to the object page, and click on the new action button
Deduct Discount
. Check the action result: the booking fee must have been reduced by the percentage specified in the action method.If you wish, you can also define the button on the list report page in the CDS metadata extension, activate the changes, and re-test the app.
Log in to complete tutorial - Step 4
You will enhance the action
deductDiscount
with an input parameter (discount_percent
) to allow end-users or calling APIs to freely specify the percentage to be deducted from the booking fee (BookingFee
) of a travel instance at runtime.Action input parameters are modelled with abstract CDS entities (abstract entities). In the present example, we will use the abstract entity
/dmo/a_travel_discount
which defines a structure containing only one field,discount_percent
, for the purpose. It is located in the package/DMO/FLIGHT_DRAFT
of the flight reference scenario.An abstract CDS entity defines the type properties of a CDS entity. Consequently, it provides metadata on element level or parameter level using CDS annotations and does not have a corresponding implementation nor an underlying persistency.
Let’s have a look at the abstract entity defined in the data definition
/DMO/A_Travel_Discount
. You can use the shortcutCtrl+Shift+A
to open the data definition.Short explanation:
- An abstract entity is defined by the statement define abstract entity followed by the CDS entity name.
- The current abstract entity defines a structure with only one field or element; The element name (
discount_percent
) and the element type (abap.int1
) are specified. - It is not here the case, but if required, it is possible to…
- specify a label using the element annotation
@EndUserText.label
. - specify a value help using the element annotation
@Consumption.valueHelpDefinition
. - hide an element using the element
annotation @UI.hidden
.
Go to the behavior definition
ZRAP100_R_TRAVELTP_###
and add the addition parameter/dmo/a_travel_discount
to the action definition.The source code should now look as follows:
ABAPCopyaction deductDiscount parameter /dmo/a_travel_discount result [1] $self;
The abstract entity
/dmo/a_travel_discount
is used after the keyword parameter to specify the parameter structure. The present action will only have one parameter (discount_percent
) as defined in the abstract entity.Save
and activatethe changes.
Log in to complete tutorial - Step 5
You will now adjust the implementation of the business logic for the instance non-factory action
deductDiscount
with the input parameterdeduct_discount
in the behavior poolZRAP100_BP_TRAVELTP_###
.Only entered values greater than 0 and lower than 100 will be allowed.
Depending on the type of action, the importing parameter keys has different components. The parameter structure
%param
for parameter input is imported by action with parameters. The parameter structure is used to access the passed values of the input parameters:deduct_discount
in the present scenario - i.e.%param-deduct_discount
.Go to your behavior pool
ZRAP100_BP_TRAVELTP_###
and adjust the action methoddeductDiscount
.The required adjustments of the current business logic:
For all passed Travel instances: Read and check the validity of the specified discount values from the parameter structure
%param
, and remove invalid ones:0 < %param-deduct_discount <= 100
Implement the custom logic to determine the new values: The discounted booking must be calculated for each instance according to the respective%param-deduct_discount value - instead of the fix discount (30%)
.Replace the current method implementation with the code snippet provided below. Do not forget to replace all occurrences of the placeholder
###
with your group ID.You can use the ABAP Pretty Printer (Ctrl+F1) to format your source code.
ABAPCopy************************************************************************** * Instance-bound non-factory action with parameter `deductDiscount`: * Deduct the specified discount from the booking fee (BookingFee) ************************************************************************** METHOD deductDiscount. DATA travels_for_update TYPE TABLE FOR UPDATE ZRAP100_R_TravelTP_###. DATA(keys_with_valid_discount) = keys. " check and handle invalid discount values LOOP AT keys_with_valid_discount ASSIGNING FIELD-SYMBOL(<key_with_valid_discount>) WHERE %param-discount_percent IS INITIAL OR %param-discount_percent > 100 OR %param-discount_percent <= 0. " report invalid discount value appropriately APPEND VALUE #( %tky = <key_with_valid_discount>-%tky ) TO failed-travel. APPEND VALUE #( %tky = <key_with_valid_discount>-%tky %msg = NEW /dmo/cm_flight_messages( textid = /dmo/cm_flight_messages=>discount_invalid severity = if_abap_behv_message=>severity-error ) %element-TotalPrice = if_abap_behv=>mk-on %op-%action-deductDiscount = if_abap_behv=>mk-on ) TO reported-travel. " remove invalid discount value DELETE keys_with_valid_discount. ENDLOOP. " check and go ahead with valid discount values CHECK keys_with_valid_discount IS NOT INITIAL. " read relevant travel instance data (only booking fee) READ ENTITIES OF ZRAP100_R_TravelTP_### IN LOCAL MODE ENTITY Travel FIELDS ( BookingFee ) WITH CORRESPONDING #( keys_with_valid_discount ) RESULT DATA(travels). LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>). DATA percentage TYPE decfloat16. DATA(discount_percent) = keys_with_valid_discount[ key draft %tky = <travel>-%tky ]-%param-discount_percent. percentage = discount_percent / 100 . DATA(reduced_fee) = <travel>-BookingFee * ( 1 - percentage ) . APPEND VALUE #( %tky = <travel>-%tky BookingFee = reduced_fee ) TO travels_for_update. ENDLOOP. " update data with reduced fee MODIFY ENTITIES OF ZRAP100_R_TravelTP_### IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( BookingFee ) WITH travels_for_update. " read changed data for action result READ ENTITIES OF ZRAP100_R_TravelTP_### IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( travels ) RESULT DATA(travels_with_discount). " set action result result = VALUE #( FOR travel IN travels_with_discount ( %tky = travel-%tky %param = travel ) ). ENDMETHOD.
The result should look like this:
Save
and activatethe changes.
Log in to complete tutorial - Step 6
You can now test the new behavior of the action button
Deduct Discount
on the enhanced travel app.- Preview and test the enhanced Fiori elements travel app. Go to the object page of a given entry, click on the new action button Deduct Discount. A dialog will now be prompted for you to enter a valid discount value, i.e.
> 0 and <= 100
, and confirm.Check the result.
You can also repeat the test with an invalid value.
Log in to complete tutorial - Preview and test the enhanced Fiori elements travel app. Go to the object page of a given entry, click on the new action button Deduct Discount. A dialog will now be prompted for you to enter a valid discount value, i.e.