Skip to Content
Technical Articles
Author's profile photo Ananthanarayanan P

How to create Master Detail DetailDetail views with Fiori 2.0 guidelines

Hi Colleagues,

In this blog I will be explaining about the SAP Fiori 2.0 App by following the Fiori 2.0 guidelines and also about the flexible column layout.

Before pitching into the coding, let me take you through what are the latest guidelines as per Fiori 2.0.

As we know the Fiori guidelines are  : Role based , Adaptive, Coherent,  Simple and Delightful.

But when it comes to  SAP Fiori 2.0 which is the newest evolution of the user experience for SAP S/4HANA.

The design allows the users to concentrate on core tasks while still keeping track of activities in other areas. SAP fiori elements (Previously known as smart templates) and SAP Fiori launchpad has brand new visual designs.

There are so many fiori design guidelines for different categories like : UI elements, layouts, floor-plans and frameworks.

Under the category Layouts and floor plans and framework, we will deep-dive into the concept of Flexible Column Layout & Dynamic Page.

For our Fiori 2.0 app we need to understand these two guidelines before proceeding.

Flexible Column Layout

The flexible column layout is a layout control that displays multiple views on a single page. As shown as below in image1

The columns are known as beginColumnPages midColumnPages and endColumnPages .

This is a very creative and allows faster and more fluid navigation between multiple floorplans than the usual page-by-page navigation.The flexible column layout offers different layouts with up to three columns. Users can expand the column they want to focus on, switch between different layouts, and view the needed column in full screen mode.

The relative sizes and the visibility of the three columns are determined based on the value of the sap.f.LayoutType property

Consider a scenario where the user wants to view a list of sales order and the corresponding Sales Order Item.

The usual previous approach to this scenario is using a tree table where the parent node contains “Sales order” and Child node contains “Sales order items”. But if we need to use a tree table we need to keep the data model very specific with respect to this treetable alone i.e creating a view in odata and add fields like HierarchyLevel , Drill State etc. And also this might be even more complicated for a simple scenarios . Even with the WorkList View and Object Page this scenario can be handled, but the pages/views of the sales order and sales order item cannot be shown in one frame but will be navigated to different frames.

With the help of flexible column layout, these kind of scenarios can be handled so easily and also the flexible column layout behaves responsively, which makes it suitable for both desktop and mobile devices. Depending on the available screen width, an optimized layout is loaded to ensure the best possible user experience on each device. With this layout we can create a master detail and detail-detail scenario (SalesOrder in master&detail , and SalesorderItem in detail-detail) in which the user can drill down or navigate.

NOTE:  If you are planning to create a workbench or dashboard or open multiple instances of the same object type then it is not recommended to follow Flexible Column Layout.

So simply we use these layout arrows <> to expand or collapse the floor plans or views. And based on the different screen resolutions like tablet desktop and mobile the navigation pane will be responsive.

Dynamic Page 

Dynamic page is a layout which is designed to support various floorplans like List report, Overview page, wizard, worklist etc.

In simple words : Dynamic page is advanced version of sap.m.Page which comes with a Header Title and Header Content and optional Footer toolbar along with the page content with a built-in responsive behavior.

Also the dynamic page will have navigation action buttons for Fullscreen, Exit Fullscreen and Close the page.

The skeleton of the Dynamic page is like this :

NOTE: As per the guidelines, the dynamic header doesn’t contain a back navigation button in the header and the user has to register the app with the dynamic page to the Launchpad so that the shell container has a back button by default.

So now on combining these two layouts and by following the new guidelines we can make a Master Detail- DetailDetail Application and it will look like this :

The above screen shows the  Products in the master page as a list (Responsive Table), then on clicking the list it opens the Detail which contains the Dynamic Page with the headers and list of suppliers, and supplier information can be viewed in the Detail-detail page.

The greatest advantage of this design is, one can copy the URL drilled till the supplier details and run it another session. There wont be any loss of binding in detail  or detail-detail via the route navigation.

This concept is really a useful for many use-cases.  The most attractive part is the navigation to the pages which happens very smoothly across many devices and landscapes.

By Now I assume you have got an idea  on what is flexible column layout and dynamic page.

Well, creating this app is very simple. we can directly download the sample application available in the demokit link,  and import this in your latest webide

For importing select File – > Import->File or Project-> Select the file from downloads folder and press OK.

Once after importing this sample app. we need to understand the logic handled in this app, which is very important .  I will be explaining these in detail. Before I explain the logic one must be aware of the other UI5 concepts like data binding and routing.

On importing the app to webide , we can see the “view” folder under the webapp and we have the 4 main views required for our scenario, which are App.view, Master.view, Detail.view. DetailDetail.view . Now as per this architecture The App.view is the main view or container of the master and detail views. So logically the FlexibleColumnLayout will reside here.

