Rob's SAPUI5/Fiori Tips: How to preview Smartform as a PDF in a Fiori Elements App

Rob's SAPUI5/Fiori Tips: How to preview Smartform as a PDF in a Fiori Elements App

The Background

Over the years we have seen the proliferation of a range of print form technologies within the SAP domain. With the evolution from Sapscripts to Smartforms to Adobe forms, as SAP developers we’ve had to be fluent in all these technologies because over time organizations have accumulated the older types of forms in their landscapes, and sometimes migrating these digital assets to say, Adobe forms is not always a priority (as long as it still works don’t touch it, they say). Some time ago I had to work in one such landscape where the client had invested heavily in smartforms but wanted to preview and download custom smartform documents from their shiny new SAP Fiori UI.

My task was to achieve this whilst re-using as much of their existing software artifacts as possible. The solution that I designed had the following components:

1.?????Smartforms – I reused the existing smartforms already developed and working as desired. I would only add code to convert the smartforms to pdf (enter that handy little heaven-sent CONVERT_OTF function module!)

2.?????OData Service for PDF handling – Since the end consumer was to be a SAPUI5 app, naturally I would wrap the generation of the pdf from the smartform in the GET_STREAM method of a media-enabled OData service.

3.?????SAP Fiori Elements – Call me lazy but I loooove Fiori Elements; the ability to create an entire Fiori-compliant UI just by using annotations on your OData service is absolute genius. Granted you lose the freedom that comes with freestyle UI5 coding but when you’re trying to standardize UIs and your app is simple enough sometimes it’s better to work within those confines. It saves a ton of time. Kudos to the Fiori team at SAP!

4.?????CDS Views – In keeping with the ABAP Programming Model for SAP Fiori, the creation of the main OData service for the App would be realized via CDS View Annotations (another handy little trick from SAP - @OData.publish: true). To those who might be interested in how to implement CDS-BOPF CRUD operations, I’ve also added a BOPF Layer to the model as a bonus. I’ll do a separate post detailing CDS-BOPF in order to keep this one focused mostly on the PDF part.

?OK, enough evangelism about the merits of CDS, Fiori Elements, OData et al. Let’s get to the fun part and see these components in action. To illustrate the design, I developed a similar solution in my local AS ABAP installation as follows:

?The Data

I have two underlying transparent tables; one for Order Header details and the other for Order Line Items:

No alt text provided for this image

Table: ZSORDER_HDR

No alt text provided for this image

Table: ZSORDER_ITM

The Smartform

I created a simple smartform to print out an order and its line items. This will be the source of the PDF data:

No alt text provided for this image

Smartform: ZPDF_SO_01

The PDF Handler OData Service

Next, I created an SEGW project for a media-enabled OData service with the sole purpose of handling the ‘Preview’ request to be triggered from the Fiori App.

Side note: Before purists have my head on a spike, for the purposes of this illustration this means the solution will use two OData services (this one specifically for PDF handling, and another existing CDS View based service for the main app functionality). The recommendation however is to combine in one service but I decided not to change the existing one since I needed it unchanged for a training session.

No alt text provided for this image

1.?????Entity: SO01_PRINT – this is the Entity Type for the PDF request handling. It only needs two properties (Orderid, MimeType)

2.?????Property: Orderid – this property holds the Order id which we will use to select the relevant order data from our persistence tables and pass it on to our Smartform

3.?????Property: MimeType – we will use this property to set the mime type of the file to be downloaded by our SAPUI5 App

After saving the OData project and generating the Runtime Artifacts we will need to make two crucial code adjustments to the generated Model Provider Class and Data Provider Class Extensions shown below:

No alt text provided for this image

1.?????Model Provider Class Extension: ZCL_ZPDF_HANDLER_01_MPC_EXT

In here we will redefine the DEFINE method of the class and set the property/field MimeType as the source of the content type for the Entity like this:

No alt text provided for this image

This tells the Model that whatever value we will set in the property “MimeType” is to be taken as the content type of the SO01_PRINT Entity.

