SAPUI5, SAP S/4HANA

How to build a production grade UI5 application with GIS integration using 3rd party API (ARCGIS)

In one of my customer engagements the business requirement was to build a UI5 application with geospatial capabilities. I used Interactive maps from ARCGIS enriched with business data from SAP to accomplish this. This blog outlines my journey and challenges I faced to impliment it.

We faced a major hurdle in deploying the application to the Frontend server due to Cross origin resource Sharing (CORS) errors but I will not discuss it here. (Maybe a separate blog later)

Here are the steps I followed.

  • Created a CDS view to read the assets from backend SAP system. Included UI annotations so that the smart control directly build the view and I don’t have to worry about enabling it at the UI5 app level (That’s why S/4Hana is fun).
  • Used Webide to build a List report application. I was tempted earlier to use the smart template but then decided not to use as I needed more control doing the GIS integration and facet replacement has its own set of restrictions. So I ended up using smart controls like Smart Filter and Smart Table to build a list report. The idea was to show a list of all assets( from mapping perspective assets have a physical location associated with them and can be identified by coordinates) and clicking on one will display it on the ESRI map

<mvc:View xmlns=”sap.m” xmlns:mvc=”sap.ui.core.mvc” xmlns:smartFilterBar=”sap.ui.comp.smartfilterbar”
xmlns:smartTable=”sap.ui.comp.smarttable” xmlns:sem=”sap.m.semantic” controllerName=”zxxx.controller.list”>
<App id=”app”>
<sem:FullscreenPage id=”fp” title=”List Search”>
<smartFilterBar:SmartFilterBar id=”smartFilterBar” entitySet=”ZXXX_ ASSET_XXX” enableBasicSearch=”true” basicSearchFieldName=”Name”
showFilterConfiguration=”true”/>
<smartTable:SmartTable id=”smartTable” smartFilterId=”smartFilterBar” tableType=”Table” editable=”false” entitySet=”ZXXX_ ASSET_XXX”
useVariantManagement=”true” useTablePersonalisation=”true” header=”Assets” showRowCount=”true” useExportToExcel=”false”
enableAutoBinding=”true”>
<Table>
<items>
<ColumnListItem type=”Navigation” press=”onListItemPress”></ColumnListItem>
</items>
</Table>
</smartTable:SmartTable>
</sem:FullscreenPage>
</App>
</mvc:View>

  • Now the fun part. There were 2 options to load the ARCGIS JavaScript APIS. One that is available on their website. You can use a jQuery.sap.registerModulePath to load this and it works perfectly in WebIDE environment. But the problem with this approach was that the API here reside remotely and not in our environment. So when you deploy it on Frontend server (on premise) chrome (browser) thinks it is a cross origin reference (like a malicious activity) and blocks it and you cannot load the maps.
  • So I went with option two. I downloaded the latest version of ESRI JS API  and loaded it in frontend server as a library You may have to create an account for that if you want to try it. Now this package is pretty big and webide will not able to handle such volume. So I used the report /UI5/UI5_REPOSITORY_LOAD to upload it directly from system (laptop) in Frontend server. I can now reference the library from any GIS enabled UI5 apps(consuming application) without making the apps bulky and unmanageable. Declare it as a dependency in Manifest along with standard UI5 library calls. The final structure of the ARCGIS should look like the second screenshot with the entire package loaded.

SAPUI5, SAP S/4HANA

SAPUI5, SAP S/4HANA

  • Now I needed a perfect UI5 control to display maps. My initial idea was to extend the SAP standard Viewport (sap.ui.vk.Viewport) control. Which worked out initially. But as I went on adding more widgets to the interactive maps I realized that the standard control was actually restricting some of the ESRI features(like search widget). So I built a very simple UI5 control (viewPort.js) with just html <div> and loaded the map in its onAfterRendering() method.Please Note The code here is just for understanding the concepts and cannot be copy pasted. The ARCGIS query reads asset key from SAP and navigates to it using goto method.

