Skip to Content

Previous post https://blogs.sap.com/2017/10/31/using-a-third-party-library-with-sapui5-application-sap-cloud-development-scenario-part-3/

Prerequisite :

Download the last Fullcalendar version from https://fullcalendar.io/download/

In my project i used FullCalendar v3.4.0 version

Download the zip file and save it on your local machine. Back to your WEB IDE Project to import files below

SAPUI5 application :

I will not comment each part of my source code because i’m not giving the unique solution. My goal is to show steps needed to build a complete Cloud project.

Create new Folder “lib” under webapp folder and import FullCalendar files

i18n content :

#~~~ Global ~~~~~~~~~~~~~~~~~~~~~~~~~~
title=Title

appTitle = App Title

appDescription=App Description

#~~~ Event View ~~~~~~~~~~~~~~~~~~~~~~~~~~
eventTitle=SAP EBC France - Calendar Planning

unknownError=Unknown Error!

#~~~ Request View ~~~~~~~~~~~~~~~~~~~~~~~~~~
requestTitle=SAP EBC France - Booking Request

formRequestTitle=Booking Request Detail :
hostText=Host
emailcustomer=Email
lastnamecustomer=Last Name
firstnamecustomer=First Name
costcentercustomer=Cost Center

emailrep=Email
lastnamerep=Last Name
firstnamerep=First Name
costcenterrep=Cost Center

evetData=Event data :
requesterText=Requester

evetIDText=Event ID
evetTitleText=Title
descriptionText=Description
saveText=Send Booking Request
cancelText=Cancel

Index.html content :

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

		<title>BookingEBC</title>

		<script id="sap-ui-bootstrap"
			src="../../resources/sap-ui-core.js"
			data-sap-ui-libs="sap.m"
			data-sap-ui-theme="sap_belize"
			data-sap-ui-compatVersion="edge"
			data-sap-ui-preload="async"
			data-sap-ui-resourceroots='{"BookingEBC": ""}'>
		</script>
		
		<!-- Add FullCalendar API & Script -->
		<link href='./lib/fullcalendar.css' rel='stylesheet' />
		<script src='./lib/moment.min.js'></script>
		<script src='./lib/fullcalendar.js'></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 : "BookingEBC"
					})
				}).placeAt("content");
			});
		</script>
		
		<style>
			body {
				width: 800px;
				margin: 40px 10px;
				padding: 10;
				font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
				font-size: 14px;
			}
		
			#calendar {
				style="width:60%"
				margin: 0 auto;
			}
		</style>
	</head>

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

View folder content :

V_ROOT.view.xml

<mvc:View controllerName="BookingEBC.controller.V_ROOT" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc"
	displayBlock="true" xmlns="sap.m">
	<App id="V_Root">
		<pages>
			<Page title="ROOT">
				<content></content>
			</Page>
		</pages>
	</App>
</mvc:View>

V_MAIN.view.xml

<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:html="http://www.w3.org/1999/xhtml" controllerName="BookingEBC.controller.V_MAIN">
	<App class="sapUiResponsiveMargin" width="auto">
		<pages>
			<Page title="{i18n>eventTitle}">
				<content><BusyDialog id="BusyDialog" /></content>
			</Page>
		</pages>
	</App>
</mvc:View>

V_EVENT.view.xml

The integration of Fullcalendar component is done in this view. I added an div tag in the html content, to identify my div i used calendar as an id see also the official documentation here https://fullcalendar.io/docs/usage/

<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:html="http://www.w3.org/1999/xhtml" controllerName="BookingEBC.controller.V_EVENT">
	<App class="sapUiResponsiveMargin" width="auto">
		<pages>
			<Page title="{i18n>eventTitle}">
				<content>
					<Label text="{userapi>/name}" visible="false" />
                	<html:div id="calendar"></html:div>
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

V_REQUEST.view.xml

<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:l="sap.ui.layout" xmlns:f="sap.ui.layout.form" controllerName="BookingEBC.controller.V_REQUEST">
	<App class="sapUiResponsiveMargin" width="auto">
		<pages>
			<Page title="{i18n>requestTitle}" showNavButton="true" navButtonPress="onNavBack">
				<content>
					<f:Form id="FormChange354wideDual1" editable="true" >
						<f:title>
							<core:Title text="{i18n>hostText}"/>
						</f:title>