Redefined method DEFINE full code snippet:

? method define

??? data: lo_entity?? type ref to /iwbep/if_mgw_odata_entity_typ,
????????? lo_property type ref to /iwbep/if_mgw_odata_property.

??? super->define( ).

??? lo_entity = model->get_entity_type( iv_entity_name = 'ZSO_PRINT' ).

??? if lo_entity is bound.
????? lo_property = lo_entity->get_property( iv_property_name = 'MimeType' ).
????? lo_property->set_as_content_type( ).

??? endif.

??endmethod..        

2.?????Data Provider Class Extension: ZCL_ZPDF_HANDLER_01_DPC_EXT

Here we will redefine the /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM method which will call a separate static method which encapsulates the Smartform generation logic, receiving the PDF data in an XSTRING type variable. This will be passed on to the Gateway as a media resource object together with the mime type ‘application/pdf’ to denote that the media type is PDF format.

No alt text provided for this image

Redefined method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM full code snippet:

method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREA

??? data: lt_keys??? type /iwbep/t_mgw_tech_pairs,
????????? ls_key???? type /iwbep/s_mgw_tech_pair,
????????? lv_orderid type snwd_so_id,
????????? lv_xstring type xstring,
????????? ls_stream? type ty_s_media_resource.

??? try.
??????? lt_keys = io_tech_request_context->get_keys( ).

??????? read table lt_keys with key name = 'ORDERID' into ls_key.

??????? lv_orderid = ls_key-value.

??????? call method zcl_pdf_utils=>gen_pdf_from_smartform
????????? exporting
??????????? iv_orderid???? = lv_orderid
????????? receiving
??????????? rv_pdf_xstring = lv_xstring.

??????? ls_stream-value = lv_xstring.
??????? ls_stream-mime_type = 'application/pdf'.

??????? data(lv_name) = |inline; filename={ lv_orderid };|.
??????? data(ls_header) = value ihttpnvp( name? = 'Content-Disposition'
????????????????????????????????????????? value = lv_name
??????????????????????????????????????? ).
??????? set_header( is_header = ls_header ).

??????? copy_data_to_ref( exporting
??????????????????????????? is_data = ls_stream
????????????????????????? changing
??????????????????????????? cr_data = er_stream ).

????? catch /iwbep/cx_mgw_busi_exception .

????? catch /iwbep/cx_mgw_tech_exception .

??? endtry.
??
 endmethod.        

The static method ZCL_PDF_UTILS=> GEN_PDF_FROM_SMARTFORM called to perform data retrieval for the smartform and generate the PDF XSTRING data is shown below:

? method gen_pdf_from_smartform
??? "Data Declarations
??? data: lv_fm_name??????????? type rs38l_fnam,
????????? ls_output_options???? type ssfcompop,
????????? lv_language?????????? type tdspras,
????????? ls_control_parameters type ssfctrlop,
????????? ls_output_data??????? type ssfcrescl,
????????? lv_pdf_len??????????? type i,
????????? lv_pdf_xstring??????? type xstring,
????????? lt_lines????????????? type table of tline,
????????? lv_devtype??????????? type rspoptype,
????????? lv_app_type?????????? type string,
????????? lv_guid?????????????? type guid_32,
????????? lo_cached_response??? type ref to if_http_response,
????????? lt_tstotf ????????????type tsfotf.

??? data: lt_order_itms type zsoitm_pdf_tt,
????????? ls_itm??????? like line of lt_order_itms.

??? lv_language = sy-langu.
??? translate lv_language to upper case.
??? ls_control_parameters-langu = lv_language.

??? "* set control parameters to get the output text format (OTF) from Smart Forms
??? ls_control_parameters-no_dialog = 'X'.
??? ls_control_parameters-getotf?? = 'X'.
??? ls_control_parameters-preview = space. "* No preview

??? "* get device type from language
??? call function 'SSF_GET_DEVICE_TYPE'
????? exporting
??????? i_language???????????? = lv_language
????? importing
??????? e_devtype????????????? = lv_devtype
????? exceptions
??????? no_language??????????? = 1
??????? language_not_installed = 2
??????? no_devtype_found?????? = 3
??????? system_error?????????? = 4
??????? others???????????????? = 5.

??? "* set device type in output options
??? ls_output_options-tdprinter = lv_devtype.
?
 ?? "* set relevant output options
??? ls_output_options-tdnewid = 'X'. "Print parameters,
??? ls_output_options-tddelete = space. "Print parameters

??? call function 'SSF_FUNCTION_MODULE_NAME'
????? exporting
??????? formname?????????? = 'ZPDF_SO_01'
????? importing
??????? fm_name??????????? = lv_fm_name
????? exceptions
??????? no_form??????????? = 1
??????? no_function_module = 2
??????? others???????????? = 3.

??? "Data retrieval and supplying it to Smart form FM
??? select single orderguid, orderid, partner, \_businesspartner-companyname,    status, \_status\_text-salesordoverallstatusname, currencycode, invoicetotal, createdon, createdby
    ?????? from zc_sorder_hdr? where orderid = @iv_orderid and \_status\_text-language = 'E'
    ?????? into @data(ls_hdr).
    
    ??? select orderguid, itemid, product, currencycode, grossamount, quantity, \_product\_text-productname
    ?????? from zc_sorder_itm where orderguid =? @ls_hdr-orderguid and \_product\_text-language = 'E'
    ?????? into table @data(lt_itms).
    
    ??? loop at lt_itms assigning field-symbol(<fs>).
    ????? move-corresponding <fs> to ls_itm.
    ????? append ls_itm to lt_order_itms.
    ??? endloop.
    
    ??? "Call Smart form generated FM
    ??? call function lv_fm_name
    ????? exporting
    ??????? control_parameters = ls_control_parameters
    ??????? output_options???? = ls_output_options
    ??????? user_settings????? = space
    ??????? orderid??????????? = iv_orderid
    ??????? hdr??????????????? = ls_hdr
    ??????? itms?????????????? = lt_order_itms
    ????? importing
    ??????? job_output_info??? = ls_output_data
    ????? exceptions
    ??????? formatting_error?? = 1
    ??????? internal_error???? = 2
    ??????? send_error???????? = 3
    ??????? user_canceled????? = 4
    ??????? others???????????? = 5.
    
    ??? append lines of ls_output_data-otfdata[] to lt_tstotf[].
    
    ??? "Convert to OTF
    ??? call function 'CONVERT_OTF'
    ????? exporting
    ??????? format??????????????? = 'PDF'
    ????? importing
    ??????? bin_filesize????????? = lv_pdf_len
    ??????? bin_file????????????? = lv_pdf_xstring?????? "binary file
    ????? tables
    ??????? otf?????????????????? = lt_tstotf
    ??????? lines???????????????? = lt_lines
    ????? exceptions
    ??????? err_max_linewidth???? = 1
    ??????? err_format??????????? = 2
    ??????? err_conv_not_possible = 3
    ??????? err_bad_otf?????????? = 4
    ??????? others??????????????? = 5.
    
    ??? if sy-subrc = 0.
     ????? rv_pdf_xstring = lv_pdf_xstring.
    ??? endif.
   
    ? endmethod..        

This completes the PDF Handler OData Service implementation.

The CDS View Data Model

The following details the CDS Views which make up the Data Model for the Fiori Elements application. The model is made up of Basic Interface Views built on top of the database tables. A CDS BOPF layer is built on top of these Interface Views to facilitate CRUD operations. Then finally we will have a Consumption layer made up of Consumption CDS Views for consumption by Fiori Elements.

Interface Views: ZI_SORDER_HDR and ZI_SORDER_ITM

No alt text provided for this image

BOPF Layer: ZI_SO01_HDR_BO (Header CDS View):

