RAP - Custom Entity Value Help (Deep Dive)
With the Custom Entity you have the most freedom in RAP when developing ABAP Cloud applications, but what about potential errors?
Table of contents
In this article we want to implement a value help with a custom entity. We will go through the different pitfalls step by step and what you should pay attention to during development.
Introduction
We have already described the custom entity in an older article and explained how it works. The custom entity provides a structure and implements the logic via an ABAP class. The entity itself does not contain any data, but must always be called by the SADL framework first. This is why custom entities are mainly used in RAP applications.
In this example, we want to extend our simple RAP application to include a search function for cities and will go through everything step by step again.
Installation
In the first step, we begin by creating the Core Data Service and the ABAP class.
Class
Since the class in the annotation is also checked when the custom entity is created, we implement the empty class first. Use the context menu in the "Project Explorer" to select we can start the wizard.
To do this, we create the class and use the interface IF_RAP_QUERY_PROVIDER, which expects the custom entity.
The class now looks like this, but the ABAP Cleaner was also run once here:
CLASS zcl_bs_demo_city_query DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
ENDCLASS.
CLASS zcl_bs_demo_city_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.
ENDMETHOD.
ENDCLASS.
Core Data Service
In the next step, we create the Core Data Service using the context menu. We use the CDS template "defineCustomEntityWithParameters" as a template:
If you chose the wrong template when creating the object, you do not have to delete the object, but can choose a different CDS template. We define two fields, the city we are looking for and a short key that we can also use to search. Since we are using built-in data types, we also set the label and tooltip for the user so that the texts appear in the UI.
@EndUserText.label: 'City Value Help'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CITY_QUERY'
define custom entity ZBS_I_RAPCityVH
{
@EndUserText.label: 'City'
@EndUserText.quickInfo: 'Name of the City'
key City : abap.char(60);
@EndUserText.label: 'City (Short)'
@EndUserText.quickInfo: 'Short name of the City'
CityShort : abap.char(10);
}
Implementation
We have now created the basic elements that we need for the search help. In this section we implement the necessary logic to see data in the UI.
Integration
To do this, we bind the search help to the field in the projection view ZBS_C_RAPPartner, using the annotation "Consumption.valueHelpDefinition":
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZBS_I_RAPCityVH', element: 'City' } }]
City,
Since value help must also be released externally, you should include the view in your service definition. In the preview it usually works without release, but the lack of access will be noticed after deployment at the latest.
@EndUserText.label: 'Simple Partner Service'
define service ZBS_SIMPLE_PARTNER {
expose ZBS_C_RAPPartner as Partner;
expose ZBS_I_RAPCityVH;
}
Query class
Let's now extend the query class with the logic we need to be able to use the value help easily. To do this, we need an internal table with the data type of the custom entity. We fill this with the appropriate data.
DATA lt_values TYPE STANDARD TABLE OF ZBS_I_RAPCityVH WITH EMPTY KEY.
lt_values = VALUE #( ( City = 'Walldorf' CityShort = 'DE' )
( City = 'Redmond' CityShort = 'US' )
( City = 'Menlo Park' CityShort = 'US' )
( City = 'Hangzhou' CityShort = 'CN' )
( City = 'Munich' CityShort = 'DE' )
( City = 'Vevey' CityShort = 'CH' )
( City = 'Sankt Petersburg' CityShort = 'RU' )
( City = 'Seattle' CityShort = 'US' )
( City = 'Wolfsburg' CityShort = 'DE' )
( City = 'Cologne' CityShort = 'DE' ) ).
To return the data, we still have to call the appropriate REPSONSE methods. Here it is important to only call the methods if the framework requests it, otherwise an error will occur. Therefore, we first ask whether the fields have been requested and then call the method. It is also important that the two methods must always be implemented and should not, for example, be in a TRY/CATCH, where they may not be called if an error occurs.
IF io_request->is_data_requested( ).
io_response->set_data( lt_values ).
ENDIF.
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( lines( lt_values ) ).
ENDIF.
If we now call up the value help, we unfortunately get no result, the list remains empty.
We find the problem in the ABAP Development Tools via the "Feed Reader". Two error messages appear in the Gateway Log.
The "Backend Error" is interesting here, as we can find further information about our problem here. We have not called the GET_SORT_ELEMENTS method. However, this is not described in the interface or in the method.
In addition to this method, we also have to call the GET_PAGING method, for which we receive a similar error. The call to the methods must be implemented within the method call.
io_request->get_sort_elements( ).
io_request->get_paging( ).
If we run the search help again, the data is displayed and we can adopt entries for the filter. This is a very simple search help without much additional information and can be used as a "fixed value help" defined.
Extension
The value help works, but no additional functions such as filtering, sorting or paging, which are passed to us from outside. In this section we will also implement these basic functions.
Classic ABAP
In classic ABAP there is the helper class /IWBEP/CL_MGW_DATA_UTIL, which has all the methods for filtering data. However, this is not available in ABAP Cloud and in the ABAP Environment.
Copy
If you use the class beforehand, you must first convert the RAP objects and structures into the gateway structures. So that we can also work with this function in the cloud, we copy the gateway class and perform a refactoring. Let's look at the ORDERBY method.
DATA: lt_otab TYPE abap_sortorder_tab,
ls_oline TYPE abap_sortorder.
DATA: ls_order LIKE LINE OF it_order.
LOOP AT it_order INTO ls_order.
ls_oline-name = ls_order-property.
TRANSLATE ls_oline-name TO UPPER CASE.
IF ls_order-order = gcs_sorting_order-descending.
ls_oline-descending = 'X'.
ENDIF.
APPEND ls_oline TO lt_otab.
CLEAR ls_oline.
ENDLOOP.
SORT ct_data BY (lt_otab).
After comparing the target structure and using inline declarations, as well as Modern ABAP, the logic is reduced to two lines:
DATA(lt_sort) = CORRESPONDING abap_sortorder_tab( it_sort MAPPING name = element_name ).
SORT ct_data BY (lt_sort).
You can find the complete logic of the class in the commit below or directly in the GitHub repository for the latest version.
Request
Now we have three methods and a Request object, and we will create a method that takes on the work of carrying out all the necessary steps and calling the methods in the correct order. To do this, we first read all the necessary restrictions from the REQUEST object, such as sorting, pagination and filters.
DATA(lt_sort) = io_request->get_sort_elements( ).
DATA(ld_offset) = io_request->get_paging( )->get_offset( ).
DATA(ld_page_size) = io_request->get_paging( )->get_page_size( ).
TRY.
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
CATCH cx_rap_query_filter_no_range.
CLEAR lt_filter.
ENDTRY.
Then we call the methods in the correct order. Pagination should be done last because we need the correct order and filter before we can generate a snippet of the data.
filter_data( EXPORTING it_filter = lt_filter
CHANGING ct_data = ct_data ).
order_data( EXPORTING it_sort = lt_sort
CHANGING ct_data = ct_data ).
page_data( EXPORTING id_offset = ld_offset
id_page_size = ld_page_size
CHANGING ct_data = ct_data ).
As a final step, we need to call the logic in our query implementation. To do this, we first note the number of all existing data records before we change the data.
DATA(ld_all_entries) = lines( lt_values ).
NEW zcl_bs_demo_adjust_data( )->adjust_via_request( EXPORTING io_request = io_request
CHANGING ct_data = lt_values ).
Result
We have now implemented the basic functions of the search help and can reuse the class. Here is a short demo of the FILTER and SORT functions in the search help.
Summary
Here is a short summary of the article and the most important learnings from the implementation.
- Quick implementation - The implementation of a simple search help without much additional work is carried out with two objects and two adjustments.
- Service - Inclusion of the entity in the service definition, otherwise the search help cannot be called up later in the test.
- Hidden mandatory fields - The actual "obligations" when implementing a custom entity query are not quite so transparent. Here you should make sure to call the corresponding methods for request and response.
- Additional functions - The implementation such as filtering, sorting and paging means a lot of manual effort in development, with Core Data Services everything is already free.
Example
You can find all adjustments as Commit in our GitHub repository and you can look at the changes made and new objects in detail there.
Conclusion
In this article, you learned how to use the custom entity for your own value helps and what pitfalls you might encounter when implementing it. However, this should not deter you from using the object, as it offers a lot of flexibility.