Overview
You will learn
- How to create virtual elements in CDS projection view for fields which are not part of the data model
- Calculate the values of virtual elements using ABAP resources during runtime
Prerequisites
Prerequisites
- You have done one of the following:
- Tutorial: Create an SAP BTP ABAP Environment Trial User
- You have bought a licensed version of SAP BTP ABAP Environment
- You have installed ABAP Development Tools, latest version
Steps
Intro
For some business use cases, it may be necessary to add new elements to the data model of an application whose values cannot be fetched from the database. Virtual elements are used if these elements are not provided as part of the original persistence data model but can be calculated using ABAP resources during runtime. For more details refer to Using Virtual Elements in CDS Projection Views
Create a new package for this tutorial, by right clicking on Favourtite Packages from Project Explorer. Now choose New > ABAP Package.
In the wizard enter the following then follow the wizard, choosing a new transport request:
- Name:
ZABAP_VIRT_ELEM_### - Description: Package for Virtual Elements

New Package - Name:
Choose your package, then choose New > Other Repository Object from the context menu.
Search for Database Table, select it and then click Next>.

Database Table Enter the following information (### is your group ID), then choose Next.
- Name:
ztravel_### - Description: Table for Travel Data

Create Table - Name:
Choose the transport request, then choose Finish to create the database table.
The table appears in a new editor.
Replace the default code with the code snippet below and replace all occurences of the placeholder ### with your group ID using the Replace All function (Ctrl+F).:
ABAP@EndUserText.label : 'Database Table for ZTRAVEL###' @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE @AbapCatalog.tableCategory : #TRANSPARENT @AbapCatalog.deliveryClass : #A @AbapCatalog.dataMaintenance : #RESTRICTED define table ztravel_### { key client : abap.clnt not null; key uuid : sysuuid_x16 not null; key travel_id : /dmo/travel_id not null; customer_id : /dmo/customer_id; description : /dmo/description; status : /dmo/travel_status; begin_date : abap.datn; end_date : abap.datn; @Semantics.amount.currencyCode : 'ztravel_###.currency_code' flight_price : /dmo/flight_price; currency_code : /dmo/currency_code; local_created_by : abp_creation_user; local_created_at : abp_creation_tstmpl; local_last_changed_by : abp_locinst_lastchange_user; local_last_changed_at : abp_locinst_lastchange_tstmpl; last_changed_at : abp_lastchange_tstmpl; }Format, save, and activate your table (
Shift+F1, Ctrl+S, Ctrl+F3).
Create your OData v4 based UI services and the RAP business object (BO) with all the needed ABAP artefacts - e.g. CDS view entities, behavior definition and implementation - using the built-in ADT generator. The generated business service will be transactional, draft-enabled, and enriched with UI semantics for the generation of the Fiori elements app.
Right-click your database table table ZTRAVEL_### and select Generate ABAP Repository Objects from the context menu.
Select OData UI Service and click Next >.

OData Service Click Next >, enter your package name and click Next < again.
Maintain the required information on the Configure Generator dialog to provide the suffix, name of your data model as shown in the below screenshot and generate them. For that, navigate through the wizard tree (Business Objects, Data Model, etc…), maintain the artefact names and press Next >.
Verify the maintained entries and press Next > and Finish to confirm. The needed artefacts will be generated.