<!--						<f:layout>
							<f:ResponsiveGridLayout labelSpanXL="4" labelSpanL="3" labelSpanM="4" labelSpanS="12" adjustLabelSpan="false" emptySpanXL="0" emptySpanL="4" emptySpanM="0" emptySpanS="0" columnsXL="2" columnsL="1" columnsM="1" singleContainerFullSize="false"/>
						</f:layout>-->
						<f:layout>
							<f:ResponsiveGridLayout   labelSpanXL="5" labelSpanL="2" labelSpanM="5" labelSpanS="5" adjustLabelSpan="true" emptySpanXL="0" emptySpanL="0" emptySpanM="0" emptySpanS="0"  columnsXL="1" columnsL="1" columnsM="1" singleContainerFullSize="true"/>
						</f:layout>						
						<f:formContainers>
							<f:FormContainer>
								<f:formElements>
									<f:FormElement label=" ">
										<f:fields>
											<Switch id="switchId" state="{globalData>/enableState}" change="onSwitch">
												<layoutData>
													<FlexItemData growFactor="1"/>
												</layoutData>
											</Switch>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>emailcustomer}">
										<f:fields>
											<Input value=" " id="emailCustomer" enabled="{globalData>/enableState}" required="{globalData>/enableState}"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>lastnamecustomer}">
										<f:fields>
											<Input value=" " id="lastnameCustomer" enabled="{globalData>/enableState}" required="{globalData>/enableState}"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>firstnamecustomer}">
										<f:fields>
											<Input value=" " id="firstnameCustomer" enabled="{globalData>/enableState}" required="{globalData>/enableState}"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>costcentercustomer}">
										<f:fields>
											<Input id="costcenterCustomer" value="{CUST_COSTCENTER}" required="true" showValueHelp="true" valueHelpOnly="false" valueHelpRequest="onValueHelpRequestCustomer"/>
										</f:fields>
									</f:FormElement>
								</f:formElements>
							</f:FormContainer>
							</f:formContainers>
							</f:Form>
	<f:Form id="FormChange354wideDual2" editable="true" >
						<f:title>
							<core:Title text="{i18n>requesterText}"/>
						</f:title>
<!--						<f:layout>
							<f:ResponsiveGridLayout labelSpanXL="4" labelSpanL="3" labelSpanM="4" labelSpanS="12" adjustLabelSpan="false" emptySpanXL="0" emptySpanL="4" emptySpanM="0" emptySpanS="0" columnsXL="2" columnsL="1" columnsM="1" singleContainerFullSize="false"/>
						</f:layout>-->
						<f:layout>
							<f:ResponsiveGridLayout   labelSpanXL="5" labelSpanL="2" labelSpanM="5" labelSpanS="5" adjustLabelSpan="true" emptySpanXL="0" emptySpanL="0" emptySpanM="0" emptySpanS="0"  columnsXL="1" columnsL="1" columnsM="1" singleContainerFullSize="true"/>
						</f:layout>						
						<f:formContainers>
							<f:FormContainer>
									<f:FormElement label="" visible="false">
										<f:fields>
											<Input value="{userapi>/name}" id="nameRep" enabled="false"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>emailrep}">
										<f:fields>
											<Input value="{userapi>/email}" id="emailRep" enabled="false"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>lastnamerep}">
										<f:fields>
											<Input value="{userapi>/lastName}" id="lastnameRep" enabled="false"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>firstnamerep}">
										<f:fields>
											<Input value="{userapi>/firstName}" id="firstnameRep" enabled="false"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>costcenterrep}">
										<f:fields>
											<Input id="costcenterRep" value="{SREP_COSTCENTER}" required="true" showValueHelp="true" valueHelpOnly="false" valueHelpRequest="onValueHelpRequestSalesRep"/>
										</f:fields>
									</f:FormElement>									
								<f:formElements>
									<f:FormElement label="{i18n>evetIDText}" visible="false">
										<f:fields>
											<Input value="{ID}" id="eventID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>evetTitleText}">
										<f:fields>
											<Input value="{TITLE}" id="titleID" maxLength="50"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>startDateText}" visible="false">
										<f:fields>
											<Input value="{START_DATE}" id="startDateID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>endDateText}" visible="false">
										<f:fields>
											<Input value="{END_DATE}" id="endDateID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>colorText}" visible="false">
										<f:fields>
											<Input value="{COLOR}" id="colorID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>statusText}" visible="false">
										<f:fields>
											<Input value="{STATUS}" id="statusID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>descriptionText}">
										<f:fields>
											<TextArea value="{DESCRIPTION}" id="descriptionID" rows="8"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>yearText}" visible="false">
										<f:fields>
											<Input value="{YEAR}" id="yearID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>monthText}" visible="false">
										<f:fields>
											<Input value="{MONTH}" id="monthID"/>
										</f:fields>
									</f:FormElement>
									<f:FormElement label="{i18n>dayText}" visible="false">
										<f:fields>
											<Input value="{DAY}" id="dayID"/>
										</f:fields>
									</f:FormElement>									
								</f:formElements>
							</f:FormContainer>
						</f:formContainers>
					</f:Form>				
				</content>
				<footer>
					<Bar>
						<contentRight>
							<Button id="save" text="{i18n>saveText}" type="Emphasized" press="onSave"/>
							<Button id="cancel" text="{i18n>cancelText}" press="onNavBack"/>
						</contentRight>
					</Bar>
				</footer>
			</Page>
		</pages>
	</App>
