Skip to Content
Technical Articles

Fiori My Inbox 2.0 Extension

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:

  1. 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)
  2. Show custom header content and item details in the Info Section depending upon the selected task
  3. Customize Task Filter section to list
  4. Hide standard tabs (Comments and Attachment)
  5. Add Custom Tabs (Custom Comments)
  6. Custom Action button and custom Decision dialog
  7. Hide Standard Action buttons and provide Semantic Coloring for custom buttons
  8. Navigation to My Inbox application via Notifications
  9. Item detail  screen
  10. 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"
			}
		},

Conclusion:

We were  able to implement the extension for my Inbox 2.0. By referring to the cookbook and few help from SAP we resolved the issues for navigation from notifications.

Reference :

CookBook for My Inbox extension

Be the first to leave a comment
You must be Logged on to comment or reply to a post.