Skip to Content

This is the second part of my blog series around component reuse. These are the contents of the blog series

  1. Demystifying the art of component reuse in SAPUI5
  2. Component Reuse – 4 ways of sharing data between the apps
  3. Simplifying the component reuse to work both on premise and in cloud and using the manifest.json

 

One of the things I’ve always found being really complex was component reuse. This means to use one app inside another. In this blog series, I will try my best to demystify this, as it really isn’t that hard at all.

First of all, why would you even want to do this? Well let me try to explain. As you probably know, developers are lazy and if we can reuse previous generated code for other apps, not only is it smarter, because we only change in one place, but also much more effective of our precious lazy time. As an example, I am working for a customer right now, where the search helps we use in the app are other independent apps and also we are building extensions to My Inbox workflow items, that will be consisting of about 6 different apps, one for each tab in the app. Sounds complicated right, but hopefully by the end of this blog series, you’ll realise that it really isn’t.

I’ve been wanting to write this blog series for quite a while, so here is the contents of the blog series:

  1. Demystifying the art of component reuse in SAPUI5
  2. Component Reuse – 4 ways of sharing data between the apps
  3. Simplifying the component reuse to work both on prem and in cloud and using the manifest.json
  4. Taking your component reuse to a whole new level!

 

My first blog will be a basic example on how to use one app within another and also a library. A library is a set of custom made controls or javascript files, that makes sense to share among multiple apps to create a modular application landscape. There is already a good blog about this stuff, but i thought i’d try to simplify and explain in a bit more detail of the important parts of this.

First I have created two independent apps using the SAPUI5 application template. One named MyParentApp and the other MyChildApp (Classy right!)

MyChildApp doesn’t really have anything special, but I’ve added a view with a hard coded text for now.

<mvc:View controllerName="bourneMyChildApp.controller.View1" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc"
	displayBlock="true" xmlns="sap.m">
	<App>
		<pages>
			<Page title="{i18n>title}">
				<content>
					<Text text="World" />
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

Now deploy our first app to SAP Cloud Platform, otherwise this won’t work. Also keep in mind that for your reuse applications, you need to deploy your changes to see them, WebIde can’t pick them up from an undeployed app.

MyParentApp also has a simple view with a similar text and then I’ve added a component container. When you read the documentation about component reuse, you will realise that you can’t initialise another component without a component container.

<mvc:View controllerName="bourneMyParentApp.controller.View1" xmlns:core="sap.ui.core" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc"
	displayBlock="true" xmlns="sap.m">
	<App>
		<pages>
			<Page title="{i18n>title}">
				<content>
					<Text text="Hello"/>
					<core:ComponentContainer width="100%" 
						name="bourneMyChildApp" 
						component="bourneMyChildApp" />
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

Now the magic happens in two specific files, when you are working in SAP Cloud Platform (SCP), the component.js file and the neo-app.json file.

The component.js file is initialised and this is where we declare the path and namespace for our child application. This is done with two lines of code:

jQuery.sap.registerModulePath("bourneMyChildApp", "../../mychildapp/");
			
jQuery.sap.require("bourneMyChildApp.Component");

The first registers the namespace and name of my child application and the path to the app. The second initialises the component.

The namespace and name of my child application is also what you add in the component container of your view, see a pattern here? 😉

Now for the neo-app.json file, the way SCP works is really via this file, you use it already for pointing odata calls through your cloud connector to your backend system. Now we have to do a similar thing, so when SCP sees that particular URL pattern, it will be routed to where we point it to.

In my example it is the path for /mychildapp/ that is pointing to an application deployed on SCP named mychildapp.

 {
      "path": "/mychildapp/",
      "target": {
        "type": "application",
        "name": "mychildapp"
      },
      "description": "My Child App"
    }

That is it, when you run the app, you should see something similar to this. 

Neat right!

 

Alright now for our second course of actions, which is slightly more tricky, how to use a shared library. For more info on that topic, have a look at this blog.

The idea for a shared library is that if you have custom controls or maybe a formatter or other shared functions, you can store them in a shared library that can be used by multiple apps.

In my example I will show both usecases.

Firstly I have added another textfield to my view in the parent app, where I want to use a formatter.