</mvc:View>

Controller folder content :

V_ROOT.controller.js 

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

	return Controller.extend("BookingEBC.controller.V_ROOT", {

	});
});

V_MAIN.controller.js

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

	return Controller.extend("BookingEBC.controller.V_MAIN", {
		//Initial Load
		onInit: function()
		{
		},
			
		// After Loading UI5 component
		onAfterRendering: function()
		{
			var oDialog = this.getView().byId("BusyDialog");
			oDialog.open();
 
			jQuery.sap.delayedCall(2000, this, function () {
				oDialog.close();
			});
			this.goToEventCalendar();
		},
		
		goToEventCalendar: function()
		{
			var date = new Date();
			var currentDay = date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2);
			
			// Now Get the Router Info
			var oRouter = sap.ui.core.UIComponent.getRouterFor(this);

			// Tell the Router to Navigate To Route_Event which is linked to V_EvenT view
			oRouter.navTo("Route_Event", {SelectedDate: currentDay});
		}
	});
});

V_EVENT.controller.js

This is the main point to integrate Fullcalndar component so i will explain how we can do it.

OnInit() function validate the Rout config and call the _onRouterFound. See

https://sapui5.hana.ondemand.com/1.36.9/docs/guide/e5200ee755f344c8aef8efcbab3308fb.html for more information about routing and navigation

_onRouterFound() read the navigation route arguments and call _readBackEndEvents()

Note : I’m using a date as a parameter in my Route Pattern

_readBackEndEvents() call the Events Entity from our Odata service

_mapResults() Read data from Events Entity and save it to a local array variable “events”.  To manage and use the Fullcalendar component we have this instruction :

this.byId("calendar").$().fullCalendar({});
		//MAP JSON Resut To Model
		_mapResults: function(data, SelectedDate) 
		{
			var events = [];
			var self = this;
			
			 for (var i = 0; i < data.results.length; i++)
				{
					events.push({
							id: data.results[i].ID,
	                        title: data.results[i].TITLE,
	                        start: data.results[i].START_DATE,
	                        end: data.results[i].END_DATE,
	                        status: data.results[i].STATUS,
	                        color: data.results[i].COLOR
	                    });
	       		}
				
				this.byId("calendar").$().fullCalendar(
				{
					 // Alow click    
					eventClick: function(calEvent, jsEvent, view) 
					{
						self.goToRequestForm(calEvent);
					},
        			
        			// put your options and callbacks here
		        	header: 
		        	{
						left: 'prev,next today',
						center: 'title',
						right: 'month,agendaWeek'
					},
					height: 700,
					weekends: false,
					minTime: "09:00:00",
					maxTime: "20:0:00",
					defaultDate: SelectedDate, //'2017-05-12',
					defaultView: 'agendaWeek',
					navLinks: true, // can click day/week names to navigate views
					editable: false,
					eventLimit: true, // allow "more" link when too many events
					//events: events,
					allDay: false
			});
			
			this.byId("calendar").$().fullCalendar('removeEvents');
			this.byId("calendar").$().fullCalendar( 'addEventSource', events);
			this.byId("calendar").$().fullCalendar( 'refetchEvents' );
		},