@AbapCatalog.sqlViewName: 'ZISO01HDR_BO
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'BOPF Interface View - Sales Order HDR'
@Search.searchable: true

@ObjectModel: {
   //semantic categorization only; no runtime effect
??? modelCategory: #BUSINESS_OBJECT,
??? //Human-readable key
??? semanticKey: ['OrderID'],
??? //defines this as root node of compositional hierarchy
??? compositionRoot: true,
??? 
    //Transactional processing annotations
??? transactionalProcessingEnabled: true,
??? createEnabled: true,
??? deleteEnabled: true,
??? updateEnabled: true,
??? writeActivePersistence: 'ZSORDER_HDR',
??? //Additional ETag annotation (time stamp)
??? entityChangeStateId: 'ChangedOn'
}

define view ZI_SO01_HDR_BO as
??? select from ZI_SORDER_HDR as SalesOrder?
??? //Compositional Association to Items BO
??? association [0..*] to ZI_SO01_ITM_BO as _Item on $projection.OrderGuid = _Item.OrderGuid

??? //BP association
??? association [0..1] to SEPM_I_BusinessPartner as _BusinessPartner on $projection.Partner = _BusinessPartner.BusinessPartner

??? //Status Value help association
??? association [0..1] to Sepm_I_SalesOrdOverallStatus as _Status on $projection.Status = _Status.SalesOrderOverallStatus

??? //Associations for Value Help
??? association [0..1] to SEPM_I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency??

{

??? @ObjectModel.readOnly: true
??? key SalesOrder.orderguid as OrderGuid,

??? @Search.defaultSearchElement: true
??? @ObjectModel.readOnly: true
??? SalesOrder.orderid as OrderID,

??? @ObjectModel.foreignKey.association: '_BusinessPartner'
??? SalesOrder.partner as Partner,

??? @Search.defaultSearchElement: true
??? @ObjectModel.foreignKey.association: '_Status'
??? SalesOrder.status as Status,

??? @ObjectModel.foreignKey.association: '_Currency'
??? @Semantics.currencyCode: true
??? @ObjectModel.readOnly: true
??? SalesOrder.currencycode as CurrencyCode,

??? @Semantics.amount.currencyCode: 'CurrencyCode'
??? @ObjectModel.readOnly: true
??? SalesOrder.invoicetotal as InvoiceTotal,

??? @Semantics.systemDateTime.lastChangedAt: true
??? @ObjectModel.readOnly: true
??? SalesOrder.changedon as ChangedOn,

??? @Semantics.user.lastChangedBy: true
??? @ObjectModel.readOnly: true
??? SalesOrder.changedby as ChangedBy,

??? @Semantics.systemDateTime.createdAt: true
??? @ObjectModel.readOnly: true
??? SalesOrder.createdon as CreatedOn,

??? @Semantics.user.createdBy: true
??? @ObjectModel.readOnly: true
??? SalesOrder.createdby as CreatedBy,

??? //Expose the Associations
??? @ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
  ? _Item,
??? _BusinessPartner,
??? _Status,
??? _Currency

}        

BOPF Layer: ZI_SO01_ITM_BO (Items CDS View):

@AbapCatalog.sqlViewName: 'ZISO01ITM_BO
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'BOPF Interface View - Sales Order ITM'
@Search.searchable:?????? true

@ObjectModel: {
??? //semantic categorization only; no runtime effect
??? modelCategory: #BUSINESS_OBJECT,
??? //Human-readable key
??? semanticKey:? [ 'ItemID' ],
??? //Transactional processing
??? writeActivePersistence: 'ZSORDER_ITM',
??? createEnabled: true,
??? deleteEnabled: true,
??? updateEnabled: true
}

define view ZI_SO01_ITM_BO as
? select from ZI_SORDER_ITM as OrderItem
? //Compositional Association to Header
? association [1..1] to ZI_SO01_HDR_BO as _SalesOrder on $projection.OrderGuid = _SalesOrder.OrderGuid

