Defining Actions and Messages

Objectives

After completing this lesson, you will be able to:
  • Define and implement an action.
  • Expose an action to an OData service.
  • Define a button in SAP Fiori elements.
  • Access application data in behavior implementations.
  • Define and return messages.

Action Definition

Definition of an Action, it's types and required parameters in RAP

In RAP, an action is a non-standard modifying operation that is part of the business logic.

You define an action for an entity in the behavior definition using the action keyword. The logic of the action is implemented in a dedicated method of the handler class.

By default, actions have an instance reference and are accessible from outside the business object (public instance actions).

The following special types of actions exist:

Internal Action

With the Internal  option, you define an internal action. An internal action can only be accessed from the business logic inside the business object implementation.

Static Action

The Static option allows you to define a static action that is not bound to any instance but relates to the complete entity.

Factory Action

With factory actions, you can create entity instances by executing an action. Factory actions can be instance-bound or static. Instance-bound factory actions can be useful if you want to copy specific values of an instance. Static factory actions can be used to create instances with default values.

Note

Instance factory actions are not supported for the managed implementation type.

When defining an action, defining parameters is optional. Parameters can be input or output parameters.

Input Parameters

Actions can pass abstract CDS entities or other structures as input parameters. They are defined by the keyword parameter.

Output Parameters

The output parameter for an action is defined with the keyword result. The result parameter for actions is optional. However, if a result parameter is declared in the action definition, it must be filled in the implementation. If the result parameter is defined with addition selective, the action consumer can decide whether the result shall be returned completely or only parts of it. This can help improve performance. A result cardinality determines the multiplicity of the output.

An example of action definition

The behavior definition in the example defines two public actions: a static action and an instance action. The static action has no parameters at all. The instance action has a result parameter with cardinality [0..1] and the symbolic type $self. The type $self indicates that the result has the same type as the entity. The action can return up to one data sets of type Z00_R_Travel.

Action Implementation

Steps to create the Action Handler method

After adding the action to the behavior definition, you have to add the corresponding implementation method to the behavior implementation class. There is a quick fix for updating the behavior pool.

Actions in Projections and Interfaces

An example off action projection

The syntax element use action is used to add actions from the underlying behavior definition to a BO projection or a BO interface. Only those actions can be reused that are defined in the underlying behavior definition.

Some additions exist to adjust or extend the action definition. The as addition defines an alias for the action in the projection or interface layer. The external addition defines a name for external usage, for example in the OData service. This external name can be longer than the alias name in ABAP. If an external name exists for an action it is mandatory to use this external name when addressing the action from the OData client, for example the SAP Fiori application.

Note

