Behavior Implementations Part 3: Actions, Results and User-Facing Messages
Implement every kind of RAP action correctly — instance actions with results, actions with parameters, factory actions with MAPPED — and give users clear, translatable messages from message classes.
Explanation
📌 KEY POINTS
✅ Action pattern: check → MODIFY in local mode → READ → fill result.
✅ Parameters arrive in keys-%param.
✅ Factory actions use the caller's %cid and return MAPPED.
⚠️ Never trust the UI button state — use feature control.
▶ WHERE WE ARE
Part 2 covered automatic logic. Actions are the explicit business steps users and APIs trigger. This part shows the implementation pattern for each action type and how to communicate results.
━━━━━━━━━━━━━━━━━━━━
▶ 1. INSTANCE ACTION WITH RESULT
Pattern for "action approve result [1] $self":
- Optionally read and check preconditions that feature control does not cover (e.g. amount limits). Report failures per instance.
- MODIFY ENTITIES … IN LOCAL MODE for the valid instances.
- READ ENTITIES … ALL FIELDS for these instances and fill result with %tky and %param = the instance. The UI then refreshes without a reload.
IN LOCAL MODE matters: the action changes read-only fields such as Status, which consumers are not allowed to change.
Do not rely on the UI's button state: an API can call any action. Feature control rejects calls in the wrong state automatically; additional business checks belong in the action (or a precheck).
━━━━━━━━━━━━━━━━━━━━
▶ 2. ACTION WITH PARAMETER
For "action reject parameter ZD_RejectReason result [1] $self", each key carries the input in %param: keys[ … ]-%param-Reason. Validate the parameter (e.g. reason mandatory) and report a failure if missing. Fiori Elements shows a dialog with the abstract entity's fields automatically.
━━━━━━━━━━━━━━━━━━━━
▶ 3. FACTORY ACTION
For "factory action copyRequest [1]", each key has a %cid supplied by the caller. The implementation creates new instances with MODIFY ENTITIES … CREATE, using the key's %cid as the new instance's %cid, and passes the resulting MAPPED to the action's mapped parameter. Copy children with CREATE BY \_Item in the same MODIFY, referencing the new root's %cid via %cid_ref.
━━━━━━━━━━━━━━━━━━━━
▶ 4. STATIC ACTION
Static actions have no instance keys; keys contain only %cid (and %param). Typical use: create from a template, or trigger a mass process. Authorization is checked globally.
━━━━━━━━━━━━━━━━━━━━
▶ 5. MESSAGES THAT HELP USERS
- Use a message class (e.g. ZPR) with numbered texts and placeholders: new_message( id = 'ZPR' number = '010' severity = … v1 = … ). Messages become translatable and consistent.
- Bound messages: reported-purchaserequest with %tky — shown next to the instance/object page.
- Field highlighting: %element-<Field> = if_abap_behv=>mk-on.
- Unbound messages: reported-%other — for messages that concern no single instance (e.g. "3 requests approved").
- Severity: error (with failed), warning, information, success. A success message after an action ("Request 4711 approved") improves the user experience.
- Never use MESSAGE statements or pop-ups in RAP code.
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
- Create message class ZPR with the texts your actions need.
- Implement approve (instance action), reject (parameter), copyRequest (factory) as in the code example.
- Test with EML: approve two requests where one exceeds the limit — expect one success and one failure with a message.
- Test the reject dialog in Fiori preview.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER PART 3
Robust actions and meaningful messages. Part 4 moves to the saver class.
Code example
" ===== Instance action with business check and success message =====METHOD approve. READ ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest FIELDS ( RequestID TotalAmount ) WITH CORRESPONDING #( keys ) RESULT DATA(requests). DATA ok_requests LIKE requests. LOOP AT requests INTO DATA(request). IF request-TotalAmount > 1000000. " limit not covered by feature control APPEND VALUE #( %tky = request-%tky ) TO failed-purchaserequest. APPEND VALUE #( %tky = request-%tky %msg = new_message( id = 'ZPR' number = '010' " Amount &1 exceeds your limit severity = if_abap_behv_message=>severity-error v1 = request-TotalAmount ) ) TO reported-purchaserequest. ELSE. APPEND request TO ok_requests. ENDIF. ENDLOOP. CHECK ok_requests IS NOT INITIAL. MODIFY ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest UPDATE FIELDS ( Status ) WITH VALUE #( FOR r IN ok_requests ( %tky = r-%tky Status = 'A' ) ). READ ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest ALL FIELDS WITH CORRESPONDING #( ok_requests ) RESULT DATA(approved). result = VALUE #( FOR a IN approved ( %tky = a-%tky %param = a ) ). LOOP AT approved INTO DATA(a). APPEND VALUE #( %tky = a-%tky %msg = new_message( id = 'ZPR' number = '011' " Request &1 approved severity = if_abap_behv_message=>severity-success v1 = a-RequestID ) ) TO reported-purchaserequest. ENDLOOP.ENDMETHOD. " ===== Action with parameter (assumes a field RejectReason was added to the BO) =====METHOD reject. LOOP AT keys INTO DATA(key) WHERE %param-Reason IS INITIAL. APPEND VALUE #( %tky = key-%tky ) TO failed-purchaserequest. APPEND VALUE #( %tky = key-%tky %msg = new_message( id = 'ZPR' number = '012' " Enter a rejection reason severity = if_abap_behv_message=>severity-error ) ) TO reported-purchaserequest. ENDLOOP. MODIFY ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest UPDATE FIELDS ( Status RejectReason ) WITH VALUE #( FOR k IN keys WHERE ( %param-Reason IS NOT INITIAL ) ( %tky = k-%tky Status = 'R' RejectReason = k-%param-Reason ) ). READ ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(requests). result = VALUE #( FOR r IN requests ( %tky = r-%tky %param = r ) ).ENDMETHOD. " ===== Factory action: copy a request =====METHOD copyRequest. READ ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest FIELDS ( Description CurrencyCode Supplier ) WITH CORRESPONDING #( keys ) RESULT DATA(sources). MODIFY ENTITIES OF zr_purchreq IN LOCAL MODE ENTITY PurchaseRequest CREATE FIELDS ( Description CurrencyCode Supplier ) WITH VALUE #( FOR key IN keys LET src = VALUE #( sources[ %tky = key-%tky ] OPTIONAL ) IN ( %cid = key-%cid " caller's content ID Description = |Copy of { src-Description }| CurrencyCode = src-CurrencyCode Supplier = src-Supplier ) ) MAPPED DATA(new_mapped). mapped-purchaserequest = new_mapped-purchaserequest. " return new keysENDMETHOD. " ===== Unbound message (not tied to one instance) =====" APPEND VALUE #( %msg = new_message( id = 'ZPR' number = '020'" severity = if_abap_behv_message=>severity-information ) )" TO reported-%other.Real project scenario
An Approve action updated every selected request, trusting that the Fiori button was only enabled for submitted requests. An integration later called the action through the API for requests in status New and approved them, bypassing the workflow.
Two changes fixed it: instance feature control for approve (so RAP rejects calls in the wrong state regardless of the channel) and an explicit amount-limit check inside the action, reported per instance with a message from the new message class ZPR. Users also started receiving a success message "Request 4711 approved", which reduced repeated clicks and duplicate tickets.
Common mistakes
- Relying on the UI button state instead of feature control and checks in the action.
- Not returning result, so the UI shows stale data.
- Ignoring the parameter's validity in parameter actions.
- Factory actions that do not pass the caller's %cid or do not return MAPPED.
- Hard-coded message texts everywhere; messages without the instance key.
- Using MESSAGE statements or pop-ups in RAP code.
- Processing only the first key.
Best practices
- Feature control for state rules, explicit checks in the action for business limits.
- Always return the changed instances in result.
- Use IN LOCAL MODE for changes of read-only fields by the BO itself.
- One message class per BO; success messages for important actions.
- Test actions with multiple keys and with invalid instances mixed in.
Interview angle
Questions: "How do you implement an action with a result?", "How do you read an action parameter?", "How does a factory action return new instances?", "How do you send messages to the user in RAP?"
Strong answer: modify in local mode, read back and fill result with %tky and %param; parameters come in keys-%param; factory actions create with the caller's %cid and return MAPPED; messages go to reported with new_message from a message class, bound via %tky and highlighted via %element, or unbound via %other. Never rely on the UI for state rules.
Practice this lesson
Quick check
1. An API approved requests in status New although the Fiori Approve button was disabled. What is the correct fix?
2. A behavior pool implements a custom action that is defined as a factory action in the behavior definition. What distinguishes a factory action from a regular instance action?
3. In a managed RAP business object, a developer wants to prevent the 'Approve' action from being executed when the order status is already 'Approved'. Where should this logic be implemented?
Interview questions
1. An integration called the approve action for requests in status New and approved them, bypassing the workflow. The Fiori button was correctly disabled. What went wrong?
Answer it in your own words first, then compare.
2. Implement a 'copy request' feature in RAP. Which action type do you use and how does it return the new instance?
Answer it in your own words first, then compare.
Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.
More lessons in this topic
- Behavior Implementations Part 1: The Behavior Pool — Handler and Saver Classes
- Behavior Implementations Part 2: Determinations and Validations Without Side Effects
- Behavior Implementations Part 4: Save Sequence, Late Numbering and Unmanaged Save
- Behavior Implementations Part 5: Performance, Unit Testing and Debugging