Introduction:
In this blog, we explore how RAP and Adobe Forms can be integrated to deliver a fully digital, end-to-end document generation experience directly from Fiori. By leveraging custom actions, virtual elements, and media-stream handling, we can trigger Adobe Form generation from the UI, manage the file output within RAP, and enable users to download the PDF instantly—ensuring a clean, cloud-ready and future-proof architecture.
Solution Overview:
In modern SAP applications, combining RAP (RESTful ABAP Programming Model) with Adobe Forms unlocks a clean, scalable way to generate and manage documents directly from Fiori. By extending the Fiori Elements UI with a custom action through the Integrative Extension Controller, we can trigger Adobe form generation seamlessly from the frontend. Using RAP virtual elements and media-stream properties, the generated PDF can be stored, retrieved, and downloaded. This approach delivers a fully end-to-end, cloud-ready solution for document creation, storage, and user-triggered actions—all built natively within RAP.
Root Entity:
@AccessControl.authorizationCheck: #NOT_REQUIRED // No authorization checks required for read access
@EndUserText.label: ‘ROOT ENTITY FOR ADOBE FORM’ // Description for display in tools
@Metadata.ignorePropagatedAnnotations: true // Avoid unwanted propagated annotations
define root view entity ZRV_AFORM
as select from vbap // Based on VBAP (Sales Order Item Data)
{
/*———————————————————–
Key Fields for RAP Root Entity
———————————————————–*/
key vbeln, // Sales Document Number
posnr, // Item Number
matnr, // Material Number
/*———————————————————–
Display text: Shown in UI for SEGW/Fiori preview
Not used for actual functionality, only for readability.
———————————————————–*/
‘Preview(SEGW)’ as ShowPDF,
/*———————————————————–
Dynamic URL to download the Adobe Form PDF
Format:
/sap/opu/odata/SAP/ZRAP_ADOBE_FORM_SRV/adobeSet(‘<VBELN>’)/$value
This URL triggers GET_STREAM in the OData service.
———————————————————–*/
concat(
‘/sap/opu/odata/SAP/ZRAP_ADOBE_FORM_SRV/adobeSet(”’,
concat( vbeln, ”’)’) /$value’
) as LinkToPDF
}
OData Service:
Include vbeln as a property in your entity type, and when the user provides a sales document number, the service should return the corresponding Adobe Form data in the response, implement GET_STREAM Method .
Implementation:
METHOD /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM.
“—————————————————————
” Data Declarations
“—————————————————————
DATA: lv_funcname TYPE funcname, ” Generated FM name for Adobe Form
ls_o_p TYPE sfpoutputparams, ” Output parameters for Adobe Forms
ls_formoutput TYPE fpformoutput, ” Adobe form output structure
ls_stream TYPE ty_s_media_resource, ” Stream returned to OData
gv_bin_file TYPE xstring. ” PDF binary data (unused variable)
“—————————————————————
” Read Input Key from OData Request
” Example: /sap/opu/odata/SRV/FormSet(vbeln=’12345′)/$value
“—————————————————————
DATA(lv_vbeln) = VALUE vbeln_vl(
it_key_tab[ name = ‘vbeln’ ]-value OPTIONAL ).
“—————————————————————
” STEP 1: Retrieve Adobe Form Generated Function Module
“—————————————————————
TRY.
CALL FUNCTION ‘FP_FUNCTION_MODULE_NAME’
EXPORTING
i_name = ‘ZSALES_FORM_ITEMS’ ” Adobe Form interface name
IMPORTING
e_funcname = lv_funcname. ” Generated FM name
IF sy-subrc <> 0.
RETURN. ” Stop processing if FM not found
ENDIF.
“———————————————————–
” STEP 2: Adobe Form Output Setup (Request PDF Only)
“———————————————————–
ls_o_p-nodialog = abap_true. ” No dialog popups
ls_o_p-getpdf = abap_true. ” Request PDF output
ls_o_p-nopreview = abap_true. ” Skip preview
” Open Adobe spool job
CALL FUNCTION ‘FP_JOB_OPEN’
CHANGING
ie_outputparams = ls_o_p
EXCEPTIONS
cancel = 1
usage_error = 2
system_error = 3
internal_error = 4
OTHERS = 5.
CATCH cx_root.
” Ideally log or raise error
RETURN.
ENDTRY.
“—————————————————————
” STEP 3: Call Adobe Form Generated Function Module
“—————————————————————
CALL FUNCTION lv_funcname
EXPORTING
p_vbeln = lv_vbeln ” Sales document number
IMPORTING
/1bcdwb/formoutput = ls_formoutput ” PDF Output (XSTRING)
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
RETURN. ” Handle form rendering errors
ENDIF.
“—————————————————————
” STEP 4: Close Adobe Print Job
“—————————————————————
CALL FUNCTION ‘FP_JOB_CLOSE’
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
RETURN.
ENDIF.
“—————————————————————
” STEP 5: Populate OData Media Stream
“—————————————————————
ls_stream-mime_type = ‘application/pdf’. ” MIME type for PDF
ls_stream-value = ls_formoutput-pdf. ” XSTRING PDF data
“—————————————————————
” STEP 6: Set Filename in Response Header
“—————————————————————
DATA: ls_header TYPE ihttpnvp,
lv_filename TYPE string.
” Dynamic file name e.g. SalesDoc_80001234.pdf
lv_filename = |SalesDoc_{ lv_vbeln }.pdf|.
ls_header-name = ‘Content-Disposition’.
ls_header-value = |attachment; filename=”{ lv_filename }”|.
” Add header to OData HTTP response
me->set_header( is_header = ls_header ).
“—————————————————————
” STEP 7: Return Stream to OData Framework
“—————————————————————
CALL METHOD me->copy_data_to_ref
EXPORTING
is_data = ls_stream
CHANGING
cr_data = er_stream.
ENDMETHOD.
Projection View:
To expose the Adobe Form download link in the Fiori UI, I used a projection view with ui.lineItem and type: WITH_URL so the PDF URL becomes directly clickable in the list and object page. The actual PDF content BASE64 is not stored in the CDS view; instead, I used a RAP virtual element attach calculated by an ABAP class for Custom Action. This virtual element lets me fetch and return the generated Adobe Form dynamically at runtime without persisting the binary in the database.
@AccessControl.authorizationCheck: #NOT_REQUIRED // No auth checks for read access
@EndUserText.label: ‘PV FOR ADOBE FORM’ // Description of the view
@Metadata.ignorePropagatedAnnotations: true // Prevent annotation inheritance from base view
define root view entity ZPV_AFORM
as projection on ZRV_AFORM // Projection on RAP consumption view
{
/*———————————————————–
Key Field: Sales Document (VBELN)
Displayed in UI (List + Object Page)
———————————————————–*/
@ui.facet: [{ type: #IDENTIFICATION_REFERENCE }]
@ui.lineItem: [{ position: 10 }]
@ui.identification: [{ position: 10 }]
key vbeln,
/*———————————————————–
Item Number
———————————————————–*/
@ui.lineItem: [{ position: 20 }]
@ui.identification: [{ position: 20 }]
posnr,
/*———————————————————–
Material
———————————————————–*/
@ui.lineItem: [{ position: 30 }]
@ui.identification: [{ position: 30 }]
matnr,
/*———————————————————–
PDF Link Column (UI: Display as clickable URL)
This displays a clickable link “Show PDF” in the List Report.
———————————————————–*/
@ui.lineItem: [{
position: 10,
type: #WITH_URL, // URL-enabled column
url: ‘LinkToPDF’ // Field used as hyperlink
}]
@ui.identification: [{
position: 61,
type: #WITH_URL,
url: ‘LinkToPDF’
}]
ShowPDF, // UI label for the clickable link
/*———————————————————–
URL Field – Populated in Behavior Implementation
Used by Fiori to download/open the PDF.
———————————————————–*/
LinkToPDF,
/*———————————————————–
Virtual Field for Base64 PDF content
RAP calculates this via ZCL_ADOBEFORMM class.
———————————————————–*/
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_ADOBEFORMM’
virtual attach : abap.string(0) // Base64 PDF to be fetched on-demand
}
Virtual Elements Implementation:
CLASS zcl_adobeformm IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~calculate.
” Internal table for virtual elements defined in projection view
DATA: lt_virtual TYPE TABLE OF zpv_aform,
lv_base64 TYPE string.
” Move original RAP data into a working virtual table
lt_virtual = CORRESPONDING #( it_original_data ).
” Loop over each row to calculate the virtual element (Base64 PDF)
LOOP AT lt_virtual INTO DATA(ls_virtual).
” Get Sales Order number from the current row
DATA(lv_vbeln) = ls_virtual-vbeln.
” Create instance of Adobe Form handler class
DATA(lo_inst) = NEW zcl_adobeform( ).
” Call Adobe Form generator to get raw PDF (XSTRING)
DATA(rv_pdf) = lo_inst->get_data(
EXPORTING
p_vbeln = lv_vbeln ” Sales Order
).
” Convert PDF (XSTRING) → Base64 string, required for RAP media download
CALL FUNCTION ‘SCMS_BASE64_ENCODE_STR’
EXPORTING
input = rv_pdf
IMPORTING
output = lv_base64.
” Assign Base64 string to virtual element field
ls_virtual-attach = lv_base64.
ENDLOOP.
” Move calculated data back to RAP consumption structure
ct_calculated_data = CORRESPONDING #( lt_virtual ).
ENDMETHOD.
ENDCLASS.
To retrieve the Adobe Form as an XSTRING, I implemented a dedicated ABAP class responsible for generating and returning the PDF content. The implementation code is as follows:
method GET_DATA.
” Adobe Form name
DATA(lv_form_name) = CONV tdsfname( ‘ZSALES_FORM_ITEMS’ ).
” Name of the generated function module for the form
DATA(lv_form_fm_name) = VALUE rs38l_fnam( ).
” Structures for Adobe Form parameters
DATA(ls_docparams) = VALUE sfpdocparams( ).
DATA(ls_outputparams) = VALUE sfpoutputparams( ).
DATA(ls_formoutput) = VALUE fpformoutput( ).
*——————————————————————–*
* STEP 1: Get the Generated Function Module of the Adobe Form
*——————————————————————–*
TRY.
CALL FUNCTION ‘FP_FUNCTION_MODULE_NAME’
EXPORTING
i_name = lv_form_name ” Form name
IMPORTING
e_funcname = lv_form_fm_name. ” Generated FM name
CATCH cx_fp_api INTO DATA(lx_fp_api).
” Handle form API errors
MESSAGE ID lx_fp_api->msgid TYPE lx_fp_api->msgty
NUMBER lx_fp_api->msgno
WITH lx_fp_api->msgv1 lx_fp_api->msgv2
lx_fp_api->msgv3 lx_fp_api->msgv4.
RETURN.
ENDTRY.
*——————————————————————–*
* STEP 2: Prepare Output Parameters for PDF Generation
*——————————————————————–*
ls_outputparams-nodialog = ‘X’. ” Avoid popup / preview dialog
ls_outputparams-getpdf = ‘X’. ” Request PDF output only
*——————————————————————–*
* STEP 3: Open Adobe Form Spool Job
*——————————————————————–*
CALL FUNCTION ‘FP_JOB_OPEN’
CHANGING
ie_outputparams = ls_outputparams
EXCEPTIONS
cancel = 1
usage_error = 2
system_error = 3
internal_error = 4
OTHERS = 5.
IF sy-subrc <> 0.
” Display error message and exit
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
RETURN.
ENDIF.
*——————————————————————–*
* STEP 4: Call the Generated Adobe Form Function Module
*——————————————————————–*
CALL FUNCTION lv_form_fm_name
EXPORTING
/1BCDWB/DOCPARAMS = ls_docparams ” Document parameters
p_vbeln = p_vbeln ” Sales document input
IMPORTING
/1BCDWB/FORMOUTPUT = ls_formoutput ” Output structure
EXCEPTIONS
USAGE_ERROR = 1
SYSTEM_ERROR = 2
INTERNAL_ERROR = 3
OTHERS = 4.
IF sy-subrc <> 0.
” Errors during form rendering
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
RETURN.
ENDIF.
*——————————————————————–*
* STEP 5: Close Adobe Form Spool Job
*——————————————————————–*
CALL FUNCTION ‘FP_JOB_CLOSE’
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
” Handle spool closing issues
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
RETURN.
ENDIF.
*——————————————————————–*
* STEP 6: Return Base64 PDF string to RAP Virtual Element
*——————————————————————–*
rv_string = ls_formoutput-pdf. ” Base64 encoded PDF
endmethod.generate service definition and binding.bind our service to list report.
The link is highlighted in the UI, and when the user clicks it, the corresponding Adobe Form for that specific sales document is downloaded instantly.
Right click on web app and click on open guided development.
We need custom action to be implemented.Click on add a custom action.Add necessary details and click on insert code snippet it will be added to manifest.json, it will add one more controller, where we can add our custom logic.
To handle multi-PDF downloads from the List Report, I implemented a custom action in the controller extension. When the user selects one or more rows, a confirmation popup is shown using MessageBox.confirm. After the user confirms, the extension reads each selected entity via the OData model and retrieves the attach virtual element, which contains the Adobe Form as a Base64 string. The code then converts the Base64 into a PDF Blob and automatically triggers a download for each sales document, generating individual files such as Sales_Document_<VBELN>.pdf. This approach provides a smooth, scalable way to download multiple Adobe Forms directly from the Fiori UI without additional backend endpoints.
sap.ui.define([
“sap/m/MessageToast”,
“sap/m/MessageBox”
], function (MessageToast, MessageBox) {
“use strict”;
return {
adf: function (oEvent) {
// Get the SmartTable from the view
var oSmartTable = this.getView().byId(“listReport”);
if (!oSmartTable) {
MessageToast.show(“SmartTable not found”);
return;
}
// Get the inner table (GridTable/ResponsiveTable)
var oTable = oSmartTable.getTable();
if (!oTable || typeof oTable.getSelectedContexts !== “function”) {
// If table is not ready or does not support selection
MessageToast.show(“Table not ready or selection not supported”);
return;
}
// Get selected row contexts
var aSelectedContexts = oTable.getSelectedContexts();
if (aSelectedContexts.length === 0) {
// User must select at least one row
MessageToast.show(“Please select at least one row.”);
return;
}
// Ask user for confirmation before downloading PDFs
MessageBox.confirm(“Do you want to download the selected PDF(s)?”, {
title: “Confirm Download”,
actions: [MessageBox.Action.OK, MessageBox.Action.CANCEL],
onClose: function (oAction) {
if (oAction === MessageBox.Action.OK) {
// Get OData model
var oModel = this.getOwnerComponent().getModel();
// Loop through all selected rows
aSelectedContexts.forEach(function (oContext, index) {
var sPath = oContext.getPath(); // Entity path of the row
// Read entity to fetch Base64 PDF (virtual element)
oModel.read(sPath, {
success: function (odata) {
// Extract Base64 string from response
var base64String = odata.attach;
try {
// Convert Base64 → binary
var binaryString = atob(base64String);
var len = binaryString.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// Create Blob for PDF download
var blob = new Blob([bytes], { type: “application/pdf” });
// Create a temporary download link
var link = document.createElement(“a”);
link.href = URL.createObjectURL(blob);
// File name: Sales_Document_<VBELN>.pdf
link.download = “Sales_Document_” + odata.vbeln + “.pdf”;
// Trigger download
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (e) {
// Invalid Base64 or conversion failure
MessageToast.show(“Invalid base64 for: ” + odata.vbeln);
}
},
// Error while calling OData service
error: function () {
MessageToast.show(“Error fetching PDF for: ” + sPath);
}
});
});
}
}.bind(this)
});
}
};
});
Conclusion:
This end-to-end setup demonstrates how RAP, Adobe Forms, and Fiori Elements can work together seamlessly to deliver a modern document-generation experience. By leveraging virtual elements for XSTRING handling, URL-based PDF links, and a custom UI action for multi-file downloads, we can generate, expose, and retrieve Adobe Forms without any custom Gateway coding. The solution is clean, extensible, and fully aligned with SAP’s cloud-ready RAP architecture making it a practical template for building similar document-driven features in enterprise applications.
Thanks For Reading, Good Wishes
Introduction:In this blog, we explore how RAP and Adobe Forms can be integrated to deliver a fully digital, end-to-end document generation experience directly from Fiori. By leveraging custom actions, virtual elements, and media-stream handling, we can trigger Adobe Form generation from the UI, manage the file output within RAP, and enable users to download the PDF instantly—ensuring a clean, cloud-ready and future-proof architecture.Solution Overview:In modern SAP applications, combining RAP (RESTful ABAP Programming Model) with Adobe Forms unlocks a clean, scalable way to generate and manage documents directly from Fiori. By extending the Fiori Elements UI with a custom action through the Integrative Extension Controller, we can trigger Adobe form generation seamlessly from the frontend. Using RAP virtual elements and media-stream properties, the generated PDF can be stored, retrieved, and downloaded. This approach delivers a fully end-to-end, cloud-ready solution for document creation, storage, and user-triggered actions—all built natively within RAP.Root Entity:@AccessControl.authorizationCheck: #NOT_REQUIRED // No authorization checks required for read access
@EndUserText.label: ‘ROOT ENTITY FOR ADOBE FORM’ // Description for display in tools
@Metadata.ignorePropagatedAnnotations: true // Avoid unwanted propagated annotations
define root view entity ZRV_AFORM
as select from vbap // Based on VBAP (Sales Order Item Data)
{
/*———————————————————–
Key Fields for RAP Root Entity
———————————————————–*/
key vbeln, // Sales Document Number
posnr, // Item Number
matnr, // Material Number
/*———————————————————–
Display text: Shown in UI for SEGW/Fiori preview
Not used for actual functionality, only for readability.
———————————————————–*/
‘Preview(SEGW)’ as ShowPDF,
/*———————————————————–
Dynamic URL to download the Adobe Form PDF
Format:
/sap/opu/odata/SAP/ZRAP_ADOBE_FORM_SRV/adobeSet(‘<VBELN>’)/$value
This URL triggers GET_STREAM in the OData service.
———————————————————–*/
concat(
‘/sap/opu/odata/SAP/ZRAP_ADOBE_FORM_SRV/adobeSet(”’,
concat( vbeln, ”’)’) /$value’
) as LinkToPDF
} OData Service:Include vbeln as a property in your entity type, and when the user provides a sales document number, the service should return the corresponding Adobe Form data in the response, implement GET_STREAM Method .Implementation: METHOD /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM.
“—————————————————————
” Data Declarations
“—————————————————————
DATA: lv_funcname TYPE funcname, ” Generated FM name for Adobe Form
ls_o_p TYPE sfpoutputparams, ” Output parameters for Adobe Forms
ls_formoutput TYPE fpformoutput, ” Adobe form output structure
ls_stream TYPE ty_s_media_resource, ” Stream returned to OData
gv_bin_file TYPE xstring. ” PDF binary data (unused variable)
“—————————————————————
” Read Input Key from OData Request
” Example: /sap/opu/odata/SRV/FormSet(vbeln=’12345′)/$value
“—————————————————————
DATA(lv_vbeln) = VALUE vbeln_vl(
it_key_tab[ name = ‘vbeln’ ]-value OPTIONAL ).
“—————————————————————
” STEP 1: Retrieve Adobe Form Generated Function Module
“—————————————————————
TRY.
CALL FUNCTION ‘FP_FUNCTION_MODULE_NAME’
EXPORTING
i_name = ‘ZSALES_FORM_ITEMS’ ” Adobe Form interface name
IMPORTING
e_funcname = lv_funcname. ” Generated FM name
IF sy-subrc <> 0.
RETURN. ” Stop processing if FM not found
ENDIF.
“———————————————————–
” STEP 2: Adobe Form Output Setup (Request PDF Only)
“———————————————————–
ls_o_p-nodialog = abap_true. ” No dialog popups
ls_o_p-getpdf = abap_true. ” Request PDF output
ls_o_p-nopreview = abap_true. ” Skip preview
” Open Adobe spool job
CALL FUNCTION ‘FP_JOB_OPEN’
CHANGING
ie_outputparams = ls_o_p
EXCEPTIONS
cancel = 1
usage_error = 2
system_error = 3
internal_error = 4
OTHERS = 5.
CATCH cx_root.
” Ideally log or raise error
RETURN.
ENDTRY.
“—————————————————————
” STEP 3: Call Adobe Form Generated Function Module
“—————————————————————
CALL FUNCTION lv_funcname
EXPORTING
p_vbeln = lv_vbeln ” Sales document number
IMPORTING
/1bcdwb/formoutput = ls_formoutput ” PDF Output (XSTRING)
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
RETURN. ” Handle form rendering errors
ENDIF.
“—————————————————————
” STEP 4: Close Adobe Print Job
“—————————————————————
CALL FUNCTION ‘FP_JOB_CLOSE’
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
RETURN.
ENDIF.
“—————————————————————
” STEP 5: Populate OData Media Stream
“—————————————————————
ls_stream-mime_type = ‘application/pdf’. ” MIME type for PDF
ls_stream-value = ls_formoutput-pdf. ” XSTRING PDF data
“—————————————————————
” STEP 6: Set Filename in Response Header
“—————————————————————
DATA: ls_header TYPE ihttpnvp,
lv_filename TYPE string.
” Dynamic file name e.g. SalesDoc_80001234.pdf
lv_filename = |SalesDoc_{ lv_vbeln }.pdf|.
ls_header-name = ‘Content-Disposition’.
ls_header-value = |attachment; filename=”{ lv_filename }”|.
” Add header to OData HTTP response
me->set_header( is_header = ls_header ).
“—————————————————————
” STEP 7: Return Stream to OData Framework
“—————————————————————
CALL METHOD me->copy_data_to_ref
EXPORTING
is_data = ls_stream
CHANGING
cr_data = er_stream.
ENDMETHOD.Projection View:To expose the Adobe Form download link in the Fiori UI, I used a projection view with ui.lineItem and type: WITH_URL so the PDF URL becomes directly clickable in the list and object page. The actual PDF content BASE64 is not stored in the CDS view; instead, I used a RAP virtual element attach calculated by an ABAP class for Custom Action. This virtual element lets me fetch and return the generated Adobe Form dynamically at runtime without persisting the binary in the database.@AccessControl.authorizationCheck: #NOT_REQUIRED // No auth checks for read access
@EndUserText.label: ‘PV FOR ADOBE FORM’ // Description of the view
@Metadata.ignorePropagatedAnnotations: true // Prevent annotation inheritance from base view
define root view entity ZPV_AFORM
as projection on ZRV_AFORM // Projection on RAP consumption view
{
/*———————————————————–
Key Field: Sales Document (VBELN)
Displayed in UI (List + Object Page)
———————————————————–*/
@ui.facet: [{ type: #IDENTIFICATION_REFERENCE }]
@ui.lineItem: [{ position: 10 }]
@ui.identification: [{ position: 10 }]
key vbeln,
/*———————————————————–
Item Number
———————————————————–*/
@ui.lineItem: [{ position: 20 }]
@ui.identification: [{ position: 20 }]
posnr,
/*———————————————————–
Material
———————————————————–*/
@ui.lineItem: [{ position: 30 }]
@ui.identification: [{ position: 30 }]
matnr,
/*———————————————————–
PDF Link Column (UI: Display as clickable URL)
This displays a clickable link “Show PDF” in the List Report.
———————————————————–*/
@ui.lineItem: [{
position: 10,
type: #WITH_URL, // URL-enabled column
url: ‘LinkToPDF’ // Field used as hyperlink
}]
@ui.identification: [{
position: 61,
type: #WITH_URL,
url: ‘LinkToPDF’
}]
ShowPDF, // UI label for the clickable link
/*———————————————————–
URL Field – Populated in Behavior Implementation
Used by Fiori to download/open the PDF.
———————————————————–*/
LinkToPDF,
/*———————————————————–
Virtual Field for Base64 PDF content
RAP calculates this via ZCL_ADOBEFORMM class.
———————————————————–*/
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_ADOBEFORMM’
virtual attach : abap.string(0) // Base64 PDF to be fetched on-demand
} Virtual Elements Implementation:CLASS zcl_adobeformm IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~calculate.
” Internal table for virtual elements defined in projection view
DATA: lt_virtual TYPE TABLE OF zpv_aform,
lv_base64 TYPE string.
” Move original RAP data into a working virtual table
lt_virtual = CORRESPONDING #( it_original_data ).
” Loop over each row to calculate the virtual element (Base64 PDF)
LOOP AT lt_virtual INTO DATA(ls_virtual).
” Get Sales Order number from the current row
DATA(lv_vbeln) = ls_virtual-vbeln.
” Create instance of Adobe Form handler class
DATA(lo_inst) = NEW zcl_adobeform( ).
” Call Adobe Form generator to get raw PDF (XSTRING)
DATA(rv_pdf) = lo_inst->get_data(
EXPORTING
p_vbeln = lv_vbeln ” Sales Order
).
” Convert PDF (XSTRING) → Base64 string, required for RAP media download
CALL FUNCTION ‘SCMS_BASE64_ENCODE_STR’
EXPORTING
input = rv_pdf
IMPORTING
output = lv_base64.
” Assign Base64 string to virtual element field
ls_virtual-attach = lv_base64.
ENDLOOP.
” Move calculated data back to RAP consumption structure
ct_calculated_data = CORRESPONDING #( lt_virtual ).
ENDMETHOD.
ENDCLASS.To retrieve the Adobe Form as an XSTRING, I implemented a dedicated ABAP class responsible for generating and returning the PDF content. The implementation code is as follows: method GET_DATA.
” Adobe Form name
DATA(lv_form_name) = CONV tdsfname( ‘ZSALES_FORM_ITEMS’ ).
” Name of the generated function module for the form
DATA(lv_form_fm_name) = VALUE rs38l_fnam( ).
” Structures for Adobe Form parameters
DATA(ls_docparams) = VALUE sfpdocparams( ).
DATA(ls_outputparams) = VALUE sfpoutputparams( ).
DATA(ls_formoutput) = VALUE fpformoutput( ).
*——————————————————————–*
* STEP 1: Get the Generated Function Module of the Adobe Form
*——————————————————————–*
TRY.
CALL FUNCTION ‘FP_FUNCTION_MODULE_NAME’
EXPORTING
i_name = lv_form_name ” Form name
IMPORTING
e_funcname = lv_form_fm_name. ” Generated FM name
CATCH cx_fp_api INTO DATA(lx_fp_api).
” Handle form API errors
MESSAGE ID lx_fp_api->msgid TYPE lx_fp_api->msgty
NUMBER lx_fp_api->msgno
WITH lx_fp_api->msgv1 lx_fp_api->msgv2
lx_fp_api->msgv3 lx_fp_api->msgv4.
RETURN.
ENDTRY.
*——————————————————————–*
* STEP 2: Prepare Output Parameters for PDF Generation
*——————————————————————–*
ls_outputparams-nodialog = ‘X’. ” Avoid popup / preview dialog
ls_outputparams-getpdf = ‘X’. ” Request PDF output only
*——————————————————————–*
* STEP 3: Open Adobe Form Spool Job
*——————————————————————–*
CALL FUNCTION ‘FP_JOB_OPEN’
CHANGING
ie_outputparams = ls_outputparams
EXCEPTIONS
cancel = 1
usage_error = 2
system_error = 3
internal_error = 4
OTHERS = 5.
IF sy-subrc <> 0.
” Display error message and exit
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
RETURN.
ENDIF.
*——————————————————————–*
* STEP 4: Call the Generated Adobe Form Function Module
*——————————————————————–*
CALL FUNCTION lv_form_fm_name
EXPORTING
/1BCDWB/DOCPARAMS = ls_docparams ” Document parameters
p_vbeln = p_vbeln ” Sales document input
IMPORTING
/1BCDWB/FORMOUTPUT = ls_formoutput ” Output structure
EXCEPTIONS
USAGE_ERROR = 1
SYSTEM_ERROR = 2
INTERNAL_ERROR = 3
OTHERS = 4.
IF sy-subrc <> 0.
” Errors during form rendering
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
RETURN.
ENDIF.
*——————————————————————–*
* STEP 5: Close Adobe Form Spool Job
*——————————————————————–*
CALL FUNCTION ‘FP_JOB_CLOSE’
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
” Handle spool closing issues
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
RETURN.
ENDIF.
*——————————————————————–*
* STEP 6: Return Base64 PDF string to RAP Virtual Element
*——————————————————————–*
rv_string = ls_formoutput-pdf. ” Base64 encoded PDF
endmethod.generate service definition and binding.bind our service to list report. The link is highlighted in the UI, and when the user clicks it, the corresponding Adobe Form for that specific sales document is downloaded instantly.Right click on web app and click on open guided development.We need custom action to be implemented.Click on add a custom action.Add necessary details and click on insert code snippet it will be added to manifest.json, it will add one more controller, where we can add our custom logic.To handle multi-PDF downloads from the List Report, I implemented a custom action in the controller extension. When the user selects one or more rows, a confirmation popup is shown using MessageBox.confirm. After the user confirms, the extension reads each selected entity via the OData model and retrieves the attach virtual element, which contains the Adobe Form as a Base64 string. The code then converts the Base64 into a PDF Blob and automatically triggers a download for each sales document, generating individual files such as Sales_Document_<VBELN>.pdf. This approach provides a smooth, scalable way to download multiple Adobe Forms directly from the Fiori UI without additional backend endpoints.sap.ui.define([
“sap/m/MessageToast”,
“sap/m/MessageBox”
], function (MessageToast, MessageBox) {
“use strict”;
return {
adf: function (oEvent) {
// Get the SmartTable from the view
var oSmartTable = this.getView().byId(“listReport”);
if (!oSmartTable) {
MessageToast.show(“SmartTable not found”);
return;
}
// Get the inner table (GridTable/ResponsiveTable)
var oTable = oSmartTable.getTable();
if (!oTable || typeof oTable.getSelectedContexts !== “function”) {
// If table is not ready or does not support selection
MessageToast.show(“Table not ready or selection not supported”);
return;
}
// Get selected row contexts
var aSelectedContexts = oTable.getSelectedContexts();
if (aSelectedContexts.length === 0) {
// User must select at least one row
MessageToast.show(“Please select at least one row.”);
return;
}
// Ask user for confirmation before downloading PDFs
MessageBox.confirm(“Do you want to download the selected PDF(s)?”, {
title: “Confirm Download”,
actions: [MessageBox.Action.OK, MessageBox.Action.CANCEL],
onClose: function (oAction) {
if (oAction === MessageBox.Action.OK) {
// Get OData model
var oModel = this.getOwnerComponent().getModel();
// Loop through all selected rows
aSelectedContexts.forEach(function (oContext, index) {
var sPath = oContext.getPath(); // Entity path of the row
// Read entity to fetch Base64 PDF (virtual element)
oModel.read(sPath, {
success: function (odata) {
// Extract Base64 string from response
var base64String = odata.attach;
try {
// Convert Base64 → binary
var binaryString = atob(base64String);
var len = binaryString.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// Create Blob for PDF download
var blob = new Blob([bytes], { type: “application/pdf” });
// Create a temporary download link
var link = document.createElement(“a”);
link.href = URL.createObjectURL(blob);
// File name: Sales_Document_<VBELN>.pdf
link.download = “Sales_Document_” + odata.vbeln + “.pdf”;
// Trigger download
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (e) {
// Invalid Base64 or conversion failure
MessageToast.show(“Invalid base64 for: ” + odata.vbeln);
}
},
// Error while calling OData service
error: function () {
MessageToast.show(“Error fetching PDF for: ” + sPath);
}
});
});
}
}.bind(this)
});
}
};
}); Conclusion:This end-to-end setup demonstrates how RAP, Adobe Forms, and Fiori Elements can work together seamlessly to deliver a modern document-generation experience. By leveraging virtual elements for XSTRING handling, URL-based PDF links, and a custom UI action for multi-file downloads, we can generate, expose, and retrieve Adobe Forms without any custom Gateway coding. The solution is clean, extensible, and fully aligned with SAP’s cloud-ready RAP architecture making it a practical template for building similar document-driven features in enterprise applications.Thanks For Reading, Good Wishes Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog