In this exercise, you extend the UI metadata of your OData service with the definition of a button. By choosing this button, the user can cancel selected flight travels. To achieve this, you first define an action in the behavior definition of your business object. Then you make the action available in the behavior projection and extend the UI metadata with a button to trigger the action. Finally, you implement the action.
Note
In this exercise, replace ## with your group number.
Solution
| Repository Object Type | Repository Object ID |
|---|
| CDS Behavior Definition (Model) | /LRN/437B_R_TRAVEL |
| ABAP Class | /LRN/BP_437B_R_TRAVEL |
| Behavior Definition (Projection) | /LRN/437B_C_TRAVEL |
| Metadata Extension | /LRN/437B_C_TRAVEL |
| ABAP Class (for messages) | /LRN/CM_437B_TRAVEL |
Task 1: Define an Instance Action
For the root node of your business object, define a new action (suggested name: cancel_travel) and use a quick fix to generate the method for the action implementation.
Steps
Open the behavior definition for your data model view Z##_R_TRAVEL and add the statement action, followed by the name of the new action (suggested name: cancel_travel) and ; as the statement delimiter.
Adjust the code as follows:
1234567
{
create;
update;
delete;
field ( readonly ) AgencyId, TravelId;
action cancel_travel;
Use the quick fix that is related to the syntax warning to add the implementation method to the handler class.
Note
To use this quick fix, the behavior definition must be saved and active.
Press Ctrl + F3 to activate the behavior definition.
Either choose the warning icon next to the action statement or right-click the name of the action and select Quick Fix.
Double-click Add method for action cancel_travel of entity z##_r_travel in local class lhc_travel.
Result
The quick fix adds a new method cancel_travel to the local class lhc_travel in your global class ZBP_##_R_TRAVEL.
Activate the behavior implementation class.
Press Ctrl + F3 to activate the development object.
Task 2: Link the Action to a Button
Add the action to the behavior projection to make it available in the OData UI service and comment out the exposure of the basic operations Create, Update, and Delete. In the metadata extension for your projection view Z##_C_TRAVEL, add the necessary annotations to define a button on the list report page that is linked to the action. Set a breakpoint in the action implementation method and test the action by choosing the button in the preview for the OData UI service.
Steps
Edit the behavior definition for the projection view (behavior projection) and comment out the statements that make the standard operations visible for the OData service.
Adjust the code as follows:
12345
{
// use create;
// use update;
// use delete;
}
Add the statement that makes the action visible for the OData service.
Adjust the code as follows:
1234567
{
// use create;
// use update;
// use delete;
use action cancel_travel;
}
Activate the behavior definition.
Press Ctrl + F3 to activate the development object.
Open the metadata extension for your projection view and scroll down to the annotations for the Status view element.
Locate the metadata extension in the Project Explorer and double-click it.
Scroll down to view the Status element.
In the annotation array for annotation @UI.lineItem, add an array element for the sub annotations type, dataAction, and label.
Note
An annotation array is a comma-separated list of array elements in square brackets. Now, the annotation array of annotation@UI.lineItem consists of just one array element { position: 10, importance: #HIGH }. To add another array element, add a comma and a pair of curly brackets.
Adjust the code as follows:
1234567
@UI: {
lineItem: [ { position: 10, importance: #HIGH },
{ }
],
identification: [ { position: 70, importance: #HIGH } ]
}
Status;
Inside the annotation array element, add three sub annotations with the following values:
| Sub annotation | Value |
|---|
| type | #FOR_ACTION |
| dataAction | 'cancel_travel' |
| label | 'Cancel the Travel' |
Note
If your action has a different name, you must adjust the value for annotation dataAction accordingly.
Adjust the code as follows:
12345678910
@UI: {
lineItem: [ { position: 10, importance: #HIGH },
{ type: #FOR_ACTION,
dataAction: 'cancel_travel',
label: 'Cancel the Travel'
}
],
identification: [ { position: 70, importance: #HIGH } ]
}
Status;
Activate the metadata extension.
Press Ctrl + F3 to activate the development object.
Close and reopen the OData service preview and make sure that the new button is visible.
If the browser window with the preview of the OData service is still open, close it.
Open your service binding, select the name of the projection view and choose Preview....
Return to the behavior pool and set a breakpoint in the action implementation method.
Hint
There is no need to place any code between METHOD and ENDMETHOD. You can set the breakpoint at either the METHOD or the ENDMETHOD statement.
Return to the editor with the source code of ABAP class ZBP_##_R_TRAVEL.
Find code row METHOD cancel_travel and double-click the column left from the row number.
Return to the browser window where the app is running, cancel a travel, and confirm that the breakpoint is hit.
Return to the OData service preview and execute the query by choosing Go.
Select a travel and choose Cancel the Travel from the header toolbar of the table.
Result
This opens the debugger.
In the debugger, press F8 to resume program execution.
Task 3: Implement the Action
In the handler method for the action, use EML to retrieve the data of the selected flight travel. Loop at the data to check the current status of the travel. If the travel is not already canceled (the value of STATUS does not equal C), update the status of this travel with value C. If the travel is already canceled, add the key of the travel to the FAILED structure and a suitable error message to the REPORTED structure.
Steps
In the implementation of method cancel_travel, implement a READ ENTITIES statement for the root entity of your business object Z##_R_Travel. Read all fields of all travels that the user selected before triggering the action and store the result in an inline-declared data object (suggested name: travels).
Note
The import parameter keys contains the keys of the selected travels. Use a CORRESPONDING expression to convert the content of keys to the type that is needed as input of the READ ENTITIES statement.
Adjust the code as follows:
123456789
METHOD cancel_travel.
READ ENTITIES OF Z##_R_Travel
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
ENDMETHOD.
Analyze the content of the Problems view.
Add IN LOCAL MODE to the EML statement.
Adjust the code as follows:
123456789
METHOD cancel_travel.
READ ENTITIES OF Z##_R_Travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
ENDMETHOD.
Implement a loop over the result, using either an inline declared work area or an inline declared field symbol.
Adjust the code as follows:
12345678910111213
METHOD cancel_travel.
READ ENTITIES OF Z##_R_Travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels INTO DATA(travel).
ENDLOOP.
ENDMETHOD.
Alternative with field symbol:
12345678910111213
METHOD cancel_travel.
READ ENTITIES OF Z##_R_Travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
ENDLOOP.
ENDMETHOD.
Inside the loop, evaluate the value of component status for the current row. If the value does not equal C, implement a MODIFY ENTITIES statement (with addition IN LOCAL MODE) to update the root entity of your business object Z##_R_Travel and set the value of status for the current travel to C .
Hint
We recommend that you use the component group %tky to identify the current travel instance. This way no extra adjustment is needed later, when you draft-enable your business object.
Adjust the code as follows:
123456789101112
LOOP AT travels INTO DATA(travel).
IF travel-status <> 'C'.
MODIFY ENTITIES OF Z##_R_Travel IN LOCAL MODE
ENTITY Travel
UPDATE
FIELDS ( status )
WITH VALUE #( ( %tky = travel-%tky
status = 'C' ) ).
ENDIF.
ENDLOOP.
If the current travel is already canceled, add its key values to the FAILED structure and a suitable error message to the REPORTED structure.
For the error message, create an instance of ABAP class /LRN/CM_S4D437 with a suitable constant for the TEXTID parameter of the constructor.
Adjust the code as follows:
12345678910111213141516171819202122
LOOP AT travels INTO DATA(travel).
IF travel-status <> 'C'.
MODIFY ENTITIES OF Z##_R_Travel IN LOCAL MODE
ENTITY Travel
UPDATE
FIELDS ( status )
WITH VALUE #( ( %tky = travel-%tky
status = 'C' ) ).
ELSE.
APPEND VALUE #( %tky = travel-%tky )
TO failed-travel.
APPEND VALUE #(
%tky = travel-%tky
%msg = NEW /LRN/CM_S4D437(
textid =
/LRN/CM_S4D437=>already_canceled ) )
TO reported-travel.
ENDIF.
ENDLOOP.
Activate the behavior implementation class and test your OData service.
Press Ctrl + F3 to activate the development object.
Return to the OData service preview, select a travel that is not yet canceled and choose Cancel the Travel.
Result
The content of the Travel Status column changes to C.
Select the same travel again and choose Cancel the Travel.
Result
You now see the error message.
Task 4: Optional: Define a Wrapper Class for Messages
Define your own ABAP class for messages (suggested name: ZCM_##_TRAVEL). Let it implement the interface IF_ABAP_BEHV_MESSAGE and inherit from super class CX_STATIC_CHECK. In the class, define a public structured constant for TEXTID that refers to message 130 of message class /LRN/S4D437 (suggested name for the constant: already_canceled). Make sure that the constructor offers an optional parameter to set the attribute if_abap_behv_message~m_severity.
Steps
Create a new ABAP class inheriting from class CX_STATIC_CHECK and implementing the interface IF_ABAP_BEHV_MESSAGE.
In the Project Explorer window, right-click your own package and select New→ABAP Class.
Enter the name of the new class, and a description and super class CX_STATIC_CHECK.
To add the interface, choose Add....
On the Add ABAP Interface window, enter the name of the interface and choose OK to close the dialog window.
Choose Next, and Finish to confirm the preselected transport request.
Check the implementation part of the class. If you find method bodies without implementation for the methods if_message~get_longtext and/or if_message~get_text, delete them.
In the public section of the class, define a structured constant (suggested name: already_canceled).
Note
Use the source code template textIdExceptionClass.
Navigate to the definition part of the class and place the cursor after the statement PUBLIC SECTION.
Enter text and press Ctrl + Space.
On the suggestion list, place the cursor on textIdExceptionClass and press Enter.
Use a quick fix to rename the constant.
After begin of, place the cursor on ZCM_##_TRAVEL and press Ctrl + 1 to invoke the quick fixes.
In the list of available quick fixes, double-click Rename zcm_##_travel.
While the old name is still highlighted, enter already_canceled as the new name.
Edit the component values of the structured constant. Make the constant refer to message 130 of message class /LRN/S4D437.
Adjust the code as follows:
123456789
CONSTANTS:
BEGIN OF already_canceled,
msgid TYPE symsgid VALUE '/LRN/S4D437',
msgno TYPE symsgno VALUE '130',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF already_canceled.
Edit the definition of the constructor of your exception class. Remove or comment parameter PREVIOUS and add an optional parameter (suggested name: severity) with the same type that is used for attribute if_abap_behv_message~m_severity.
Adjust the code as follows:
12345
METHODS constructor
IMPORTING
!textid LIKE if_t100_message=>t100key OPTIONAL
* !previous LIKE previous OPTIONAL .
severity LIKE if_abap_behv_message~m_severity OPTIONAL.
Edit the implementation of the constructor. Remove the optional parameter PREVIOUS from the call of the super constructor.
Adjust the code as follows:
1234
CALL METHOD super->constructor
* EXPORTING
* previous = previous
.
At the end of the method, add a statement to set the value of attribute if_abap_behv_message~m_severity to the value of the severity parameter, but only if the value of that parameter is not initial. Otherwise, set the attribute to if_abap_behv_message~severity-error.
Hint
You can use the logic for attribute if_t100_message~t100key as a role model.
Adjust the code as follows:
1234567891011
IF textid IS INITIAL.
if_t100_message~t100key = if_t100_message=>default_textid.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
IF severity IS INITIAL.
if_abap_behv_message~m_severity = if_abap_behv_message~severity-error.
ELSE.
if_abap_behv_message~m_severity = severity.
ENDIF.
Activate the wrapper class for messages.
Press Ctrl + F3 to activate the development object.
In your behavior implementation, replace /LRN/CM_S4D437 with your own class ZCM_##_TRAVEL and activate the behavior pool.
Return to your action implementation (method CANCEL_TRAVEL in local class LHC_TRAVEL of global class ZBP_##_R_TRAVEL).
Adjust the code as follows:
123456789
APPEND VALUE #(
%tky = travel-%tky
* %msg = NEW /LRN/CM_S4D437(
* textid =
* /LRN/CM_S4D437=>already_canceled )
%msg = NEW ZCM_##_TRAVEL(
textid =
ZCM_##_TRAVEL=>already_canceled ) )
TO reported-travel.
Press Ctrl + F3 to activate the development object.