
RAP - Custom Entity mit Aktion
Wie kannst du eigentlich in einer Custom Entity eine Action sauber implementieren, um am Ende auch das UI zu aktualisieren? Schauen wir uns die verschiedenen Schritte im Detail an.
Inhaltsverzeichnis
Die Frage des heutigen Artikels kam aus der Community von Abdullah ATAN. Dabei gehen wir auf die Details er Implementierung ein und wie das Beispiel genau funktioniert.
Einleitung
Die Frage wurde auch in der SAP Community gestellt, wie eigentlich eine Action in einer Custom Entity implementiert werden kann. Dabei wurde auch normales EML verwendet, wobei allerdings einiges zu beachten ist, damit es sauber funktioniert. Deshalb haben wir dazu ein kleines Beispiel erstellt, welches du einfach übernehmen und ausprobieren kannst.
Umsetzung
In diesem Kapitel schauen wir in die Umsetzung schauen uns verschiedene Details der Implementierung an, die in diesem Fall wichtig sind. Wie du allgemein eine Custom Entity aufbaust, findest du in diesem Artikel beschrieben.
Custom Entity
Starten wir im ersten Schritt mit einer Custom Entity, die unsere Struktur und das UI abbilden wird. Neben der Definition des Typen, müssen wir noch die Queryklasse angeben, die die Daten liefern soll.
@EndUserText.label: 'Custom Entity with Action'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_ACTION_QRY'
define root custom entity ZBS_R_CusActEntityTP
{
key my_key : abap.char(15);
description : abap.char(60);
icon : abap.sstring(250);
}
Query
Im nächsten Schritt implementieren wir die Query, die uns hart codierte Daten zur Verfügung stellen. Damit wollen wir im ersten Schritt eine API simulieren, die uns vielleicht per HTTP die Daten zur Verfügung stellt. Die Menge der Daten müssen wir dann noch über die Filter und die Sortierung entsprechend für die Rückgabe vorbereiten.
CLASS zcl_bs_demo_custom_action_qry DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
TYPES result_type TYPE STANDARD TABLE OF ZBS_R_CusActEntityTP WITH EMPTY KEY.
METHODS get_dummy_data
RETURNING VALUE(result) TYPE result_type.
ENDCLASS.
CLASS zcl_bs_demo_custom_action_qry IMPLEMENTATION.
METHOD if_rap_query_provider~select.
DATA result TYPE STANDARD TABLE OF ZBS_R_CusActEntityTP.
DATA count TYPE int8.
result = get_dummy_data( ).
NEW zcl_bs_demo_adjust_data( )->adjust_via_request( EXPORTING io_request = io_request
CHANGING ct_data = result
cd_count = count ).
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( count ).
ENDIF.
IF io_request->is_data_requested( ).
io_response->set_data( result ).
ENDIF.
ENDMETHOD.
METHOD get_dummy_data.
RETURN VALUE #( ( my_key = 'TEST' description = 'First One' )
( my_key = 'NOTHING' description = 'Second One' )
( my_key = 'MORE' description = 'Third One' ) ).
ENDMETHOD.
ENDCLASS.
Verhalten
Im nächsten Schritt definieren wir auf der Custom Entity ein Verhalten, um später die Implementierung in RAP zu machen. Wir möchten später den Update verwenden, um den Status in einer Datenbank zu aktualisieren, diesen aber nur INTERNAL, innerhalb des RAP Objekts. Wir definieren uns eine normale Aktion, die uns ebenfalls ein Ergebnis (sich selbst) zurückgibt. Damit wollen wir die Aktualisierung des Datensatzes erreichen. Die statische Aktion verwenden wir für das Zurücksetzen aller Status.
unmanaged implementation in class zbp_bs_demo_custom_action unique;
strict ( 2 );
define behavior for ZBS_R_CusActEntityTP alias CustomAction
lock master
authorization master ( instance )
{
internal update;
field ( readonly ) my_key;
action myCustomAction result [1] $self;
static action resetAllIcons;
}
UI
Damit die Liste und Felder angezeigt werden, benötigen wir noch UI Annotationen. Diese müssen wir direkt in der Custom Entity definieren. Dabei definieren wir auch die zwei Aktionen im UI. Für das Feld ICON definieren wir noch, dass dieses als Bild dargestellt wird.
@EndUserText.label: 'Custom Entity with Action'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_ACTION_QRY'
define root custom entity ZBS_R_CusActEntityTP
{
@UI.facet : [{
position : 10,
label : 'General',
type : #IDENTIFICATION_REFERENCE
}]
@UI.lineItem: [{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'myCustomAction', label: 'Custom Action' },
{ type: #FOR_ACTION, dataAction: 'resetAllIcons', label: 'Reset' } ]
@UI.selectionField: [{ position: 10 }]
@UI.identification: [{ position: 10 }]
@EndUserText.label: 'Identifier'
key my_key : abap.char(15);
@UI.lineItem: [{ position: 20, iconUrl: 'icon' }]
@UI.identification: [{ position: 20 }]
@EndUserText.label: 'Description'
description : abap.char(60);
@UI.lineItem: [{ position: 30 }]
@EndUserText.label: 'Icon'
@Semantics.imageUrl: true
icon : abap.sstring(250);
}
Implementierung
Kommen wir nun zur eigentlichen Implementierung der Aktion in der Verhaltensimplementierung. Die Custom Action würden wir nun wie folgt implementieren.
METHOD myCustomAction.
READ ENTITIES OF ZBS_R_CusActEntityTP IN LOCAL MODE
ENTITY CustomAction
FROM CORRESPONDING #( keys )
RESULT DATA(found_data).
LOOP AT found_data INTO DATA(found).
MODIFY ENTITIES OF ZBS_R_CusActEntityTP IN LOCAL MODE
ENTITY CustomAction
UPDATE FROM VALUE #( ( my_key = found-my_key
description = found-description
%control-description = if_abap_behv=>mk-on ) ).
INSERT VALUE #( my_key = found-my_key
%param = found ) INTO TABLE result.
ENDLOOP.
ENDMETHOD.
Wir lesen die selektierten Daten, führen dann einen Update durch und geben die Daten zurück, damit das UI den Datensatz aktualisieren kann. Damit allerdings auch das EML funktioniert, müssen wir die beiden Methoden implementieren. In der READ Methode lassen wir uns die Daten geben und müssen den Schlüssel dann filtern, um die korrekten Sätze zu lesen. Die Implementierung ist hier sehr einfach gehalten.
METHOD read.
DATA(all) = NEW zcl_bs_demo_custom_action_qry( )->get_dummy_data( ).
LOOP AT keys INTO DATA(key).
INSERT CORRESPONDING #( all[ my_key = key-my_key ] ) INTO TABLE result.
ENDLOOP.
ENDMETHOD.
In der Update Methode befüllen wir eigentlich nur den Puffer, da die eigentliche Aktion erst beim Speichern passieren soll.
METHOD update.
INSERT LINES OF entities INTO TABLE lcl_data_buffer=>updates.
ENDMETHOD.
In der SAVE Methode führen wir dann das eigentliche Handling der Speicherung durch. In diesem Fall befüllen wir eine Tabelle, die zum Schlüssel den aktuellen Status enthält. Damit können wir Zusatzdaten zu unseren "API" Daten sichern und in der Queryklasse dazu lesen.
METHOD save.
LOOP AT lcl_data_buffer=>updates INTO DATA(update).
SELECT SINGLE FROM zbs_dmo_cstat
FIELDS status
WHERE my_key = @update-my_key
INTO @DATA(found_status).
IF sy-subrc <> 0.
INSERT zbs_dmo_cstat FROM @( VALUE #( my_key = update-my_key
status = 1 ) ).
ELSE.
UPDATE zbs_dmo_cstat FROM @( VALUE #( my_key = update-my_key
status = found_status + 1 ) ).
ENDIF.
ENDLOOP.
ENDMETHOD.
Anreicherung
Im letzten Schritt reichern wir in der Queryklasse die Daten des Ergebnisses an. Dazu lesen wir die Statustabelle und leiten das entsprechende Icon ab, dass wir ausgeben wollen.
LOOP AT result REFERENCE INTO DATA(extend).
SELECT SINGLE FROM zbs_dmo_cstat
FIELDS status
WHERE my_key = @extend->my_key
INTO @DATA(found_status).
IF sy-subrc <> 0.
extend->icon = icons-none.
CONTINUE.
ENDIF.
CASE found_status.
WHEN 1.
extend->icon = icons-one.
WHEN 2.
extend->icon = icons-two.
WHEN 3.
extend->icon = icons-three.
WHEN OTHERS.
extend->icon = icons-four.
ENDCASE.
ENDLOOP.
Ergebnis
Hier ein Beispiel wie nun die Anwendung funktioniert und sich verhält. Dabei verändern wir den Status der einzelnen Zeilen und machen zum Abschluss einen Reset des Status über eine weitere Action.
Vollständiges Beispiel
Das vollständige Beispiel aus diesem Artikel findest du bei uns im GitHub Repository im Paket ZBS_DEMO_RAP_CUSTOM_ACTION. Im Commit kannst du noch einmal die verschiedenen Änderungen anschauen, die ins Repo gekommen sind.
Fazit
Bei der Implementierung einer Aktion in einer Custom Entity kannst du dich auf das RAP Framework verlassen, musst allerdings auch das Handling selbst übernehmen. Bei einem Unmanaged Szenario allerdings auch eindeutig klar. Da von Custom Entities aktuell kein Draft unterstützt wird, reicht in vielen Fällen ein OData v2 Service, wenn du auch die Standardaktionen verwenden willst.