
RAP - Custom Pattern (Behavior)
In this article, we extend the Custom Pattern in RAP with additional data and behavior, allowing us to create added value in the implementation.
Table of contents
In this article, we extend our RAP object for the software components with behavior and supplement the current data with additional information.
Introduction
In one of the last articles, we introduced the Custom Pattern and how it helps us provide various functions and interfaces as a RAP object. We have already implemented data retrieval via our reusable component, thus reducing the effort and coding required. In this article, we'll look at the specifics regarding behavior.
Service
In this article, we need to create an additional service binding to enable change operations. If you look at the current service binding (OData v4 for UI), you'll get a warning message.
If we were to implement a behavior now, we would have to equip it with Draft for OData v4. When creating the behavior and defining the draft, we receive a warning that the draft is only partially supported. If we then look at the service binding, we receive the corresponding error message.
For this reason, we must define an OData v2 binding for changing behavior. However, to do so, we would have to forego the draft functionality and would not be able to expand the app using the Flexible Programming Model.
Data
In the second step, we expand our database with some information that we want to persist locally in the system and maintain together with the data in the app.
Fields
We will add the following fields:
- Staging - We will use this later to determine whether the software components are loaded from the current system or another system. Together with the SWC, it forms the new key.
- Information - Here we can store additional information about the SWC, since the description of a component can only be maintained once.
- Team - Here we can assign a responsible team to ensure we have a specific contact person for development later.
- Application - Here we can assign the software component to an application; this helps to summarize different SWCs.
Table
To do this, we create a database table in the system where we will then store the additional information. We will also create data elements for some of the elements.
@EndUserText.label : 'Custom Pattern Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dcp {
key client : abap.clnt not null;
key staging : zbs_demo_dcp_staging not null;
key sc_name : abap.char(18) not null;
information : abap.char(200);
team : zbs_demo_dcp_team;
application : zbs_demo_dcp_appl;
}
Custom Entity
In the final step, we extend our custom entity with the additional fields and keys from the database. Here, we must specify the data types in addition to the fields, since we don't have a reference object; the fields are created directly.
@EndUserText.label: 'Software Component'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DCP_SWC_QUERY'
@ObjectModel.semanticKey: [ 'staging', 'sc_name' ]
@UI.headerInfo: { typeName: 'Software Component',
typeNamePlural: 'Software Components',
title.value: 'sc_name',
description.value: 'descr' }
define root custom entity ZBS_R_DCPSoftwareComponent
{
@UI.facet: [ { id: 'idRepositoryFields',
label: 'Technical Details',
position: 10,
type: #IDENTIFICATION_REFERENCE,
targetQualifier: 'REPO' },
{ id: 'idTeam',
label: 'Team Information',
position: 20,
type: #IDENTIFICATION_REFERENCE,
targetQualifier: 'TEAM' } ]
@UI.identification: [ { position: 10, qualifier: 'REPO' } ]
@UI.lineItem: [ { position: 10 } ]
@UI.selectionField: [ { position: 10 } ]
key staging : zbs_demo_dcp_staging;
@EndUserText.label: 'SWC'
@UI.lineItem: [ { position: 20 } ]
@UI.selectionField: [ { position: 20 } ]
key sc_name : abap.char(18);
@EndUserText.label: 'Description'
@UI.identification: [ { position: 20, qualifier: 'REPO' } ]
@UI.lineItem: [ { position: 30 } ]
descr : abap.char(60);
@EndUserText.label: 'Type'
@UI.lineItem: [ { position: 40 } ]
sc_type_descr : abap.char(40);
@EndUserText.label: 'Available'
@UI.lineItem: [ { position: 50 } ]
@UI.selectionField: [ { position: 50 } ]
avail_on_inst : abap_boolean;
@EndUserText.label: 'Branch'
@UI.identification: [ { position: 30, qualifier: 'REPO' } ]
@UI.lineItem: [ { position: 60 } ]
active_branch : abap.char(40);
@EndUserText.label: 'Info'
@UI.identification: [ { position: 40, qualifier: 'TEAM' } ]
@UI.multiLineText: true
information : abap.char(200);
@UI.identification: [ { position: 50, qualifier: 'TEAM' } ]
@UI.lineItem: [ { position: 70 } ]
@UI.selectionField: [ { position: 60 } ]
team : zbs_demo_dcp_team;
@UI.identification: [ { position: 60, qualifier: 'TEAM' } ]
application : zbs_demo_dcp_appl;
}
We'll slightly adapt the UI and add the new fields to the app's object page. We want to display the information in a separate section and create a separate facet for this in the UI annotations. The information should be displayed in a text box so that a longer sentence can be easily read.
Behavior
To ensure that the data is sent from the UI to the database and saved, we'll create a behavior on the custom entity.
Definition
Here we can only define the "Unmanaged" Choose an approach. We cannot define persistence in the form of a table or draft table in this scenario. We would like to change the data, so we implement UPDATE and set the fields from the service to read.
unmanaged implementation in class zbp_bs_dcp_softwarecomponent unique;
strict ( 2 );
define behavior for ZBS_R_DCPSoftwareComponent alias SWC
lock master
authorization master ( instance )
{
update;
field ( readonly )
staging,
sc_name,
active_branch,
avail_on_inst,
descr,
sc_type_descr;
mapping for zbs_dcp
{
staging = staging;
sc_name = sc_name;
application = application;
information = information;
team = team;
}
}
Finally, we define a mapping from the custom entity to the database table. We've basically chosen the same field names here, but if they were completely different, the mapping would help us assign the data.
Implementation
We can create the class using the Quick Fix (CTRL + 1). Many empty methods are already created for us there. However, we don't need all of them for a simple implementation.
Buffer
In the first step, we create a very simple buffer as a local class. In principle, you can also consider a singleton with instantiation or an interface for the corresponding testability. In our case, we simply want to save the current updates in a static table.
CLASS lcl_buffer DEFINITION.
PUBLIC SECTION.
TYPES swc_updates TYPE TABLE FOR UPDATE zbs_r_dcpsoftwarecomponentswc.
CLASS-DATA updates TYPE swc_updates.
ENDCLASS.
Update
In the next step, we need to react to the change in the data. To do this, the UPDATE method is called when the data record is saved. Here, we transfer the entities to the buffer; you can also perform content checks beforehand.
METHOD update.
INSERT LINES OF entities INTO TABLE lcl_buffer=>updates.
ENDMETHOD.
Finally, we implement the SAVE method and transfer the buffer to the database. In the first step, we attempt the insert, since we don't know whether the data has already been created. In the second step, we transfer the data via an update, which is where the mapping from the behavior definition comes into play. We also use the %CONTROL structure to avoid emptying unfilled fields.
METHOD save.
LOOP AT lcl_buffer=>updates INTO DATA(update).
INSERT zbs_dcp FROM @update MAPPING FROM ENTITY.
IF sy-subrc <> 0.
UPDATE zbs_dcp FROM @update INDICATORS SET STRUCTURE %control MAPPING FROM ENTITY.
ENDIF.
ENDLOOP.
ENDMETHOD.
Filtering
Finally, we need to adjust the query class. Currently, there are fields in the entity that are not remotely available when it comes to filtering and displaying data. Furthermore, no data is currently being derived or added, and filtering using these fields is also not possible.
Deleting
To do this, we can populate the DELETE_FIELDS table via the reusable component configuration. This ensures that the fields are removed from the delimiter, sorting, requested fields, and other OData areas. If we didn't remove these fields, a reading error would occur.
NEW zcl_bs_demo_custom_git( )->get_software_component(
EXPORTING setting = VALUE #( entity_name = 'REPOSITORIES'
request = request
delete_fields = VALUE #( ( `STAGING` )
( `INFORMATION` )
( `APPLICATION` )
( `TEAM` ) ) )
IMPORTING business_data = DATA(remote_data)
count = count ).
Enrich
In the next step, we read the data from the database and map it to the information read from the OData service. This gives us all the data in our table, which we can then pass to the frontend.
LOOP AT remote_data INTO DATA(remote).
INSERT CORRESPONDING #( remote ) INTO TABLE business_data REFERENCE INTO DATA(line).
SELECT SINGLE FROM zbs_dcp
FIELDS information, application, team
WHERE staging = @test_stage
AND sc_name = @line->sc_name
INTO CORRESPONDING FIELDS OF @line->*.
line->staging = test_stage.
ENDLOOP.
Adapt
In the final step, we need to adopt the filtering and sorting from the query so that the database fields are also taken into account. To do this, we use our component for adapting the data, which you can find in the Code Snippets.
DATA(adjust_custom) = NEW zcl_bs_demo_adjust_data( ).
TRY.
DATA(filter) = request->get_filter( )->get_as_ranges( ).
CATCH cx_rap_query_filter_no_range.
CLEAR filter.
ENDTRY.
adjust_custom->filter_data( EXPORTING it_filter = filter
CHANGING ct_data = software_components ).
adjust_custom->order_data( EXPORTING it_sort = request->get_sort_elements( )
CHANGING ct_data = software_components ).
Test
With the final adjustment, we can now test the application. We load all available components and add the additional information to one component. We then use the filter on the list page and restrict it to the product we just maintained to also test the filters for the database.
Complete example
You can find all examples in the RAP series in this GitHub repository. The changes from today's article were implemented via the following Commit. You can take a closer look at the changes and new objects.
Conclusion
You should now have defined behavior in the application. Additionally, we can now store further information about the software component and equip our Fiori Elements app with useful features.