Sometimes a simple button click is not enough. Before a booking is cancelled, the user should provide a reason. Before a rebooking, new data needs to be entered. Popup Actions in RAP enable exactly this: structured user input before executing an action.
The Concept: Action with Parameter Entity
A popup action consists of three components:
- Abstract Entity: Defines the popup structure (fields, data types)
- Behavior Definition: Links the action with the parameter
- Implementation: Processes the entered values
┌─────────────────────────────────────────────────────────────────┐│ Cancel Booking [X] │├─────────────────────────────────────────────────────────────────┤│ ││ Cancellation Reason * ││ ┌─────────────────────────────────────┐ ││ │ Customer Change ▼ │ ││ └─────────────────────────────────────┘ ││ ││ Remarks ││ ┌─────────────────────────────────────┐ ││ │ Customer rebooked to later flight │ ││ │ to Munich. │ ││ └─────────────────────────────────────┘ ││ ││ ☑ Notify customer by email ││ ││ Refund Amount ││ ┌───────────────┐ EUR ││ │ 450.00 │ ││ └───────────────┘ ││ ││ [ Cancel ] [ Confirm ] │└─────────────────────────────────────────────────────────────────┘Step 1: Define Abstract Entity
The Abstract Entity describes the popup dialog fields:
@EndUserText.label: 'Cancellation Data'define abstract entity ZA_CancelBookingParams{ @EndUserText.label: 'Cancellation Reason' @Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_CancellationReasonVH', element: 'ReasonCode' } }] CancellationReason : abap.char(2);
@EndUserText.label: 'Remarks' @UI.multiLineText: true Remarks : abap.string(500);
@EndUserText.label: 'Notify Customer' NotifyCustomer : abap_boolean;
@EndUserText.label: 'Refund Amount' @Semantics.amount.currencyCode: 'Currency' RefundAmount : abap.curr(15,2);
@EndUserText.label: 'Currency' @Semantics.currencyCode: true Currency : abap.cuky;}Important annotations for the popup:
| Annotation | Effect |
|---|---|
@EndUserText.label | Field label in popup |
@Consumption.valueHelpDefinition | Dropdown/Value Help |
@UI.multiLineText: true | Multi-line text field |
@Semantics.amount.currencyCode | Currency field with formatting |
Step 2: Behavior Definition
Link the action with the parameter:
managed implementation in class zbp_i_flightbooking unique;strict ( 2 );
define behavior for ZI_FlightBooking alias FlightBookingpersistent table zflight_booklock masterauthorization master ( instance ){ // Standard CRUD create; update; delete;
// Action WITH Parameter Entity action cancelBooking parameter ZA_CancelBookingParams result [1] $self;
// Action without parameter (for comparison) action confirmBooking result [1] $self;
// Validation for cancellation validation validateCancellation on save { field BookingStatus; }}The parameter keyword causes Fiori Elements to automatically generate a dialog.
Step 3: Action Implementation
In the Behavior Implementation Class, process the parameters:
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. " Read current data 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>). " Get booking from result READ TABLE bookings INTO DATA(booking) WITH KEY booking_id = <key>-booking_id.
IF sy-subrc <> 0. " Booking not found APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Booking not found' ) ) TO reported-flightbooking. APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking. CONTINUE. ENDIF.
" === Read parameters from popup === 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.
" Validation: Required field cancellation reason IF cancel_reason IS INITIAL. APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Cancellation reason is a required field' ) ) TO reported-flightbooking. APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking. CONTINUE. ENDIF.
" Validation: Refund not higher than booking price IF refund_amount > booking-flight_price. APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Refund cannot be higher than { booking-flight_price } { booking-currency_code }| ) ) TO reported-flightbooking. APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking. CONTINUE. ENDIF.
" Set status to cancelled 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: Notify customer IF notify = abap_true. send_cancellation_email( booking_id = booking-booking_id reason = cancel_reason remarks = remarks ). ENDIF.
" Optional: Process refund IF refund_amount > 0. process_refund( booking_id = booking-booking_id amount = refund_amount currency = currency ). ENDIF. ENDLOOP.
" Return updated data 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 message APPEND VALUE #( %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = |{ lines( updated_bookings ) } booking(s) cancelled| ) ) TO reported-flightbooking. ENDMETHOD.ENDCLASS.UI Annotations for Popup Layout
Refine the popup layout with additional annotations:
@EndUserText.label: 'Cancellation Data'define abstract entity ZA_CancelBookingParams{ @EndUserText.label: 'Cancellation Reason' @UI.defaultValue: 'KA' @Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_CancellationReasonVH', element: 'ReasonCode' }, useForValidation: true }] CancellationReason : abap.char(2);
@EndUserText.label: 'Remarks' @UI.multiLineText: true @UI.placeholder: 'Optional: Additional cancellation information' Remarks : abap.string(500);
@EndUserText.label: 'Notify Customer' @UI.defaultValue: 'true' NotifyCustomer : abap_boolean;
@EndUserText.label: 'Refund Amount' @Semantics.amount.currencyCode: 'Currency' @UI.defaultValue: '0.00' RefundAmount : abap.curr(15,2);
@EndUserText.label: 'Currency' @Semantics.currencyCode: true @UI.defaultValue: 'EUR' @Consumption.valueHelpDefinition: [{ entity: { name: 'I_Currency', element: 'Currency' } }] Currency : abap.cuky;}Important UI Annotations
| Annotation | Effect | Example |
|---|---|---|
@UI.defaultValue | Pre-filled value | 'EUR' |
@UI.placeholder | Placeholder text | 'Enter remarks...' |
@UI.multiLineText | Multi-line input field | For long texts |
@UI.hidden | Hide field | Technical fields |
@Consumption.valueHelpDefinition | Value Help/Dropdown | Selection fields |
Different Field Types in Popup
Dropdown with Value Help
@EndUserText.label: 'Priority'@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_PriorityVH', element: 'Priority' }}]Priority : abap.char(1);Date Field
@EndUserText.label: 'New Flight Date'@UI.defaultValue: '#(TODAY)'NewFlightDate : abap.dats;Checkbox
@EndUserText.label: 'Send Email Confirmation'@UI.defaultValue: 'false'SendConfirmation : abap_boolean;Multi-line Text
@EndUserText.label: 'Justification'@UI.multiLineText: trueJustification : abap.string(1000);Currency Amount
@EndUserText.label: 'Amount'@Semantics.amount.currencyCode: 'Currency'Amount : abap.curr(15,2);
@EndUserText.label: 'Currency'@Semantics.currencyCode: trueCurrency : abap.cuky;Quantity Field
@EndUserText.label: 'Quantity'@Semantics.quantity.unitOfMeasure: 'Unit'Quantity : abap.quan(13,3);
@EndUserText.label: 'Unit'@Semantics.unitOfMeasure: trueUnit : abap.unit(3);Complete Flight Booking Example
Here’s a complete end-to-end example for a rebooking action:
Abstract Entity: Rebooking Parameters
@EndUserText.label: 'Rebooking Parameters'define abstract entity ZA_RescheduleParams{ @EndUserText.label: 'New Flight Date' NewFlightDate : abap.dats;
@EndUserText.label: 'New Flight Number' @Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_FlightConnectionVH', element: 'FlightId' } }] NewFlightId : abap.numc(4);
@EndUserText.label: 'Accept Price Difference' AcceptPriceDiff : abap_boolean;
@EndUserText.label: 'Remarks' @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. " Read current bookings 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.
" === Parameters from popup === DATA(new_date) = <key>-%param-NewFlightDate. DATA(new_flight) = <key>-%param-NewFlightId. DATA(accept_diff) = <key>-%param-AcceptPriceDiff. DATA(remarks) = <key>-%param-Remarks.
" Validation: Date in the future 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 = 'Flight date must be in the future' ) ) TO reported-flightbooking. APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking. CONTINUE. ENDIF.
" Calculate price difference DATA(new_price) = get_flight_price( new_flight ). DATA(price_diff) = new_price - booking-flight_price.
" Price increase without acceptance = error 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 = |Price difference of { price_diff } { booking-currency_code } must be accepted| ) ) TO reported-flightbooking. APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking. CONTINUE. ENDIF.
" Perform rebooking 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.
" Return result 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 message APPEND VALUE #( %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = |{ lines( updated ) } booking(s) rescheduled| ) ) TO reported-flightbooking.ENDMETHOD.UI Integration in CDS View
@EndUserText.label: 'Flight Booking'define view entity ZC_FlightBooking as projection on ZI_FlightBooking{ @UI.lineItem: [ { position: 10 }, { type: #FOR_ACTION, dataAction: 'cancelBooking', label: 'Cancel', criticality: #NEGATIVE }, { type: #FOR_ACTION, dataAction: 'rescheduleBooking', label: 'Reschedule' } ] @UI.identification: [ { position: 10 }, { type: #FOR_ACTION, dataAction: 'rescheduleBooking', label: 'Reschedule' } ] key BookingId,
// additional fields...}Validating Popup Input
In Action Code (Recommended)
" Check required fieldIF <key>-%param-CancellationReason IS INITIAL. APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Cancellation reason is required' ) ) TO reported-flightbooking. APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking. RETURN.ENDIF.With Validation in Behavior
define behavior for ZI_FlightBooking{ action cancelBooking parameter ZA_CancelBookingParams result [1] $self;
" Validation is executed after the action validation validateAfterCancel on save { field booking_status; }}Best Practices for Popup Actions
1. Mark Required Fields
@EndUserText.label: 'Reason *' " Asterisk in label@ObjectModel.mandatory: true " Required fieldReason : abap.char(2);2. Sensible Default Values
@UI.defaultValue: 'EUR' " For currency@UI.defaultValue: 'true' " For checkbox@UI.defaultValue: '#(TODAY)' " For date3. Value Helps for Selection Fields
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_ReasonVH', element: 'Code' }, useForValidation: true}]4. Clear Error Messages
" ❌ Badtext = 'Input error'
" ✅ Goodtext = |Refund ({ refund_amount } { currency }) cannot be higher than booking price ({ booking-flight_price } { booking-currency_code })|5. Limit Popup Size
Maximum 5-6 fields per popup. For more complex input: use wizard or separate page.
Popup vs. Inline Editing
| Scenario | Recommendation |
|---|---|
| 1-2 additional values | Popup Action |
| Complex forms | Separate Object Page |
| Mass changes | Inline edit or separate app |
| Confirmation without input | Action without parameter |
Summary
Popup Actions in RAP enable interactive user input before execution:
| Component | Purpose |
|---|---|
| Abstract Entity | Defines popup fields and data types |
| Behavior Definition | Links Action with parameter |
| Implementation | Reads %param and processes values |
| UI Annotations | Controls appearance and behavior |
With just a few annotations and clear structure, you create user-friendly dialogs that integrate seamlessly into Fiori Elements.
Related articles: RAP Actions and Functions for action fundamentals, RAP Messages for error handling, RAP CDS Pattern for simplified architecture, and CDS Annotations for more annotation options.