Complete source code :

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

	return Controller.extend("BookingEBC.controller.V_EVENT", {
		//Initial Load
		onInit: function()
		{
			// Call UserAPI and stored into a JSON Model
			var userModel = new sap.ui.model.json.JSONModel("/services/userapi/currentUser"); 
			this.getView().setModel(userModel, "userapi");
				
			// Get the Router Info
			var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
				 
			// Validate/Match the Router Details sent from source using oRouter.navTo("Route_Event", {SelectedDate: selectedDate});
			oRouter.getRoute("Route_Event").attachMatched(this._onRouteFound, this);
		},
		
		_onRouteFound: function(oEvt)
		{
			var oArgument = oEvt.getParameter("arguments");
			this._readBackEndEvents(oArgument.SelectedDate);
		},

		_readBackEndEvents: function(SelectedDate)
		{
			var oODataModel = sap.ui.getCore().getModel();
			var self = this;
			
			oODataModel.read("/Events", {
				method: "GET",
    			success: function(data, oResponse) 
    			{
    				self._mapResults(data, SelectedDate);		
    			},
    			error: function() 
    			{
					// show error messge
					sap.m.MessageToast.show("unknownError");
    			}
			});	
		},
		
		// After Loading UI5 component
		onAfterRendering: function()
		{
			//this._readBackEndEvents();
/*						var oODataModel = sap.ui.getCore().getModel();
			var self = this;
				
			oODataModel.read("/Event", {
				method: "GET",
    			success: function(data, oResponse) 
    			{
    				self._mapResults(data);		
    			},
    			error: function() 
    			{
					// show error messge
					sap.m.MessageToast.show("unknownError");
    			}
			});	*/
		},
		
		//MAP JSON Resut To Model
		_mapResults: function(data, SelectedDate) 
		{
			var events = [];
			var self = this;
			
			 for (var i = 0; i < data.results.length; i++)
				{
					events.push({
							id: data.results[i].ID,
	                        title: data.results[i].TITLE,
	                        start: data.results[i].START_DATE,
	                        end: data.results[i].END_DATE,
	                        status: data.results[i].STATUS,
	                        color: data.results[i].COLOR
	                    });
	       		}
				
				this.byId("calendar").$().fullCalendar(
				{
					 // Alow click    
					eventClick: function(calEvent, jsEvent, view) 
					{
						self.goToRequestForm(calEvent);
					},
        			
        			// put your options and callbacks here
		        	header: 
		        	{
						left: 'prev,next today',
						center: 'title',
						right: 'month,agendaWeek'
					},
					height: 700,
					weekends: false,
					minTime: "09:00:00",
					maxTime: "20:0:00",
					defaultDate: SelectedDate, //'2017-05-12',
					defaultView: 'agendaWeek',
					navLinks: true, // can click day/week names to navigate views
					editable: false,
					eventLimit: true, // allow "more" link when too many events
					//events: events,
					allDay: false
			});
			
			this.byId("calendar").$().fullCalendar('removeEvents');
			this.byId("calendar").$().fullCalendar( 'addEventSource', events);
			this.byId("calendar").$().fullCalendar( 'refetchEvents' );
		},
		
		goToRequestForm: function(calEvent)
		{
			//The requester can click only on a Green Slot
			if (calEvent.status !== "F")
			{
				return;
			}
			// Get Property of the Clicked Item. i.e. Event.id of the item which was clicked
			var selectEventID = calEvent.id; //calEvent.getSource().getBindingContext().getProperty("id");
 
			// Now Get the Router Info
			var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
 
			// Tell the Router to Navigate To Route_Request which is linked to V_REQUEST view
			oRouter.navTo("Route_Request", {SelectedItem: selectEventID});
		}
	});
});