Development Objects Go to the Project Explorer, select your package package
ZABAP_VIRT_ELEM_###, refresh it by pressing F5, and check all generated ABAP repository objects.Open your service binding ZUI_TRAVEL_###_O4 and click Publish.
Double-click on the entity Travel in the Entity Set and Association section to open the Fiori elements App Preview.
Press Go to load the data in the app. The list should be empty at this point since no demo data has been created yet.
Define and expose new associations in the base BO data model defined in the CDS view entity Associations to the business entities Customer (_Customer) Associations to helpful information about Status (_Status) and Currency (_Currency)
- Define the new associations _Customer, _Status, and _Currency. Open your data definition data definition ZR_TRAVEL### and format the source code with the ABAP Formatter (aka Pretty Printer) by pressing Shift+F1. Insert the following code snippet after the select statement as shown on the screenshot below and format the source code (Shift+F1).
association [0..1] to /DMO/I_Customer as _Customer on $projection.CustomerID = _Customer.CustomerID
association [0..1] to I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency
association [1..1] to /DMO/I_Overall_Status_VH as _OverallStatus on $projection.Status = _OverallStatus.OverallStatus- Expose the defined associations _Customer, _OverallStatus and _Currency in the selection list. For that, insert the code snippet provided below in the selection list between the curly brackets ({…}) as shown on the screenshot and format the source code (Shift+F1).
_Customer,
_Currency,
_OverallStatusYour source code should look like this:
@AccessControl.authorizationCheck: #MANDATORY
@Metadata.allowExtensions: true
@ObjectModel.sapObjectNodeType.name: 'ZTRAVEL_###'
@EndUserText.label: '###GENERATED Core Data Service Entity'
define root view entity ZR_TRAVEL_###
as select from ZTRAVEL_### as Travel
association [0..1] to /DMO/I_Customer as _Customer on $projection.CustomerID = _Customer.CustomerID
association [0..1] to I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency
association [1..1] to /DMO/I_Overall_Status_VH as _OverallStatus on $projection.Status = _OverallStatus.OverallStatus
{
key uuid as UUID,
key travel_id as TravelID,
customer_id as CustomerID,
description as Description,
status as Status,
begin_date as BeginDate,
end_date as EndDate,
@Semantics.amount.currencyCode: 'CurrencyCode'
flight_price as FlightPrice,
@Consumption.valueHelpDefinition: [ {
entity.name: 'I_CurrencyStdVH',
entity.element: 'Currency',
useForValidation: true
} ]
currency_code as CurrencyCode,
@Semantics.user.createdBy: true
local_created_by as LocalCreatedBy,
@Semantics.systemDateTime.createdAt: true
local_created_at as LocalCreatedAt,
@Semantics.user.localInstanceLastChangedBy: true
local_last_changed_by as LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
_Customer,
_Currency,
_OverallStatus
}Save and activate activate the changes.
Enhance the CDS data model of the projected travel BO entity with new elements from associations, adjusting the defined value help definitions and removing use case irrelevant elements. Replace the whole data definition of the travel BO projection view datadefinition ZC_Travel### with the source code from the document provided below.
@Metadata.allowExtensions: true
@Metadata.ignorePropagatedAnnotations: true
@EndUserText: {
label: '###GENERATED Core Data Service Entity'
}
@ObjectModel: {
sapObjectNodeType.name: 'ZTRAVEL_###'
}
@AccessControl.authorizationCheck: #MANDATORY
define root view entity ZC_TRAVEL_###
provider contract transactional_query
as projection on ZR_TRAVEL_###
association [1..1] to ZR_TRAVEL_### as _BaseEntity on $projection.UUID = _BaseEntity.UUID and $projection. TravelID = _BaseEntity.TravelID
{
key UUID,
key TravelID,
@Consumption.valueHelpDefinition: [{entity: {name: '/DMO/I_Customer_StdVH', element: 'CustomerID' }, useForValidation: true}]
@ObjectModel.text.element: ['CustomerName']
@Search.defaultSearchElement: true
CustomerID as CustomerID,
_Customer.FirstName as CustomerName,
Description,
@ObjectModel.text.element: ['OverallStatusText'] //case-sensitive
@Consumption.valueHelpDefinition: [{ entity: {name: '/DMO/I_Overall_Status_VH', element: 'OverallStatus' }, useForValidation: true }]
Status,
_OverallStatus._Text.Text as OverallStatusText : localized,
BeginDate,
EndDate,
@Semantics: {
amount.currencyCode: 'CurrencyCode'
}
FlightPrice,
@Consumption: {
valueHelpDefinition: [ {
entity.element: 'Currency',
entity.name: 'I_CurrencyStdVH',
useForValidation: true
} ]
}
CurrencyCode,
@Semantics: {
user.createdBy: true
}
LocalCreatedBy,
@Semantics: {
systemDateTime.createdAt: true
}
LocalCreatedAt,
@Semantics: {
user.localInstanceLastChangedBy: true
}
LocalLastChangedBy,
@Semantics: {
systemDateTime.localInstanceLastChangedAt: true
}
LocalLastChangedAt,
@Semantics: {
systemDateTime.lastChangedAt: true
}
LastChangedAt,
_BaseEntity
}Save and activate activate the changes.
Preview and Test the Enhanced Travel App You can now preview and test the changes by creating a new travel instance in the Travel app.
- Refresh your application in the browser using F5 if the browser is still open - or go to your service binding ZUI_TRAVEL_###_O4 and start the Fiori elements App preview for the Travel entity set.
- Create a new Travel instance.
A dialog for manually entering a Travel ID should be displayed now. When you create a new entry you see that the value helps for the fields Customer ID offer an out of the box frontend validation. Enter some demo data required as shown below:

