Skip to Content
Technical Articles

How to add a Fiori 2.0 (Master Detail under flexible column layout) within an existing SAPUI5 application

In my previous blog I explained how to create a Master detail app with fiori 2.0 guidelines using flexible column layout and dynamic page.

Now let us think about a scenario where we have a full screen page either sap.m.Page or sap.f.DynamicPage and in this existing App we need to add new master detail pages (Fiori 2.0 guideline) in between and handle navigation within these pages.

In this blog I will explain how to do handle this scenario.

As a prerequisite, you need to create a SAPUI5 application in webIDE via New > Project from template > SAPUI5 Application.

Enter the Project name , Namespace , view names etc and click finish button. A simple SAPUI5 App is generated and in that you will have View1.view.xml and its controller, And this contains a sap.m.Page

Once we create this we need to add and modify few files :

  1. For our scenario just create a Generic Tile inside the View1.xml as shown below.              Note : This is just an example, it is not always required to have GenericTile in the first page, it can be anything, but as simple example, I am keeping this control
    <App id="idAppControl">
    		<pages>
    			<Page title="{i18n>title}">
    				<content>
    					<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="Master Detail Fiori 2.0 App" press="press">
    				</GenericTile>
    				</content>
    			</Page>
    		</pages>
    	</App>​
  2. Now from this generic tile we should navigate to a master detail page which we will be creating in our Application. So for this we need to create a new View as MainApp.view.xml, add a flexibleColumnLayout and then add the beginView as “TestAppUI.TestAppUI5.view.View1”. In our previous blog the App.view.xml is the one which is used here but this is now going to hold the View1 as the begin column pages. Also it is very important to remember that whenever the existing application needs to have a masterdetail view of Fiori 2.0 guideline then the main file to be taken care is MainApp.view.xml, where this has to be the first view to be triggered on launch of the app and always the pages should be under the beginColumnPages section.
    <View xmlns="sap.f" displayBlock="true" height="100%" xmlns:m="sap.m" xmlns:mvc="sap.ui.core.mvc" controllerName="TestAppUI.TestAppUI5.controller.MainApp">
    	<m:App
    		id="app">
    
    		<FlexibleColumnLayout
    			id="MainApplicationId"
    			layout="{MasterDetail>/layout}"
    			backgroundDesign="Translucent"
    			stateChange="onStateChanged" 
    			>
    		<beginColumnPages> 
    			<mvc:XMLView id="beginView" viewName="TestAppUI.TestAppUI5.view.View1" />
    		</beginColumnPages>
    		</FlexibleColumnLayout>
    	</m:App>
    </View>​
  3. As we kept the Id of the flexibleColumnlayout as “MainApplicationId“, we need to use replace the ID of the “controlId”: “idAppControl” by “MainApplicationId” and “controlAggregation”: “beginColumnPages“. Because these are the aggregations for the FlexibleColumnLayout. So the manifest.json will be like
    				"routerClass": "sap.m.routing.Router",
    				"viewType": "XML",
    				"async": true,
    				"viewPath": "TestAppUI.TestAppUI5.view",
    				"controlAggregation": "beginColumnPages",
    				"transition": "slide",
    				"controlId": "MainApplicationId",
    				"clearControlAggregation": false
    			​
  4. Now in the MainApp.controller.js we need to keep the same logic how we had it previously in App.controller.js , so the code will look like :
    sap.ui.define([
    	"sap/ui/core/mvc/Controller"
    ], function (Controller) {
    	"use strict";
    
    	return Controller.extend("TestAppUI.TestAppUI5.controller.MainApp", {
    		onInit: function () {
    			this.oOwnerComponent = this.getOwnerComponent();
    			this.oRouter = this.oOwnerComponent.getRouter();
    			this.oRouter.attachRouteMatched(this.onRouteMatched, this);
    			this.oRouter.attachBeforeRouteMatched(this.onBeforeRouteMatched, this);
    		},
    
    		onBeforeRouteMatched: function(oEvent) {
    			var oModel = this.oOwnerComponent.getModel('MasterDetail'),
    				sLayout = oEvent.getParameters().arguments.layout,
    				oNextUIState;
    
    			// If there is no layout parameter, query for the default level 0 layout (normally OneColumn)
    			if (!sLayout) {
    				oNextUIState = this.oOwnerComponent.getHelper().getNextUIState(0);
    				sLayout = oNextUIState.layout;
    			}
    
    			oModel.setProperty("/layout", sLayout);
    		},
    
    		onRouteMatched: function (oEvent) {
    			var sRouteName = oEvent.getParameter("name"),
    				oArguments = oEvent.getParameter("arguments");
    
    			this._updateUIElements();
    
    			// Save the current route name
    			this.currentRouteName = sRouteName;
    			this.currentProduct = oArguments.product;
    			this.currentSupplier = oArguments.supplier;
    		},
    
    		onStateChanged: function (oEvent) {
    			var bIsNavigationArrow = oEvent.getParameter("isNavigationArrow"),
    				sLayout = oEvent.getParameter("layout");
    
    			this._updateUIElements();
    
    			// Replace the URL with the new layout if a navigation arrow was used
    			if (bIsNavigationArrow) {
    				this.oRouter.navTo(this.currentRouteName, {layout: sLayout, product: this.currentProduct, supplier: this.currentSupplier}, true);
    			}
    		},
    
    		// Update the close/fullscreen buttons visibility
    		_updateUIElements: function () {
    			var oModel = this.oOwnerComponent.getModel();
    			var oUIState = this.oOwnerComponent.getHelper().getCurrentUIState();
    			oModel.setData(oUIState);
    		},
    
    		onExit: function () {
    			this.oRouter.detachRouteMatched(this.onRouteMatched, this);
    			this.oRouter.detachBeforeRouteMatched(this.onBeforeRouteMatched, this);
    		}
    	});
    
    });​
  5. Next we have to add the master detail views and their controllers. For the code we can refer this for Master.view.xml :
    <mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" controllerName="TestAppUI.TestAppUI5.controller.Master"
    	xmlns:f="sap.f">
    	<f:DynamicPage id="dynamicPageId" toggleHeaderOnTitleClick="false">
    		<!-- DynamicPage Title -->
    		<f:title>
    			<f:DynamicPageTitle>
    				<f:heading>
    					<Title text="Products"/>
    				</f:heading>
    			</f:DynamicPageTitle>
    		</f:title>
    		<!-- DynamicPage Content -->
    		<f:content>
    			<VBox fitContainer="true">
    				<OverflowToolbar class="sapFDynamicPageAlignContent">
    					<ToolbarSpacer/>
    					<SearchField search="onSearch" width="17.5rem"/>
    					<OverflowToolbarButton icon="sap-icon://add" text="Add" type="Transparent" press="onAdd"/>
    					<OverflowToolbarButton icon="sap-icon://sort" text="Sort" type="Transparent" press="onSort"/>
    				</OverflowToolbar>
    				<Table id="productsTable" inset="false" items="{ path: 'products>/ProductCollection', sorter: { path: 'Name' } }"
    					updateFinished="onListUpdateFinished" class="sapFDynamicPageAlignContent" width="auto">
    					<columns>
    						<Column width="12em">
    							<Text text="Product"/>
    						</Column>
    						<Column hAlign="End">
    							<Text text="Price"/>
    						</Column>
    					</columns>
    					<items>
    						<ColumnListItem type="Navigation" press=".onListItemPress">
    							<cells>
    								<ObjectIdentifier title="{products>Name}" text="{products>ProductId}"/>
    								<ObjectNumber
    									number="{ parts:[ {path:'products>Price'}, {path:'products>CurrencyCode'} ], type: 'sap.ui.model.type.Currency', formatOptions: {showMeasure: false} }"
    									unit="{products>CurrencyCode}"/>
    							</cells>
    						</ColumnListItem>
    					</items>
    				</Table>
    			</VBox>
    		</f:content>
    		<!-- DynamicPage Footer -->
    		<f:footer>
    			<OverflowToolbar>
    				<ToolbarSpacer/>
    				<Button type="Accept" text="Accept"/>
    				<Button type="Reject" text="Reject"/>
    			</OverflowToolbar>
    		</f:footer>
    	</f:DynamicPage>
    </mvc:View>​
  6. Detail.view.xml should be like :
    <mvc:View xmlns:core="sap.ui.core" xmlns:f="sap.f" xmlns:form="sap.ui.layout.form" xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" 	xmlns="sap.uxap"
    	controllerName="TestAppUI.TestAppUI5.controller.Detail" xmlns:html="http://www.w3.org/1999/xhtml">
    	<ObjectPageLayout id="ObjectPageLayout" showTitleInHeaderContent="true" alwaysShowContentHeader="false" preserveHeaderStateOnScroll="false"
    		headerContentPinnable="true" isChildPage="true" upperCaseAnchorBar="false">
    		<headerTitle>
    			<ObjectPageDynamicHeaderTitle>
    				<expandedHeading>
    					<m:Title text="{products>Name}" wrapping="true" class="sapUiSmallMarginEnd"/>
    				</expandedHeading>
    				<snappedHeading>
    					<m:FlexBox wrap="Wrap" fitContainer="true" alignItems="Center">
    						<m:FlexBox wrap="NoWrap" fitContainer="true" alignItems="Center" class="sapUiTinyMarginEnd">
    							<f:Avatar src="https://sapui5.hana.ondemand.com/{products>ProductPicUrl}" displaySize="S" displayShape="Square" class="sapUiTinyMarginEnd"/>
    							<m:Title text="{products>Name}" wrapping="true"/>
    						</m:FlexBox>
    					</m:FlexBox>
    				</snappedHeading>
    				<navigationActions>
    					<m:OverflowToolbarButton type="Transparent" icon="sap-icon://full-screen" press=".handleFullScreen" tooltip="Enter Full Screen Mode"
    						visible="{= ${/actionButtonsInfo/midColumn/fullScreen} !== null }"/>
    					<m:OverflowToolbarButton type="Transparent" icon="sap-icon://exit-full-screen" press=".handleExitFullScreen" tooltip="Exit Full Screen Mode"
    						visible="{= ${/actionButtonsInfo/midColumn/exitFullScreen} !== null }"/>
    					<m:OverflowToolbarButton type="Transparent" icon="sap-icon://decline" press=".handleClose" tooltip="Close column"
    						visible="{= ${/actionButtonsInfo/midColumn/closeColumn} !== null }"/>
    				</navigationActions>
    				<actions>
    					<m:ToggleButton text="Edit" type="Emphasized" press=".onEditToggleButtonPress"/>
    					<m:Button text="Delete" type="Transparent"/>
    					<m:Button text="Copy" type="Transparent"/>
    					<m:Button icon="sap-icon://action" type="Transparent"/>
    				</actions>
    			</ObjectPageDynamicHeaderTitle>
    		</headerTitle>
    		<headerContent>
    			<m:FlexBox wrap="Wrap" fitContainer="true" alignItems="Stretch">
    				<f:Avatar src="https://sapui5.hana.ondemand.com/{products>ProductPicUrl}" displaySize="L" displayShape="Square" class="sapUiTinyMarginEnd"></f:Avatar>
    				<m:VBox justifyContent="Center" class="sapUiSmallMarginEnd">
    					<m:Label text="Main Category"/>
    					<m:Text text="{products>MainCategory}"/>
    				</m:VBox>
    				<m:VBox justifyContent="Center" class="sapUiSmallMarginEnd">
    					<m:Label text="Subcategory"/>
    					<m:Text text="{products>Category}"/>
    				</m:VBox>
    				<m:VBox justifyContent="Center" class="sapUiSmallMarginEnd">
    					<m:Label text="Price"/>
    					<m:ObjectNumber number="{products>CurrencyCode} {products>Price}" emphasized="false"/>
    				</m:VBox>
    			</m:FlexBox>
    		</headerContent>
    		<sections>
    			<ObjectPageSection title="General Information">
    				<subSections>
    					<ObjectPageSubSection>
    						<blocks>
    							<form:SimpleForm maxContainerCols="2" editable="false" layout="ResponsiveGridLayout" labelSpanL="12" labelSpanM="12" emptySpanL="0"
    								emptySpanM="0" columnsL="1" columnsM="1">
    								<form:content>
    									<m:Label text="Product ID"/>
    									<m:Text text="{products>ProductId}"/>
    									<m:Label text="Description"/>
    									<m:Text text="{products>Description}"/>
    									<m:Label text="Supplier"/>
    									<m:Text text="{products>SupplierName}"/>
    								</form:content>
    							</form:SimpleForm>
    						</blocks>
    					</ObjectPageSubSection>
    				</subSections>
    			</ObjectPageSection>
    			<ObjectPageSection title="Suppliers">
    				<subSections>
    					<ObjectPageSubSection>
    						<blocks>
    							<m:Table id="suppliersTable" items="{path : 'products>/ProductCollectionStats/Filters/1/values'}">
    								<m:columns>
    									<m:Column/>
    								</m:columns>
    								<m:items>
    									<m:ColumnListItem type="Navigation" press=".onSupplierPress">
    										<m:cells>
    											<m:ObjectIdentifier text="{products>text}"/>
    										</m:cells>
    									</m:ColumnListItem>
    								</m:items>
    							</m:Table>
    						</blocks>
    					</ObjectPageSubSection>
    				</subSections>
    			</ObjectPageSection>
    		</sections>
    		<footer>
    			<m:OverflowToolbar>
    				<m:ToolbarSpacer/>
    				<m:Button type="Accept" text="Save"/>
    				<m:Button type="Reject" text="Cancel"/>
    			</m:OverflowToolbar>
    		</footer>
    	</ObjectPageLayout>
    </mvc:View>​

    The above code for Master and Detail views are actually the same logic present in my previous blog

  7. Make sure the “MasterDetail” model and the “products” models are defined in the Component.js (As this model is set for layouts of flexibleColumnLayout) as shown below:
    sap.ui.define([
    	"sap/ui/core/UIComponent",
    	"sap/ui/Device",
    	"TestAppUI/TestAppUI5/model/models",
    	'sap/ui/model/json/JSONModel',
    	'sap/f/FlexibleColumnLayoutSemanticHelper'
    ], function (UIComponent, Device, models,JSONModel, FlexibleColumnLayoutSemanticHelper) {
    	"use strict";
    
    	return UIComponent.extend("TestAppUI.TestAppUI5.Component", {
    
    		metadata: {
    			manifest: "json"
    		},
    
    		/**
    		 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
    		 * @public
    		 * @override
    		 */
    		init: function () {
    			// call the base component's init function
    			UIComponent.prototype.init.apply(this, arguments);
    			var oModel,
    				oProductsModel;
    			// enable routing
    			oModel = new JSONModel();
    			this.setModel(oModel);
    			var oMasterDetailModel = new JSONModel();
    					
    			this.setModel(oMasterDetailModel, "MasterDetail");     
    
    			// set products demo model on this sample
    			oProductsModel = new JSONModel("https"+"://openui5.hana.ondemand.com/test-resources/sap/ui/documentation/sdk/products.json");
    			oProductsModel.setSizeLimit(1000);
    			this.setModel(oProductsModel, 'products');
    
    			// set the device model
    			this.setModel(models.createDeviceModel(), "device");
    			this.getRouter().initialize();
    		},
    		getHelper: function () {
    			var oFCL = this.getRootControl().byId('MainApplicationId'),
    				oSettings = {
    					defaultTwoColumnLayoutType: sap.f.LayoutType.TwoColumnsMidExpanded,
    					defaultThreeColumnLayoutType: sap.f.LayoutType.ThreeColumnsMidExpanded,
    					initialColumnsCount: 2
    				//	maxColumnsCount: 2
    				};
    
    			return FlexibleColumnLayoutSemanticHelper.getInstanceFor(oFCL, oSettings);
    		}
    	});
    });​
  8. In the manifest.json we need to define the routes for the Master, detail pages which is a mandatory step. Hence the routes and targets will be like this.
    "routes": [{
    				"name": "RouteView1",
    				"pattern": "",
    				"target": [
    					"TargetView1"
    				],
    				"layout": "OneColumn"
    			}, {
    				"pattern": ":layout:",
    				"name": "master",
    				"target": [
    					"master",
    					"detail"
    				]
    			}, {
    				"pattern": "detail/{product}/{layout}",
    				"name": "detail",
    				"target": [
    					"master",
    					"detail"
    				]
    			}],
    			"targets": {
    				"TargetView1": {
    					"viewType": "XML",
    					"transition": "slide",
    					"controlAggregation": "beginColumnPages",
    					"viewName": "View1"
    				},
    				"MainApp": {
    					"viewType": "XML",
    					"viewName": "MainApp"
    				},
    				"master": {
    					"viewName": "Master",
    					"controlAggregation": "beginColumnPages"
    				},
    				"detail": {
    					"viewName": "Detail",
    					"controlAggregation": "midColumnPages"
    				}
    			}​

    And the root view should be MainApp.view.xml instead of View1.view.xml 

    "rootView": {
    			"viewName": "TestAppUI.TestAppUI5.view.MainApp",
    			"type": "XML"
    		},
  9. Now in the View1.controller.js we need to add onPress event handler for the tile press. Here we give two parameters for the layout as “TwoColumnsMidExpanded” and product as just a dummy value say “Products“.
    onPress: function () {
    			this.oRouter.navTo("detail", {
    				layout: "TwoColumnsMidExpanded",
    				product: "Products"
    			});
    		}​

     

Finally on running this app it will be like this :

By this way UI5 developers can create a master detail view within an existing SAPUI5 application. And navigate within those views.

For accessing the Code you can download/clone from my git repository.

Further references and useful link regarding the Fiori 2.0 App can be found in https://sapui5.hana.ondemand.com/#/topic/c4de2df385174e58a689d9847c7553bd 

Thank you.

4 Comments
You must be Logged on to comment or reply to a post.