<mvc:View
	controllerName="sap.ui.demo.fiori2.controller.App"
	displayBlock="true"
	height="100%"
	xmlns="sap.f"
	xmlns:mvc="sap.ui.core.mvc">
	<FlexibleColumnLayout
		id="flexibleColumnLayout"
		stateChange=".onStateChanged"
		backgroundDesign="Solid"
		layout="{/layout}"/>
</mvc:View>

The FlexibleColumnLayout should have these parameters to be bound with certain values i.e stateChange and layout. StateChange event is always triggered whenever there is any kind of changes to the layout due to user interaction are communicated to the app.

So based on user’s call any of these layouts can be set for the flexible column layout

1. In manifest.json we can notice these dependencies as we need these APIs for our app.

"dependencies": {
"minUI5Version": "1.60.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.f": {},
"sap.uxap": {}
}

In the rootView the view name should point to App.view.xml
“viewName”: “sap.ui.demo.fiori2.view.App”, and along with this “controlId”: “flexibleColumnLayout”, in the routing, which is the ID of the flexible column layout present in the App.view.xml which is mandatory.

"routing": {
			"config": {
				"routerClass": "sap.f.routing.Router",
				"viewType": "XML",
				"viewPath": "sap.ui.demo.fiori2.view",
				"controlId": "flexibleColumnLayout",
				"transition": "slide",
				"bypassed": {
				},
				"async": true
},

Also we use sap.f.routing here instead of the sap.m.routing .

Now when it comes to routes it should be as shown below:

{
	"pattern": ":layout:",
	"name": "master",
	"target": [
		"master",
		"detail"
	]
},
{
	"pattern": "detail/{product}/{layout}",
	"name": "detail",
	"target": [
		"master",
		"detail"
	]
},
{
	"pattern": "detail/{product}/detailDetail/{supplier}/{layout}",
	"name": "detailDetail",
	"target": [
		"master",
		"detail",
		"detailDetail"
	]
}

The first route is master and the target is master and detail , because whenever we navigate to master then the detail page also has to be matched in the route match every time.  For “detail” route the pattern will hold “detail/{Object}/{layout}” because when this detail is navigated, the {object} will have the bindingContext or Path from the master list and the {layout} holds the information about the layouts like TwoColumnsMidExpanded or TwoColumnsBeginExpanded so that this is set on the flexiblecolumnlayout.

Same goes for the third route detail-detail , the pattern should contain both “detail/{product}” and “/detailDetail/{supplier}/layout”, because when we navigate from detail to detail-detail all the binding information of the detail page (via {product}) must be preserved along with the binding information of the detailDetail( via {supplier}) . If we miss this pattern, then the middle page binding information wont be available when the URL (along with the detailDetail)is hit on another session. This route should always have master, detail and detailDetail in the target.

2. In the master and detail pages, in the current code it contains List and the dynamic page respectively . Here the logic is straight forward , as we have a responsive table bound to a simple json model,  and on click of the item, it navigates to the detail page. Detail page contains a dynamic page with ObjectPageLayout and header content, page content with optional footer.

3. Now the most important step is to handle to routes and navigation. In our App.controller.js under onInit.

onInit: function () {
			this.oOwnerComponent = this.getOwnerComponent();
			this.oRouter = this.oOwnerComponent.getRouter();
			this.oRouter.attachRouteMatched(this.onRouteMatched, this);
			this.oRouter.attachBeforeRouteMatched(this.onBeforeRouteMatched, this);
		},

We attach these events “BeforeRouteMatched” and “attachRouteMatched”, the reason is when the event BeforeRouteMatched is triggered, the model for setting the layout is called and first gets the Layout . either “TwoColumnMidExpanded” or “ThreeColumnMidExpanded” etc.

onBeforeRouteMatched: function(oEvent) {
			var oModel = this.oOwnerComponent.getModel(),
				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");

			// Save the current route name
			this.currentRouteName = sRouteName;
			this.currentProduct = oArguments.product;
		},

So here we have this.oOwnerComponent.getHelper() method defined inside the Component.js and this is a method from Flexible Column Layout Semantic Helper.

FlexibleColumnLayout gives you the freedom to implement any app logic that involves changing the layout (showing/hiding columns) as a result of the user’s actions. However, there are certain UX patterns that are considered as optimal and are recommended for SAP Fiori 2.0 apps. The FlexibleColumnLayoutSemanticHelper class helps you implement them by giving you tips about what layout to display when. i.e for eg . On clicking the close button (Close button on the detail screen), with the help of this class we can get the next layout the is OneColumn.

So this code is added in Component.js and this method is called and returns the layout based on the current layout set in the FlexibleColumnLayout.

In this we give the values for defaultTwoColumnLayoutType as TwoColumnsMidExpanded and defaultThreeColumnLayoutType as ThreeColumnsMidExpanded . Based on your preferences we can change these values to any of the layouts available like TwoColumnBeginExpanded or ThreeColumnEndExpanded. etc. InitialColumnCounts is 2 here and this value is determines whether a single-column or a 2-column layout will be suggested for logical level 0.

Possible values are :

  • Value of 1 (default) – A single-column layout will be suggested for logical level 0.
  • Value of 2 – A 2-column layout will be suggested for logical level 0.
getHelper: function () {
			var oFCL = this.getRootControl().byId('flexibleColumnLayout'),
				oSettings = {
					defaultTwoColumnLayoutType: sap.f.LayoutType.TwoColumnsMidExpanded,
					defaultThreeColumnLayoutType: sap.f.LayoutType.ThreeColumnsMidExpanded,
					initialColumnsCount: 2
				};

			return FlexibleColumnLayoutSemanticHelper.getInstanceFor(oFCL, oSettings);
		}

After the BeforeRouteMatched is completed, onRouteMatched is triggered, If from a master list when an item is selected then

this.oRouter.navTo("detail");

is executed and then it goes to onBeforeRouteMatched and onRouteMatched and then it goes to the detailRouteMatched. This cycle is common in this case , because we first set the layout of the App first and then set the arguments that are to passed to the Detail or DetailDetail pages routes.

In Detail.controller.js we attach the PatternMatched for Master, detail and for detailDetail here so that everytime whenever the URL is triggered with either OneColumn or TwoColumn Or ThreeColumn the onProductMatched should be handled where the binding of the Detail view happens so that the binding never lose the context.

this.oRouter.getRoute("master").attachPatternMatched(this._onProductMatched, this);
this.oRouter.getRoute("detail").attachPatternMatched(this._onProductMatched, this);
this.oRouter.getRoute("detailDetail").attachPatternMatched(this._onProductMatched, this);

Similarly in DetailDetail is registered always in onInit for the same reason mentioned above.

this.oRouter.getRoute("detailDetail").attachPatternMatched(this._onPatternMatch, this);

 

So every time this logic flow is handled in a cyclic way whenever the detail or detail-detail is navigated or when the navigation pane <> arrows , or when exit/close-column buttons or fullscreen button is pressed.

4. To handle the Exit-FullScreen and Open-in Fullscreen, with the help of FlexibleColumnLayoutSemanticHelper class as mentioned in previous step , we need to create the navigation Action buttons in detail.view as shown below.

<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>

These visible properties i.e /actionButtonsInfo/midColumn/fullScreen is set in the _updateUIElements method under the onStateChanged method of App.controller.js.

So via getCurrentUIState() method from the FlexibleColumnLayoutSemanticHelper instance the model is set to the properties.

// Update the close/fullscreen buttons visibility
		_updateUIElements: function () {
			var oModel = this.oOwnerComponent.getModel();
			var oUIState = this.oOwnerComponent.getHelper().getCurrentUIState();
			oModel.setData(oUIState);
		},
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);
			}
		},

