Popup-Aktionen in RAP: Benutzereingaben vor der Ausführung abfragen

Kategorie
RAP
Veröffentlicht
Autor
Johannes

Manchmal reicht ein einfacher Button-Klick nicht aus. Bevor eine Buchung storniert wird, soll der Benutzer einen Grund angeben. Vor einer Umbuchung müssen neue Daten erfasst werden. Popup-Aktionen in RAP ermöglichen genau das: strukturierte Benutzereingaben vor der Ausführung einer Action.

Das Konzept: Action mit Parameter-Entity

Eine Popup-Aktion besteht aus drei Komponenten:

  1. Abstract Entity: Definiert die Struktur des Popups (Felder, Datentypen)
  2. Behavior Definition: Verknüpft die Action mit dem Parameter
  3. Implementation: Verarbeitet die eingegebenen Werte
┌─────────────────────────────────────────────────────────────────┐
│ Buchung stornieren [X] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Stornierungsgrund * │
│ ┌─────────────────────────────────────┐ │
│ │ Kundenänderung ▼ │ │
│ └─────────────────────────────────────┘ │
│ │
│ Bemerkung │
│ ┌─────────────────────────────────────┐ │
│ │ Kunde hat umgebucht auf späteren │ │
│ │ Flug nach München. │ │
│ └─────────────────────────────────────┘ │
│ │
│ ☑ Kunde per E-Mail benachrichtigen │
│ │
│ Erstattungsbetrag │
│ ┌───────────────┐ EUR │
│ │ 450,00 │ │
│ └───────────────┘ │
│ │
│ [ Abbrechen ] [ Stornieren ] │
└─────────────────────────────────────────────────────────────────┘

Schritt 1: Abstract Entity definieren

Die Abstract Entity beschreibt die Felder des Popup-Dialogs:

@EndUserText.label: 'Stornierungsdaten'
define abstract entity ZA_CancelBookingParams
{
@EndUserText.label: 'Stornierungsgrund'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_CancellationReasonVH', element: 'ReasonCode' }
}]
CancellationReason : abap.char(2);
@EndUserText.label: 'Bemerkung'
@UI.multiLineText: true
Remarks : abap.string(500);
@EndUserText.label: 'Kunde benachrichtigen'
NotifyCustomer : abap_boolean;
@EndUserText.label: 'Erstattungsbetrag'
@Semantics.amount.currencyCode: 'Currency'
RefundAmount : abap.curr(15,2);
@EndUserText.label: 'Währung'
@Semantics.currencyCode: true
Currency : abap.cuky;
}

Wichtige Annotationen für das Popup:

AnnotationWirkung
@EndUserText.labelFeldbezeichnung im Popup
@Consumption.valueHelpDefinitionDropdown/Value Help
@UI.multiLineText: trueMehrzeiliges Textfeld
@Semantics.amount.currencyCodeWährungsfeld mit Formatierung

Schritt 2: Behavior Definition

Verknüpfen Sie die Action mit dem Parameter:

managed implementation in class zbp_i_flightbooking unique;
strict ( 2 );
define behavior for ZI_FlightBooking alias FlightBooking
persistent table zflight_book
lock master
authorization master ( instance )
{
// Standard CRUD
create;
update;
delete;
// Action MIT Parameter-Entity
action cancelBooking
parameter ZA_CancelBookingParams
result [1] $self;
// Action ohne Parameter (zum Vergleich)
action confirmBooking result [1] $self;
// Validierung für Stornierung
validation validateCancellation on save
{ field BookingStatus; }
}

Das Schlüsselwort parameter bewirkt, dass Fiori Elements automatisch einen Dialog generiert.

Schritt 3: Implementation der Action

In der Behavior Implementation Class verarbeiten Sie die Parameter:

