Using a Third-party Library with SAPUI5 Application – SAP Cloud Development Scenario – Part 4
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 :
- Front End/UI Part ( JavaScript, HTML, CSS, SAPUI5 ), WEB IDE utilisation
- SCP Cloud Platform ( set up, connectivity )
- Odata Knowledge and how to consume our service in the Front-End app
- 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 )
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 🙂