Demo Data
Now, go ahead and define 2 virtual elements Trip Duration and Remaining Days to Flight that will be used to specify the duration of the trip and number of days left before the flight date.
Create an ABAP class ZCL_CALC_VIRT_ELEM_###, save and activate it. This class will be used to handle the logic to populate the virtual element values.

ABAP Class Now add the statements to declare the virtual elements in the data definition of the travel BO projection view ZC_TRAVEL_### after the OverallStatusText in the SELECT list. The keyword virtual must be specified in front of the element and the name of the calculation class must be specified in the annotation @ObjectModel.virtualElementCalculatedBy. The ABAP class ZCL_CALC_VIRT_ELEM_### created above will be used to calculate this virtual element is specified.
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_CALC_VIRT_ELEM_###'
@EndUserText.label: 'Trip Duration'
virtual TripDuration : abap.int2,
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_CALC_VIRT_ELEM_###'
@EndUserText.label: 'Remaining Days to Flight'
virtual RemainingDaysToFlight : abap.int1,
Your code should look like this:
@Metadata.allowExtensions: true
@Metadata.ignorePropagatedAnnotations: true
@EndUserText: {
label: '###GENERATED Core Data Service Entity'
}
@ObjectModel: {
sapObjectNodeType.name: 'ZTRAVEL_###'
}
@AccessControl.authorizationCheck: #MANDATORY
define root view entity ZC_TRAVEL_###
provider contract transactional_query
as projection on ZR_TRAVEL_###
association [1..1] to ZR_TRAVEL_### as _BaseEntity on $projection.UUID = _BaseEntity.UUID
and $projection.TravelID = _BaseEntity.TravelID
{
key UUID,
key TravelID,
@Consumption.valueHelpDefinition: [{entity: {name: '/DMO/I_Customer_StdVH', element: 'CustomerID' }, useForValidation: true}]
@ObjectModel.text.element: ['CustomerName']
@Search.defaultSearchElement: true
CustomerID as CustomerID,
_Customer.FirstName as CustomerName,
Description,
@ObjectModel.text.element: ['OverallStatusText'] //case-sensitive
@Consumption.valueHelpDefinition: [{ entity: {name: '/DMO/I_Overall_Status_VH', element: 'OverallStatus' }, useForValidation: true }]
Status,
_OverallStatus._Text.Text as OverallStatusText : localized,
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_CALC_VIRT_ELEM_###'
@EndUserText.label: 'Trip Duration'
virtual TripDuration : abap.int2,
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_CALC_VIRT_ELEM_###'
@EndUserText.label: 'Remaining Days to Flight'
virtual RemainingDaysToFlight : abap.int1,
BeginDate,
EndDate,
@Semantics: {
amount.currencyCode: 'CurrencyCode'
}
FlightPrice,
@Consumption: {
valueHelpDefinition: [ {
entity.element: 'Currency',
entity.name: 'I_CurrencyStdVH',
useForValidation: true
} ]
}
CurrencyCode,
@Semantics: {
user.createdBy: true
}
LocalCreatedBy,
@Semantics: {
systemDateTime.createdAt: true
}
LocalCreatedAt,
@Semantics: {
user.localInstanceLastChangedBy: true
}
LocalLastChangedBy,
@Semantics: {
systemDateTime.localInstanceLastChangedAt: true
}
LocalLastChangedAt,
@Semantics: {
systemDateTime.lastChangedAt: true
}
LastChangedAt,
_BaseEntity
}
Implement the logic of the virtual elements Trip Duration and Remaining Days to Flight in the ABAP Class ZCL_CALC_VIRT_ELEM_###.
- Open your ABAP class ZCL_CALC_VIRT_ELEM_### and replace the entire code with the code provided below. Replace all occurences of the placeholder ### with your assigned suffix using Ctrl+F.
CLASS zcl_calc_virt_elem_### DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_sadl_exit_calc_element_read .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_calc_virt_elem_### IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~calculate.
IF it_requested_calc_elements IS INITIAL.
EXIT.
ENDIF.
LOOP AT it_requested_calc_elements ASSIGNING FIELD-SYMBOL(<fs_req_calc_elements>).
CASE <fs_req_calc_elements>.
"virtual elements from BOOKING entity
WHEN 'TRIPDURATION' OR 'REMAININGDAYSTOFLIGHT'.
DATA lt_book_original_data TYPE STANDARD TABLE OF zc_travel_### WITH DEFAULT KEY.
lt_book_original_data = CORRESPONDING #( it_original_data ).
LOOP AT lt_book_original_data ASSIGNING FIELD-SYMBOL(<fs_book_original_data>).
*<fs_book_original_data> = zcl_calc_virt_elem_###=>calculate_days_to_flight( <fs_book_original_data> ).
ENDLOOP.
ct_calculated_data = CORRESPONDING #( lt_book_original_data ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
METHOD if_sadl_exit_calc_element_read~get_calculation_info.
IF iv_entity EQ 'ZR_TRAVEL_###'. "Booking BO node
LOOP AT it_requested_calc_elements ASSIGNING FIELD-SYMBOL(<fs_booking_calc_element>).
CASE <fs_booking_calc_element>.
WHEN 'TRIPDURATION'.
COLLECT `BOOKINGDATE` INTO et_requested_orig_elements.
COLLECT `FLIGHTDATE` INTO et_requested_orig_elements.
WHEN 'REMAININGDAYSTOFLIGHT'.
COLLECT `FLIGHTDATE` INTO et_requested_orig_elements.
ENDCASE.
ENDLOOP.
ENDIF.
ENDMETHOD.
ENDCLASS.
The class implements the virtual element interface IF_SADL_EXIT_CALC_ELEMENT_READ that must be implemented by calculation classes for virtual elements.
The method IF_SADL_EXIT_CALC_ELEMENT_READ~GET_CALCULATION_INFO provides a list of all elements that are required for calculating the values of the virtual elements in the requested entity. This method is called during runtime before the retrieval of data from the database to ensure that all necessary elements for calculation are filled with data.
The method IF_SADL_EXIT_CALC_ELEMENT_READ~CALCULATE executes the value calculation for the virtual element. This method is called during runtime after data is retrieved from the database. The elements needed for the calculation of the virtual elements are already inside the data table passed to this method. The method returns a table that contains the values of the requested virtual elements.
- Define the class method calculate_days_to_flight in the public section of the class definition where the proper calculation of the virtual elements TripDuration and RemainingDaysToFlight will take place. The method is declared as class method to have the possibility to access it externaly, for example, from a function.
For that, insert the code snippet provided below after the statement interfaces IF_SADL_EXIT_CALC_ELEMENT_READ. in the class definition and replace all occurences of the placeholder ### with your assigned suffix.
CLASS-METHODS:
calculate_days_to_flight
IMPORTING is_original_data TYPE ZC_TRAVEL_###
RETURNING VALUE(result) TYPE ZC_TRAVEL_###.
Press the light bulb symbol on the left side or use the ADT Quick Fix (Ctrl+1) to add the missing method implementations. Set the cursor before your method calculate_trav_status_ind and press CTRL + 1, select Add implementation for calculate_trav_status_ind.