CLASS lhc_flightbooking DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS cancelBooking FOR MODIFY
IMPORTING keys FOR ACTION FlightBooking~cancelBooking
RESULT result.
ENDCLASS.
CLASS lhc_flightbooking IMPLEMENTATION.
METHOD cancelBooking.
" Aktuelle Daten lesen
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
FIELDS ( booking_id booking_status flight_price currency_code )
WITH CORRESPONDING #( keys )
RESULT DATA(bookings).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Buchung aus Ergebnis holen
READ TABLE bookings INTO DATA(booking)
WITH KEY booking_id = <key>-booking_id.
IF sy-subrc <> 0.
" Buchung nicht gefunden
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Buchung nicht gefunden' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" === Parameter aus dem Popup auslesen ===
DATA(cancel_reason) = <key>-%param-CancellationReason.
DATA(remarks) = <key>-%param-Remarks.
DATA(notify) = <key>-%param-NotifyCustomer.
DATA(refund_amount) = <key>-%param-RefundAmount.
DATA(currency) = <key>-%param-Currency.
" Validierung: Pflichtfeld Stornierungsgrund
IF cancel_reason IS INITIAL.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Stornierungsgrund ist ein Pflichtfeld' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Validierung: Erstattung nicht höher als Buchungspreis
IF refund_amount > booking-flight_price.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Erstattung kann nicht höher als {
booking-flight_price } { booking-currency_code
} sein| )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Status auf Storniert setzen
MODIFY ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
UPDATE FIELDS ( booking_status cancellation_reason remarks )
WITH VALUE #( (
%tky = <key>-%tky
booking_status = 'X' " Cancelled
cancellation_reason = cancel_reason
remarks = remarks
) ).
" Optional: Kunde benachrichtigen
IF notify = abap_true.
send_cancellation_email(
booking_id = booking-booking_id
reason = cancel_reason
remarks = remarks
).
ENDIF.
" Optional: Erstattung verarbeiten
IF refund_amount > 0.
process_refund(
booking_id = booking-booking_id
amount = refund_amount
currency = currency
).
ENDIF.
ENDLOOP.
" Aktualisierte Daten zurückgeben
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_bookings).
result = VALUE #( FOR bk IN updated_bookings (
%tky = bk-%tky
%param = bk
) ).
" Success-Nachricht
APPEND VALUE #(
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = |{ lines( updated_bookings ) } Buchung(en) storniert| )
) TO reported-flightbooking.
ENDMETHOD.
ENDCLASS.

UI-Annotationen für das Popup-Layout

Verfeinern Sie das Popup-Layout mit zusätzlichen Annotationen:

@EndUserText.label: 'Stornierungsdaten'
define abstract entity ZA_CancelBookingParams
{
@EndUserText.label: 'Stornierungsgrund'
@UI.defaultValue: 'KA'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_CancellationReasonVH', element: 'ReasonCode' },
useForValidation: true
}]
CancellationReason : abap.char(2);
@EndUserText.label: 'Bemerkung'
@UI.multiLineText: true
@UI.placeholder: 'Optional: Zusätzliche Informationen zur Stornierung'
Remarks : abap.string(500);
@EndUserText.label: 'Kunde benachrichtigen'
@UI.defaultValue: 'true'
NotifyCustomer : abap_boolean;
@EndUserText.label: 'Erstattungsbetrag'
@Semantics.amount.currencyCode: 'Currency'
@UI.defaultValue: '0.00'
RefundAmount : abap.curr(15,2);
@EndUserText.label: 'Währung'
@Semantics.currencyCode: true
@UI.defaultValue: 'EUR'
@Consumption.valueHelpDefinition: [{
entity: { name: 'I_Currency', element: 'Currency' }
}]
Currency : abap.cuky;
}

Wichtige UI-Annotationen

AnnotationWirkungBeispiel
@UI.defaultValueVorbelegter Wert'EUR'
@UI.placeholderPlatzhaltertext'Bemerkung eingeben...'
@UI.multiLineTextMehrzeiliges EingabefeldFür lange Texte
@UI.hiddenFeld versteckenTechnische Felder
@Consumption.valueHelpDefinitionValue Help/DropdownAuswahlfelder

Verschiedene Feldtypen im Popup

@EndUserText.label: 'Priorität'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_PriorityVH', element: 'Priority' }
}]
Priority : abap.char(1);

Datumsfeld

@EndUserText.label: 'Neues Flugdatum'
@UI.defaultValue: '#(TODAY)'
NewFlightDate : abap.dats;

Checkbox

@EndUserText.label: 'Bestätigung per E-Mail senden'
@UI.defaultValue: 'false'
SendConfirmation : abap_boolean;

Mehrzeiliger Text

@EndUserText.label: 'Begründung'
@UI.multiLineText: true
Justification : abap.string(1000);

Währungsbetrag

@EndUserText.label: 'Betrag'
@Semantics.amount.currencyCode: 'Currency'
Amount : abap.curr(15,2);
@EndUserText.label: 'Währung'
@Semantics.currencyCode: true
Currency : abap.cuky;

Mengenfeld

@EndUserText.label: 'Anzahl'
@Semantics.quantity.unitOfMeasure: 'Unit'
Quantity : abap.quan(13,3);
@EndUserText.label: 'Einheit'
@Semantics.unitOfMeasure: true
Unit : abap.unit(3);

Vollständiges Flugbuchungs-Beispiel

Hier ein komplettes End-to-End Beispiel für eine Umbuchungs-Aktion:

Abstract Entity: Umbuchungsparameter

@EndUserText.label: 'Umbuchungsparameter'
define abstract entity ZA_RescheduleParams
{
@EndUserText.label: 'Neues Flugdatum'
NewFlightDate : abap.dats;
@EndUserText.label: 'Neue Flugnummer'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_FlightConnectionVH', element: 'FlightId' }
}]
NewFlightId : abap.numc(4);
@EndUserText.label: 'Preisdifferenz zahlen'
AcceptPriceDiff : abap_boolean;
@EndUserText.label: 'Bemerkung'
@UI.multiLineText: true
Remarks : abap.string(500);
}

Behavior Definition