<mvc:View controllerName="bourneMyParentApp.controller.View1" xmlns:core="sap.ui.core" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc"
	displayBlock="true" xmlns="sap.m">
	<App>
		<pages>
			<Page title="{i18n>title}">
				<content>
					<VBox>
					<Text id="myNumber" text="{ path: '/MyNumber', 
						formatter: '.sharedFormatter.numberUnit' }" />
					<Text text="Hello"/>
					</VBox>
					<core:ComponentContainer width="100%" 
						name="bourneMyChildApp" 
						component="bourneMyChildApp" />
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

Secondly I have initialised a jsonmodel that I am binding my field to. I have added a new jsonmodel to my parent application and added the following data to the model

	var oModel = this.getModel(),
				oJsonData = {
					MyNumber: 100000.1234
				};
			oModel.setData(oJsonData);

Now in the view controller is where i declare my formatter.

sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"mysharedlibrary/SharedFormatter"
], function(Controller, SharedFormatter) {
	"use strict";

	return Controller.extend("bourneMyParentApp.controller.View1", {
		sharedFormatter: SharedFormatter
	});
});

This is done by the mysharedlibrary/SharedFormatter to declare the path to th efile and then I initialise the javascript file in the  sharedFormatter: SharedFormatter line.

Now we need to make SCP recognise that path, we do this again in the neo-app.json file.

{
      "path": "/resources/mysharedlibrary/",
      "target": {
        "type": "application",
        "name": "mysharedlibrary",
        "entryPath": "/"
      },
      "description": "My shared library"
    }

Please notice that we need to use the resources path here, as SAPUI5 automatically will look in that folder to try and find our library. Also we need to set the entrypath of our library to the root. I’ll explain why later.

Alright, to extend the example I will now add a custom control into the child applications view.

For that we declare the namespace in the XML view, similar to how you do it normally, and then you refer to that namespace, when declaring your control

<mvc:View controllerName="bourneMyChildApp.controller.View1" 
	xmlns:bourne="mysharedlibrary" 
	xmlns:html="http://www.w3.org/1999/xhtml" 
	xmlns:mvc="sap.ui.core.mvc"
	displayBlock="true" xmlns="sap.m">
	<App>
		<pages>
			<Page title="{i18n>title}">
				<content>
					<Text text="World" />
					<bourne:ProductRating />
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

Also add the following to the neo-app.json file in the child component

{
      "path": "/resources/mysharedlibrary/",
      "target": {
        "type": "application",
        "name": "mysharedlibrary",
        "entryPath": "/"
      },
      "description": "My shared library"
    }

REMEMBER to redeploy your application. Otherwise you won’t see the changes.

Now for the library. Create a new folder in your workspace and name it “MySharedLibrary”.

Copy a neo-app.json file from your parent app as well as a manifest.json file to the folder.

Remove all references except the vanilla ones in the neo-app.json file, so it looks like this:

{
  "routes": [
    {
      "path": "/resources",
      "target": {
        "type": "service",
        "name": "sapui5",
        "entryPath": "/resources"
      },
      "description": "SAPUI5 Resources"
    },
    {
      "path": "/test-resources",
      "target": {
        "type": "service",
        "name": "sapui5",
        "entryPath": "/test-resources"
      },
      "description": "SAPUI5 Test Resources"
    }
  ]
}

 

Now for the manifest.json file, we need change the type to be of “library” instead of “application”

{
	"_version": "1.7.0",
	"sap.app": {
		"id": "mysharedlibrary",
		"type": "library",
		"i18n": "i18n/i18n.properties",
		"applicationVersion": {
			"version": "1.0.0"
		},
		"title": "{{appTitle}}",
		"description": "{{appDescription}}",
		"sourceTemplate": {
			"id": "ui5template.basicSAPUI5ApplicationProject",
			"version": "1.40.12"
		}
	},
	"sap.ui": {
		"technology": "UI5",
		"icons": {
			"icon": "",
			"favIcon": "",
			"phone": "",
			"phone@2": "",
			"tablet": "",
			"tablet@2": ""
		},
		"deviceTypes": {
			"desktop": true,
			"tablet": true,
			"phone": true
		},
		"supportedThemes": ["sap_hcb", "sap_belize"]
	},
	"sap.ui5": {
		"dependencies": {
			"minUI5Version": "1.30.0",
			"libs": {
				"sap.ui.core": {},
				"sap.m": {},
				"sap.ui.layout": {},
				"sap.ushell": {},
				"sap.collaboration": {},
				"sap.ui.comp": {},
				"sap.uxap": {}
			}
		},
		"contentDensities": {
			"compact": true,
			"cozy": true
		},
		"models": {
			"i18n": {
				"type": "sap.ui.model.resource.ResourceModel",
				"settings": {
					"bundleName": "BournemySharedLibrary.i18n.i18n"
				}
			}
		},
		"resources": {
			"css": [{
				"uri": "css/style.css"
			}]
		}
	}
}

 

