Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
UxKjaer
Product and Topic Expert
Product and Topic Expert
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.

 
29 Comments
Labels in this area