Skip to Content

Last year we were contacted by a couple of people trying to integrate a Network Graph control into an object page. As this might be a common scenario for Fiori apps, we decided to put together this step-by-step guide that will lead you through the process.

Facet Breakout

The most common way to include a custom control into an object page is to implement a Facet Breakout. With a Facet Breakout, you can define a custom view for a portion of your object page. The problem is that a facet was designed to hold a smaller amount of content and is placed into an infinite scrolling container with all other facets. Network Graph, on the other hand, was designed as a content-rich component. It is expected to visualize more data and to take up most of the space on the page. It also supports zooming and has its own scroll bars. If you place it in a Facet Breakout, you will most probably end up with double scroll bars and poor user experience.

Another problem with a Facet Breakout is that the header section on an object page is quite large and it eats up the precious space that could be used to visualize the graph itself. With larger network graphs, this could be quite annoying.

That’s why we would recommend that you include the Network Graph in a Page-Level Breakout instead. If you still would like to place a Network Graph inside a Facet Breakout, we would recommend that you do that only for a small number of nodes. You should apply a fixed height and set enableWheelZoom to false. This will prevent the network graph from interfering with the facet container scrolling.

Page-Level Breakout

Our demo application will visualize a list of documents. Each document represents one step in a complex process our company has implemented to process orders. To allow the user to figure out quickly what the document represents, we want to provide them with an option to click a document in a list report and see it in a network graph representing the entire process.

Before we create a Page-Level Breakout, we need an OData service and a List Report application. If you already have both, you can skip the following two section and continue with Creating Your Own UI Component.

OData Service

We are going to build a simple OData service using a CDS view and a Gateway. In the CDS view, we will use a simple table named general_table:

ID Type Title Value Status CreatedFld Ref1 Ref2
1 NODE Customer Order Success 20180601
2 NODE Proof of Shipment Standard 20180604
3 NODE Generated Invoice Error 20180605
4 LINE 1 2
5 LINE 1 3
6 ATTR Document Type Order 1
7 ATTR Processor 356859 1
8 ATTR Document Type Shipment 2
9 ATTR Processor 258456 2
10 ATTR Document Type Invoice 3
11 ATTR Processor 123586 3

We will need 4 entity sets for our app. The first entity set will be used as a model for a list of documents.

define view DOCUMENTS as select general_table as main
 left outer to one join general_table as attr on attr.type = 'ATTR' and attr.title = 'Document Type' and main.id = attr.ref1
 association [0..1] to NODES as _node on $projection.Id = _node.Id
{
 @UI: {
       lineItem: {position: 10},
       selectionField: {position: 10}
      }
  key main.id as Id,
  @UI: {
       lineItem: {position: 20}
      }
  main.title as Title,
  @UI: {
       lineItem: {position: 30},
       selectionField: {position: 20}
      }
  attr.value as DocumentType,
  @UI: {
       lineItem: {position: 40},
       selectionField: {position: 30}
      }
  main.createdfld as Createdm,
  _node
} where main.type = 'NODE'

The other three entity sets will provide the models for the data needed for network graph. In our app, we will only use nodes, lines, and attributes. If we want to join nodes into groups and include node details, we would need more entity sets.

define view NODES as select from general_table
association [0..*] to ATTRIBUTES as _attribute on $projection.Id = _attribute.ParentNode
{
  key id as Id,
  title as Title,
  status as Status,
  _attribute
} where type = 'NODE'
define view ATTRIBUTES as select from general_table {
  key id as Id,
  ref1 as ParentNode,
  title as LabelC,
  value as Value
} where type = 'ATTR'
define view LINES as select from general_table {
  key id as Id,
  ref1 as IdFrom,
  ref2 as IdTo
} where type = 'LINE'

Creating a Simple List Report Application

