Technical Articles
Extension of standard RAP based Fiori Object Page facet using Custom entity ODATA and UI5 Adaptation project
Hello Experts,
Since the newly delivered SAP applications from Version 2021 S4 On-Premise will be based on the RAP based Fiori application,there could be multiple extension scenariosof the Fiori applications out of which adding a new facet in the object page will be most commonly requested scenario.
Below are the different steps to be followed for the RAP based scenario.
If the scenario is to extend a classical Odata based applciation you can follow the below blog :
http://www.hanaexam.com/2021/05/adaptation-project-new-facet-with-smart.html
Extensibility features of RAP, such as extending behavior definition is not available as part of S/4HANA 2021 any-premise release .
We have to follow the below procedure :
For example we will be analyzing adding a new facet to show the open items in the Fiori application
Manage Credit Accounts: https://fioriappslibrary.hana.ondemand.com/sap/fix/externalViewer/#/detail/Apps(‘F4596’)/S23OP
List Report
Object Page
- Create a Custom Odata RAP based service ,In this example i have used a custom Entity to fetch the open items
@Metadata: { allowExtensions: true }
@ObjectModel: {
query: { implementedBy: 'ABAP:ZCLOPENITEMS' }
}
@EndUserText.label: 'Open Items'
define custom entity ZcopenItems
{
key CompanyCode : fis_bukrs;
AccountingDocument : farp_belnr_d;
FiscalYear : fis_gjahr;
AccountingDocumentItem : fis_buzei;
ClearingDate : fis_augdt;
ClearingAccountingDocument : fis_augbl;
PostingKey : fis_bschl;
FinancialAccountType : farp_koart;
Supplier : md_supplier;
Customer: kunnr;
GLAccount : fis_racct;
PostingDate : fis_budat;
CompanyCodeCurrency : fis_hwaer;
@Semantics.amount.currencyCode : 'CompanyCodeCurrency'
AmountInCompanyCodeCurrency : fis_hsl;
}
Implementation of the query Class :
class ZCLOPENITEMS definition
public
inheriting from CL_RAP_QUERY_PROVIDER_SELECTOR
final
create public .
public section.
interfaces IF_SADL_EXIT .
interfaces IF_RAP_QUERY_PROVIDER .
data COMP_CODE type BUKRS .
data CUSTOMER type KUNNR .
data:
output TYPE STANDARD TABLE OF ZcopenItems
WITH EMPTY KEY .
protected section.
private section.
methods GET_OPEN_ITEMS
importing
!OFFSET type INT8
!PAGE_SIZE type INT8 .
ENDCLASS.
CLASS ZCLOPENITEMS IMPLEMENTATION.
METHOD get_open_items.
DATA :
openitems_o TYPE ZcopenItems,
table_size TYPE count.
DATA(max_rows) = COND #( WHEN page_size = if_rap_query_paging=>page_size_unlimited THEN 0
ELSE page_size ).
SELECT
CompanyCode,
AccountingDocument,
FiscalYear,
ClearingDate ,
ClearingAccountingDocument,
PostingDate,
FinancialAccountType,
Supplier,
Customer,
GLAccount,
PostingKey,
CompanyCodeCurrency,
AmountInCompanyCodeCurrency
FROM I_OperationalAcctgDocItem
WHERE
FiscalYear EQ '2022'
AND customer EQ @customer
AND ClearingAccountingDocument IS INITIAL
AND AccountingDocumentCategory NE 'D'
AND AccountingDocumentCategory NE 'M'
INTO TABLE @DATA(open_items).
LOOP AT open_items ASSIGNING FIELD-SYMBOL(<open_items>).
IF sy-tabix > offset.
MOVE-CORRESPONDING <open_items> TO openitems_o .
APPEND openitems_o TO output.
CLEAR openitems_o.
table_size = lines( output ).
IF max_rows IS NOT INITIAL AND
sy-tabix >= max_rows.
EXIT.
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD if_rap_query_provider~select.
TRY.
DATA(filter) = io_request->get_filter( )->get_as_ranges( ).
LOOP AT filter ASSIGNING FIELD-SYMBOL(<filter>).
CASE <filter>-name.
WHEN 'CUSTOMER'.
Customer = VALUE #( <filter>-range[ 1 ]-low OPTIONAL ).
WHEN 'COMPANYCODE'.
comp_code = VALUE #( <filter>-range[ 1 ]-low OPTIONAL ).
WHEN OTHERS.
ENDCASE.
ENDLOOP.
get_open_items(
offset = io_request->get_paging( )->get_offset( )
page_size = io_request->get_paging( )->get_page_size( ) ).
IF io_request->is_total_numb_of_rec_requested( ).
DATA(count) = CONV int8( lines( output ) ).
io_response->set_total_number_of_records( count ).
ENDIF.
IF io_request->is_data_requested( ).
io_response->set_data( output ).
ENDIF.
CATCH cx_rap_query_response_set_twic.
CATCH cx_rap_query_filter_no_range.
* Forward exeption
RAISE EXCEPTION TYPE cx_rap_message_error
EXPORTING
textid = VALUE #(
msgid = 'SY'
msgno = '499'
attr1 = |Filter must be provided| ).
CATCH cx_rap_query_provider.
ENDTRY.
ENDMETHOD.
ENDCLASS.
Generated Service biding from Service definition
- Now Create the Adaptation project for extending the Object Page with new section as shown in the below steps :
- From the BAS go to File – > New Project from Template
Standard Application selection
Now add the Custom ODATA RAP based Service in the manifest.json
"changeType": "appdescr_app_setTitle",
"content": {
"dataSource": {
"customer.openitems": {
"uri": "/sap/opu/odata/sap/ZUI_OPENITEMS/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
},
"model": {
"customer.openitems": {
"dataSource": "customer.openitems",
"settings": {}
}
}
}
Manifest.json
Now preview the application by right clicking on the manifest.json and open with Visual Editor ,
navigate to the object page and add a new section by selection whole page in edit mode and also create a controllerextension .
Add a section in Object Page
Fragment Creation
Add the below Smart table in the Extended Fragment
<!-- Use stable and unique IDs!-->
<core:FragmentDefinition xmlns:core='sap.ui.core'
xmlns:uxap='sap.uxap'
xmlns='sap.m'
xmlns:table="sap.ui.table"
xmlns:mvc="sap.ui.core.mvc"
xmlns:u="sap.ui.unified"
xmlns:smartFilterBar="sap.ui.comp.smartfilterbar"
xmlns:smartTable="sap.ui.comp.smarttable"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:app="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1"
controllerName="customer.zz1mangcreacctext.OpenItems" height="100%">
<uxap:ObjectPageSection id="sample.Id" title="Open Items">
<uxap:subSections>
<uxap:ObjectPageSubSection id="idOpen_Items" title="Open Items">
<uxap:blocks>
<VBox id="idVboxMain" fitContainer="true">
<Text id="idTextSection"></Text>
<smartTable:SmartTable id="idOpenItems" entitySet="ZcopenItems" tableType="Table"
useVariantManagement="true" useTablePersonalisation="true"
header="Open Items" showRowCount="true"
enableAutoBinding="true" class="sapUiResponsiveContentPadding"
showFullScreenButton="true" placeToolbarInTable="true"
initiallyVisibleFields="CompanyCode,AccountingDocument,FiscalYear,AccountingDocumentItem,Customer
" initialNoDataText="Loading..."
requestAtLeastFields="CompanyCode,AccountingDocument,FiscalYear,AccountingDocumentItem,Customer">
<smartTable:layoutData >
<FlexItemData growFactor="1" baseSize="0%" id="idFid1"/>
</smartTable:layoutData>
</smartTable:SmartTable>
</VBox>
</uxap:blocks>
</uxap:ObjectPageSubSection>
</uxap:subSections>
</uxap:ObjectPageSection>
</core:FragmentDefinition>
Open Items section added as shown below, you can also change the sequence of the section
Fragment Display
However the view model is not set from the model defined in the manifest.json. To Bind the same extend the controller as shown below using the visual editor and add the binding method .
Controller Extension
ID mentioned should be from the Smart Table in the fragment
onBeforeRendering: function() {
let oComponent = this.getView().getController().getOwnerComponent(),
oModel = oComponent.getModel("customer.openitems");
this.byId("idOpen_Items").setModel(oModel);
},
onAfterRendering: function() {
},
Next Challenge would be to pass the Filter to the Service from the List Page or Object page Header .
Since smart table is auto binded to oData service it will return complete data of entityset. So here Odata has to be filtered while loading the data .
For this you can use the below two approaches by overriding the OnInit() function as shown below
ExtensionAPI:
SAP ListReport Fiori elements base controller is accessed by this.base templateBaseExtension provides public API for SAP Fiori elements extensions, like the extensionAPI
- Use Extension API and call beforeRebindTable inside attachPageDataLoaded function
- Define the below function in the Smart table
beforeRebindTable=”.extension.customer.zz1mangcreacctext.OpenItems.onBeforeRebindTable”
and implement below in the controller extension
onBeforeRebindTable: function (oEvent) {
var binding = oEvent.getParameter("bindingParams");
let oComponent = this.getView().getController().getOwnerComponent();
let oCustdata = oComponent.getModel("filterModel").getData();
if (oCustdata) {
var oFilter = new sap.ui.model.Filter({
filters: [
new sap.ui.model.Filter("Customer", "EQ", oCustdata.Customer),
],
and: true
});
binding.filters.push(oFilter);
}
}
- Use method attachBeforeRebindTable in the Extension API attachPageDataLoaded function
onInit: function() {
let that = this;
this.oExtensionAPI = this.base.templateBaseExtension.getExtensionAPI();
this.oExtensionAPI.attachPageDataLoaded(function(oEvent) {
let oSmartTable = that.getView().byId("fin.fscm.cr.creditaccounts.manage::sap.suite.ui.generic.template.ObjectPage.view.Details::CrdtMBusinessPartner--customer.zz1mangcreacctext.idOpenItems");
if (oSmartTable) {
oSmartTable.attachBeforeRebindTable(function(oEvent1) {
var oCustdata = oEvent.context.getObject();
var oFilter = new sap.ui.model.Filter({
filters: [
new sap.ui.model.Filter("Customer", "EQ", oCustdata.Customer),
],
and: true
});
oEvent1.getParameter("bindingParams").filters.push(oFilter);
});
}
oSmartTable.rebindTable(true);
});
Extended Facet
Thus you can create read-only Facet Extensions with Modification Free Extension technique .
Hi Vijay,
Thanks for sharing..it’s really helpful!!
Thank you,
Syam
Hi Vijay,
Very well explained.
Just want to add an important aspect from performance point -of-view, we should always use lazy loading concept for additional/extended sections so that data for that section will be fetched on demand when it is requested.
Thanks and regards,
Prabhjot
Great Blog Vijay - helped us a lot at a customer implementation.
One remark:
The step "Now add the Custom ODATA RAP based Service in the manifest.json" wasn't working for us when we tried to update the datasource and model directly in the "manifest.json" - it resulted in some error.
When adding them via the specific menu entries of the BAS everything worked fine.
Cheers, Philipp
Hello Vijay Chintarlapalli,
isn't there also an option to re-define the Standard OData Service and add the custom entity there? Then you could replace the datasource of the default model and the extension to bind to another model would not be needed.
Best Regards
Gregor
Hi Gregor Wolf ,
The above examples are extensions of the standard Fiori applications which are already delivered using the RAP model and no RAP Extensibility is provided .
unfortionatlly not able to navigate to object page any more using BAS UI5 Visual Editor