V_EVENT.controller.js

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

	return Controller.extend("BookingEBC.controller.V_EVENT", {
		//Initial Load
		onInit: function()
		{
			// Call UserAPI and stored into a JSON Model
			var userModel = new sap.ui.model.json.JSONModel("/services/userapi/currentUser"); 
			this.getView().setModel(userModel, "userapi");
				
			// Get the Router Info
			var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
				 
			// Validate/Match the Router Details sent from source using oRouter.navTo("Route_Event", {SelectedDate: selectedDate});
			oRouter.getRoute("Route_Event").attachMatched(this._onRouteFound, this);
		},
		
		_onRouteFound: function(oEvt)
		{
			var oArgument = oEvt.getParameter("arguments");
			this._readBackEndEvents(oArgument.SelectedDate);
		},

		_readBackEndEvents: function(SelectedDate)
		{
			var oODataModel = sap.ui.getCore().getModel();
			var self = this;
			
			oODataModel.read("/Events", {
				method: "GET",
    			success: function(data, oResponse) 
    			{
    				//self.byId("calendar").$().fullCalendar('removeEvents');
    				self._mapResults(data, SelectedDate);		
    			},
    			error: function() 
    			{
					// show error messge
					sap.m.MessageToast.show("unknownError");
    			}
			});	
		},
		
		// After Loading UI5 component
		onAfterRendering: function()
		{
			//this._readBackEndEvents();
/*						var oODataModel = sap.ui.getCore().getModel();
			var self = this;
				
			oODataModel.read("/Event", {
				method: "GET",
    			success: function(data, oResponse) 
    			{
    				self._mapResults(data);		
    			},
    			error: function() 
    			{
					// show error messge
					sap.m.MessageToast.show("unknownError");
    			}
			});	*/
		},
		
		//MAP JSON Resut To Model
		_mapResults: function(data, SelectedDate) 
		{
			//sap.ui.commons.MessageBox.alert("Current Month" + SelectedDate);
			var events = [];
			var self = this;
			
			 for (var i = 0; i < data.results.length; i++)
				{
					events.push({
							id: data.results[i].ID,
	                        title: data.results[i].TITLE,
	                        start: data.results[i].START_DATE,
	                        end: data.results[i].END_DATE,
	                        status: data.results[i].STATUS,
	                        color: data.results[i].COLOR
	                    });
	       		}
				
				
				this.byId("calendar").$().fullCalendar(
				{
					// Good method to change rendering			
/*					eventAfterRender: function (event, element, view) {
					        if (event.status === "F") 
					        {
					        	element.css('background-color', '#77DD77');
					        } else if (event.status === "W") 
					        {
					            element.css('background-color', '#FFFF00');
					        } else if (event.status === "B") 
					        {
					            element.css('background-color', '#FF0000');
					        }
					   },*/
    
					 // Alow click    
					eventClick: function(calEvent, jsEvent, view) 
					{
						self.goToRequestForm(calEvent);
					},
    
    				// Catch navigation click on calendar object
					/*	viewRender: function (view, element) 
					{
						var b = view.start._d;
						var m = b.getMonth();
				    	sap.ui.commons.MessageBox.alert("Current Month" + m);
					},*/
        			
        			// put your options and callbacks here
		        	header: 
		        	{
						left: 'prev,next today',
						center: 'title',
						right: 'month,agendaWeek'
					},
					height: 700,
					weekends: false,
					minTime: "09:00:00",
					maxTime: "20:0:00",
					defaultDate: SelectedDate, //'2017-05-12',
					defaultView: 'agendaWeek',
					navLinks: true, // can click day/week names to navigate views
					editable: false,
					eventLimit: true, // allow "more" link when too many events
					//events: events,
					allDay: false
			});
			
			this.byId("calendar").$().fullCalendar('removeEvents');
			this.byId("calendar").$().fullCalendar( 'addEventSource', events);
			this.byId("calendar").$().fullCalendar( 'refetchEvents' );
		},
		
		goToRequestForm: function(calEvent)
		{
			//The requester can click only on a Green Slot
			if (calEvent.status !== "F")
			{
				return;
			}
			// Get Property of the Clicked Item. i.e. Event.id of the item which was clicked
			var selectEventID = calEvent.id; //calEvent.getSource().getBindingContext().getProperty("id");
 
			// Now Get the Router Info
			var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
 
			// Tell the Router to Navigate To Route_Request which is linked to V_REQUEST view
			oRouter.navTo("Route_Request", {SelectedItem: selectEventID});
		}
	});
});

Conclusion :

This is the last part of my series i hope this content will help you to understand what we need to complete an End-To-End SAP development Cloud scenario. Below a list of skills needed :

  1. Front End/UI Part ( JavaScript, HTML, CSS, SAPUI5 ), WEB IDE utilisation
  2. SCP Cloud Platform ( set up, connectivity )
  3. Odata Knowledge and how to consume our service in the Front-End app
  4. Back-End Development ( it will depend of your technologie ) in my case i used a HANA MDC so XSJS and SQL Skills, table creation, some admin tasks ( creating users/roles )

 

To report this post you need to login first.

1 Comment

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

  1. Mariem DAHMOUN

    Hi Othmane,

    Thank you for the article, very interesting. it seems that you forgot to add the the V_REQUEST controller. Could you please if possible give us related js content. Thank you 🙂

    (0) 

Leave a Reply