When the app in TwoColumnMidExpanded mode

When you get the instance of the model (oModel.getData()) it retrieves the data as :

{"layout":"TwoColumnsMidExpanded",
"maxColumnsCount":3,
"columnsSizes":{"beginColumn":33,"midColumn":67,"endColumn":0},
"columnsVisibility:{"beginColumn":true,"midColumn":true,"endColumn":false},
"isFullScreen":false,
"isLogicallyFullScreen":false,
"actionButtonsInfo":{"midColumn":{
"fullScreen":"MidColumnFullScreen",
"exitFullScreen":null,
"closeColumn":"OneColumn"},
"endColumn":{"fullScreen":null,"exitFullScreen":null,"closeColumn":null}
}}

Now if we notice the values of actionButtonsInfo.midColumn.fullScreen = MidColumnFullScreen. So the button visibility will be true as per the expression Binding given in the above xml fragment.

And for the second button Exit Full screen is now null for actionButtonsInfo.midColumn.exitFullScreen. So hence visibility will be false. As we needn’t require this button to appear when the screen is not in FullScreen mode.

So now we can get to understand the exact significance of using FlexibleColumnLayoutSemanticHelper for this scenario.

5. Similarly we handle the navigation action buttons in DetailDetail.view.xml

<navigationActions>
					<m:OverflowToolbarButton
						type="Transparent"
						icon="sap-icon://full-screen"
						press=".handleFullScreen"
						tooltip="Enter Full Screen Mode"
						visible="{= ${/actionButtonsInfo/endColumn/fullScreen} !== null }"/>
					<m:OverflowToolbarButton
						type="Transparent"
						icon="sap-icon://exit-full-screen"
						press=".handleExitFullScreen"
						tooltip="Exit Full Screen Mode"
						visible="{= ${/actionButtonsInfo/endColumn/exitFullScreen} !== null }"/>
					<m:OverflowToolbarButton
						type="Transparent"
						icon="sap-icon://decline"
						press=".handleClose"
						tooltip="Close column"
						visible="{= ${/actionButtonsInfo/endColumn/closeColumn} !== null }"/>
