Introduction –
SAP Build Workzone is a powerful platform for modern enterprise applications. However, during my development journey, I encountered a significant hurdle: The standard offering does not currently provide a built-in option for creating custom visualizations. While I initially relied on official documentation and community blogs, I discovered that these resources were insufficient or focused on SAP Fiori Launchpad or SAP Cloud Portal service, etc. This blog details my approach and the workarounds I developed to bridge the gap.
The Challenge –
Overcoming a Lack of option to implement Custom visualization. It left developers like me to carve our own path in delivering the tailored user experiences our projects demanded.
Official Documentation –
This is the only official documentation available in help.sap.com which aims to explain how to implement a custom visualization. But, as it only speaks moderately about the manifests of both the file, it becomes tough to implement the solution.
A glance at the custom tile –
Both the standard tile types provided by SAP offers only basic configurable fields like title, subtitle, icon, and description. Additionally, the dynamic tile can display a single value (fetching dynamically from backend service). But both of them lacks in customizing the tile’s visualization.
The 1×2 tile above is custom implemented. Its loading a custom visualization and when clicked, it navigates to the main/target application within the launchpad with personalized site settings (as expected from a launchpad tile). Below is the target app that launches on click of the custom tile. By the end of the blog, you too would be able to achieve this for your app.
Let’s dive into the solution –
You need to build two apps with different IDs. One for the visualization itself and one that implements the visualization. If you already have your [target] app in your Build Workzone, you can modify its manifest and re-deploy along with its visualization app.
Custom Visualization App:
This app will have the visual content to load on the target app’s tile. The trick is in the app’s controller and the manifest, where we take care of the navigation to the target app and launchpad configs.
manifest.json
The crucial section of manifest.json is sap.flp, which tells the app should be considered by the launchpad as a tile and of size 1×2.
{
“_version”: “1.65.0”,
“sap.app”: {
“id”: “customtileviz”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“applicationVersion”: {
“version”: “0.0.1”
},
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”,
“resources”: “resources.json”
},
“sap.ui”: {
“technology”: “UI5”,
“icons”: {
“icon”: “”,
“favIcon”: “”,
“phone”: “”,
“phone@2”: “”,
“tablet”: “”,
“tablet@2”: “”
},
“deviceTypes”: {
“desktop”: true,
“tablet”: true,
“phone”: true
}
},
“sap.ui5”: {
“flexEnabled”: false,
“dependencies”: {
“minUI5Version”: “1.132.1”,
“libs”: {
“sap.m”: {},
“sap.ui.core”: {}
}
},
“contentDensities”: {
“compact”: true,
“cozy”: true
},
“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “customtileviz.i18n.i18n”
}
}
},
“resources”: {
“css”: [
{
“uri”: “css/style.css”
}
]
},
“rootView”: {
“viewName”: “customtileviz.view.MainView”,
“type”: “XML”,
“id”: “customtileviz”
}
},
“sap.cloud”: {
“public”: true,
“service”: “customtileviz”
},
“sap.flp”: {
“type”: “tile”,
“tileSize”: “1×2”
}
}
MainView.controller.js
In the visualization app’s controller (shown below), I’m using the sap.ushell.Container.getServiceAsyn to use its CrossApplicationNavigation service for navigating to the target app. The shell hash takes the values of semantic object and action that is defined within the target app’s manifest file.
sap.ui.define([
“sap/ui/core/mvc/Controller”
], (Controller) => {
“use strict”;
return Controller.extend(“customtileviz.controller.MainView”, {
onInit: function () {
const oView = this.getView();
oView.addEventDelegate({
onAfterRendering: () => {
const mainDom = oView.getDomRef();
if (mainDom) {
mainDom.addEventListener(“click”, () => {
this.navigateToAnotherApp();
});
}
}
});
},
navigateToAnotherApp: async function () {
try {
const oCrossAppNav = await sap.ushell.Container.getServiceAsync(“CrossApplicationNavigation”);
const sHash = “targetapp-display”; // Replace with you target app’s semantic object and action
oCrossAppNav.toExternal({
target: {
shellHash: sHash
}
});
} catch (err) {
console.error(“Navigation service unavailable:”, err);
}
},
});
});
Target App: App that you want to launch from the tile
Manifest.json of the target app is crucial where you need to set the app’s intent for cross-navigation and set the visualization properties of the app. Apart from the manifest we do not need to take care of any other files in the target app.
manifest.json
{
“_version”: “1.65.0”,
“sap.app”: {
“id”: “targetapp”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“applicationVersion”: {
“version”: “0.0.1”
},
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”,
“crossNavigation”: {
“inbounds”: {
“intent1”: {
“semanticObject”: “targetapp”,
“action”: “display”,
“title”: “Target App”,
“subTitle”: “{{flpSubtitle}}”,
“signature”: {
“parameters”: {},
“additionalParameters”: “allowed”
}
}
}
},
“resources”: “resources.json”,
“sourceTemplate”: {
“id”: “@sap/generator-fiori:basic”,
“version”: “1.17.3”,
“toolsId”: “69ae75e3-85b4-4ffb-bfd8-b73b13f30c65”
}
},
“sap.ui”: {
“technology”: “UI5”,
“icons”: {
“icon”: “”,
“favIcon”: “”,
“phone”: “”,
“phone@2”: “”,
“tablet”: “”,
“tablet@2”: “”
},
“deviceTypes”: {
“desktop”: true,
“tablet”: true,
“phone”: true
}
},
“sap.ui5”: {
“flexEnabled”: false,
“dependencies”: {
“minUI5Version”: “1.135.0”,
“libs”: {
“sap.m”: {},
“sap.ui.core”: {}
}
},
“contentDensities”: {
“compact”: true,
“cozy”: true
},
“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “targetapp.i18n.i18n”
}
}
},
“resources”: {
“css”: [
{
“uri”: “css/style.css”
}
]
},
“routing”: {
“config”: {
“routerClass”: “sap.m.routing.Router”,
“controlAggregation”: “pages”,
“controlId”: “app”,
“transition”: “slide”,
“type”: “View”,
“viewType”: “XML”,
“path”: “targetapp.view”,
“async”: true,
“viewPath”: “targetapp.view”
},
“routes”: [
{
“name”: “RouteDynamicPageAnalyticalTable”,
“pattern”: “:?query:”,
“target”: [
“TargetDynamicPageAnalyticalTable”
]
}
],
“targets”: {
“TargetDynamicPageAnalyticalTable”: {
“id”: “DynamicPageAnalyticalTable”,
“name”: “DynamicPageAnalyticalTable”
}
}
},
“rootView”: {
“viewName”: “targetapp.view.App”,
“type”: “XML”,
“id”: “App”,
“async”: true
}
},
“sap.platform.hcp”: {
“uri”: “”,
“appName”: “targetapp”,
“_version”: “1.1.0”
},
“sap.cloud”: {
“public”: true,
“service”: “targetapp”
},
“sap.cloud.portal”: {
“targetapp-display”: {
“vizType”: “customtileviz”
}
}
}
Changes you need to make in the target app’s manifest –
1. In “crossNavigation” section I’ve set the app’s intent i.e., semantic object and action. You have to use this same combination in the controller of the visualization app to enable the launchpad navigation to the target app. You cannot test this in preview as this service would be available only when the app loads in the launchpad.
2. In “sap.cloud.portal” I’ve used the same combination of “semantic object-action” to define this app’s visualization to be loaded from the custom tile app. In the vizType property I have mentioned the app id of the visualization app.
Final steps:
Once you’ve maintained these configs in respective apps, you have to deploy them in your Cloud foundry environment/space. On successful deployment, refresh the contents in Build Workzone’s Channel manager. You will only see the Target app in the content manager. The visualization app wouldn’t appear in the launchpad’s content manager. Do not worry! you just need to add the target app to the required role and group. And voila!! it’s that simple to implement a custom visualization.
Exception and fix –
If you’re using a backend service to load data in your visualization, you might get to see the tile loading with an error saying, “cannot load tile”.
In that case, go to your site settings from the site directory in Build Workzone.
Under Browser settings, set the Asynchronous Module Loading as No (shown above). This should fix the tile loading issue. The same error could appear if your app has some bugs. Advising you to run the app locally to preview content before deploying.
Conclusion –
While the standard option to create custom visualizations remains unavailable, End users who are transitioning to the Cloud foundry environment are missing out on the custom tile feature they had in Neo environment. I hope this implementation helps in resolving the visualization challenges faced while migrating from Neo to Cloud foundry environment and also helps them who are just exploring the launchpad capabilities.
By tweaking this implementation, it’s possible to have a custom tile to launch a website/URL in a new tab outside of the launchpad. It’s also possible to just load quick links on the custom tile and it’s possible too just to load visualization on a tile without any action on Click. Keep exploring and holler if you need any help or want to share feedback.
Introduction -SAP Build Workzone is a powerful platform for modern enterprise applications. However, during my development journey, I encountered a significant hurdle: The standard offering does not currently provide a built-in option for creating custom visualizations. While I initially relied on official documentation and community blogs, I discovered that these resources were insufficient or focused on SAP Fiori Launchpad or SAP Cloud Portal service, etc. This blog details my approach and the workarounds I developed to bridge the gap.The Challenge -Overcoming a Lack of option to implement Custom visualization. It left developers like me to carve our own path in delivering the tailored user experiences our projects demanded.Official Documentation -This is the only official documentation available in help.sap.com which aims to explain how to implement a custom visualization. But, as it only speaks moderately about the manifests of both the file, it becomes tough to implement the solution.A glance at the custom tile -Both the standard tile types provided by SAP offers only basic configurable fields like title, subtitle, icon, and description. Additionally, the dynamic tile can display a single value (fetching dynamically from backend service). But both of them lacks in customizing the tile’s visualization.The 1×2 tile above is custom implemented. Its loading a custom visualization and when clicked, it navigates to the main/target application within the launchpad with personalized site settings (as expected from a launchpad tile). Below is the target app that launches on click of the custom tile. By the end of the blog, you too would be able to achieve this for your app.Let’s dive into the solution -You need to build two apps with different IDs. One for the visualization itself and one that implements the visualization. If you already have your [target] app in your Build Workzone, you can modify its manifest and re-deploy along with its visualization app.Custom Visualization App: This app will have the visual content to load on the target app’s tile. The trick is in the app’s controller and the manifest, where we take care of the navigation to the target app and launchpad configs.manifest.jsonThe crucial section of manifest.json is sap.flp, which tells the app should be considered by the launchpad as a tile and of size 1×2.{
“_version”: “1.65.0”,
“sap.app”: {
“id”: “customtileviz”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“applicationVersion”: {
“version”: “0.0.1”
},
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”,
“resources”: “resources.json”
},
“sap.ui”: {
“technology”: “UI5”,
“icons”: {
“icon”: “”,
“favIcon”: “”,
“phone”: “”,
“phone@2”: “”,
“tablet”: “”,
“tablet@2”: “”
},
“deviceTypes”: {
“desktop”: true,
“tablet”: true,
“phone”: true
}
},
“sap.ui5”: {
“flexEnabled”: false,
“dependencies”: {
“minUI5Version”: “1.132.1”,
“libs”: {
“sap.m”: {},
“sap.ui.core”: {}
}
},
“contentDensities”: {
“compact”: true,
“cozy”: true
},
“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “customtileviz.i18n.i18n”
}
}
},
“resources”: {
“css”: [
{
“uri”: “css/style.css”
}
]
},
“rootView”: {
“viewName”: “customtileviz.view.MainView”,
“type”: “XML”,
“id”: “customtileviz”
}
},
“sap.cloud”: {
“public”: true,
“service”: “customtileviz”
},
“sap.flp”: {
“type”: “tile”,
“tileSize”: “1×2”
}
} MainView.controller.jsIn the visualization app’s controller (shown below), I’m using the sap.ushell.Container.getServiceAsyn to use its CrossApplicationNavigation service for navigating to the target app. The shell hash takes the values of semantic object and action that is defined within the target app’s manifest file.sap.ui.define([
“sap/ui/core/mvc/Controller”
], (Controller) => {
“use strict”;
return Controller.extend(“customtileviz.controller.MainView”, {
onInit: function () {
const oView = this.getView();
oView.addEventDelegate({
onAfterRendering: () => {
const mainDom = oView.getDomRef();
if (mainDom) {
mainDom.addEventListener(“click”, () => {
this.navigateToAnotherApp();
});
}
}
});
},
navigateToAnotherApp: async function () {
try {
const oCrossAppNav = await sap.ushell.Container.getServiceAsync(“CrossApplicationNavigation”);
const sHash = “targetapp-display”; // Replace with you target app’s semantic object and action
oCrossAppNav.toExternal({
target: {
shellHash: sHash
}
});
} catch (err) {
console.error(“Navigation service unavailable:”, err);
}
},
});
}); Target App: App that you want to launch from the tileManifest.json of the target app is crucial where you need to set the app’s intent for cross-navigation and set the visualization properties of the app. Apart from the manifest we do not need to take care of any other files in the target app.manifest.json{
“_version”: “1.65.0”,
“sap.app”: {
“id”: “targetapp”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“applicationVersion”: {
“version”: “0.0.1”
},
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”,
“crossNavigation”: {
“inbounds”: {
“intent1”: {
“semanticObject”: “targetapp”,
“action”: “display”,
“title”: “Target App”,
“subTitle”: “{{flpSubtitle}}”,
“signature”: {
“parameters”: {},
“additionalParameters”: “allowed”
}
}
}
},
“resources”: “resources.json”,
“sourceTemplate”: {
“id”: “@sap/generator-fiori:basic”,
“version”: “1.17.3”,
“toolsId”: “69ae75e3-85b4-4ffb-bfd8-b73b13f30c65”
}
},
“sap.ui”: {
“technology”: “UI5”,
“icons”: {
“icon”: “”,
“favIcon”: “”,
“phone”: “”,
“phone@2”: “”,
“tablet”: “”,
“tablet@2”: “”
},
“deviceTypes”: {
“desktop”: true,
“tablet”: true,
“phone”: true
}
},
“sap.ui5”: {
“flexEnabled”: false,
“dependencies”: {
“minUI5Version”: “1.135.0”,
“libs”: {
“sap.m”: {},
“sap.ui.core”: {}
}
},
“contentDensities”: {
“compact”: true,
“cozy”: true
},
“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “targetapp.i18n.i18n”
}
}
},
“resources”: {
“css”: [
{
“uri”: “css/style.css”
}
]
},
“routing”: {
“config”: {
“routerClass”: “sap.m.routing.Router”,
“controlAggregation”: “pages”,
“controlId”: “app”,
“transition”: “slide”,
“type”: “View”,
“viewType”: “XML”,
“path”: “targetapp.view”,
“async”: true,
“viewPath”: “targetapp.view”
},
“routes”: [
{
“name”: “RouteDynamicPageAnalyticalTable”,
“pattern”: “:?query:”,
“target”: [
“TargetDynamicPageAnalyticalTable”
]
}
],
“targets”: {
“TargetDynamicPageAnalyticalTable”: {
“id”: “DynamicPageAnalyticalTable”,
“name”: “DynamicPageAnalyticalTable”
}
}
},
“rootView”: {
“viewName”: “targetapp.view.App”,
“type”: “XML”,
“id”: “App”,
“async”: true
}
},
“sap.platform.hcp”: {
“uri”: “”,
“appName”: “targetapp”,
“_version”: “1.1.0”
},
“sap.cloud”: {
“public”: true,
“service”: “targetapp”
},
“sap.cloud.portal”: {
“targetapp-display”: {
“vizType”: “customtileviz”
}
}
}Changes you need to make in the target app’s manifest -1. In “crossNavigation” section I’ve set the app’s intent i.e., semantic object and action. You have to use this same combination in the controller of the visualization app to enable the launchpad navigation to the target app. You cannot test this in preview as this service would be available only when the app loads in the launchpad.2. In “sap.cloud.portal” I’ve used the same combination of “semantic object-action” to define this app’s visualization to be loaded from the custom tile app. In the vizType property I have mentioned the app id of the visualization app.Final steps:Once you’ve maintained these configs in respective apps, you have to deploy them in your Cloud foundry environment/space. On successful deployment, refresh the contents in Build Workzone’s Channel manager. You will only see the Target app in the content manager. The visualization app wouldn’t appear in the launchpad’s content manager. Do not worry! you just need to add the target app to the required role and group. And voila!! it’s that simple to implement a custom visualization.Exception and fix -If you’re using a backend service to load data in your visualization, you might get to see the tile loading with an error saying, “cannot load tile”.In that case, go to your site settings from the site directory in Build Workzone.Under Browser settings, set the Asynchronous Module Loading as No (shown above). This should fix the tile loading issue. The same error could appear if your app has some bugs. Advising you to run the app locally to preview content before deploying.Conclusion -While the standard option to create custom visualizations remains unavailable, End users who are transitioning to the Cloud foundry environment are missing out on the custom tile feature they had in Neo environment. I hope this implementation helps in resolving the visualization challenges faced while migrating from Neo to Cloud foundry environment and also helps them who are just exploring the launchpad capabilities.By tweaking this implementation, it’s possible to have a custom tile to launch a website/URL in a new tab outside of the launchpad. It’s also possible to just load quick links on the custom tile and it’s possible too just to load visualization on a tile without any action on Click. Keep exploring and holler if you need any help or want to share feedback. Read More Technology Blogs by SAP articles
#SAP
#SAPTechnologyblog