define behavior for ZI_FlightBooking alias FlightBooking
{
action rescheduleBooking
parameter ZA_RescheduleParams
result [1] $self;
}

Projection Behavior

projection implementation in class zbp_c_flightbooking unique;
define behavior for ZC_FlightBooking alias FlightBooking
{
use action rescheduleBooking;
}

Implementation

METHOD rescheduleBooking.
" Aktuelle Buchungen lesen
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(bookings).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
READ TABLE bookings INTO DATA(booking)
WITH KEY booking_id = <key>-booking_id.
" === Parameter aus dem Popup ===
DATA(new_date) = <key>-%param-NewFlightDate.
DATA(new_flight) = <key>-%param-NewFlightId.
DATA(accept_diff) = <key>-%param-AcceptPriceDiff.
DATA(remarks) = <key>-%param-Remarks.
" Validierung: Datum in der Zukunft
IF new_date < cl_abap_context_info=>get_system_date( ).
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Flugdatum muss in der Zukunft liegen' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Preisdifferenz berechnen
DATA(new_price) = get_flight_price( new_flight ).
DATA(price_diff) = new_price - booking-flight_price.
" Preiserhöhung ohne Akzeptanz = Fehler
IF price_diff > 0 AND accept_diff = abap_false.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Preisdifferenz von { price_diff } {
booking-currency_code } muss akzeptiert werden| )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Umbuchung durchführen
MODIFY ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
UPDATE FIELDS ( flight_date flight_id flight_price remarks )
WITH VALUE #( (
%tky = <key>-%tky
flight_date = new_date
flight_id = new_flight
flight_price = new_price
remarks = remarks
) ).
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR bk IN updated (
%tky = bk-%tky
%param = bk
) ).
" Success-Nachricht
APPEND VALUE #(
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = |{ lines( updated ) } Buchung(en) umgebucht| )
) TO reported-flightbooking.
ENDMETHOD.

UI Integration in CDS View

@EndUserText.label: 'Flugbuchung'
define view entity ZC_FlightBooking
as projection on ZI_FlightBooking
{
@UI.lineItem: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'cancelBooking',
label: 'Stornieren', criticality: #NEGATIVE },
{ type: #FOR_ACTION, dataAction: 'rescheduleBooking',
label: 'Umbuchen' }
]
@UI.identification: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'rescheduleBooking',
label: 'Umbuchen' }
]
key BookingId,
// weitere Felder...
}

Validierung von Popup-Eingaben

Im Action-Code (empfohlen)

" Pflichtfeld prüfen
IF <key>-%param-CancellationReason IS INITIAL.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Stornierungsgrund ist erforderlich' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
RETURN.
ENDIF.

Mit Validation im Behavior

define behavior for ZI_FlightBooking
{
action cancelBooking
parameter ZA_CancelBookingParams
result [1] $self;
" Validation wird nach der Action ausgeführt
validation validateAfterCancel on save
{ field booking_status; }
}

Best Practices für Popup-Aktionen

1. Pflichtfelder kennzeichnen

@EndUserText.label: 'Grund *' " Sternchen im Label
@ObjectModel.mandatory: true " Pflichtfeld
Reason : abap.char(2);

2. Sinnvolle Standardwerte

@UI.defaultValue: 'EUR' " Für Währung
@UI.defaultValue: 'true' " Für Checkbox
@UI.defaultValue: '#(TODAY)' " Für Datum

3. Value Helps für Auswahlfelder

@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_ReasonVH', element: 'Code' },
useForValidation: true
}]

4. Klare Fehlermeldungen

" ❌ Schlecht
text = 'Fehler bei der Eingabe'
" ✅ Gut
text = |Erstattung ({ refund_amount } { currency }) kann nicht höher als Buchungspreis ({ booking-flight_price } { booking-currency_code }) sein|

5. Popup-Größe begrenzen

Maximal 5-6 Felder pro Popup. Bei komplexeren Eingaben: Wizard oder separate Seite.

SzenarioEmpfehlung
1-2 zusätzliche WertePopup-Action
Komplexe FormulareSeparate Object Page
MassenänderungInline-Edit oder eigene App
Bestätigung ohne EingabeAction ohne Parameter

Zusammenfassung

Popup-Aktionen in RAP ermöglichen interaktive Benutzereingaben vor der Ausführung:

KomponenteAufgabe
Abstract EntityDefiniert Popup-Felder und Datentypen
Behavior DefinitionVerknüpft Action mit parameter
ImplementationLiest %param und verarbeitet Werte
UI-AnnotationenSteuert Aussehen und Verhalten

Mit wenigen Annotationen und klarer Struktur erstellen Sie benutzerfreundliche Dialoge, die nahtlos in Fiori Elements integriert sind.

Weiterführende Artikel: RAP Actions und Functions für Action-Grundlagen, RAP Nachrichten für Fehlerbehandlung, RAP CDS Pattern für vereinfachte Architektur und CDS Annotations für weitere Annotationsmöglichkeiten.