</navigationActions>

Here the visible property is bound to /actionButtonsInfo/endColumn .

6. To handle Press function of these buttons, in Detail.controller.js we have this logic :

handleFullScreen: function () {
			var sNextLayout = this.oModel.getProperty("/actionButtonsInfo/midColumn/fullScreen");
			this.oRouter.navTo("detail", {layout: sNextLayout, product: this._product});
		},

handleExitFullScreen: function () {
			var sNextLayout = this.oModel.getProperty("/actionButtonsInfo/midColumn/exitFullScreen");
			this.oRouter.navTo("detail", {layout: sNextLayout, product: this._product});
		},

handleClose: function () {
			var sNextLayout = this.oModel.getProperty("/actionButtonsInfo/midColumn/closeColumn");
			this.oRouter.navTo("master", {layout: sNextLayout});
		},

When fullScreen is pressed then the  this.oModel.getProperty(“/actionButtonsInfo/midColumn/fullScreen”) will return layout as MidColumnFullScreen

Now in the next line we have the router.navTo(“detail”).. here it passes the MidColumnFullScreen and the corresponding binding object under the {product}. So it navigates to the same Detail Screen and changes the Layout to MidColumnFullScreen 

Now when you compare the TwoColumnMidExpanded and MidColumnFullScreen you can see the buttons are different in the navigation action area (Top right) , So this is how the model gets updated based on the _updateUIElements  as everytime if there is a stateChanged or route navigation on the FlexibleColumnLayout then the Model must update the layout definitions, otherwise the logic flow will be affected and transitions to full screen or twoColumn or threecolumn screen wont appear properly.

7. To handle the layout arrow <> navigation the code is present in onStateChanged event in App.controller. So in this logic the bIsNavigationArrow flag is retrieved if the event is triggered  via the <> buttons .

oEvent.getParameter(“layout”) returns as “TwoColumnsBeginExpanded” and then it navigates to the corresponding route.

8. Once after binding the sap.m.Table  in the Master.view.xml , to handle the navigation to detail, we should handle it on onListItemPress event, where the bindingContext of the item selected is retrieved, pass it to the {product} as path, and navigate to the “detail” and the oNextUIState is retrieved via FlexibleColumnLayoutSemanticHelper method getNextUIState().

onListItemPress: function (oEvent) {
			var productPath = oEvent.getSource().getBindingContext("products").getPath(),
				product = productPath.split("/").slice(-1).pop(),
				oNextUIState = this.getOwnerComponent().getHelper().getNextUIState(1);

			this.oRouter.navTo("detail", {layout: oNextUIState.layout, product: product});
		}

 

Whenever retrieve the getNextUIState(), we should pass the value as a parameter for getting the next state, based on the parameters the layouts will be appeared, as shown below.

Since this route navigates to detail page , we can use getNextUIState(1) and give the layout as TwoColumnsMidExpanded.

Similarly when the route navigates from Detail to DetailDetail page , we handle a similar logic in Detail.controller.js under onSupplierPress event of the Supplier List.

onSupplierPress: function (oEvent) {
			var supplierPath = oEvent.getSource().getBindingContext("products").getPath(),
				supplier = supplierPath.split("/").slice(-1).pop(),
				oNextUIState = this.oOwnerComponent.getHelper().getNextUIState(2);

			this.oRouter.navTo("detailDetail", {layout: oNextUIState.layout, supplier: supplier, product: this._product});
		},

And now from the oNextUIState we get the getNextUIState(2) for ThreeColumnMidExpanded as the Layout. And then pass it to the navTo method along with the supplier and product path. And finally the third column is shown with middle column expanded.

 

Hence now we can play around with different scenarios like minimizing the middle column and expanding the third column.

By this way UI5 developers can easily create master detail detail-detail pattern using FlexibleColumnLayout and I hope this blog would have helped in understanding the important concepts in the FlexibleColumnLayout floor plan and the routing concept behind the navigation used here.

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

Further more, I will come up with few more blogs related to Fiori 2.0 which has more complicated scenarios in the master detail detail-detail pattern and how to handle those logic.

Thank you.

Assigned Tags

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