Introduction:
There are already many blogs available regarding the My Inbox 2.0 extension. I would like to share my experience and challenges we faced while working on this extension application. With help of many blogs and Inbox cookbook we were able to successfully meet to our client’s requirements. In this blog I have mostly covered the coding part which we have over written for extension. I have excluded other simple bindings and view parts.
Our Requirements:
- Need to have multiple Inbox application which will be able to show task specific views in Info Tab and Separate Tiles for different categories of Tasks (example: PR Approval, PO approval, Requester View)
- Show custom header content and item details in the Info Section depending upon the selected task
- Customize Task Filter section to list
- Hide standard tabs (Comments and Attachment)
- Add Custom Tabs (Custom Comments)
- Custom Action button and custom Decision dialog
- Hide Standard Action buttons and provide Semantic Coloring for custom buttons
- Navigation to My Inbox application via Notifications
- Item detail screen
- Banner Name change depending upon the selected tile from Fiori launchpad
Starting Point:
Create an extension project from WEBIDE.
Select application CA_FIORI_INBOX from ABAP repository
Below is the screenshot of the extended files which we implemented.
While Extending the controller files we should select to copy the Code from Standard Controller. Thus we can modify the required methods to over write.
To have multiple Inbox application which will be able to show task specific views in Info Tab and Separate Tiles for different categories of Tasks (example: PR Approval, PO approval, Requester View) we followed the scenario-based Task filtration. Scenario are defined in system as shown below. Same scenarios are passed as parameters in FIORI launchpad tile configuration. Thus multiple tiles were created based on scenarios.
Parameters:
scenarioId=ScenarioName &enablePaging=true&pageSize=20
Show custom header content and item details in the Info Section depending upon the selected task
After extending the Info tab we have “S3_CustomerExtensionForInfoTabContentCustom.fragment.xml”
In this fragment create multiple Vertical Box Contents with visible false.
Inside each Vertical Box container we added respective contents based on scenario.
In S3Custom controller we need to overwrite below method for further changes.
This is router match event method so this method will be executed every time we select the items in the master list in my Inbox. Visibility of the selected UI Content and binding are taken care .
From the routing parameters we will be able to get the selected instance id and other details in that task.
We had the requirement to show the PR details and its PR items in the table.
We can get the PR number from the workflow instance.
From the oData service we can bind the view content with selected PR or Invoice based on selected task. Same time we will control the visibility of Content based on Task type.
/*
Routter patterned match handler event on Selection of Task, Depending upon the task type for PR, PO and WPR Info tab content is over written
*/
handleNavToDetail: function (e) {
var that = this;
this.oRoutingParameters = e.getParameters().arguments;
var busy = new sap.m.BusyDialog();
this.oInvoice = {};
if (e.getParameter("name") === "detail" || e.getParameter("name") === "detail_deep") {
this.bIsTableViewActive = e.getParameter("name") === "detail_deep" && !this.bNavToFullScreenFromLog;
this.oInvoice.WorkitemID = e.getParameter("arguments").InstanceID;
this.oInvoice.SAP__Origin = e.getParameter("arguments").SAP__Origin;
var i = e.getParameter("arguments").InstanceID;
i = i.replace(":", ""); // Removing last ":" on refresh
var c = e.getParameters().arguments.contextPath;
var taskData = this.getView().getModel().getProperty("/" + c);
// Added to get taskData when directly navigated from Notification
if(!taskData){
taskData = {};
taskData.TaskDefinitionID = "";
var serviceURL = this.getView().getModel().sServiceUrl + "/" + c;
jQuery.ajax({
type: 'GET',
url: serviceURL,
dataType: 'json',
async: false,
success: function(oData, response) {
taskData = oData.d;
}.bind(this),
error: function(err, response) {}.bind(this)
});
}
//Dummy Task Names
var prTasklist = ["TS00000001", "TS00000002","TS00000003","TS00000004"];
var poTaskList = ["TS90000010", "TS90000020"];
var wprTaskList = ["TS99000010", "TS99000020","TS99000030"];
var taskID = taskData.TaskDefinitionID.split("_")[0];
if (taskData.TaskDefinitionID && taskData.TaskDefinitionID.indexOf("TS***") > -1) {
this.getView().byId("approveShoppinfCartContentID").setVisible(true);
this.getView().byId("acceptPoInvoiceContentID").setVisible(false);
this.getView().byId("acceptPRContentID").setVisible(false);
this.getView().byId("acceptWPRContentID").setVisible(false);
} else if (taskData.TaskDefinitionID && taskData.TaskDefinitionID.indexOf("TS0000***") > -1) {
this.getView().byId("approveShoppinfCartContentID").setVisible(true);
this.getView().byId("acceptPoInvoiceContentID").setVisible(false);
this.getView().byId("acceptPRContentID").setVisible(false);
this.getView().byId("acceptWPRContentID").setVisible(false);
}else {
this.getView().byId("approveShoppinfCartContentID").setVisible(true);
this.getView().byId("acceptPoInvoiceContentID").setVisible(false);
this.getView().byId("acceptPRContentID").setVisible(false);
this.getView().byId("acceptWPRContentID").setVisible(false);
}
//other standard code from SAP
var o = e.getParameter("arguments").SAP__Origin;
if (i && i.lastIndexOf(":") === i.length - 1) {
return;
}
if (jQuery.isEmptyObject(this.getView().getModel().oData)) {
var t = this;
var d = sap.ca.scfld.md.app.Application.getImpl().getComponent().getDataManager();
d.setCallFromDeepLinkURL(true);
d.oDataRead("/TaskCollection(SAP__Origin='" + jQuery.sap.encodeURL(e.getParameter("arguments").SAP__Origin) + "',InstanceID='" +
jQuery.sap.encodeURL(i) + "')", null,
function (D) {
d = sap.ca.scfld.md.app.Application.getImpl().getComponent().getDataManager();
if (D === undefined || jQuery.isEmptyObject(D)) {
d.setDetailPageLoadedViaDeepLinking(false);
} else {
var I = jQuery.extend(true, {}, D);
if (t.fnIsTaskInstanceAllowed(I, d)) {
d.setDetailPageLoadedViaDeepLinking(true);
t.fnPerpareToRefreshData(c, i, o);
} else {
d.setDetailPageLoadedViaDeepLinking(false);
}
}
},
function (E) {
sap.ca.scfld.md.app.Application.getImpl().getComponent().getDataManager().setDetailPageLoadedViaDeepLinking(false);
return;
});
} else {
this.fnPerpareToRefreshData(c, i, o);
}
}
this.setBannerName(); //Custom Method to set Banner name based on selected Scenario
},
After every task selection, buttons are loaded based on the task type
We need to remove few standard action buttons, over write the functionality of the button and add some extra buttons
extHookChangeFooterButtons: function (oButtonList) {
var that = this;
var prEditableTasklist = ["T***00001","T***00002"];
var prTasklist = ["T***00001","T***00002","T***00003","T***00004","T***00005"];
var poTaskList = [T***000010, "T***000020"];
var wprTaskList = ["T***00033","T***00044","T***00055","T***00099","T***00000"];
var viewBindingPath = this.getView().getBindingContext().sPath;
var taskDefinitionID = this.getView().getModel().getProperty(viewBindingPath + "/TaskDefinitionID");
var taskID = taskDefinitionID.split("_")[0];
if (prEditableTasklist.indexOf(taskID) > -1) { // PR ITems
oButtonList.oPositiveAction = null;
oButtonList.oNegativeAction = null;
oButtonList.aButtonList.unshift({
iDisplayOrderPriority: 500,
sBtnTxt: "Edit",
onBtnPressed: function (event) {/*custom code on press Edit*/}
});
}
var buttonCount = oButtonList.aButtonList.length;
//Code to control POSITIVE Type and NEGATIVE Type buttons for approve and reject
for (var oCount = buttonCount - 1; oCount > -1; oCount--) {
if (oButtonList.aButtonList[oCount].sBtnTxt === "Approve" || oButtonList.aButtonList[oCount].sBtnTxt === "Accept"
|| oButtonList.aButtonList[oCount].sBtnTxt === "Submit" || oButtonList.aButtonList[oCount].sBtnTxt === "Approve WPR") {
oButtonList.aButtonList[oCount].nature = "POSITIVE";
}
if (oButtonList.aButtonList[oCount].sBtnTxt === "Reject" || oButtonList.aButtonList[oCount].sBtnTxt === "Reject WPR") {
oButtonList.aButtonList[oCount].nature = "NEGATIVE";
}
}
//Code to remove Forward buttons
buttonCount = oButtonList.aButtonList.length;
for (var k = buttonCount - 1; k > -1; k--) {
if (oButtonList.aButtonList[k].sI18nBtnTxt === "XBUT_FORWARD") {
if(this.AuthorizedUser.Flag !== "X"){ //keep Forward button only for Authorized User
oButtonList.aButtonList.splice(k, 1);
}
}
if (oButtonList.aButtonList[k].sI18nBtnTxt === "XBUT_SHOWLOG") {
oButtonList.aButtonList.splice(k, 1);
}
}
buttonCount = oButtonList.aButtonList.length;
if (wprTaskList.indexOf(taskID) > -1) { // WPR ITems
for (var i = buttonCount - 1; i > -1; i--) {
if (oButtonList.aButtonList[i].sI18nBtnTxt === "XBUT_OPEN") {
oButtonList.aButtonList.splice(i, 1, {
iDisplayOrderPriority: 1510,
sBtnTxt: "Display WPR",
onBtnPressed: function (event) {/*custom code*/}
});
break;
}
}
}
if (prTasklist.indexOf(taskID) > -1 ) {
for (var j = buttonCount - 1; j > -1; j--) {
if (oButtonList.aButtonList[j].sI18nBtnTxt === "XBUT_OPEN") {
oButtonList.aButtonList.splice(j, 1, {
iDisplayOrderPriority: 1510,
sBtnTxt: "Display PR",
onBtnPressed: function (event) {/*Custom code*/}
});
}
}
}
},
On Press of Standard Action buttons below method is executed, so we need to over write this method to customize standard action buttons events
/*Standard method overwritten for handling the custom decision dialog for Approve/Reject etc depending upon the task*/
showDecisionDialog: function (f, d, s) {
var that = this;
var oViewContextPath = this.getView().getBindingContext().sPath;
var taskDefinitionID = this.getView().getModel().getProperty(oViewContextPath + "/TaskDefinitionID");
var prTasklist = ["TS***1", "TS***2","TS***3" ];
var poTaskList = ["TS***11", "TS***12"];
var wprTaskList = ["TS******21", "TS****22","TS******23"];
var taskID = taskDefinitionID.split("_")[0];
if (d.DecisionText === "Reject" && taskID != "TS000000") {
if (prTasklist.indexOf(taskID) > -1) {
if (!this._valueHelpRejectPR) {
this._valueHelpRejectPR = sap.ui.xmlfragment("RejectDailogPR",
"cross.fnd.fiori.inbox.ZMY_INBOX.view.RejectfragPR",
this
);
this.getView().addDependent(this._valueHelpRejectPR);
}
this._valueHelpRejectPR.open();
}else if (d.DecisionText === "Reject" && wprTaskList.indexOf(taskID) > -1) {
if (!this._valueHelpRejectWPR) {
this._valueHelpRejectWPR = sap.ui.xmlfragment("RejectDailogWPR",
"cross.fnd.fiori.inbox.ZMY_INBOX.view.RejectfragWPR",
this
);
this.getView().addDependent(this._valueHelpRejectWPR);
}
this._valueHelpRejectWPR.open();
}
else if (d.DecisionText === "Accept" && poTaskList.indexOf(taskID) > -1) {
/*Custom Code*/
} else if (d.DecisionText === "Open Task") {
/*Custom Code on click of Open Task*/
} else {
this.oConfirmationDialogManager.showDecisionDialog({
question: this.i18nBundle.getText("XMSG_DECISION_QUESTION", d.DecisionText),
textAreaLabel: this.i18nBundle.getText("XFLD_TextArea_Decision"),
showNote: s,
title: this.i18nBundle.getText("XTIT_SUBMIT_DECISION"),
confirmButtonLabel: this.i18nBundle.getText("XBUT_SUBMIT"),
noteMandatory: d.CommentMandatory,
confirmActionHandler: jQuery.proxy(function (d, n) {
this.sendAction(f, d, n);
}, this, d)
});
}
}
},
For Hiding standard Tabs and Add Custom Tab we need to modify manifest file Extends section as below.
"extends": {
"component": "cross.fnd.fiori.inbox",
"extensions": {
"sap.ui.controllerExtensions": {
"cross.fnd.fiori.inbox.view.S3": {
"controllerName": "cross.fnd.fiori.inbox.ZMYINBOX.view.S3Custom"
},
"cross.fnd.fiori.inbox.view.S2": {
"controllerName": "cross.fnd.fiori.inbox.ZMYINBOX.view.S2Custom"
},
"cross.fnd.fiori.inbox.view.Empty": {
"controllerName": "cross.fnd.fiori.inbox.ZMYINBOX.view.EmptyCustom"
}
},
"sap.ui.viewExtensions": {
"cross.fnd.fiori.inbox.view.S3": {
"CustomerExtensionForInfoTabContent": {
"className": "sap.ui.core.Fragment",
"fragmentName": "cross.fnd.fiori.inbox.ZMYINBOX.view.S3_CustomerExtensionForInfoTabContentCustom",
"type": "XML"
},
"CustomerExtensionForObjectHeader": {
"className": "sap.ui.core.Fragment",
"fragmentName": "cross.fnd.fiori.inbox.ZMYINBOX.view.S3_CustomerExtensionForObjectHeader",
"type": "XML"
},
//Adding Custom tab
"CustomerExtensionForAdditionalTabs": {
"className": "sap.ui.core.Fragment",
"fragmentName": "cross.fnd.fiori.inbox.ZMYINBOX.view.S3_CustomerExtensionForAdditionalTabs",
"type": "XML"
}
}
},
//extension to hide standard tabs
"sap.ui.viewModifications": {
"cross.fnd.fiori.inbox.view.S3": {
"MIBAttachmentIconTabFilter": {
"visible": false
},
"MIBObjectLinksTabFilter": {
"visible": false
}
}
}
}
},
To Customize Task Filter section to list:
When we navigate to my Inbox application from notifications, it directly navigates to the items. It navigates with allItems=true parameter. Thus we need to restrict this to prevent user from checking other unwanted tasks which we filtered based on scenario.
Task filter is opened by clicking on Filter icon on bottom of Master page. It will load all available task types. This is stored in one JSON model created initially. Thus we need to remove unwanted tasks.
Also when no scenario is defined, we have set to default scenario on below method
initTaskDefnandCustomAttrDefnnModel: function (d) {
if (this.getView().getModel("taskDefinitionsModel")) {
//modified customization
var oTasklists = ["task1","task2",........."taskN"];
//list of valid tasks to be all task which user should see
for(var i=d.results.length-1;i>-1; i--){
var taskID = d.results[i].TaskDefinitionID.split("_")[0];
if(oTasklists.indexOf(taskID) < 0){
d.results.splice(i, 1);
}
}
this.getView().getModel("taskDefinitionsModel").setData(d.results, true);
}
},
loadInitialAppData: function () {
this.oDataManager.fetchTaskDefinitionsandCustomAttributeDefinitions(jQuery.proxy(this.initTaskDefnandCustomAttrDefnnModel, this));
//added for customization
if(!this.oDataManager.sScenarioId){
this.oDataManager.sScenarioId = "defaultScenarioName";
}
this.oDataManager.bShowAdditionalAttributes= false;
this.oDataManager.bAllItems= false;
this.getList().setGrowing(true).setGrowingScrollToLoad(true).setGrowingThreshold(20);
//Further other codes from SAP
},
//Method by SAP to get the applied filters
getAllFilters: function (a) {
var f = [];
var s = this.oDataManager.getScenarioConfig();
var A = s.AllItems;
var m = this.getView().getModel();
//Commented and get always scenario Based filted
/* if (A)
f = this.getFiltersWithoutScenario(m);
else
f = this.getFiltersWithScenario(m);*/
f = this.getFiltersWithScenario(m);
if (a)
f.push(a);
var S = m.aStatusFilterKeys;
var b = cross.fnd.fiori.inbox.util.TaskStatusFilterProvider.getAllFilters(this.oDataManager.bOutbox, S, f);
f.push(new sap.ui.model.Filter(b, false));
return [new sap.ui.model.Filter(f, true)];
},
Navigate to Item detail Screen Screen on row selection:
We need to add routing configuration firstly. Then we need to create view and controller for navigation.
//Manifest.json part
"routing": {
"routes": {
"masterDetail": {
"subroutes": {
"master": {
"subroutes": {
"prItemDetail": {
"pattern": "prItemDetail/{InstanceID}/{PRNumber}/{ItemNumber}",
"viewPath": "cross.fnd.fiori.inbox.ZMY_INBOX.view",
"view": "PRItemDetail"
},
"wprItemDetail": {
"pattern": "wprItemDetail/{InstanceID}/{WPRNumber}/{Year}/{ItemNumber}",
"viewPath": "cross.fnd.fiori.inbox.ZMY_INBOX.view",
"view": "WPRItemDetail"
}
}
}
}
}
},
"targets": {
"PRItemDetail": {
"viewType": "XML",
"viewName": "PRItemDetail"
},
"WPRItemDetail": {
"viewType": "XML",
"viewName": "WPRItemDetail"
},
"POItemDetail": {
"viewType": "XML",
"viewName": "POItemDetail"
}
}
}
},
//Code to navigate to S4 Screen in S3Custom.controller.js
this.oRouter.navTo("prItemDetail", {
"InstanceID": sInstanceID,
"PRNumber": sPRNo,
"ItemNumber": itemNumber
});
To change Banner Name depending upon the selected tile from Fiori launchpad :
We have same custom My Inbox for multiple scenario we need to change the Banner name at the top instead of showing “MY INBOX” for all scenarios.
This method will be called on Init method.
Same is done in init method of Component file in case no items are matched.
setBannerName : function (){
if(this.getOwnerComponent().getComponentData().startupParameters.scenarioId){
var scenarioId = this.getOwnerComponent().getComponentData().startupParameters.scenarioId[0];
var titleString = "";
switch(scenarioId){
case "ALL_APPROVAL":
titleString = "ALL Approval";
break;
case "PR_APPROVAL":
titleString = "Approve Purchase Requisition";
break;
case "REQUESTOR" :
titleString = "Requester Tasks";
break;
case "PO_APPROVAL":
titleString = "PO Approval";
break;
default :
titleString = "My Inbox Custom";
}
if(titleString){
this.getOwnerComponent().getService("ShellUIService").then( // promise is returned
function (oService) {
// var sTitle = that.getView().getModel("i18n").getResourceBundle().getText("TIMESHEET_TITLE") + oInfo.EmployeeName;
oService.setTitle(titleString);
},
function (oError) {
//jQuery.sap.log.error("Cannot get ShellUIService", oError, "sd");
}
);
}
}
},
**in manifest.json
"sap.ui5": {
"_version": "1.1.0",
"dependencies": {
"minUI5Version": "1.38.9",
"libs": {}
},
"services": {
"ShellUIService": {
"factoryName": "sap.ushell.ui5service.ShellUIService"
}
},