This blog post describes how to create a PDF document based on input from the UI5 front-end and return a (temporary) download link to the user. It is mostly a collection of things I discovered whilst handling a task which involved exactly this king of thing.
1. Building the .pdf
There are several ways to build the pdf itself. The easiest that I could find was using smartforms (you can find more details on how to create and use smartforms in the link). One thing to note is that the smart form has an interface similar with that of a function module, i.e. you can pass data (values, structures, tables, basically anything) to it in order to fill the placeholders, generate tables, etc. Once you have created your "template" using the smartforms feature you should create a function module / static method which uses this smart form, converts it into pdf format (because smart forms are not directly translated into .pdf files when executed) and saves it on the application server.
- Getting the raw (otf) format output of the smart-form:
DATA:
ls_job_output_info TYPE ssfcrescl,
ls_document_output_info TYPE ssfcrespd,
ls_job_output_options TYPE ssfcresop,
ls_output_options TYPE ssfcompop,
ls_control_parameters TYPE ssfctrlop,
lv_fm_name TYPE rs38l_fnam.
CONSTANTS c_formname TYPE tdsfname VALUE'<<smart form name>>'.
CONSTANTS c_filename TYPE string VALUE'<<filename>>.pdf'.
CALLFUNCTION'SSF_GET_DEVICE_TYPE'
EXPORTING
i_language = sy-langu
i_application = 'SAPDEFAULT'
IMPORTING
e_devtype = ls_output_options-tdprinter.
ls_control_parameters-no_dialog = 'X'.
ls_control_parameters-getotf = 'X'.
CALLFUNCTION'SSF_FUNCTION_MODULE_NAME'
EXPORTING
formname = c_formname
IMPORTING
fm_name = lv_fm_name.
CALLFUNCTION lv_fm_name
EXPORTING
control_parameters = ls_control_parameters
output_options = ls_output_options
"add your custom smart form parameters here
IMPORTING
document_output_info = ls_document_output_info
job_output_info = ls_job_output_info
job_output_options = ls_job_output_options.
- Converting the otf to pdf format:
DATA: lt_lines TYPESTANDARDTABLEOF tline,
lv_pdf_content TYPE xstring.
CALLFUNCTION'CONVERT_OTF'
EXPORTING
format = 'PDF'
IMPORTING
bin_file = lv_pdf_content
TABLES
otf = ls_job_output_info-otfdata
lines = lt_lines
EXCEPTIONS
OTHERS = 1.
- You can either save the .pdf temporarily or you can persistently save the xstring value (returned in lv_pdf_content) in a database table and build a GW function import / entity which provides the binary content as a property. We will explore the first version. The first version has the advantage that it does not store large amounts of data in the database, although it will have to recreate the .pdf each time the user may want to download it (this is generally a good solution, as users mostly download receipts/confirmations/other pdf stuff just one time).
DATA: lr_cached_response TYPEREFTO if_http_response,
lv_header_text TYPE string,
lt_lines TYPESTANDARDTABLEOF tline.
CONSTANTS: c_path TYPE string VALUE`<<desired url local path>>` ,
c_exp TYPE i VALUE360."<<how long (secs) should the pdf be 'stored'>>
CREATEOBJECT lr_cached_response TYPE cl_http_response EXPORTING add_c_msg = 1.
lr_cached_response->set_header_field(
name = if_http_header_fields=>content_type
value = 'application/pdf').
***optional_start*** "should only be used if you wish to force a download
"(some browsers open .pdf files in tabs if you don't use it)
CONCATENATE`attachment; filename="` c_filename `"` INTO lv_header_text.
lr_cached_response->set_header_field(
name = if_http_header_fields=>content_disposition
value = lv_header_text ).
***optional_end***
lr_cached_response->set_status(code = 200 reason = 'OK').
lr_cached_response->server_cache_expire_rel( expires_rel = c_exp ).
*you can get the server base url using: cl_http_server=>get_location( ).
*when you build your path.
lr_cached_response->set_data( lv_pdf_content ).
cl_http_server=>server_cache_upload( url = c_path
response = lr_cached_response
scope = ihttp_inv_global ).
- You have your .pdf saved at the url given in c_path (you may want to replace this constant with a url created at run-time, e.g. with a guid appended to the server base url.
2. Using it in GW
Let's link the pdf creation function module / static method to the gateway service. I suggest using a function import which receives parameters derived from the structure(s) used in the smart form interface and returns a complex type which contains the URL (and other metadata if necessary). If appropriate, you can also create an entity set or use an existing entity and pass the URL as a property.
Gateway service 'nodes':
Function import settings:
After you generate the run-time artifacts, you must redefine the method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION of the _DPC_EXT class and call your PDF creation function module (after you have checked that your function import is requested and not some other one). More on function imports here.
3. Linking it to the UI
The gateway function import must communicate with the UI5 app. To call the function import use the callFunction method of your oDataModel object. You must write a javascript function to handle the success event (check out the parameters of the above-mentioned function). The function should be something like this:
function(oData, response) { var pdfURL = oData['getPdfFile'].url; //use the url however you want. }
You have multiple ways to display the PDF once you have the URL. One thing that I have done, is to check if the PDF file still exists before redirecting the user to download it (the PDF may have expired if the user took a long time between creating the PDF and trying to download it; if this is the case and he presses the download link, a 404 Not Found default file will be opened and the UI5 context will be destroyed). You can use the following snippet to check if the pdf exists:
jQuery.ajax({ type: 'HEAD', url: pdfURL, success: function() { //this will make the browser download the pdf window.location.href=pdfURL; }, error: function(){ /* error handling */ } });
If you want to display the .pdf file, here are some thoughts (you must not include the optional part in the PDF creation code):
- Directly open it using window.location.href=pdfURL; but this will basically exit from your application
- Use the window.open function to open a new tab / browser window
- Open it in an iframe (you can include the iframe in a UI5 dialog, or any other UI5 container control):
new sap.ui.core.HTML( {content: "<iframe style=\"width:100%;height:100%\" src=\"" + pdfURL+ "\">Not supported</iframe>"} )
- All the above methods assume that the browser can display PDF files. If your PDF is accessible via a public URL (not on intranet and not behind a login), you can also use the Google Documents API. You can combine this method with all of the above. To use the API, you just have to build this URL:
var googleURL = "http://docs.google.com/viewer?url=" + pdfURL + "&embedded=true"
and use it instead of the regular PDF URL.