Popup Actions in RAP: Collecting User Input Before Execution

Category
RAP
Published
Author
Johannes

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:

  1. Abstract Entity: Defines the popup structure (fields, data types)
  2. Behavior Definition: Links the action with the parameter
  3. 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:

AnnotationEffect
@EndUserText.labelField label in popup
@Consumption.valueHelpDefinitionDropdown/Value Help
@UI.multiLineText: trueMulti-line text field
@Semantics.amount.currencyCodeCurrency 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 FlightBooking
persistent table zflight_book
lock master
authorization 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

AnnotationEffectExample
@UI.defaultValuePre-filled value'EUR'
@UI.placeholderPlaceholder text'Enter remarks...'
@UI.multiLineTextMulti-line input fieldFor long texts
@UI.hiddenHide fieldTechnical fields
@Consumption.valueHelpDefinitionValue Help/DropdownSelection fields

Different Field Types in Popup

@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: true
Justification : abap.string(1000);

Currency Amount

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

Quantity Field

@EndUserText.label: 'Quantity'
@Semantics.quantity.unitOfMeasure: 'Unit'
Quantity : abap.quan(13,3);
@EndUserText.label: 'Unit'
@Semantics.unitOfMeasure: true
Unit : 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

" Check required field
IF <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 field
Reason : abap.char(2);

2. Sensible Default Values

@UI.defaultValue: 'EUR' " For currency
@UI.defaultValue: 'true' " For checkbox
@UI.defaultValue: '#(TODAY)' " For date

3. Value Helps for Selection Fields

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

4. Clear Error Messages

" ❌ Bad
text = 'Input error'
" ✅ Good
text = |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.

ScenarioRecommendation
1-2 additional valuesPopup Action
Complex formsSeparate Object Page
Mass changesInline edit or separate app
Confirmation without inputAction without parameter

Summary

Popup Actions in RAP enable interactive user input before execution:

ComponentPurpose
Abstract EntityDefines popup fields and data types
Behavior DefinitionLinks Action with parameter
ImplementationReads %param and processes values
UI AnnotationsControls 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.