Everything else is also vanilla.

Now create we need to create the library file, which tells SAPUI5 that this is a library. So create a .library file

Copy in the following

<?xml version="1.0" encoding="UTF-8" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd" >

  <name>sap.ui.unified</name>
  <vendor>SAP SE</vendor>
  <copyright>${copyright}</copyright>
  <version>${version}</version>
  
  <documentation>Unified controls intended for both, mobile and desktop scenarios</documentation>
  
  <dependencies>
	<dependency>
	  <libraryName>sap.ui.core</libraryName>
	</dependency>
  </dependencies>
  
  <appData>
	<selenium xmlns="http://www.sap.com/ui5/buildext/selenium" package="com.sap.ui5.selenium.unified" />
	<!-- excludes for the JSCoverage -->
	<jscoverage xmlns="http://www.sap.com/ui5/buildext/jscoverage" >
	  <exclude name="sap.ui.unified.js." />
	</jscoverage>
	<documentation xmlns="http://www.sap.com/ui5/buildext/documentation"
		indexUrl="../../../../test-resources/sap/ui/unified/demokit/docuindex.json"
		resolve="lib" />
  </appData>

</library>

 

Next create a library.js file and load the following

/* global my:true */

sap.ui.define([
		"jquery.sap.global",
		"sap/ui/core/library"
	], // library dependency
	function(jQuery) {

		"use strict";

		sap.ui.getCore().initLibrary({
			name: "mysharedlibrary",
			version: "1.0.0",
			dependencies: ["sap.ui.core"],
			types: [],
			interfaces: [],
			controls: [
				"mysharedlibrary.ProductRating"
			],
			elements: []
		});

		return my.custom.control;

	}, /* bExport= */ false);

In here, we keep a reference to our ProductRating custom control, the formatter is independently referenced.

Create a ProductRating.js file and add the following:

sap.ui.define([
	"sap/ui/core/Control",
	"sap/m/RatingIndicator",
	"sap/m/Label",
	"sap/m/Button"

], function (Control, RatingIndicator, Label, Button) {
	"use strict";
	return Control.extend("mysharedlibrary.ProductRating", {
		metadata : {
			properties : {
				value: 	{type : "float", defaultValue : 0}
			},
			aggregations : {
				_rating : {type : "sap.m.RatingIndicator", multiple: false, visibility : "hidden"},
				_label : {type : "sap.m.Label", multiple: false, visibility : "hidden"},
				_button : {type : "sap.m.Button", multiple: false, visibility : "hidden"}
			},
			events : {
				change : {
					parameters : {
						value : {type : "int"}
					}
				}
			}
		},
		init : function () {
			this.setAggregation("_rating", new RatingIndicator({
				value: this.getValue(),
				iconSize: "2rem",
				visualMode: "Half",
				liveChange: this._onRate.bind(this)
			}));
			this.setAggregation("_label", new Label({
				text: "{i18n>productRatingLabelInitial}"
			}).addStyleClass("sapUiTinyMargin"));
			this.setAggregation("_button", new Button({
				text: "{i18n>productRatingButton}",
				press: this._onSubmit.bind(this)
			}));
		},

		setValue: function (iValue) {
			this.setProperty("value", iValue, true);
			this.getAggregation("_rating").setValue(iValue);
		},

		_onRate : function (oEvent) {
			var oRessourceBundle = this.getModel("i18n").getResourceBundle();
			var fValue = oEvent.getParameter("value");

			this.setValue(fValue);

			this.getAggregation("_label").setText(oRessourceBundle.getText("productRatingLabelIndicator", [fValue, oEvent.getSource().getMaxValue()]));
			this.getAggregation("_label").setDesign("Bold");
		},

		_onSubmit : function (oEvent) {
			var oResourceBundle = this.getModel("i18n").getResourceBundle();

			this.getAggregation("_rating").setEnabled(false);
			this.getAggregation("_label").setText(oResourceBundle.getText("productRatingLabelFinal"));
			this.getAggregation("_button").setEnabled(false);
			this.fireEvent("change", {
				value: this.getValue()
			});
		},
		renderer : function (oRM, oControl) {
			oRM.write("<div");
			oRM.writeControlData(oControl);
			oRM.addClass("myAppDemoWTProductRating");
			oRM.writeClasses();
			oRM.write(">");
			oRM.renderControl(oControl.getAggregation("_rating"));
			oRM.renderControl(oControl.getAggregation("_label"));
			oRM.renderControl(oControl.getAggregation("_button"));
			oRM.write("</div>");
		}
	});
});

 

