Skip to Content

This blog post is part 2 of a series of blogs on how to create a Custom Template in SAP Web IDE with OData integration.

Prerequisites

Before reading this part please make sure that you have read, understood and implemented part 1 of this series where I have shown how to create a feature project and add plugin template to the application.

Now we will proceed to the next steps:

Adding missing files & folders

As we could see in part 1, the app was generated but a few files are missing. Let’s create them manually.
First we will create a template for Component.js: “Component.js.tmpl” file under webapp folder as below:

Now we will create a few folders under the webapp folder.

  • controller (under this folder, we will create all application specific controllers)
  • css (under this folder we will create a css file for the application)
  • i18n (under this folder we will create a properties file for internationalization)
  • model (under this folder we will create models.js file, where we will create models, which we will use throughout the application, like device & FLP model)
  • view (under this folder we will create all the views for the application)

Note: To find the app namespace, we have to open the model.json file, there we can see a Json structure having the name: basicSAPUI5ApplicationProject, where we can find the namesapce parameter with lot of properties of wizard.

Model.json is the heart of the project, where we have to define all the attributes of the wizard for our template application.

Create models.js template

Now we have to create our models.js.tmpl file under the model folder in webapp. Where we will create function to device & FLP model.

Now we have to write the below logic in models.js.tmpl :

sap.ui.define([
	"sap/ui/model/json/JSONModel",
	"sap/ui/Device"
], function(JSONModel, Device) {
	"use strict";
	return {
		createDeviceModel: function() {
			var oModel = new JSONModel(Device);
			oModel.setDefaultBindingMode("OneWay");
			return oModel;
		},
		createFLPModel : function () {
				var fnGetuser = jQuery.sap.getObject("sap.ushell.Container.getUser"),
					bIsShareInJamActive = fnGetuser ? fnGetuser().isJamActive() : false,
					oModel = new JSONModel({
						isShareInJamActive: bIsShareInJamActive
					});
				oModel.setDefaultBindingMode("OneWay");
				return oModel;
			}
	};
});

Now we will write the logic for Component.js.tmpl file.

Before writing the below code, we should know the functionality of {{basicSAPUI5ApplicationProject.parameters.namespace.value}} which receives data from the wizard and provides the namespace value.

sap.ui.define([
		"sap/ui/core/UIComponent",
		"sap/ui/Device",
		"{{basicSAPUI5ApplicationProject.parameters.namespace.value}}/model/models",
		"sap/ui/model/json/JSONModel",
		"sap/ui/model/resource/ResourceModel"
	], function (UIComponent, Device, models, JSONModel, ResourceModel) {
		"use strict";

		return UIComponent.extend("{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.Component", {

			metadata : {
				manifest: "json"
			},

			/**
			 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
			 * In this function, the FLP and device models are set and the router is initialized.
			 * @public
			 * @override
			 */
			init : function () {
				// call the base component's init function
				UIComponent.prototype.init.apply(this, arguments);
				
				// set the device model
				this.setModel(models.createDeviceModel(), "device");
				
				// set the FLP model
				this.setModel(models.createFLPModel(), "FLP");

				// create the views based on the url/hash
				this.getRouter().initialize();
			},

			/**
			 * The component is destroyed by UI5 automatically.
			 * In this method, the ErrorHandler is destroyed.
			 * @public
			 * @override
			 */
			destroy : function () {
				// call the base component's destroy function
				UIComponent.prototype.destroy.apply(this, arguments);
			},

			/**
			 * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
			 * design mode class should be set, which influences the size appearance of some controls.
			 * @public
			 * @return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
			 */
			getContentDensityClass : function() {
			if (!this._sContentDensityClass) {
				if (!sap.ui.Device.support.touch) {
					this._sContentDensityClass = "sapUiSizeCompact";
				} else {
					this._sContentDensityClass = "sapUiSizeCozy";
				}
			}
			return this._sContentDensityClass;
		}

		});

	}
);

Create controller template

Now we have to create a template for BaseController.js under the controller folder: “BaseController.js.tmpl”.
In this template, we will create common functions, which would be used from any controller of the application.

Fill the below logic into the BaseController.js.tmpland please do not forget to do the namespace reservation in the file name as below:

Now we are ready to write this logic in Basecontroller.js.tmpl file:

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

		return Controller.extend("{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.BaseController", {
			/**
			 * Convenience method for accessing the router.
			 * @public
			 * @returns {sap.ui.core.routing.Router} the router for this component
			 */
			getRouter : function () {
				return sap.ui.core.UIComponent.getRouterFor(this);
			},

			/**
			 * Convenience method for getting the view model by name.
			 * @public
			 * @param {string} [sName] the model name
			 * @returns {sap.ui.model.Model} the model instance
			 */
			getModel : function (sName) {
				return this.getView().getModel(sName);
			},

			/**
			 * Convenience method for setting the view model.
			 * @public
			 * @param {sap.ui.model.Model} oModel the model instance
			 * @param {string} sName the model name
			 * @returns {sap.ui.mvc.View} the view instance
			 */
			setModel : function (oModel, sName) {
				return this.getView().setModel(oModel, sName);
			},

			/**
			 * Getter for the resource bundle.
			 * @public
			 * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component
			 */
			getResourceBundle : function () {
				return this.getOwnerComponent().getModel("i18n").getResourceBundle();
			}

		});

	}
);

Now we will create a helper file under below location.

And include below code into the file. The purpose of the code is replace “.” into “/” for the wizard attribute namesapce.

define({
	/**
	 * Register misc helpers for handlebars templating
	 */
	registerHandlebarHelpers: function() {
		Handlebars.registerHelper("formatNamespace", function(namespace) {
			return ("" + namespace).replace(/\./g, "\/");
		});
	}
});

Next is to create App.controller.js.tmpl template as below:

Enter the below logic into the App.controller.js.tmpl:

sap.ui.define([
		"{{formatNamespace basicSAPUI5ApplicationProject.parameters.namespace.value}}/controller/BaseController",
		"sap/m/BusyDialog"
	], function (BaseController,BusyDialog) {
		"use strict";
		return BaseController.extend("{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.App", {
			onInit : function () {}
		});

	}
);

 

Create view template

In the same manner, create template for the App.view.xml in the folder view as below:

Write the below XML in the file. Add a reference to the namespace in controllerName attribute and provide App id as well.

<mvc:View controllerName="{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.App" xmlns:html="http://www.w3.org/1999/xhtml"
	xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" >
	<App id="mainApp" >
	</App>
</mvc:View>

Create index.html template

Now we will create a template for index.html as below location:

Now we have to include below code into the file:

<!DOCTYPE HTML>
<html>

	<head>
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta charset="UTF-8">

		<title>{{projectName}}</title>

		<script id="sap-ui-bootstrap"
			src="{{sapUI5Url}}"
			data-sap-ui-libs="sap.m"
			data-sap-ui-theme={{#if ui5Config.Theme}}"{{ui5Config.Theme}}"{{else}}"sap_bluecrystal"{{/if}}
			data-sap-ui-compatVersion="edge"
			data-sap-ui-xx-bindingSyntax="complex"
			data-sap-ui-preload="async"
			data-sap-ui-resourceroots='{"{{basicSAPUI5ApplicationProject.parameters.namespace.value}}": "./"}'>
		</script>

		<link rel="stylesheet" type="text/css" href="css/style.css">

		<script>
			sap.ui.getCore().attachInit(function() {
				new sap.m.Shell({
					app: new sap.ui.core.ComponentContainer({
						height : "100%",
						name : "{{basicSAPUI5ApplicationProject.parameters.namespace.value}}"
					})
				}).placeAt("content");
			});
		</script>
	</head>

	<body class="sapUiBody" id="content">
	</body>

</html>

Create i18n template

Now we will create a template for i18n under i18n folder.

Add the below code in the i18n.properties.tmpl file for title of pages as well dynamic value of Application Title , Application Description and Growing Trigger Text of List Base, all the last three value would get data from the wizard of the application.

AppTitle, AppDescription, GrowingTriggerText all the are the json property of model.json of webapp folder.

titleView1=View 1
titleView2=View 2
toSecondView=To Second View
appTitle= {{basicSAPUI5ApplicationProject.parameters.AppTitle.value}}
appDescription= {{basicSAPUI5ApplicationProject.parameters.AppDescription.value}}
growingTriggerText= {{basicSAPUI5ApplicationProject.parameters.GrowingTriggerText.value}}

Now create style.css under css folder as below:

Now we have to insert below code into the file.

.cartLayout {
    border: 1px solid #dddddd;
    margin: 0.5rem;
    transition-duration: 0.2s;
    transition-property: transform3d;
    box-sizing: border-box;
    display: inline-block;
    box-shadow: 10px 6px 7px rgba(0, 0, 0, 0.15);
    float: left;
    line-height: 15%;
    padding: 0.6rem!important;
    padding-right: -1rem!important;
    display: block;
}
.sapMLIBCustom {
    display: -webkit-box;
    display: inline-flex !important;
    -webkit-box-align: center;
    align-items: center;
    position: relative;
    background: #ffffff;
    border-bottom: 1px solid #e5e5e5;
    /*padding: 0 1rem 0 1rem;*/
}

Now create folders “businesscontroller“, “businessviews” under controller & view folder respectively. And create the below files as well.

We have to use proper name space reference with proper view & controller mapping.

{{basicSAPUI5ApplicationProject.parameters.namespace.value}}

Now we will write the logic for template : View1.controller.js.tmpl

sap.ui.define([
		"{{formatNamespace basicSAPUI5ApplicationProject.parameters.namespace.value}}/controller/BaseController",
		"sap/ui/model/json/JSONModel",
		"sap/m/MessageBox",
		"sap/m/MessageToast"
	], function (BaseController, JSONModel, MessageBox, MessageToast) {
		"use strict";

		return BaseController.extend("{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.businesscontroller.{{basicSAPUI5ApplicationProject.parameters.name1.value}}", {
			
			onInit: function() {
				var route = this.getRouter().getRoute("RouteView1");
				route.attachPatternMatched(this._onRouteMatchedRequest, this);
				
			},
			
			toSecondView:function(oEvent){
			
			var oProductId = oEvent.getSource().getSubheader();
			
			this.getRouter().navTo("RouteView2",{
				productId: oProductId
			});
			},
			
			itemCloseHandler: function(oEvent) {
				// prevent the tab being closed by default
				oEvent.preventDefault();

				var oTabContainer = this.getView().byId("myTabContainer");
				var oItemToClose = oEvent.getParameter("item");

				MessageBox.confirm("Do you want to close the tab '" + oItemToClose.getName() + "'?", {
					onClose: function (oAction) {
						if (oAction === MessageBox.Action.OK) {
							oTabContainer.removeItem(oItemToClose);
							MessageToast.show("Item closed: " + oItemToClose.getName(), {duration: 500});
						} else {
							MessageToast.show("Item close canceled: " + oItemToClose.getName(), {duration: 500});
						}
					}
				});
			},
			/* =========================================================== */
			/* begin: internal methods                                     */
			/* =========================================================== */
			/**
			 * Binds the view to the object path. Makes sure that detail view displays
			 * @function
			 * @param {string} sObjectPath path to the object to be bound to the view.
			 * @private
			 */
			_onRouteMatchedRequest: function(oEvent) {
			}
		});

	}
);

Now we have to write logic for template : View2.controller.js.tmpl

sap.ui.define([
		"{{formatNamespace basicSAPUI5ApplicationProject.parameters.namespace.value}}/controller/BaseController",
		"sap/ui/core/routing/History"
	], function (BaseController, History) {
		"use strict"; 

		return BaseController.extend("{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.businesscontroller.{{basicSAPUI5ApplicationProject.parameters.name2.value}}", {
			onInit: function() {
				var route = this.getRouter().getRoute("RouteView2");
				route.attachPatternMatched(this._onRouteMatchedRequest, this);
			},
			/**
			 * Event handler  for navigating back.
			 * It there is a history entry or an previous app-to-app navigation we go one step back in the browser history
			 * If not, it will replace the current entry of the browser history with the TargetView1 route.
			 * @public
			 */
			onNavBack: function () {
			var oHistory = History.getInstance();
			var sPreviousHash = oHistory.getPreviousHash();

			if (sPreviousHash !== undefined) {
				window.history.go(-1);
			} else {
				var oRouter = this.getRouter();
				oRouter.navTo("TargetView1", true);
			}
			},
			/* =========================================================== */
			/* begin: internal methods                                     */
			/* =========================================================== */

			/**
			 * Binds the view to the object path. Makes sure that detail view displays
			 * @function
			 * @param {string} sObjectPath path to the object to be bound to the view.
			 * @private
			 */
			_bindView : function (sObjectPath) {
				this.getView().bindElement({
					path : sObjectPath,
					model : "{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}"
				});
			},
			/**
			 * Binds the view to the object path and expands the aggregated line items.
			 * @function
			 * @param {sap.ui.base.Event} oEvent pattern match event in route 'RouteView2'
			 * @private
			 */
			_onRouteMatchedRequest: function(oEvent) {
			var sProductId =  oEvent.getParameter("arguments").productId;
			
			this.getModel("{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}").metadataLoaded().then( function() {
					var sObjectPath = this.getModel("{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}").createKey("{{basicSAPUI5ApplicationProject.parameters.ODataCollection.value.name}}", {
						{{basicSAPUI5ApplicationProject.parameters.ObjectCollection_Key.value.name}} : sProductId
					});
					this._bindView("/" + sObjectPath);
				}.bind(this));
			
			}
		});

	}
);

Now we have to write logic for template : View1.view.xml.tmpl

<mvc:View xmlns:core="sap.ui.core" 
	xmlns:mvc="sap.ui.core.mvc" 
	xmlns="sap.m" 
	xmlns:l="sap.ui.layout" 
	xmlns:f="sap.ui.layout.form"
	controllerName="{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.businesscontroller.{{basicSAPUI5ApplicationProject.parameters.name1.value}}" 
	xmlns:html="http://www.w3.org/1999/xhtml">
	<Page title="{i18n>titleView1}">
		<content>
		<TabContainer items="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>/{{basicSAPUI5ApplicationProject.parameters.ODataCollectionCategory.value.name}}{{/addCurlyBrackets}}" id="myTabContainer"
		showAddNewButton="false" class="sapUiResponsiveContentPadding" itemClose="itemCloseHandler">
		<items>
		<TabContainerItem name="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.Object_IdentifierCategory.value.name}}{{/addCurlyBrackets}}">
		<content>
		<ScrollContainer  horizontal="false" vertical="true" height="98%">
		<List 
			items="{{#addCurlyBrackets}}path:'{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.ODataCollection.value.name}}',templateShareable:false{{/addCurlyBrackets}}"
			growing="true" growingThreshold="{{basicSAPUI5ApplicationProject.parameters.GrowingThreshold.value}}" growingTriggerText="{i18n>growingTriggerText}" growingScrollToLoad="{= ${device>/system/phone} ? true : false}">
			<CustomListItem class="sapMLIBCustom">
			<GenericTile press="toSecondView" class="sapUiTinyMarginBegin sapUiTinyMarginTop cartLayout"
					header="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.Object_Identifier.value.name}}{{/addCurlyBrackets}}"
					subheader="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.ObjectCollection_Key.value.name}}{{/addCurlyBrackets}}">
			<TileContent>
				<content>
				<l:VerticalLayout>
				{{#if basicSAPUI5ApplicationProject.parameters.Object_Number.value.name}}
				<l:HorizontalLayout>
				<Text text="{{basicSAPUI5ApplicationProject.parameters.Object_Number.value.name}} : "></Text>
				<Text text="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.Object_Number.value.name}}{{/addCurlyBrackets}}"></Text>
				</l:HorizontalLayout>
				{{/if}}
				<Button iconFirst="false" type="Emphasized" icon="sap-icon://navigation-right-arrow" text="{i18n>toSecondView}"></Button>
				</l:VerticalLayout>
				</content>
				</TileContent>
				</GenericTile>
				</CustomListItem>
				</List>
				</ScrollContainer>
				</content>
		</TabContainerItem>	
		</items>
		</TabContainer>
		</content>
	</Page>
</mvc:View>

Now we have to write logic for template : View2.view.xml.tmpl

<mvc:View xmlns:core="sap.ui.core" 
	xmlns:mvc="sap.ui.core.mvc" 
	xmlns="sap.m" 
	xmlns:l="sap.ui.layout" 
	xmlns:f="sap.ui.layout.form"
	controllerName="{{basicSAPUI5ApplicationProject.parameters.namespace.value}}.controller.businesscontroller.{{basicSAPUI5ApplicationProject.parameters.name2.value}}" 
	xmlns:html="http://www.w3.org/1999/xhtml">
	<Page title="{i18n>titleView2}" showNavButton="true" navButtonPress="onNavBack">
		<content>
		<ObjectHeader id="objectHeader" title="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.Object_Identifier.value.name}}{{/addCurlyBrackets}}" number="{{#addCurlyBrackets}}{{basicSAPUI5ApplicationProject.parameters.ModelName.value}}>{{basicSAPUI5ApplicationProject.parameters.ObjectCollection_Key.value.name}}{{/addCurlyBrackets}}"></ObjectHeader>
		</content>
	</Page>
</mvc:View>

To make file names dynamic we have to introduce below lines of code into “sapui5cart.js“. This will replace the view & controller name what we have used in our template via user data of wizard.

//replace filename from wizard
				templateZip.files["webapp/controller/businesscontroller/View1.controller.js.tmpl"].name = "webapp/controller/businesscontroller/"+model.basicSAPUI5ApplicationProject.parameters.name1.value+".controller.js.tmpl";
				templateZip.files["webapp/controller/businesscontroller/View2.controller.js.tmpl"].name = "webapp/controller/businesscontroller/"+model.basicSAPUI5ApplicationProject.parameters.name2.value+".controller.js.tmpl";
				templateZip.files["webapp/view/businessviews/View1.view.xml.tmpl"].name = "webapp/view/businessviews/"+model.basicSAPUI5ApplicationProject.parameters.name1.value+".view.xml.tmpl";
				templateZip.files["webapp/view/businessviews/View2.view.xml.tmpl"].name = "webapp/view/businessviews/"+model.basicSAPUI5ApplicationProject.parameters.name2.value+".view.xml.tmpl";

Below is the full code of the sapui5cart.js:

define(["sap/watt/lib/jszip/jszip-shim"], function(JSZip) {
	return function() {

		var NEW_TEMPLATE_VERSION = "1.40.12";
		var OLD_DEFAULT_THEME = "sap_bluecrystal";
		var NEW_DEFAULT_THEME = "sap_belize";
		var OLD_AVAILABLE_THEMES = ["sap_hcb", OLD_DEFAULT_THEME];
		var NEW_AVAILABLE_THEMES = ["sap_hcb", NEW_DEFAULT_THEME];
		return {

			configWizardSteps: function(oTemplateCustomizationStep) {},

			onBeforeTemplateGenerate: function(templateZip, model) {
				if (model.selectedTemplate.getVersion() === NEW_TEMPLATE_VERSION) {
					model.ui5Config = {
						Theme: NEW_DEFAULT_THEME,
						AvailableThemes: NEW_AVAILABLE_THEMES
					};
				} else {
					model.ui5Config = {
						Theme: OLD_DEFAULT_THEME,
						AvailableThemes: OLD_AVAILABLE_THEMES
					};
				}
				model.sapUI5Url = "../../resources/sap-ui-core.js";

				//If internal === true -> generate maven placeholders for project id in manifest.json.tmpl
				model.mode = {
					internal: sap.watt.getEnv("internal")
				};
				model.i18n_uuid = {
					uuid: this.generateUUID()
				};

				//replace filename from wizard
				templateZip.files["webapp/controller/businesscontroller/View1.controller.js.tmpl"].name = "webapp/controller/businesscontroller/" +
					model.basicSAPUI5ApplicationProject.parameters.name1.value + ".controller.js.tmpl";
				templateZip.files["webapp/controller/businesscontroller/View2.controller.js.tmpl"].name = "webapp/controller/businesscontroller/" +
					model.basicSAPUI5ApplicationProject.parameters.name2.value + ".controller.js.tmpl";
				templateZip.files["webapp/view/businessviews/View1.view.xml.tmpl"].name = "webapp/view/businessviews/" + model.basicSAPUI5ApplicationProject
					.parameters.name1.value + ".view.xml.tmpl";
				templateZip.files["webapp/view/businessviews/View2.view.xml.tmpl"].name = "webapp/view/businessviews/" + model.basicSAPUI5ApplicationProject
					.parameters.name2.value + ".view.xml.tmpl";

				return [templateZip, model];
			},
			generateUUID: function() {
				var d = new Date().getTime();
				var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
					var r = (d + Math.random() * 16) % 16 | 0;
					d = Math.floor(d / 16);
					return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
				});
				return uuid;
			},
			onAfterGenerate: function(projectZip, model) {
				return [projectZip, model];
			}
		};
	};
});

Now our all the required files are in place, in next blog we will discus way to customize wizard of sap for our customized template.

 

 

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