To create a simple List Report application, we will use SAP Web IDE (https://www.sap.com/developer/topics/sap-webide.html).

  1. In the SAP Web IDE, choose File > New > Project from Template and select List Report Application.
  2. Provide a project name and title and point the wizard to your OData service.
  3. Pick the pre-generated annotation, even though we don’t need annotations in this example.
  4. In the Template Customization step, select the DOCUMENTS entity set as OData Collection and click Finish.

You can try to run the application in Sandbox mode. When the application loads, you should see a list report application with a table that contains 3 documents. When you click on an item in the table, you should see an empty object page.

Creating Your Own UI Component

Now we need to create a UI component that we will use for the Canvas breakout. To learn more about UI components, see Components.

  1. Create a new folder named component inside the webapp folder. In the newly created component folder, create a new Component.js file:Component.js:
    sap.ui.define(["sap/ui/core/UIComponent",	"sap/suite/ui/generic/template/extensionAPI/ReuseComponentSupport"],
            function(UIComponent, ReuseComponentSupport) {
            	"use strict";
    
            	return UIComponent.extend("BlogTest.component.Component", {
            		metadata: {
            			manifest: "json",
            			library: "BlogTest.component",
            			properties: {
            				/** Properties for reuse component */
            				uiMode: {
            					type: "string",
            					group: "standard"
            				},
            				semanticObject: {
            					type: "string",
            					group: "standard"
            				},
            				stIsAreaVisible: {
            					type: "boolean",
            					group: "standard"
            				}
            			}
            		},
            		init: function() {
            			ReuseComponentSupport.mixInto(this, "component");
            			(UIComponent.prototype.init || jQuery.noop).apply(this, arguments);
            		},
            		stStart: function(oModel, oBindingContext, oExtensionAPI) {
            			var oComponentModel = this.getComponentModel();
            			this.getRootControl().setModel(oModel);
            			oComponentModel.setProperty("/navigationController", oExtensionAPI.getNavigationController());
            			oComponentModel.setProperty("/selectedNode", oExtensionAPI.getNavigationController().getCurrentKeys()[1]);
            			this.extensionAPI = oExtensionAPI;
            		},
            		stRefresh: function(oModel, oBindingContext) {
            			var oComponentModel = this.getComponentModel();
            			var oNavigationController = oComponentModel.getProperty("/navigationController");
            			oComponentModel.setProperty("/selectedNode", oNavigationController.getCurrentKeys()[1]);
            			this.getRootControl().getController().updateSelection();
            		}
            	});
            });
    • The stStart function is called each time the List Report initializes the object page for the first time.
    • stRefresh is called whenever user navigates back to object page from the List Report.Please note that the page stays initialized even when the user clicks the Back button and returns back to the List Report. When the user navigates to the object page again, the init method is not called on the controller anymore and stRefresh is the most convenient method to use if you want to get notified about this event.

    Each time the user navigates to the object page, we will store the ID of the document the user used for the navigation to be able to select corresponding node. The ID of the element the user clicks is stored in the navigation controller and can be fetched using the getCurrentKeys method. We will store this value in the component model, which can be accessed in the controller. We also need to call the updateSelection method in the controller (we will define this method in one of the next steps).

  2. Now we need to create a view and its controller. Just like the view and the controller of the main app, we will put these new view and controller into the corresponding folders, view and controller.Default.view.xml:
    <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:ng="sap.suite.ui.commons.networkgraph"
                      controllerName="BlogTest.component.controller.Default" >
            	<App>
            		<pages>
            			<Page id="group" title="{i18n>groupTitle}">
            				<content>
            					<ng:Graph id="networkGraph" nodes="{/NODES}" lines="{/LINES}">
            						<ng:nodes>
            							<ng:Node key="{Id}" title="{Title}" shape="Box" attributes="{path:'to_attribute', templateShareable:false}" status="{Status}">
            								<ng:attributes>
            									<ng:ElementAttribute label="{LabelC}" value="{Value}" />
            								</ng:attributes>
            							</ng:Node>
            						</ng:nodes>
            						<ng:lines>
            							<ng:Line from="{IdFrom}" to="{IdTo}" />
            						</ng:lines>
            					</ng:Graph>
            				</content>
            			</Page>
            		</pages>
            	</App>
            </mvc:View>

    Default.controller.js:

    sap.ui.define([
            	"sap/suite/ui/generic/template/extensionAPI/extensionAPI"
            ], function(extensionAPI) {
            	"use strict";
    
            	return sap.ui.controller("BlogTest.component.controller.Default", {
            		onInit: function() {
            			var oGraph = this.getView().byId("networkGraph");
            			oGraph.attachBeforeLayouting(function () {
            				this.bGraphReady = false;
            			}.bind(this));
            			this.getView().byId("networkGraph").attachGraphReady(function () {
            				this.bGraphReady = true;
            				this.selectNode();
            			}.bind(this));
            		},
            		selectNode: function () {
            			var oComponent = this.getOwnerComponent();
            			var oComponentModel = oComponent.getComponentModel();
            			var sSelectedNode = oComponentModel.getProperty("/selectedNode");
            			var oGraph = this.getView().byId("networkGraph");
            			var oNode = oGraph.getNodeByKey(sSelectedNode);
            			if (oNode) {
            				oGraph.getNodes().forEach(function (oN) {
            					oN.setSelected(false);
            				});
            				oNode.setSelected(true);
            				oGraph.scrollToElement(oNode);
            			}
            		},
            		updateSelection: function () {
            			if (this.bGraphReady) {
            				this.selectNode();
            			}
            		}
            	});
            });

    A few tricks here. We want to make sure that a node user selected on the List Report is selected and visible when the graph loads. This needs to be done when the graph is rendered. Remember that Network Graph first renders an empty placeholder and starts the layouting worker task which can take few seconds. When the layouting is done, the graph is rendered again and graphReady event is fired. If you need to do anything with the graph after it’s rendered, this is the event to use. We will also use newly introduced scrollToElement function, which ensures, that given element (node, line or group) will be in the visible viewBox. This is important for big graphs as the node can be outside of visible area.

  3. We also need to create a simple i18n.properties file that contains translatable text strings and a manifest.json file:i18n.properties:
    #XTIT: Application name
            appTitle=
    
            #YDES: Application description
            appDescription=
    
            #XTIT: Application name
            groupTitle=An Additional Title

    manifest.json:

    {
            	"_version": "1.7.0",
            	"sap.app": {
            		"_version": "1.1.0",
            		"id": "BlogTest.component",
            		"type": "component",
            		"i18n": "i18n/i18n.properties",
            		"title": "{{appTitle}}",
            		"description": "{{appDescription}}",
            		"ach": "xxx",
            		"offline": false,
            		"resources": "resources.json",
            		"embeddedBy": "${project.artifactId}",
            		"applicationVersion": {
            			"version": "${project.version}"
            		}
            	},
            	"sap.ui": {
            		"_version": "1.1.0",
            		"technology": "UI5",
            		"deviceTypes": {
            			"desktop": true,
            			"tablet": true,
            			"phone": true
            		}
            	},
            	"sap.ui5": {
            		"_version": "1.1.0",
            		"rootView": "BlogTest.component.view.Default",
            		"dependencies": {
            			"minUI5Version": "${sap.ui5.dist.version}",
            			"libs": {}
            		},
            		"models": {
            			"i18n": {
            				"type": "sap.ui.model.resource.ResourceModel",
            				"uri": "i18n/i18n.properties"
            			}
            		},
            		"contentDensities": {
            			"compact": true,
            			"cozy": true
            		}
            	}
            }
            
  4. Now it’s time to point the List Report template to the component. We will use sap.suite.ui.generic.template.Canvas for this.In your manifest.json file find the sap.ui.generic.app section. The configuration should have a pages property that was configured to be used with a List Report. This configuration was generated by the WebIDE wizard. You need to change the ListReport pages configuration. Since there is a couple of pages properties involved, I’ll include the entire sap.ui.generic.app configuration:

    sap.ui.generic.app

    "sap.ui.generic.app": {
            		"_version": "1.3.0",
            		"settings": {},
            		"pages": {
            			"ListReport|DOCUMENTS": {
            				"entitySet": " DOCUMENTS",
            				"component": {
            					"name": "sap.suite.ui.generic.template.ListReport",
            					"list": true,
            					"settings": {
            						"smartVariantManagement": true
            					}
            				},
            				"pages": {
            					"Canvas|NODES": {
            						"routingSpec": {
            							"noOData": true,
            							"routeName": "NODES",
            							"typeImageUrl": ""
            						},
            						"navigationProperty": "to_node",
            						"entitySet": "NODES",
            						"component": {
            							"name": "sap.suite.ui.generic.template.Canvas",
            							"settings": {
            								"requiredControls": {
            									"footerBar": false,
            									"paginatorButtons": false
            								}
            							}
            						},
            						"implementingComponent": {
            							"componentName": "BlogTest.component"
            						}
            					}
            				}
            			}
            		}
            	}
  5. However, this is not enough. If you try running the app now, the navigation will open the page with Network Graph, but the selection of corresponding node will not work.We need to create a controller extension and define the navigation manually. In order to do that, you must create an ext folder in the webapp folder, and create a controller folder inside the ext folder. In the controller folder, create a ListReportExt.controller.js file:

    ListReportExt.controller.js:

    sap.ui.define([
            	"sap/suite/ui/generic/template/extensionAPI/extensionAPI"
            ], function(extensionAPI) {
            	"use strict";
    
            	return sap.ui.controller("BlogTest.ext.controller.ListReportExt", {
            		onInit: function () {
            		},
            		onListNavigationExtension: function(oEvent) {
            			var oExtensionAPI = this.extensionAPI;
            			var oNavigationController = oExtensionAPI.getNavigationController();
            			var oItem = oEvent.getSource();
            			var oBindingContext = oItem.getBindingContext();
            			var oObject = oBindingContext.getObject();
            			oNavigationController.navigateInternal(oObject.Id, {
            				routeName: "NODES"
            			});
            			return true;
            		}
            	});
            });

    If you already have a controller extension for the List Report, you can just define the onListNavigationExtension method. You must also define the controller extension in manifest.json in sap.ui5 configuration:

    manifest.json:

    "extends": {
            			"extensions": {
            				"sap.ui.controllerExtensions": {
            					"sap.suite.ui.generic.template.ListReport.view.ListReport": {
            						"controllerName": "BlogTest.ext.controller.ListReportExt",
            						"sap.ui.generic.app": {
            							"DOCUMENTS": {
            								"EntitySet": "DOCUMENTS"
            							}
            						}
            					}
            				}
            			}
            		}
  6. At the end, your object page should look similar to this:
To report this post you need to login first.

Be the first to leave a comment

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

Leave a Reply