
RAP - Custom Entity with Action
How can you cleanly implement an action in a Custom Entity to update the UI und utilize EML? Let's take a closer look at the different steps.
Table of contents
The question for today's article came from the community of Abdullah ATAN. We'll discuss the details of the implementation and how the example works exactly.
Introduction
The question was also asked in the SAP Community, asking how an action can be implemented in a custom entity. Regular EML was also used, although there are a few things to consider to ensure it works properly. That's why we've created a small example that you can simply copy and try out.
Implementation
In this chapter, we'll look at the implementation and various details that are important in this case. You can find a general description of how to set up a custom entity in this article.
Custom Entity
In the first step, let's start with a custom entity that will map our structure and UI. In addition to defining the type, we also need to specify the query class that will provide the data.
@EndUserText.label: 'Custom Entity with Action'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_ACTION_QRY'
define root custom entity ZBS_R_CusActEntityTP
{
key my_key : abap.char(15);
description : abap.char(60);
icon : abap.sstring(250);
}
Query
In the next step, we implement the query that provides us with hard-coded data. With this, we want to simulate an API in the first step, perhaps providing us with the data via HTTP. We then need to prepare the amount of data for return using filters and sorting.
CLASS zcl_bs_demo_custom_action_qry DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
TYPES result_type TYPE STANDARD TABLE OF ZBS_R_CusActEntityTP WITH EMPTY KEY.
METHODS get_dummy_data
RETURNING VALUE(result) TYPE result_type.
ENDCLASS.
CLASS zcl_bs_demo_custom_action_qry IMPLEMENTATION.
METHOD if_rap_query_provider~select.
DATA result TYPE STANDARD TABLE OF ZBS_R_CusActEntityTP.
DATA count TYPE int8.
result = get_dummy_data( ).
NEW zcl_bs_demo_adjust_data( )->adjust_via_request( EXPORTING io_request = io_request
CHANGING ct_data = result
cd_count = count ).
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( count ).
ENDIF.
IF io_request->is_data_requested( ).
io_response->set_data( result ).
ENDIF.
ENDMETHOD.
METHOD get_dummy_data.
RETURN VALUE #( ( my_key = 'TEST' description = 'First One' )
( my_key = 'NOTHING' description = 'Second One' )
( my_key = 'MORE' description = 'Third One' ) ).
ENDMETHOD.
ENDCLASS.
Behavior
In the next step, we define a behavior on the custom entity to later implement in RAP. We want to use the update to update the status in a database, but only internally, within the RAP object. We define a normal action that also returns a result (itself). This is how we want to update the data record. We use the static action to reset all statuses.
unmanaged implementation in class zbp_bs_demo_custom_action unique;
strict ( 2 );
define behavior for ZBS_R_CusActEntityTP alias CustomAction
lock master
authorization master ( instance )
{
internal update;
field ( readonly ) my_key;
action myCustomAction result [1] $self;
static action resetAllIcons;
}
UI
In order for the list and fields to be displayed, we still need UI annotations. We must define these directly in the custom entity. We also define the two actions in the UI. For the ICON field, we also define that it is displayed as an image.
@EndUserText.label: 'Custom Entity with Action'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_ACTION_QRY'
define root custom entity ZBS_R_CusActEntityTP
{
@UI.facet : [{
position : 10,
label : 'General',
type : #IDENTIFICATION_REFERENCE
}]
@UI.lineItem: [{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'myCustomAction', label: 'Custom Action' },
{ type: #FOR_ACTION, dataAction: 'resetAllIcons', label: 'Reset' } ]
@UI.selectionField: [{ position: 10 }]
@UI.identification: [{ position: 10 }]
@EndUserText.label: 'Identifier'
key my_key : abap.char(15);
@UI.lineItem: [{ position: 20, iconUrl: 'icon' }]
@UI.identification: [{ position: 20 }]
@EndUserText.label: 'Description'
description : abap.char(60);
@UI.lineItem: [{ position: 30 }]
@EndUserText.label: 'Icon'
@Semantics.imageUrl: true
icon : abap.sstring(250);
}
Implementation
Now let's move on to the actual implementation of the action in the behavior implementation. We would now implement the custom action as follows.
METHOD myCustomAction.
READ ENTITIES OF ZBS_R_CusActEntityTP IN LOCAL MODE
ENTITY CustomAction
FROM CORRESPONDING #( keys )
RESULT DATA(found_data).
LOOP AT found_data INTO DATA(found).
MODIFY ENTITIES OF ZBS_R_CusActEntityTP IN LOCAL MODE
ENTITY CustomAction
UPDATE FROM VALUE #( ( my_key = found-my_key
description = found-description
%control-description = if_abap_behv=>mk-on ) ).
INSERT VALUE #( my_key = found-my_key
%param = found ) INTO TABLE result.
ENDLOOP.
ENDMETHOD.
We read the selected data, then perform an update, and return the data so that the UI can update the record. However, for the EML to work, we must implement both methods. In the READ method, we receive the data and then filter the key to read the correct records. The implementation is very simple here.
METHOD read.
DATA(all) = NEW zcl_bs_demo_custom_action_qry( )->get_dummy_data( ).
LOOP AT keys INTO DATA(key).
INSERT CORRESPONDING #( all[ my_key = key-my_key ] ) INTO TABLE result.
ENDLOOP.
ENDMETHOD.
In the Update method, we actually only fill the buffer, since the actual action should only happen when saving.
METHOD update.
INSERT LINES OF entities INTO TABLE lcl_data_buffer=>updates.
ENDMETHOD.
In the SAVE method, we then perform the actual saving. In this case, we fill a table that contains the current status for the key. This allows us to save additional data to our "API" data and read it in the query class.
METHOD save.
LOOP AT lcl_data_buffer=>updates INTO DATA(update).
SELECT SINGLE FROM zbs_dmo_cstat
FIELDS status
WHERE my_key = @update-my_key
INTO @DATA(found_status).
IF sy-subrc <> 0.
INSERT zbs_dmo_cstat FROM @( VALUE #( my_key = update-my_key
status = 1 ) ).
ELSE.
UPDATE zbs_dmo_cstat FROM @( VALUE #( my_key = update-my_key
status = found_status + 1 ) ).
ENDIF.
ENDLOOP.
ENDMETHOD.
Enrichment
In the final step, we enrich the result data in the query class. To do this, we read the status table and derive the corresponding icon that we want to display.
LOOP AT result REFERENCE INTO DATA(extend).
SELECT SINGLE FROM zbs_dmo_cstat
FIELDS status
WHERE my_key = @extend->my_key
INTO @DATA(found_status).
IF sy-subrc <> 0.
extend->icon = icons-none.
CONTINUE.
ENDIF.
CASE found_status.
WHEN 1.
extend->icon = icons-one.
WHEN 2.
extend->icon = icons-two.
WHEN 3.
extend->icon = icons-three.
WHEN OTHERS.
extend->icon = icons-four.
ENDCASE.
ENDLOOP.
Result
Here is an example of how the application works and behaves. We change the status of the individual lines and finally reset the status using another action.
Complete example
You can find the complete example from this article in our GitHub repository in the packageZBS_DEMO_RAP_CUSTOM_ACTION. In the commit you can take another look at the various changes that have been added to the repo.
Conclusion
When implementing an action in a custom entity, you can rely on the RAP framework, but you also have to handle it yourself. This is also clear in an unmanaged scenario. Since custom entities currently do not support a draft, an OData v2 service is sufficient in many cases if you also want to use the standard actions.