You can use both additions (as and external for the same action.

Actions in the UI Metadata

Action buttons in UI metadata

On the UI, actions are often triggered by choosing an action button. This action button can be configured in the backend in the metadata of the related CDS view entity. Depending on where you want to use an action button on the UI (list report, or object page), use the  @UI.lineItem annotation or the @UI.identification annotation to display an action button.

Note

The UI annotations for actions are element annotations. Technically, they are always linked to a view element. However, it has no impact on the layout of the UI. The action buttons are always displayed at the same position.

Hint

If the behavior projection defines an external alias for the action, you have to specify this alias after the dataAction keyword. If the projection defines an ABAP alias (keyword as) but no external alias, you have to use this ABAP alias.

EML in Behavior Implementations

EML inside a behavior implementation

If the behavior implementation of a RAP BO must access its own data, it uses EML statements.

The EML syntax is basically the same whether you access as a BO from the inside or from the outside. However, the IN LOCAL MODE addition can be added for any data access from inside the behavior implementation.

The IN LOCAL MODE addition provides a privileged data access by suppressing certain features of the RAP BO framework, for example authorization checks and feature control. It improves performance and allows the behavior implementation to change the data that are forbidden for outside consumers of the BO. As an example, the behavior logic can update fields that are labeled as read-only.

Note

The editor displays warnings if an EML statement inside a behavior implementation is missing the IN LOCAL MODE addition. Likewise, it displays a warning, if the IN LOCAL MODE addition is used in code where it is not obvious whether it is only executed as part of the behavior implementation or not.

Caution

Runtime errors occur, if the warning is ignored and the code tries to execute EML statements IN LOCAL MODE outside the behavior implementation of the respective business object.

Messages

Messages in EML and RAP

Messages offer an important way to guide and validate consumer and user actions, and help to avoid and resolve problems. Messages are important to communicate problems to a consumer or user. Well-designed messages help to recognize, diagnose, and resolve issues.

The message concept of EML and RAP is based on the proven concept of class-based messages. At runtime, a message is represented by an instance of an ABAP class that implements global interface IF_MESSAGE.

Note

In ABAP, class-based exceptions are also message objects, because exception classes inherit from CX_ROOT, which implements the IF_MESSAGEIF_MESSAGE.

For messages in EML and RAP, it is not sufficient to implement interface IF_MESSAGE. The relevant classes have to implement the specialized interface IF_ABAP_BEHV_MESSAGE, which comprises the IF_MESSAGE interface.

Note

To distinguish the classes for messages from exception classes and usual ABAP classes, their name should start with <namespace>CM_ instead of <namespace>CX_ or <namespace>CL_.

ABAP Class for Class-Based Messages

ABAP class for class-based messages

Classes which implement the IF_ABAP_BEHV_MESSAGE interface have the following important instance attributes:

IF_ABAP_BEHV_MESSAGE~M_SEVERITY

Typed with IF_ABAP_BEHV_MESSAGE~T_SEVERITY. Used to specify the message type (error, warning, information, success). Allowed values are the components of IF_ABAP_BEHV_MESSAGE~SEVERITY.

IF_T100_MESSAGE=>t100key

Typed with IF_T100_MESSAGE=>SCX_T100KEY (structure type). Used to identify the message class (ID), the message number, a type and, if applicable, values for the placeholders &1, &2, &3, and &4 in the message text.

During instantiation of the class, both attributes must be filled in the constructor logic, either hard coded or with suitable import parameters. It is a recommended practice to define one or more structured constants in the public section, to define possible value combinations for IF_T100_MESSAGE=>t100key. These constants can then be used to supply a constructor parameter textid when instantiating the message class.

Hint

The ABAP editor offers the textIDExceptionClass a source code template to easily define such a structured constant.

Reporting Static Messages

Reporting static messages

Certain behavior implementation methods, like, for example, action implementation methods and validation implementation methods, have an implicit response parameter reported. This parameter indicates that you can issue messages as part of the implementation. To do so, you create a RAP message object, that is, a class that implements interface IF_ABAP_BEHV_MESSAGE, and store a reference to this instance in parameter reported.

Static messages, which are not related to an entity instance, are stored in table-like component %other.

Reporting Instance Messages

Reporting instance messages

Instance messages, that is, messages related to an instance of a RAP BO entity, are stored in one of the other components of the reported parameter.

If, for example, the message relates to an instance of entity Z00_R_Travel, the message is stored in internal table reported-Z00_R_Travel.

Note

If the behavior definition contains an alias name for the entity, the reported parameter uses this alias name as name for the component.

A reference to the message object is stored in component %msg. In addition, it is important to populate all relevant key fields to identify the related entity instance.

Hint

It is recommended to access the complete key using named include %tky.

Define and Implement an Action

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 TypeRepository 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

  1. 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.

    1. Adjust the code as follows:

      Code Snippet
      1234567
      { create; update; delete; field ( readonly ) AgencyId, TravelId; action cancel_travel;
  2. 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.
    1. Press Ctrl + F3 to activate the behavior definition.

    2. Either choose the warning icon next to the action statement or right-click the name of the action and select Quick Fix.

    3. 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.
  3. Activate the behavior implementation class.

    1. 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

  1. 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.

    1. Adjust the code as follows:

      Code Snippet
      12345
      { // use create; // use update; // use delete; }
  2. Add the statement that makes the action visible for the OData service.

    1. Adjust the code as follows:

      Code Snippet
      1234567
      { // use create; // use update; // use delete; use action cancel_travel; }
  3. Activate the behavior definition.

    1. Press Ctrl + F3 to activate the development object.

  4. Open the metadata extension for your projection view and scroll down to the annotations for the Status view element.

    1. Locate the metadata extension in the Project Explorer and double-click it.

    2. Scroll down to view the Status element.

  5. 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.
    1. Adjust the code as follows:

      Code Snippet
      1234567
      @UI: { lineItem: [ { position: 10, importance: #HIGH }, { } ], identification: [ { position: 70, importance: #HIGH } ] } Status;
  6. Inside the annotation array element, add three sub annotations with the following values:

    Sub annotationValue
    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.
    1. Adjust the code as follows:

      Code Snippet
      12345678910
      @UI: { lineItem: [ { position: 10, importance: #HIGH }, { type: #FOR_ACTION, dataAction: 'cancel_travel', label: 'Cancel the Travel' } ], identification: [ { position: 70, importance: #HIGH } ] } Status;
  7. Activate the metadata extension.

    1. Press Ctrl + F3 to activate the development object.

  8. Close and reopen the OData service preview and make sure that the new button is visible.

    1. If the browser window with the preview of the OData service is still open, close it.

    2. Open your service binding, select the name of the projection view and choose Preview....

  9. 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.
    1. Return to the editor with the source code of ABAP class ZBP_##_R_TRAVEL.

    2. Find code row METHOD cancel_travel and double-click the column left from the row number.

  10. Return to the browser window where the app is running, cancel a travel, and confirm that the breakpoint is hit.

    1. Return to the OData service preview and execute the query by choosing Go.

    2. Select a travel and choose Cancel the Travel from the header toolbar of the table.

      Result

      This opens the debugger.
    3. 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

  1. 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.
    1. Adjust the code as follows:

      ABAP
      123456789
      METHOD cancel_travel. READ ENTITIES OF Z##_R_Travel ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels). ENDMETHOD.
  2. Analyze the content of the Problems view.

  3. Add IN LOCAL MODE to the EML statement.

    1. Adjust the code as follows:

      ABAP
      123456789
      METHOD cancel_travel. READ ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels). ENDMETHOD.
  4. Implement a loop over the result, using either an inline declared work area or an inline declared field symbol.

    1. Adjust the code as follows:

      ABAP
      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.
    2. Alternative with field symbol:

      ABAP
      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.
  5. 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.
    1. Adjust the code as follows:

      ABAP
      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.
  6. 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.
    1. Adjust the code as follows:

      ABAP
      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.
  7. Activate the behavior implementation class and test your OData service.

    1. Press Ctrl + F3 to activate the development object.

    2. 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.
    3. 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

  1. Create a new ABAP class inheriting from class CX_STATIC_CHECK and implementing the interface IF_ABAP_BEHV_MESSAGE.

    1. In the Project Explorer window, right-click your own package and select NewABAP Class.

    2. Enter the name of the new class, and a description and super class CX_STATIC_CHECK.

    3. To add the interface, choose Add....

    4. On the Add ABAP Interface window, enter the name of the interface and choose OK to close the dialog window.

    5. Choose Next, and Finish to confirm the preselected transport request.

  2. 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.

  3. In the public section of the class, define a structured constant (suggested name: already_canceled).

    Note

    Use the source code template textIdExceptionClass.
    1. Navigate to the definition part of the class and place the cursor after the statement PUBLIC SECTION.

    2. Enter text and press Ctrl + Space.

    3. On the suggestion list, place the cursor on textIdExceptionClass and press Enter.

  4. Use a quick fix to rename the constant.

    1. After begin of, place the cursor on ZCM_##_TRAVEL and press Ctrl + 1 to invoke the quick fixes.

    2. In the list of available quick fixes, double-click Rename zcm_##_travel.

    3. While the old name is still highlighted, enter already_canceled as the new name.

  5. Edit the component values of the structured constant. Make the constant refer to message 130 of message class /LRN/S4D437.

    1. Adjust the code as follows:

      Code Snippet
      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.
  6. 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.

    1. Adjust the code as follows:

      Code Snippet
      12345
      METHODS constructor IMPORTING !textid LIKE if_t100_message=>t100key OPTIONAL * !previous LIKE previous OPTIONAL . severity LIKE if_abap_behv_message~m_severity OPTIONAL.
  7. Edit the implementation of the constructor. Remove the optional parameter PREVIOUS from the call of the super constructor.

    1. Adjust the code as follows:

      Code Snippet
      1234
      CALL METHOD super->constructor * EXPORTING * previous = previous .
  8. 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.
    1. Adjust the code as follows:

      Code Snippet
      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.
  9. Activate the wrapper class for messages.

    1. Press Ctrl + F3 to activate the development object.

  10. In your behavior implementation, replace /LRN/CM_S4D437 with your own class ZCM_##_TRAVEL and activate the behavior pool.

    1. Return to your action implementation (method CANCEL_TRAVEL in local class LHC_TRAVEL of global class ZBP_##_R_TRAVEL).

    2. Adjust the code as follows:

      Code Snippet
      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.
    3. Press Ctrl + F3 to activate the development object.