In the ABAP RESTful Application Programming Model (RAP), virtual elements are essential for displaying computed data that doesn't exist in the database—such as deriving a month or year from a timestamp. However, because these elements are calculated at runtime, they lack standard OData capabilities like server-side sorting and filtering. In this article, we will explore how to overcome these limitations by implementing a SADL exit. We’ll walk through how to enable full sorting and calculation capabilities for virtual elements, ensuring a seamless experience for the end user.
In the ABAP RESTful Application Programming Model (RAP), a virtual element is a field defined in a CDS (Core Data Services) projection view that does not have a corresponding column in the underlying database table.
In many SAP implementations, the business requires reports segmented by Month and Year (e.g., for Monthly Sales Performance). While the underlying database table stores a precise (Created On) date, it does not store the Month or Year as separate columns.
If we create a Fiori Elements app and want to show "Sales Month" and "Sales Year" as separate columns:
Simple Virtual Elements can calculate these strings (e.g., "January", "2024").
The Failure: When a user tries to sort the "Sales Month" column, the UI will throw an error or do nothing because the OData service doesn't know how to sort a field that doesn't exist in the database.
By using a SADL Exit, we "map" the virtual Month/Year fields back to the physical Sales date field. This allows the database to perform the sort on the actual date, while the user sees the sorted Month or Year on their screen.
Define the physical storage. This is where the raw data lives before any calculations happen.
@EndUserText.label : 'Sales table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zsales_table {
key client : abap.clnt not null;
key sales_uuid : sysuuid_x16 not null;
sales_id : abap.char(10);
sales_date : abap.dats;
@Semantics.amount.currencyCode : 'zsales_table.currency'
total_amount : abap.curr(15,2);
currency : abap.cuky;
last_changed_at : timestampl;
}This layer projects the database fields into a clean, business-oriented structure.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface view for sales table'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
serviceQuality: #X,
sizeCategory: #S,
dataClass: #MIXED
}
define root view entity zi_sales_table
as select from zsales_table
{
key sales_uuid as SalesUuid,
sales_id as SalesId,
sales_date as SalesDate,
@Semantics.amount. currencyCode: 'Currency'
total_amount as TotalAmount,
currency as Currency,
last_changed_at as LastChangedAt
}This is where the Virtual Elements are defined. We use annotations to link the UI to the specific ABAP logic for calculation and sorting.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Projection view for sales table'
@Metadata.ignorePropagatedAnnotations: true
define root view entity ZP_SALESPROJ
provider contract transactional_query
as projection on zi_sales_table
{
.facet: [{ type: #IDENTIFICATION_REFERENCE,
position: 10,
label: 'Salestable',
targetQualifier: 'GENERAL',
purpose: #STANDARD }]
.lineItem: [{ position: 10, label: 'Sales Uuid' }]
key SalesUuid,
.lineItem: [{ position: 20, label: 'Sales Id' }]
.identification: [{ position: 20, label: 'Sales Id' }]
SalesId,
.lineItem: [{ position: 30, label: 'Sales order date' }]
.identification: [{ position: 30, label: 'Sales order date', qualifier: 'GENERAL' }]
SalesDate,
.lineItem: [{ position: 40, label: 'Sales order year' }]
.identification: [{ position: 40, label: 'Sales order year', qualifier: 'GENERAL' }]
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_SALES_VE_HANDLER'
@ObjectModel.sort.transformedBy: 'ABAP:ZCL_SALES_VE_HANDLER'
virtual SalesYear : abap.char(4),
.lineItem: [{ position: 50, label: 'Sales order month' }]
.identification: [{ position: 50, label: 'Sales order month' , qualifier: 'GENERAL' }]
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_SALES_VE_HANDLER'
@ObjectModel.sort.transformedBy: 'ABAP:ZCL_SALES_VE_HANDLER'
virtual SalesMonth : abap.char(2),
.lineItem: [{ position: 60, label: 'Total amount' }]
.identification: [{ position: 60, label: 'Total amount' }]
@Semantics.amount.currencyCode: 'Currency'
TotalAmount,
Currency
}This class implements two critical interfaces: if_sadl_exit_calc_element_read for the calculation and if_sadl_exit_sort_transform to enable sorting.
CLASS zcl_sales_ve_handler DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_sadl_exit_calc_element_read.
INTERFACES if_sadl_exit_sort_transform.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sales_ve_handler IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~get_calculation_info.
IF line_exists( it_requested_calc_elements[ table_line = 'SALESYEAR' ] ) OR
line_exists( it_requested_calc_elements[ table_line = 'SALESMONTH' ] ).
APPEND 'SALESDATE' TO et_requested_orig_elements.
ENDIF.
ENDMETHOD.
METHOD if_sadl_exit_calc_element_read~calculate.
DATA lt_data TYPE TABLE OF zp_salesproj WITH DEFAULT KEY.
lt_data = CORRESPONDING #( it_original_data ).
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<fs_row>).
<fs_row>-SALESYEAR = <fs_row>-salesdate(4).
<fs_row>-SALESMONTH = <fs_row>-salesdate+4(2).
ENDLOOP.
ct_calculated_data = CORRESPONDING #( lt_data ).
ENDMETHOD.
METHOD if_sadl_exit_sort_transform~map_element.
CASE iv_element.
WHEN 'SALESYEAR' OR 'SALESMONTH'.
" Map the UI sort request to the database field 'SALESDATE'
APPEND VALUE #( name = 'SALESDATE' ) TO et_sort_elements.
ENDCASE.
ENDMETHOD.
ENDCLASS.
With the handler class implemented, the Sales order month and Sales order year ( Virtual Elements) now behaves like a standard database fields.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 26 | |
| 25 | |
| 21 | |
| 20 | |
| 19 | |
| 14 | |
| 14 | |
| 14 | |
| 14 | |
| 10 |