And lastly create a SharedFormatter.js file and paste the following:

sap.ui.define(["sap/ui/core/format/DateFormat"], function(DateFormat) {

	"use strict";

	return {

		/**
		 * Rounds the number unit value to 2 digits
		 * @public
		 * @param {string} sValue the number string to be rounded
		 * @returns {string} sValue with 2 digits rounded
		 */
		numberUnit: function(sValue) {
			if (!sValue) {
				return "";
			}
			return sValue.toLocaleString();
		}
	};

});

 

Now deploy your application to SCP and try and test your parent application.

Hopefully you get a similar result to this:

 

It isn’t pretty, but that wasn’t the intention. Hopefully this gives you the necessary explanation of how you do component reuse.

So to summarise, the important factors is

  • your jquery calls to register your nested component.
  • Your neo-app.json file to get SCP to recognise the URL pattern and point to seperate applications
  • Component container inside your parent app, to nest another application
  • The library.js and .library file as well as declaring a library as type in the manifest.json file of a library.

Please give feedback on the blog or post questions to me via twitter on @uxkjaer

My next blog will be about how we can now share data between our applications, because right now they are running independently of each other.

 

To report this post you need to login first.

15 Comments

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

  1. Mike Doyle

    A very comprehesive blog on an important topic. Thanks Jakob! In my opinion component re-use is rather under-documented and not often discussed. A key point is that as well as eliminating code duplication it leads to a more consistent user experience too.

    (1) 
  2. Former Member

    Great ! 

    But how to load different components dynamically without listed all the paths of possible components in the neo-app.json file?

    Because if new reusable component developed, we can get the component info like its name from back-end service, but the front-end need to add the path to the neo-app.json and redeployment. So that maybe hard for maintaining with front-end.

    (0) 
    1. Jakob Marius Kjær Post author

      You will always need to handle it in the neo-app.json file for SCP content. However for backend is a different story. Look out for my blog next week on how to handle that in the manifest.json file

      (0) 
  3. Meghal Shah

    Hi Jakob ,

    I did similar but when i tied to run the application from Fiori Launchpad its giving Error.  Fiori Launchpad is generating wrong URL.  There is a small change in ” registerModulePath “.

    jQuery.sap.registerModulePath(“bourneMyChildApp”, “../mychildapp/”);

    But over all nice blog …. 🙂

     

    Regards ,

    Meghal Shah

    (0) 
  4. Former Member

    Hello Jakob,

    I am now doing an intership in a company and totally new in sapui5. My supervisor told me that I need to learn more about modularization and I have already read many others articles from the interent. However, they are all too differicult for me to understand, because most of them just mentioned about the concept and didn’t show the step cleary, and it’s let me feel so frustrated. After reading your blogs, I feel the idea reusing view is much more clear to me. However, I still have many problems about the coding part, could you send me the source code from the above program? My email is k.lam@tu-bs.de

    Many many thanks!!

    Regards,

    Cora

    (0) 
  5. Jhon Jairo Teran Salazar

    Hello Jakob,

    I really want to thank you for the effort in doing this blog, this clears many of the questions I had, just one doubt, if I want to do the same with openui5 deployed in an apache server, what would be the replacement of the neo-app.json in this kind of app’s to reference the another app.

    Thank you again.

    Jhon Jairo.

    (0) 
    1. Jakob Marius Kjær Post author

      Sorry for not getting gnback to you earlier. You should be able to do something similar to on the abap stack. So you need to declare the paths as they are hosted on the Apache server. Then it should be fine.

      (0) 
  6. Fazal Ilahi

    Hi Jakob,

    Many thanks for such a detailed blog.

    I was able to create a reusable library on SCP using the steps mentioned in this blog. I am facing couple of issues. Appreciate if you can give some insights.

    1) Reusable library does not work when I specify exact UI5 version in parent app’s index.html. e.g. src=”https://sapui5.hana.ondemand.com/1.52.9/resources/sap-ui-core.js”

    2) How can this library be used for apps deployed on SCP’s portal?

    Thanks,
    Fazal.

     

    (0) 

Leave a Reply