Integration of HTML Island in CRM BSP Application Using HANA IDE
The integration of HTML islands in CRM BSP applications is available for:
- SAP WEBCUIF Release 747 SAPK-
- SAP WEBCUIF Release 748 SAPK-74805INWEBCUIF
You can integrate HTML islands with BSP applications in SAP CRM.
By doing this, you will have an SAPUI5 application that is called from CRM through the HTML island. The HTML island will act as a bridge between the CRM BPS application and the SAPUI5 application. You can create an SAPUI5 application using either SAP Hana IDE or SAP WEBIDE. Here, we will focus on SAP Hana IDE.
Prerequisites
You have created a BSP application. For more information, see https://help.sap.com/saphelp_snc700_ehp01/helpdata/en/46/bb181aab4811d4968100a0c94260a5/frameset.htm
You have the following installed:
- ABAP Development Tools for SAP Netweaver
- UI Development Toolkit for HTML5
- SAPUI5 ABAP Repository Team Provider (Developer Edition)
The back-end SAP system has the following:
- UI5_731: SAPUI5 Team Provider on 731
- UISAPUI5: SAPUI5
- UI_INFRA: SAP UI Integration Infrastructure
Procedure
Create a View Inside a BSP Application
Open an existing BSP application. (transaction: /nBSP_WD_CMPWB) and create a view inside.
- Go to Component Structure Browser, select Views and Create
- Define the view name
- Specify the context node(s)
- When selecting the type of view, make sure that you select Table view
In the Properties section, you can select Configuration if you want to display fields in the view.
Create an SAPUI5 Application
You can create the SAPUI5 Application by creating a BSP application from SAP Hana IDE. Before deploying the application, check the following options are available when you right click on the BSP application. Menu Run As:
- Run On Server
- Java Applet
- Java Application
- Web App Preview
Right click on the created BSP application Menu Option Project Menu -> Team -> Share Project:
- Choose Share and then SAPUI5 ABAP Repository and click on Next to continue
- Enter the system connection, and choose Next to continue
- Logon to the back-end system. Here, you can create a new BSP application or select one that has already been created.
- Select Sever Runtime Version, OK and then select Team -> Submit.
The SAPUI5 application is deployed to the SAP ABAP Server.
You define the HTML island from the CRM BSP application. The idea is to have an SAPUI5 control that is used within a WEBCUIF view. This is done using the declarative tag for the HTML island.
- To write the logic for the Island, an HTML page and some JavaScript files must be created. In the consuming view, the thtmlbx:htmlIsland tag will contain the path to the static HTML page. In addition, the SAPUI5 logic must be created or uploaded as an HTML file in the MIME repository. The tag for the HTML island refers to the path of this file.
Example
<thtmlbx:htmlIsland id = “htmltestIsland”
height = “800px”
width = “100%”
htmlFile = “/sap(====)/bc/ui5_ui5/sap/islandtest/webapp/index.html” >
The htmlFile property references the SAPUI5 application.
- The xBCML Renderer component renders the mapped parts of the view content into an xBCML formatted XML document. This XML contains the data that the logic in the SAPUI5 view will consume or render.
Figure 1: Example structure of the CRM BSP Application
- For the exchange of information between CRM and the SAPUI5 application, the properties and data sources context nodes are required. Events are defined for reacting to user actions.
The htmlIslandProperty node is a normal context node where you must have defined getter and setter. When naming the getter and setter, you must include the name of the property in the name of the function when defining it in the SAPUI5 application.
For example, if the property’s name is boolValue, then the getter and setter are called getBoolValue and setBoolValue respectively.
Example
The definition of the property is done by using the < thtmlbx:htmlIslandProperty > tag name. You can define several properties. Make sure that you define the value with the format //<CONTEXT_NODE_NAME>/<ATTRIBUTE>
<thtmlbx:htmlIslandProperty id = “boolValue”
name = “boolValue”
value = “//PROPERTY/BOOL_PROPERTY” />
The htmlIslandDataSource node is linked with context nodes of type Table. For each data source a getter and setter are created and their names are extensions of the name of the data source. For example, if the data source name is flightData, then the getter and setter are called getFlightData and setFlightData respectively.
Example
The definition of the data source is done using the < thtmlbx:htmlIslandDataSource > tag.
<thtmlbx:htmlIslandDataSource id = “sflight”
name = “sflight”
data = “//FLIGHT/Table”
properties = “<%= controller->gt_properties %>” />
As you can see in our example we have the properties parameter defined as controller->gt_properties. This is a table where we associate the data source properties with the table context node attributes.
Figure 2: The association of the data with the properties.
Therefore, the GT_PROPERTIES table is defined in the view controller as an Instance Attribute with Public visibility and the Associated Type as WCF_HIT_DATASOURCE_PROPERTY.
A method can be created to define each of those properties and add them to the GT_PROPERTIES table. For instance, the first attribute is CARRID, in the method we define and append the property to the table. We do the same with each of the attributes in the context node.
ls_property-id = ‘CARRID’.
ls_property-name = ‘CARRID’.
ls_property-abap_fieldname = ‘CARRID’.
APPEND ls_property TO gt_properties.
Events are part of the HTML island and allow the communication between the BSP application and the SAPUI5 application by triggering round trips on user interactions. To define an event, the tag <thtmlbx:htmlIslandEvent >.
Example
<thtmlbx:htmlIslandEvent id = “refresh”
name = “refresh”
onAction = “refresh” />
There are two types of events; client events and server events. Client events allow Islands to listen and fire client-side events which don’t necessarily trigger a server request. For more information, see Setup the SAPUI5 Application Interaction with the HTML Island Framework, below. Server events receive and modify data. They have parameters which are passed to the API as a JavaScript object (hashmap) of name value pairs. The name of the server event is the one that will be used in the SAPUI5 application when firing the event.
Summary of Code Details:
- BSP Application: THTMLB_SCRIPTS
- island_scripts.js: HTMLIsland includes scripts_htmlisland.js which is the HTML island script
- New data elements were created in the BSP_DYN_CONFIG_TAG_LIB package.
Setup the SAPUI5 Application Interaction with the HTML Island Framework
From the SAPUI5 application, we also need to interact with the HTML island framework for two reasons. The SAPUI5 application must be able to access the context data serialized as xBCML, and secondly, it must be able to trigger a round trip on user interactions and events.
Example
In this example, the application is named ISLANDTEST, and the MVC concept is used. The following process shows how to setup this interaction.
Figure 3: The structure of an application named ISLANDTEST, using the MVC concept
index.html file
In the index.html file,
- Add the following lines to handle domain relaxation:
<script>
//domain relaxation
var liBehindFirstDot = location.hostname.indexOf(“.”)+1;
if (liBehindFirstDot > 0) {
document.domain = location.hostname.substr(liBehindFirstDot);
}
</script>
- Add the HTML island scripts
<script src=”/sap(====)/bc/bsp/sap/thtmlb_scripts/island_scripts.js?version=1″ type=”text/javascript”></script>
<title>htmlislandtest</title>
<script id=”sap-ui-bootstrap”
src=”resources/sap-ui-core.js” id=”sap-ui-bootstrap”
data-sap-ui-libs=”sap.m, sap.ui.commons, sap.ui.table”
data-sap-ui-theme=”sap_bluecrystal”
data-sap-ui-compatVersion=”edge”
data-sap-ui-resourceroots='{“htmlislandtest”: “”}’
data-sap-ui-debug=”true” >
</script>
<link rel=”stylesheet” type=”text/css” href=”css/style.css”>
</script>
The first line includes the JavaScript files that controls the HTML island. The second script tab is the normal sap-ui-bootstrap used in SAPUI5 applications.
- In the body of the HTML page, the body onload is defined as onBodyLoad, with the class defined as sapUiBody. The div id is defined as uiArea. This creates the onBodyLoad function.
<body onload=”onBodyLoad()” class=“sapUiBody” id=“content”>
<div id=“uiArea”></div>
</body>
Here, you assign an object. In this example, it is called App. App receives the div id as a parameter. This object has all the definitions of the getters and setters that are needed to communicate with the BSP application using the HTML island.
var app;
function onBodyLoad() {
app = new App(‘uiArea’); // Create an instance of an App during the onload event handler
}
- Create the App object and define the attributes and functions.
As you can see, the div id is passed to the App constructor. In the init method, it is passed as an attribute to initialize the App object.
There are several objects defined. Each correspond to every context node in the BSP application and they will hold the data that will be sent to the SAPUI5 application.
function App(rootId) {
this._boolValue = {};
this._stringValue = {};
this._integerValue = {};
this._floatValue = {};
this._binaryValue = {};
this._dateValue = {};
this._timeValue = {};
this._sflight = [];
this._UIConfig = [];
this.init(rootId);
}
Inside the init method, register the HTML island, create the getters and setters for data interchange and define the client and server events.
- Register the HTML island.
thtmlbxHIslandLib.register(this);
- Define variables and models.
Create all the necessary variables and models for the exchange of information. Based on our example, we have the following methods that belong to the island and can be used inside your application.
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_BEGIN_FREEZE, this.onBeginFreeze, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_END_FREEZE, this.onEndFreeze, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_BEGIN_UPDATE, this.onBeginUpdate, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_END_UPDATE, this.onEndUpdate, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_BEGIN_UPDATE_EVENTS, this.onBeginUpdateEvents, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_END_UPDATE_EVENTS, this.onEndUpdateEvents, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_BEGIN_UPDATE_PROPS, this.onBeginUpdateProps, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_END_UPDATE_PROPS, this.onEndUpdateProps, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_BEGIN_UPDATE_DATASOURCES, this.onBeginUpdateDataSources, this);
thtmlbxHIslandLib.addEventListener(thtmlbxHIslandLib.EVENT_END_UPDATE_DATASOURCES, this.onEndUpdateDataSources, this);
//Models
The models, based on the example, would be written as follows:
// Create the central model(s)
var model = new sap.ui.model.json.JSONModel();
model.setData({data: this.getSFlight()}); // initially empty list
// place the model instance in a central location, available to UI Components as well as the App
sap.ui.getCore().setModel(model, ‘sflight’);
var model = new sap.ui.model.json.JSONModel();
model.setData({data: this.getUIConfig}); // initially empty list
sap.ui.getCore().setModel(model, ‘UIConfig’);
var oData = {
boolVal : ” “,
timeVal : ” “,
floatVal : ” “,
dateVal : ” “,
binaryVal : ” “,
intVal : ” “,
stringVal : ” “
};
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(oData);
sap.ui.getCore().setModel(oModel, ‘datamodel’);
- Create getters and setters for properties
As explained before, every attribute in the context nodes must have a getter and setter method in the App object because this is how the data is passed from the BSP application to the SAPUI5 application. Above, the property definition for boolValue was used as an example. Here, the getter and setter methods for the same example are shown.
App.prototype.getBoolValue = function() {
return this._boolValue;
};
App.prototype.setBoolValue = function(val) {
this._boolValue = val;
var model = sap.ui.getCore().getModel(‘datamodel’); // get the central model
model.setProperty(“/boolVal”,val);
model.updateBindings(true); // let the UI Component know the data has changed
};
As you can see, the name of the getter and setter includes the exact name of the property in the BSP application. It is important to set the value of the property to the model to keep the information and to call the method updateBindings to keep the binding information available.
- Create getters and setters for data sources
The same logic applies to data sources as properties. Here, the getter and setter methods are shown for the data sources.
// getter for flightData DataSource
App.prototype.getSFlight = function() {
return this._sflight;
};
// setter for flightData DataSource
App.prototype.setSflight = function(val) {
var model = sap.ui.getCore().getModel(‘sflight’); // get the central model
model.setData({data: val});
model.updateBindings(true); // let the UI Component know the data has changed
this._sflight = val; // store on the private member variable
};
- Create client and server events calls
The listener to the client event was previously defined in the Define variables and models section, now we just create the associated function body as follows:
App.prototype.onBeginUpdate = function() {
logX(‘App1’, ‘onBeginUpdate’);
// Check some of the parameters
logX(‘App1’, ‘onBeginUpdate’, ‘Locale: ‘ + thtmlbxHIslandLib.getLocaleString());
logX(‘App1’, ‘onBeginUpdate’, ‘Decimal Separator: ‘ + thtmlbxHIslandLib.getDecimalSeparator());
logX(‘App1’, ‘onBeginUpdate’, ‘Grouping Separator: ‘ + thtmlbxHIslandLib.getGroupingSeparator());
logX(‘App1’, ‘onBeginUpdate’, ‘Date Format String: ‘ + thtmlbxHIslandLib.getDateFormatString());
var dateFormatField = sap.ui.getCore().byId(‘dateFormatField’);
if(dateFormatField)
dateFormatField.setValue(thtmlbxHIslandLib.getDateFormatString());
};
There is only one definition for server events and several events can be called from it.
App.prototype.fireServerEvent = function(name, paramMap) {
thtmlbxHIslandLib.fireEvent(name, paramMap);
};
As you can see, the name of the event and its parameters are passed to the function that will fire the event to the back-end. The event must be defined in the BSP application.
- Create the view and place it at the div that has been passed as parameter in the constructor of the App object.
sap.ui.xmlview({
viewName : “htmlislandtest.view.View1”
}).placeAt(rootID);
Associate Models to UI controllers
From the view, associate the created models to the UI controls. For example, a table with the data source as follows:
<Table id=“idflightsTable” inset=“false” items=“{sflight>/data}”>
<items>
<ColumnListItem id=“idflightscli” >
<cells>
<Text text=“{sflight>CARRID}” />
<Text text=“{sflight>CONNID}” />
<Text text=“{sflight>FLDATE}” />
<Text text=“{sflight>CURRENCY}” />
<Input value=“{sflight>PRICE}” editable=“true” />
<Input value=“{sflight>SEATSMAX}” editable=“true” />
</cells>
</ColumnListItem>
</items>
</Table>
Here’s an example of how the properties could be defined as follows:
<Label text=“Boolean”/>
<Input width=‘300px’ value=“{datamodel>/boolVal}” enabled=“false”></Input>
Controller
From the controller, the events are defined in normal methods or functions for example:
onRefresh: function() {
app.fireServerEvent(“refresh”);
},
This will fire the refresh method. In this case, no parameters are sent, if you need to send parameters you can as follows:
app.fireServerEvent(“confirm”, { BP_NUMBER: sConfirmedBP, CP_NUMBER:sConfirmedCP, SEARCHTERM: sSearchTerm });
When the event is fired, the round trip is performed and the event is triggered in the back-end system. To access the parameters sent, you define a variable type CL_THMLBX_HI_EVENT. The data sent from the SAPUI5 application can be accessed using the get_param_value method. For example,
DATA: lr_event TYPE REF TO cl_thtmlbx_hi_event,
lv_data1 TYPE string.
lr_event ?= htmlb_event_ex.
lv_data1 = lr_event->get_param_value( ‘bp_number’ ).
The results from the browser are displayed in the following image:
Figure 4: The left side of the screen shows the BSP application with some data that was passed to the SAPUI5 application, which is shown on the right half of the screen.
On the left side of the screen, is the BSP application with some data that was passed to the SAPUI5 application, located on the right side of the screen. When you modify the data in the SAPUI5 application and choose Refresh, the data will be updated in the BSP application. This is shown in the image below where the value 400 was changed on the right, in the SAPUI5 application and reflected on the left in the BSP application.
Figure 5: The value for the airfair was changed in the SAPUI5 application. After a refresh, the data is updated in the BSP application, seen on the left
Hi ,
Thanks a lot for the very informative blog.
I am successful in doing it if the UI5 application resides in my server ( i.e in CRM system ) ,
in our case I want to load the App from a central UX server , now how can i bind the fields to my value nodes and how the fireserverevent calls crm system , and not the Backend system where th app is deployed.
I hope my question is clear.
Regards,
Dhruvin
Hi Dhruvin,
Unfortunately, I have not worked having the SAPUI5 application in a different server yet. In my case both the BSP application and the SAPUI5 application are in the same server.
But I debugged the fireServerEvent and can let you know what happens.
When using the fireServerEvent, you just pass two parameters, the server event name and the map with all key-value information you want to use when triggering the event.
If you want to debug the fireServerEvent, you can find it in island_scripts.js
Here the call stack, on the bottom the first calls, I put some comments to explain a bit:
htmlbSubmitLib (events.js?sap-client=001&sap-language=EN&sap-domainRelax=min&20170607202633:322)
/*The from tag includes <form id="myFormId" name="myFormId" method="post" action="/sap(..)/bc/bsp/sap/crm_ui_frame/BSPWDApplication.do?sap-client=001&sap-domainrelax=min&sap-language=EN" target="WorkAreaFrame2__s_001_IC_ITSDAGENT_1512144094334"> */
C29_W110_V111_SearchHss_C29_W110_V111_confirm (BSPWDApplication.do?sap-client=001&sap-domainrelax=min&sap-language=EN:678) (This is my Island which id is C29_W110_V111_SearchHss and the server event name C29_W110_V111_confirm)
wcf_fi_fireEvent (scripts.js?sap-client=001&sap-language=EN&sap-domainRelax=min&20171201125436:51057)
thtmlbx_HIClient.callExternal (island_scripts.js?version=1:1394)
thtmlbx_HIEventHandler.fireEvent (island_scripts.js?version=1:1756)
thtmlbx_HIslandCntrl.onAction (island_scripts.js?version=1:2049)
thtmlbx_HIslandLib.fireEvent (island_scripts.js?version=1:393)//From here up is the island calls
App.fireServerEvent (index.html?sap-client=001&sap-ui-language=en&sap-ui-rtl=false&sap-ui-accessibility=false&sap-ui-theme=sap_corbu&id=C29_W110_V111_SearchHss:125) //Defintion from App
onConfirm (SeaSmartSearchMain.controller.js:133)//Normal call from the controller
a.fireEvent (sap-ui-core.js:449)
a.fireEvent (sap-ui-core.js:976)
(anonymous) (sap-ui-core.js:573)
a.onclick (Link.js:6) //Sending the event through a Link
a._handleEvent (sap-ui-core.js:961)
U._handleEvent (sap-ui-core.js:1185)
p (sap-ui-core.js:38)
dispatch (sap-ui-core.js:49)
c3.handle (sap-ui-core.js:49)
The action should be performed here sap(..)/bc/bsp/sap/crm_ui_frame/BSPWDApplication.do
I would recommend to debug and check the values you are getting and may be from there you can get a hint.
Hope this information will help you.
Best regards,
Elsy
Hi Elsy,
Thanks a lot , I will check and come back to you.
Regards,
Dhruvin
Hi Elsy,
Very good blog its help a lot .
but I'm facing one issue in the index.html
thtmlbxHIslandLib.register(this); while executing this statement i'm getting a error
Uncaught TypeError: Cannot set property 'invokeData' of null
at thtmlbx_HIClient.registerExternal (https://xyz.company.com:2020/sap(:2020/sap/bc/ui5_ui5/sap/zsaarisland/====)/bc/bsp/sap/thtmlb_scripts/island_scripts.js?version=1:1224:24)
at thtmlbx_HIClient.onLoaded (https://xyz.company.com:2020/sap(:2020/sap/bc/ui5_ui5/sap/zsaarisland/====)/bc/bsp/sap/thtmlb_scripts/island_scripts.js?version=1:1170:6)
at thtmlbx_HIslandLib._completeRegister (https://xyz.company.com:2020/sap(:2020/sap/bc/ui5_ui5/sap/zsaarisland/====)/bc/bsp/sap/thtmlb_scripts/island_scripts.js?version=1:75:8)
at thtmlbx_HIslandLib.register (https://xyz.company.com:2020/sap(:2020/sap/bc/ui5_ui5/sap/zsaarisland/====)/bc/bsp/sap/thtmlb_scripts/island_scripts.js?version=1:47:4)
at App.init (index.html?sap-client=800:66)
at new App (index.html?sap-client=800:62)
at onBodyLoad (index.html?sap-client=800:50)
at onload (index.html?sap-client=800:259)
How to solve this
Hi Harish,
It is difficult to know without seeing/debugging the code, but it seems it is trying to register a null value. I recall having issues at the beginning due to the naming. Please check the following:
In the index.html you should have the same ids for:
<body class="sapUiBody" onload="onBodyLoad()" id="content">
<div id="uiArea"></div>
</body>
See that for my div id the naming is "uiArea"
Then in the onBodyLoad function I have the following:
function onBodyLoad() {
app = new App('uiArea');
}
Therefore we create an instance of an App during the onload event and it will have the correct div to hold the island.
In the constructor
function App(rootId) {
//... we do all variables' initialization here and call the init function at the end.
this.init();
}
And finally in the init function we do the register:
App.prototype.init = function(){
thtmlbxHIslandLib.register(this);
}
Check this, hope it will help.
Best regards,
Elsy
Hi Elsy
Thank you very much for the reply , i solved the issue ( it is because of htmlFile path was not correct ).
Can you help me in one thing ,
when we fire the event like app.fireServerEvent(“refresh”) , Back-end will called and page will get refreshed .
can we able to stop refreshing the page ? , when we fire the event ?
Hi Harish,
No, unfortunately at the moment I have not information about avoiding the refreshing of the page once the fireServerEvent is executed. When I developed mine, I asked the island developers at the time and they said that this could not be avoided.
In my case I achieved the desired behavior, but this is more a work around. I did the following:
In my case I had a table with business partners information depending on a specific search. I created some input fields to keep the search criteria information in BSP application, those fields are associated to a Context Node and have visible attribute set to false.
In the controller of my SAPUI5 aplication, in the onInit function I register the blur method which will call a function to update the search values. Therefore when I leave the application the Input fields will be updated and the back-end will have the latest data.
When the SAP UI application is called again this information will be sent again and the information in the table will be correctly displayed.
You need to handle also the methods in the back-end to keep your information as you desire depending on what you want to achieve…
But in short this is how I could display the retrieved information again, once the server event was executed and display the correct data to the user.
Here a quick explanation how I did it:
1. I created a div located in the same areaFrameBody as the island. So, I have
2. In my SAPUI5 controller:
In the triggerUpdateSearchValues this is the code:
This worked for me, but as far as I know, the refreshing cannot be avoided.
Best regards,
Elsy
It works perfectly!!!!
Thanks a lot Elsy
Great 🙂
Hello Elsy,
Thanks a lot for this valuable blog post. But do you know if we can bind a string value containing JSON data and use it to instanciate a dedicated model?
Because I'd like to use HTMLIsalnd to embbed sap.gantt.simple chart within CRM… but it relies on a Tree table. Hence, instead of binding a flat table, I've been trying to bind a string value containing the json data I need: { "root" : { "children" : [ "id" : "1", "text" : "helloworld" ] } }.
However even if I can see this value in an input field when I copy/paste the exact same lines as within your provided example, I couldn't figure out how to get this value and put it into another model for my tree table. I tried to add some lines of script within index.html when you set the string value to no avail:
I also tried to do the same within the view controller onInit() and onBeforeRendering() functions without anymore success...
So it would be great if you could just tell me if this is feasable (I don't need two way binding for this "jsonmodel" anyway).
Thank you so much for your help!
Best regards,
Nicolas Busson.
Hi Nicolas,
So you are getting the information correctly in the Index.html, but the issue is that you cannot assign the string value to a new model?
I set the value as you do, in the Index.html as follows:
Here I needed two models, one which is holding the data as is coming from the back-end, so I set the property of the first model to: oSearchDataModel.setProperty("/allTabsInfoVal",val);
This model will have a property named /allTabsInfoVal with a string value.
Based on the information contained in val variable I will update my second model by populating items property as follows:
So in items, I will have a JSON, not a string. You could also just set the property directly and not set it manually as oData.Items and when retrieving the data you can use getProperty from the model. Whenever you need to call your model you do:
It should have the correct data.
One more thing, be careful to call the setMethod in the Index.html as the parameter is passed from the Context Node in the BSP application.
My set method is setAllTabsInfoVal and the id of my context node is "allTabsInfoVal"
I don't know if this answers the question. Hope I understood correctly the question.
Best regards,
Elsy
Hello Elsy,
You rock ! Here is the line I was missing in my implementation:
Thank you so much for taking time to provide such detailed and perfect answer. Now it's working fine and the gantt chart is displaying everything I need. Your help was really appreciated. Thanks again.
Warm regards,
Nicolas.
Thank you for your comment. I am really glad it worked 🙂