? //Cross-BO Associations
? association [0..1] to SEPM_I_Product_E as _Product on $projection.Product = _Product.Product

? //Associations for Value Help
? association [0..1] to SEPM_I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency??

{

??? @ObjectModel.readOnly: true
??? key OrderItem.itemguid as ItemGuid,

??? @ObjectModel.readOnly: true
??? key OrderItem.orderguid as OrderGuid,

??? @ObjectModel.readOnly: true
??? @Search.defaultSearchElement: true
??? OrderItem.itemid as ItemID,

??? @ObjectModel.foreignKey.association: '_Product'
??? @ObjectModel.mandatory: true
??? OrderItem.product as Product,

??? @ObjectModel.foreignKey.association: '_Currency'
??? @Semantics.currencyCode: true
 ?? @ObjectModel.readOnly: true
??? OrderItem.currencycode as CurrencyCode,

??? @Semantics.amount.currencyCode: 'CurrencyCode'
??? @ObjectModel.readOnly: true
??? OrderItem.grossamount as GrossAmount,

??? OrderItem.quantity as Quantity,

??? //Exposed Associations
??? @ObjectModel.association.type: [#TO_COMPOSITION_PARENT,#TO_COMPOSITION_ROOT]
??? _SalesOrder,
??? _Product,
??? _Currency

}        

Consumption View 1: ZC_SO01_HDR (Header Consumption View):

@AbapCatalog.sqlViewName: 'ZCSO01HDR
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption View - Sales Order HDR'
@Search.searchable: true
@Metadata.allowExtensions: true

@ObjectModel: {
??? //Annotations for transactional processing
??? semanticKey: 'OrderID',
??? compositionRoot: true,
??? transactionalProcessingDelegated: true,
??? createEnabled: true,
??? deleteEnabled: true,
??? updateEnabled: true
}

@OData.publish: true

define view ZC_SO01_HDR as
? select from ZI_SO01_HDR_BO as SalesOrder
? association [0..*] to ZC_SO01_ITM?? as _Item???? on _Item.OrderGuid = SalesOrder.OrderGuid

? //Associations for Value Help
? association [0..1] to SEPM_I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency

{

??? //UUID-based key required on the Consumption View
??? key SalesOrder.OrderGuid,

??? @Search.defaultSearchElement: true
??? SalesOrder.OrderID,

??? @ObjectModel.mandatory: true
??? SalesOrder.Partner,

??? @Search.defaultSearchElement: true
??? SalesOrder.Status,

??? SalesOrder.CurrencyCode,

??? SalesOrder.InvoiceTotal,

??? @ObjectModel.readOnly: true

??? '| Preview |' as showPDF,

??? @ObjectModel.readOnly: true
??? concat('/sap/opu/odata/sap/ZPDF_HANDLER_01_SRV/SO01_PRINTSet(''', concat(SalesOrder.OrderID, ''')/$value')) as LinkToPDF,

??? SalesOrder.ChangedOn,
??? SalesOrder.ChangedBy,
??? SalesOrder.CreatedOn,
??? SalesOrder.CreatedBy,

??? /* Expose the Associations */
??? @ObjectModel.association.type:? [ #TO_COMPOSITION_CHILD ]
??? _Item,
??? SalesOrder._BusinessPartner,
??? SalesOrder._Status,
??? _Currency

}        

Notes:

@Metadata.allowExtensions: true

This annotation allows us to provide the Fiori Elements UI annotations in a separate Metadata Extension file. This keeps the main Consumption CDS View clean and uncluttered with lots of UI stuff.

@OData.publish: true

This is the magic annotation that will use this CDS View definition to generate an OData service in the backend system, eliminating the need for us to manually create an SEGW OData project.

@ObjectModel.readOnly: true

'| Preview |' as showPDF,

@ObjectModel.readOnly: true

concat('/sap/opu/odata/sap/ZPDF_HANDLER_01_SRV/SO01_PRINTSet(''', concat(SalesOrder.OrderID, ''')/$value')) as LinkToPDF

The first field showPDF will be used as the clickable text link on the UI and the second field LinkToPDF will be the actual URL link to the PDF Handler OData Service Entity Set generated at runtime by concatenating the selected Order id into the URL. We will link these two fields in the UI Metadata Extension file for this CDS View.?

Consumption View 2: ZC_SO01_ITM (Items Consumption View):

@AbapCatalog.sqlViewName: 'ZCSO01ITM
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption View - Sales Order ITM'
@Metadata.allowExtensions: true
@Search.searchable: true

@ObjectModel: {
? ??//Human-readable key
??? semanticKey:['ItemID'],
??? //CRUD
??? createEnabled: true,
??? deleteEnabled: true,
??? updateEnabled: true
}

define view ZC_SO01_ITM as
? select from ZI_SO01_ITM_BO as OrderItem
? //Composition
? association [1..1] to ZC_SO01_HDR as _SalesOrder on $projection.OrderGuid = _SalesOrder.OrderGuid

? //Cross-BO Associations
? association [0..1] to SEPM_I_Product_E as _Product on $projection.Product = _Product.Product

? //Associations for Value Help
? association [0..1] to SEPM_I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency

{

???? @ObjectModel.readOnly: true
??? key OrderItem.ItemGuid,

??? @ObjectModel.readOnly: true
??? OrderItem.OrderGuid,

??? @Search.defaultSearchElement: true
??? _SalesOrder.OrderID,

??? OrderItem.ItemID,
??? OrderItem.Product,
??? OrderItem.CurrencyCode,
??? OrderItem.GrossAmount,
??? OrderItem.Quantity,

??? /* Exposed Associations */
 ?? _Currency,
??? _Product,
??? @ObjectModel.association.type:? [ #TO_COMPOSITION_ROOT, #TO_COMPOSITION_PARENT ]
??? _SalesOrder

}        

Notes:

We also have another @Metadata.allowExtensions: true annotation in this Items CDS View in order to facilitate Fiori Elements UI annotations in a separate Metadata Extension file.

The Fiori Elements UI Annotations

The UI annotations will be modularized into separate Metadata Extension files to keep the main Consumption CDS views cleaner. The SAP Fiori Elements framework will read these files and interpret the UI annotations to generate a no-code (or more specifically no-manual-code UI).

Metadata Extension 1: ZMDE_ZC_SO01_HDR (Extension for ZC_SO01_HDR):

@Metadata.layer: #CUSTOMER

@UI: {
??? headerInfo: {
??????? typeName: 'Sales Order',
??????? typeNamePlural: 'Sales Orders',
??????? title: { label: 'Order Number', value: 'OrderID' },
??????? description: { label: 'Customer', value: 'Partner' }
??? }
}

annotate view ZC_SO01_HDR
??? with
{

??? @UI.facet: [
??????? //General Info Parent facet > COLLECTION 1
??????? { id: 'GeneralInformation',
????????? type: #COLLECTION,
????????? label: 'Order Information',
????????? position: 10
??????? },

??????? //Basic Data facet > COLLECTION 1 -> FIELD_GRP_REF 1
??????? {
??????????? parentId: 'GeneralInformation',
??????????? id: 'BasicInfo',
??????????? type: #FIELDGROUP_REFERENCE,
??????????? targetQualifier: 'Basic',
??????????? label: 'Basic Data:',
????? ??????position: 10
??????? },

??????? //Admin Data facet > COLLECTION 1 -> FIELD_GRP_REF 2
??????? {
??????????? parentId: 'GeneralInformation',
??????????? id: 'AdminInfo',
??????????? type: #FIELDGROUP_REFERENCE,
??????????? targetQualifier: 'Admin',
??????????? label: 'Admin Data:',
??????????? position: 20
??????? },

??????? //Items List facet > COLLECTION 2 -> LINE_ITEM_REF 1
??????? {
?????????? id : 'ItemsList',
 ????????? type : #LINEITEM_REFERENCE,
?????????? targetElement: '_Item' ,
?????????? label: 'Line Items',
?????????? position: 10
??????? }

??? ]

??? @UI: {
??????? lineItem: [{ type: #FOR_ACTION, position: 1, dataAction: 'BOPF:ACT_SET_STATUS_TO_PAID', label: 'Set To Paid' }]
??? }

??? @UI.hidden: true
??? OrderGuid;
?
??? @UI: {
??????? lineItem????? : [ { position: 10, label: 'Sales Order ID', importance: #HIGH } ],
??????? selectionField: [ { position: 10 } ],
??????? identification: [ { position: 10, label: 'Sales Order ID' } ],
??????? fieldGroup: [{ qualifier: 'Basic', position: 10 }]
??? }
??? OrderID;

??? @UI: {
??????? lineItem????? : [ { position: 20, label: 'Customer', importance: #MEDIUM } ],
??????? identification: [ { position: 20, label: 'Customer' } ],
??????? fieldGroup: [{ qualifier: 'Basic', position: 20, label: 'Customer' }]
??? }
??? Partner;

??? @UI: {
??????? lineItem????? : [ { position: 30, label: 'Status', importance: #MEDIUM } ],
??????? identification: [ { position: 30, label: 'Status' } ],
??????? selectionField: [ { position: 30} ],
??????? fieldGroup: [{ qualifier: 'Basic', position: 30, label: 'Status' }]
??? }
??? Status;??

??? @UI.hidden: true
??? CurrencyCode;

??? @UI: {
??????? lineItem????? : [ { position: 40, label: 'Invoice Total', importance: #MEDIUM } ],
??????? identification: [ { position: 40, label: 'Invoice Total' } ],
??????? fieldGroup: [{ qualifier: 'Basic', position: 40, label: 'Invoice Total' }]
??? }
??? InvoiceTotal;?

??? @UI: {
??????? lineItem: [{ position: 41, label: 'PDF', type: #WITH_URL , url: 'LinkToPDF'}]
??? }
??? showPDF;

??? @UI: {
??????? identification: [ { position: 50, label: 'Last Changed On' } ],
??????? fieldGroup: [{ qualifier: 'Admin', position: 10, label: 'Last Changed On' }]
??? }
??? ChangedOn;

??? @UI: {
??????? identification: [ { position: 60, label: 'Last Changed By' } ],
??????? fieldGroup: [{ qualifier: 'Admin', position: 20, label: 'Last Changed By' }]
??? }
??? ChangedBy;

??? @UI: {
??????? identification: [ { position: 70, label: 'Created On' } ],
??????? fieldGroup: [{ qualifier: 'Admin', position: 30, label: 'Created On' }]
??? }
??? CreatedOn;

??? @UI: {
??????? identification: [ { position: 80, label: 'Created By' } ],
??????? fieldGroup: [{ qualifier: 'Admin', position: 40, label: 'Created By' }]
??? }
??? CreatedBy;

}        

Notes:

@UI: {?lineItem: [{ position: 41, label: 'PDF', type: #WITH_URL , url: 'LinkToPDF'}]}

showPDF;

The UI annotation for the showPDF field contains the attribute type with value #WITH_URL. This will inform Fiori Elements to generate this UI field as a clickable URL link. The url attribute with value ‘LinkToPDF’ will then tell the framework to look for a field with that name, and interpret whatever value is found there as the actual URL link to navigate to whenever the showPDF field is clicked.

No alt text provided for this image

Metadata Extension 2: ZMDE_ZC_SO01_ITM (Extension for ZC_SO01_ITM):

@Metadata.layer: #CUSTOMER

@UI: {
??? headerInfo: {
??????? typeName: 'Sales Order Item',
??????? typeNamePlural: 'Sales Order Items',
??????? title: { type: #STANDARD, value: 'ItemID' }
??? }
}

annotate view ZC_SORDER_ITM with
{

??? @UI.hidden: true
??? ItemGuid;

??? @UI.hidden: true
??? OrderGuid;

??? @UI.hidden: true
??? OrderID;

??? @UI: {
??????? lineItem: [ { position: 10, label: 'Position', importance: #HIGH } ],
??????? identification:[ { position: 10, label: 'Position' } ]
??? }
??? ItemID;

??? @UI: {
??????? lineItem: [ { position: 20, label: 'Product', importance: #MEDIUM } ],
??????? identification:[ { position: 20, label: 'Product' } ]
??? }
??? Product;

??? @UI: {
??????? lineItem: [ { position: 30, importance: #MEDIUM, label: 'Quantity' } ],
??????? identification:[ { position: 30, label: 'Quantity' } ]
??? }
??? Quantity;

??? @UI.hidden: true
??? CurrencyCode;

??? @UI: {
??????? lineItem: [ { position: 40, importance: #MEDIUM } ],
??????? identification:[ { position: 40 } ]
??? }???
??? GrossAmount;

}        

After creating these CDS objects we will activate all of them and the @OData.publish: true annotation on the ZC_SO01_HDR Consumption View will generate the OData service in the backend. The only step we will need to do is to register and activate that OData service on the Gateway in transaction /IWFND/MAINT_SERVICE. The generated service will have the name of the annotated view with the postfix “_CDS”. So, from the view ZC_SO01_HDR we will get the OData service ZC_SO01_HDR_CDS.

No alt text provided for this image

The SAPUI5 Application

In SAP Web IDE we will create a project with the List Report application template. First, we will select our OData service created via the CDS View annotation (NOT the earlier PDF handler service!) and click ‘Next’.

No alt text provided for this image

The following screen will have a list of available annotation files we would like to associate with our project. Make sure to select both files listed. These are derived from the backend system based on the OData service we selected in the previous step.

No alt text provided for this image

If you click on the ZC_SO01_HDR_CDS_VAN link you will find a lot of annotation data controlling UI appearance and behavior. This information is used by Fiori Elements to generate the UI at runtime.

No alt text provided for this image

We will select the ZC_SO01_HDR collection as the OData Collection and ‘to_Item’ as the Navigation. This navigation is facilitated by the compositional associations we established on the CDS Views.

No alt text provided for this image

Next, we click ‘Finish’ and we are done! In a freestyle SAPUI5 App we would then go ahead and code the Views, Controllers etc to get the desired UI behavior but the beauty of Fiori Elements is that these manual steps are not required. We now have enough for a working UI with all the ‘Create Read Update Delete’ goodness out of the box – without a single line of code needed on our part; how beautiful is that?!

The folder structure of our project will not even have the usual Views or Controllers. This is because the framework will generate these for us at runtime:

No alt text provided for this image

We can now directly run and test our Application and this is what it looks like:

No alt text provided for this image

Each ‘Preview’ link on a row is resolved to an actual URL, with the Orderid concatenated into it. For example, the fourth row with Sales Order ID 700000002 will resolve to the following URL: /sap/opu/odata/sap/ZPDF_HANDLER_01_SRV/SO01_PRINTSet('700000002')/$value'

This will in turn result in the execution of the /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM method which will execute the pdf generation logic and return the smartform converted to pdf data in xstring type. The Gateway will accordingly interpret this as a media resource of type ‘application/pdf’ as set in the MPC Extension and the PDF is returned to be displayed in our Fiori Elements application.

No alt text provided for this image

There we go, we can now preview Smartforms as PDFs in a Fiori Elements application. Perhaps for the next version we can investigate combining the functionality into one OData service and using the BOPF Layer to create a button to trigger the PDF generation by using a BOPF Action. That way we can completely eliminate the manual SEGWThis could be interesting. See you next time and happy coding!


Peter Togara

Senior Software Development Engineer | Oracle Java Certified, AWS Certified, Spring Boot, React

3 年

Quite informative and on point. Thanks Robert

要查看或添加评论,请登录

Robert Chamisa-Denhere的更多文章

社区洞察

其他会员也浏览了