Calculation Method Implement the method calculate_trav_status_ind which calculates the value of the virtual element defined in the Travel BO entity. For that, replace the empty method implementation of calculate_days_to_flight with the code snippet provided below.
METHOD calculate_days_to_flight.
DATA(today) = cl_abap_context_info=>get_system_date( ).
result = CORRESPONDING #( is_original_data ).
"VE Trip Duration: Number of between start date and end date of trip
DATA(trip_duration) = result-EndDate - result-BeginDate.
IF trip_duration > 0 and trip_duration < 999.
result-TripDuration = trip_duration.
ELSE.
result-TripDuration = 0.
ENDIF.
"VE RemainingDaysToFlight: remaining days to flight date from today
DATA(remaining_days) = result-BeginDate - today.
IF remaining_days < 0 OR remaining_days > 999.
result-RemainingDaysToFlight = 0.
ELSE.
result-RemainingDaysToFlight = result-BeginDate - today.
ENDIF.
ENDMETHOD.TripDuration : The number of days between the flight begin date and the flight end date (flight end date - flight begin date). RemainingDaysToFlight: The number of days until departure from today.
- Now, uncomment the method call calculate_trav_status_ind within the method CALCULATE
<fs_book_original_data> = zcl_calc_virt_elem_222=>calculate_days_to_flight( <fs_book_original_data> ).
- Save and activate (Ctrl+F3) the changes. Close the ABAP class.
Test the enhanced SAP Fiori elements application.
Refresh your browser or start the SAP Fiori elements app preview from your service binding ../ servicebinding ZUI_TRAVEL_123_O4 by double-clicking the Travel entity set.
Click Go on the app and check the result.
Press the respective Gear icon and add the missing column (virtual elements) on the list report and click OK.

Add Fields Now you can see the 2 virtual element fields ‘Trip Duration’ and ‘Remaining Days To Flight’.

Virtual Elements
- SAP Help Portal: Generating a RAP Business Service with the Generate ABAP Repository Objects Wizard
- SAP Help Portal: Modeling Virtual Elements
Resources
Discussion
Share feedback on this tutorial or join the conversation in SAP Community.