sap.ui.define([
‘sap/ui/core/Control’,
‘zxxx/model/models’
],
function(ViewPort, Model) {
var view;
var query;
var queryStatesTask;
var resultsLyr;
var symbol;
var webmap;
var that;

return ViewPort.extend(“zxxx.control.viewPort”, {
metadata: {
properties: {
width: {
type: “sap.ui.core.CSSSize”,
defaultValue: “100%”
},
height: {
type: “sap.ui.core.CSSSize”,
defaultValue: “100%”
}
}
},

renderer: function(oRm, oControl) {

oRm.write(“<div”);
// Write controles and classes defined by the developer
oRm.writeControlData(oControl);
oRm.writeClasses(oControl);

oRm.addStyle(“height”, oControl.getHeight());
oRm.addStyle(“width”, oControl.getWidth());
oRm.writeStyles();

oRm.write(“>”);
oRm.write(“<div id=’legendDiv’></div>”);
oRm.write(“<div id=’laylstDiv’></div>”);
oRm.write(“</div>”);

},

onAfterRendering: function(args) {
this.mapLoad();
},

mapLoad: function() {
that = this;
// Initialize ARCGIS
if (initialized == 0) {
//Include ESRI style sheets
jQuery.sap.includeStyleSheet(“https://js.arcgis.com/4.5/esri/css/main.css”);
jQuery.sap.includeStyleSheet(“https://js.arcgis.com/4.5/esri/css/view.css”);
jQuery.sap.includeStyleSheet(“https://js.arcgis.com/4.5/dijit/themes/claro/claro.css”);
//This referes to the namespace of the faceless component for ESRI libraries
jQuery.sap.require(“XXX/ZARCGIS/arcgis/dojo/dojo”);
initialized = 1;
}

require([
“esri/Map”,
“esri/WebMap”,
“esri/views/MapView”,
“esri/config”,
“esri/tasks/QueryTask”,
“esri/tasks/support/Query”,
“esri/layers/GraphicsLayer”,
“esri/widgets/LayerList”,
“esri/widgets/Legend”,
“esri/widgets/Search”,
“esri/layers/FeatureLayer”,
“esri/core/urlUtils”,
“esri/widgets/Print”,
“dojo/domReady”
], function(Map, WebMap, MapView, esriConfig, QueryTask, Query, GraphicsLayer, LayerList, Legend,
Search, FeatureLayer, urlUtils, Print, ready) {

ready(function() {
// Impliment Utility to read the ARCGIS URLs and mapid

// Poral URL to point to ARCGIS Portal
esriConfig.portalUrl = portal;

// Load webmap from the map id
webmap = new WebMap({
portalItem: {
id: mapid
}
});

//Load map to view
view = new MapView({
map: webmap,
container: that.getId()
});

view.center = [133, -27]; // Sets the center point of the view at a specified lon/lat
view.zoom = 5;

query = new Query({
returnGeometry: true,
outFields: [“*”],
});

query.geometry = view.extent;

//QueryInt points to a feature server where the query will be executed
queryStatesTask = new QueryTask({
url: queryInt
});

resultsLyr = new GraphicsLayer();

//Declate the layerlist and retrive it from the view
var layerList = new LayerList({
view: view
}, “laylstDiv”);

//Add it to view to be displayed as a widget on top left
view.ui.add(layerList, “top-left”);

// Use Javascript Promises so that widget loading happens in proper sequence.
// Here featurelayer is dependent on webmap being loaded and legend is dependent on feature layer
webmap.then(function() {
var featureLayer = webmap.layers.getItemAt(0);
featureLayer.then(function() {
var legend = new Legend({
view: view,
id: “leg”,
style: “card”
}, “legendDiv”);
view.ui.add(legend, “top-left”);
})

});

//This is where we pass the Asset id to map to show it in map
if (view) {
if (Model) {
//Idea is as soon as you click on a asset in list report
//its key is captured in Model and is retrived here
var intKey = Model.getKey();
}
//If key is found execute the query
if (intKey) {
view.then(function() {
console.log(“Querying the service”);
query.outSpatialReference = view.spatialReference;
query.spatialRelationship = “intersects”;
query.where = “Asset='” + intKey + “‘”;

queryStatesTask.execute(query).then(getResults).then(addToLayer).then(goToLayer);

//if the query retunsresult based on asset id
function getResults(result) {
console.log(“found ” + result.features.length + ” features”);

// Loop through each of the results and assign a symbol and PopupTemplate
// to each so they may be visualized on the map
var assets = [];
require([“dojo/_base/array”], function(arrayUtils) {
assets = arrayUtils.map(result.features, function(
feature) {
return feature;
});
});
return assets;
}

// Add the assets to Results Layer(graphic LAyer) declared earlier and add it to webmap
function addToLayer(assets) {
console.log(“Add Layer”);
resultsLyr.addMany(assets);
webmap.add(resultsLyr);
return assets;
}

//finally navigate to the asset
function goToLayer(assets) {
console.log(“Goto”);
view.goTo({
target: assets,
zoom: 18
});
}

});

}
}

})
});

}
});
});

  • Once the control is ready we just need to build a view to display it. My custom control was present in a folder “control” so I declared it at the top of view with the following lines where zxxxx is the namespace-

xmlns:mapCont=”zxxxx.control”

The map control can be placed at any desirable place as below.

<l:VerticalLayout id=”VL” width=”100%”>
<mapCont:viewPort id=’GIS’ class=’claro’/>
</l:VerticalLayout>

SAPUI5, SAP S/4HANA

That’s it, the UI5 app is ready and it has an embedded interactive map which work by consuming SAP business data. We can also build UI5 controls like combobox or select and use the user input to interact with the map ( zoom, highlight, goto and so on)

Leave a Reply

Your email address will not be published. Required fields are marked *