
RAP - Custom Pattern (Verhalten)
In diesem Artikel erweitern wir das Custom Pattern in RAP um zusätzliche Daten und Verhalten. Damit können wir Mehrwerte in der Implementierung schaffen.
Inhaltsverzeichnis
In diesem Artikel erweitern wir unser RAP Objekt für die Software Komponenten um Verhalten und Ergänzen die aktuellen Daten um zusätzliche Informationen.
Einleitung
In einem der letzten Artikel hatten wir das Custom Pattern vorgestellt und wie es uns hilft verschiedene Funktionen und Schnittstellen als RAP Objekt zur Verfügung zu stellen. Dabei haben wir bereits die Datenbeschaffung über unsere wiederverwendbare Komponente implementiert und so weit Aufwand und Coding reduziert. In diesem Artikel schauen wir uns die Besonderheiten im Hinblick auf das Verhalten an.
Service
In diesem Artikel müssen wir ein zusätzliches Service Binding anlegen, um die Änderungsoperationen aktivieren zu können. Wenn du dir das aktuelle Service Bindung (OData v4 für UI) anschaust, bekommst du eine Hinweismeldung.
Würden wir nun ein Verhalten implementieren, müssten wir dieses für OData v4 mit Draft ausstatten. Bereits bei der Anlage des Verhaltens und der Definition vom Draft, erhalten wir die Warnung, dass Draft nur teilweise unterstützt wird. Schauen wir uns dann das Service Binding an, erhalten wir die entsprechende Fehlermeldung.
Aus diesem Grund müssen wir für änderndes Verhalten einen OData v2 Bindung definieren. Dafür müssten wir allerdings auf die Draft Funktionalität verzichten und könnten die App auch nicht per Flexible Programming Model erweitern.
Daten
Im zweiten Schritt erweitern wir unseren Datenbestand um einige Informationen, die wir lokal im System persistieren wollen und zusammen mit den Daten in der App pflegen wollen.
Felder
Dazu ergänzen wir die folgenden Felder:
- Staging - Damit wollen wir später abgrenzen, ob die Software Komponenten aus dem aktuellen System oder einem anderen System geladen werden. Zusammen mit der SWC bildet es den neuen Schlüssel.
- Information - Hier können wir zusätzliche Informationen zur SWC speichern, da die Beschreibung an einer Komponente nur einmal gepflegt werden kann.
- Team - Hier können wir ein zuständiges Team zuordnen, um später einen gezielten Ansprechpartner für die Entwicklung zu haben.
- Applikation - Hier können wir die Software Komponente einer Applikation zuordnen, dies hilft verschiedene SWCs zusammenzufassen.
Tabelle
Dazu legen wir eine Datenbank Tabelle im System an, wo wir die zusätzlichen Informationen dann speichern werden. Für einige der Elemente legen wir auch gleich Datenelemente an.
@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
Im letzten Schritt erweitern wir unsere Custom Entity um die zusätzlichen Felder und Schlüssel aus der Datenbank. Hier müssen wir neben den Feldern auch die Datentypen angeben, da wir kein Referenzobjekt haben, sondern die Felder direkt angelegt werden.
@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;
}
Dabei passen wir das UI leicht an und übernehmen die neuen Felder auf die Objektseite der App. Die Informationen möchten wir in einem eigenen Abschnitt darstellen und legen dafür bei den UI Annotationen eine eigene Facet an. Die Information soll in einer Textbox angezeigt werden, damit ein längerer Satz leicht gelesen werden kann.
Verhalten
Damit die Daten von der UI zur Datenbank kommen und gespeichert werden, legen wir auf der Custom Entity ein Verhalten an.
Definition
Hier können wir nur den "Unmanaged" Ansatz wählen. Eine Persistierung in Form einer Tabelle oder Draft Tabelle können wir in diesem Szenario nicht definieren. Wir möchten die Daten gern ändern, deshalb implementieren wir UPDATE und setzen die Felder aus dem Service aus lesend.
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;
}
}
Zum Abschluss definieren wir noch ein Mapping von der Custom Entity zur Datenbanktabelle. Im Grunde haben wir die Feldnamen hier identisch gewählt, wären diese allerdings komplett anders, hilft uns das Mapping beim Zuweisen der Daten.
Implementierung
Über den Quick Fix (STRG + 1) können wir die Klasse anlegen lassen. Dort werden bereits viele leere Methoden für uns angelegt. Für eine einfache Implementierung benötigen wir aber nicht alle Methoden.
Puffer
Im ersten Schritt legen wir uns einen sehr einfachen Puffer als lokale Klasse an. Grundsätzlich kannst du auch über einen Singleton mit Instanziierung nachdenken oder ein Interface für die entsprechende Testbarkeit. In unserem Fall sollen einfach die aktuellen Updates in einer statischen Tabelle gesichert werden.
CLASS lcl_buffer DEFINITION.
PUBLIC SECTION.
TYPES swc_updates TYPE TABLE FOR UPDATE zbs_r_dcpsoftwarecomponentswc.
CLASS-DATA updates TYPE swc_updates.
ENDCLASS.
Update
Im nächsten Schritt müssen wir nun auf die Änderung an den Daten reagieren. Dazu wird beim Speichern des Datensatzes die UPDATE Methode aufgerufen. Hier übernehmen wir die Entitäten in den Puffer, vorher kannst du auch noch inhaltliche Prüfungen durchführen.
METHOD update.
INSERT LINES OF entities INTO TABLE lcl_buffer=>updates.
ENDMETHOD.
Zum Abschluss implementieren wir die SAVE Methode und übernehmen den Puffer auf die Datenbank. Im ersten Schritt versuchen wir den Insert, da wir nicht wissen, ob die Daten bereits angelegt wurden. Im zweiten Schritt übernehmen wir die Daten per Update, dort kommt auch das Mapping aus der Verhaltensdefinition zum Einsatz. Dabei verwenden wir auch die %CONTROL Struktur, um nicht versorgte Felder nicht zu leeren.
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.
Filterung
Zum Abschluss müssen wir nun noch die Query Klasse anpassen. Aktuell befinden sich Felder in der Entität die Remote nicht verfügbar sind, wenn es um das Filtern und Anzeigen der Daten geht. Weiterhin werden im Moment noch keine Daten abgeleitet und hinzugefügt, eine Filterung über diese Felder ist ebenfalls nicht möglich.
Löschen
Dazu können wir über die Konfiguration der wiederverwendbaren Komponente die Tabelle DELETE_FIELDS befüllen. Diese sorgt dafür, dass die Felder aus der Abgrenzung, Sortierung, den angeforderten Feldern und weiteren OData Bereichen entfernt werden. Würden wir diese Felder nicht entfernen, würde es beim Lesen zu einem Fehler kommen.
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 ).
Anreichern
Im nächsten Schritt lesen wir die Daten der Datenbank und mappen sie zu den gelesenen Informationen aus dem OData Service. Damit erhalten wir alle Daten in unserer Tabelle, die wir dann ans Frontend übergeben können.
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.
Anpassen
Im letzten Schritt müssen wir noch die Filterung und Sortierung aus der Anfrage übernehmen, damit auch die Felder der Datenbank berücksichtigt werden. Dazu verwenden wir unsere Komponente zur Anpassung der Daten, diese findest du unter den 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
Mit der letzten Anpassung können wir die Anwendung nun testen. Dabei laden wir alle verfügbaren Komponenten und ergänzen eine Komponente um die Zusatzinformationen. Im Anschluss verwenden wir den Filter auf der List Page und schränken auf das soeben gepflegte Produkt ein, um auch die Filter für die Datenbank zu testen.
Vollständiges Beispiel
Alle Beispiele der RAP Serie findest du in diesem GitHub Repository. Die Änderungen aus dem heutigen Artikel wurden über den folgenden Commit durchgeführt, dabei kannst du dir die Änderungen und neuen Objekte genauer anschauen.
Fazit
Du solltest nun auch Verhalten in der Anwendung definiert haben. Zusätzlich können wir nun weitere Informationen zur Software Komponente speichern und unsere Fiori Elements App mit nützlichen Features ausstatten.