Skip to Content
Technical Articles

Custom Tiles with Cloud Foundry Portal – Cloud Platform

I like to write some notes down, from my findings how to implement custom tiles with in Portal service on Cloud Foundry (SCP / Cloud Platform). Unfortunately, the current documentation is limited and doesn’t show a good end-to-end sample. I only could get it running with a lot of trial and error.

 

Updates

03/27/2020 – added end-to-end sample with one Portal, two custom tiles, one basic UI5 app as target and CAP based service as backend for the charts: https://github.com/citoki/portal-custom-tile

 

This is the result. You’ll get a tile, where you can put in your controls of choice. In my sample below, I use two ComparisonMicroChart controls in the left tile and in the right one two HarveyBallMicroChart controls. For the first tile I used a JS-View and for the second a XML-View, just for demonstration. You can choose what fits better to your coding style.

Contents

  • Create a Starter Project
  • Create a Tile App
  • Tile Content
  • Reading Data Continuously
  • Parameterise Custom Tile via FLP Config
  • Code – End-To-End Sample

Create a Starter Project

As a starting point for a new FLP project I like to generate the necessary artefacts by the WebIDE. You can follow the steps described by Iris Sasson in her detailed blog: SAP Fiori launchpad site with portal service on SAP Cloud Platform

One thing which is crucial to get a minimal project running on CF (Cloud Foundry), is that the custom tile points to an existing UI5 app. That is the target app which will be defined in business app file baCustomTile.json later in this blog.

In my case, I called the target app like this: tutorial.app.one.app1

Create a Tile App

Now let’s jump into the code!

For the new custom tile we need a separate UI5 app. There are a lot of ways to scaffold an UI5 app. One thing I can recommend is the Yo-plugin by Marius Obert. He started developing the Yo plugin Generator-Easy-UI5. With that plugin you’ll be fast on the track.

The only thing you need to add in the UI5 app, is the FLP configuration This tells the Portal service that the app should be handled as a Tile with a specific tileSize of 1×1.

"sap.flp": {
  "type": "tile",
  "tileSize": "1x1"
},

tileSize currently only supports 1x1 (square tile) and 1x2 (wide rectangle tile).

Fiori Launchpad Configuration

Below you can see the structure we need in the end.

flp/portal-site/CommonDataModel.json
flp/portal-site/business-apps
└── baCustomTile.json
flp/portal-site/i18n
└── i18n.properties

The configuration for the Launchpad (FLP) is done via the flp/portal-site/CommonDataModel.json file. In config path “payload.catalogs.payload.viz add following object to the viz array:

File: CommonDataModel.json

viz: [{
  "id": "tutorial.app.one.app1", // has to be equal to 'appId' wihtin groups
  "vizId": "customTile-displayToCustom" // visualization from business app configuration
}]

This tells the FLP runtime, that there is a special visualisation for  app1 available.

In the same file (CommonDataModel.json), we need to set the specific visualisation for the tile. That is done in the object path “payload.groups.payload“. Just add the following code there:

File: CommonDataModel.json

"viz": [{
  "id": "tutorial.app.one.app1-0-1573035031268",
  "appId": "tutorial.app.one.app1",
  "vizId": "customTile-displayToCustom" // visualisation ID from business app configuration
}]

The configuration and the target mapping, when clicking on the custom tile has to be made in a specific folder flp/portal-site/business-apps. Just create a JSON file (in my example: baCustomTile.json) with following code:

File: baCustomTile.json

{
    "_version": "3.0.0",
    "identification": {
        "id": "tutorial.app.one.app1", // app which should be opened by the custom tile
        "entityType": "businessapp",
        "i18n": "i18n/i18n.properties"
    },
    "payload": {
        "visualizations": { // multiple visualisations are possible to be defined here
            "customTile-displayToCustom": { // one sample of visualization for our 'app1'
                "vizType": "project.namespace.customTile", // type has to be set to the app name of our customTile
                "vizConfig": {
                    "sap.app": {
                        "title": "{{notifList.tile.title}}",
                        "subTitle": "{{notifList.tile.subtitle}}"
                    },
                    "sap.flp": {
                        "target": {
                            "inboundId": "data-display" // inbound config for the target app 'app1'
                        }
                    }
                }
            }
        }
    }
}

 

And that’s it. Just deploy the FLP like normal and the custom tile should appear right there.

Tile Content 📊

Please read the Fiori guidelines on tiles, for further details on when to use tiles and which content should be shown.

Do not play videos in their and do not provide any interactive controls like buttons, polls, list. 🤡

In some use cases the OVP (Overview Page) could be the better choice.

Reading data continuously

Now, for tiles are quickly coming up questions like “How can read the data continuously every X seconds?” Well, you might have seen the dynamic tile. For that you can configure an interval to update the data in the tile. Unfortunately, there is no API to tell the Portal that your custom tile wants to hook into the events like “dashboardTileClick”, “setTilesNoVisibility”, “onHiddenTab”. These events are managed only for DynamicTiles by DashboardLoadingManager.js.

Well, as a workaround you could handle some window/documents events by your own, like “window.blur”, “window.focus” and “document.hidden”.
But, that’s not enough. When you have an update loop running (e.g. via setInterval() to refresh data in your custom tile, it would run the whole time. It also runs when the user has click, on a different tile, endless.

Parameterise Custom Tile via FLP Config – now it’s getting dirty!!

DON’T TRY THIS AT HOME or in PRODUCTION!! 👻

Imagine you want to dynamically load data in you custom tile, e.g. a specific type of notifications, within the custom tile. And you don’t want to create another tile app and parameterise the OData/REST call.

Then you could add just another visualisation to the business app configuration, into the visualizations object:

"customTile2-display": {
    "vizType": "project.namespace.customTile2",
    "vizConfig": {
        "sap.app": {
            "title": "{{customTile2.title}}",
            "subTitle": "{{customTile2.subtitle}}"
        },
        "sap.flp": {
            "target": {
                "inboundId": "data-display",
                "parameters": {
                    "param1" : {
                        "value": {
                            "value": "new param value1",
                            "format": "plain"
                        }
                    }
                }
            }
        }
    }

The parameters property is read via a readVisualizations function readVisualizations.getOutbound(). 

During debugging the custom tile, the _oProperties object now has created the target URL with the configured param1 parameter:

targetURL#data-display?param1=new%20param%20value1&sap-ui-app-id-hint=tutorial.app.one.app1

Debug code line: https://github.com/citoki/portal-custom-tile/blob/0d91424d1f373307d49d605d66ed33f69fe7db86/app-custom-tile/webapp/DynamicTile.controller.js#L10

Technically this is the hash/link which should be called after clicking on the custom tile.

You could now parse this string and extract the param1 parameter and use this during the initialisation for the custom tile.

Code – End-To-End Sample

See code sample for an End-To-End sample project: https://github.com/citoki/portal-custom-tile

/
8 Comments
You must be Logged on to comment or reply to a post.
    • Hi Bradguver Jesus Villavicencio Rojas,

       

      Are you able to create the custom tile? I am getting deployement fail.Please check my code in below comment.

       

      Regards

      Charan

      • Hello CHARANRAJ THARIGONDA, What I could recommend and what in my case solved my problem is that for example in your business-apps.json you put as id not businesscustomapp but something like businesscustomapp.app (keep in mind that you must update the rest and change businesscustomapp by businesscustomapp.app) and after this change create an HTML5 Module where the name of your application would be app and its namespace would be businesscustomapp, after making all these changes make your build and deploy.

        Regards.

  • /
  • Hi Steffen,

    I have tried same way but my deployement is failling.

     

    manifest.json

    {
    	"_version": "1.12.0",
    	"sap.flp": {
    		"type": "tile",
    		"tileSize": "1x1"
    	},
    	"sap.app": {
    		"id": "appui5.appui5",
    		"type": "application",
    		"i18n": "i18n/i18n.properties",
    		"applicationVersion": {
    			"version": "1.0.0"
    		},
    		"title": "{{appTitle}}",
    		"description": "{{appDescription}}",
    		"tags": {
    			"keywords": []
    		},
    		"ach": "CA-UI2-INT-FE",
    		"crossNavigation": {
    			"inbounds": {
    				"products-display": {
    					"signature": {
    						"parameters": {},
    						"additionalParameters": "allowed"
    					},
    					"semanticObject": "products",
    					"action": "display"
    				}
    			}
    		}
    	},
    	"sap.ui": {
    		"technology": "UI5",
    		"icons": {
    			"icon": "",
    			"favIcon": "",
    			"phone": "",
    			"phone@2": "",
    			"tablet": "",
    			"tablet@2": ""
    		},
    		"deviceTypes": {
    			"desktop": true,
    			"tablet": true,
    			"phone": true
    		}
    	},
    	"sap.ui5": {
    		"flexEnabled": false,
    		"componentName": "appui5.appui5",
    		"rootView": {
    			"viewName": "appui5.appui5.view.App",
    			"type": "XML",
    			"async": true,
    			"id": "App"
    		},
    		"dependencies": {
    			"minUI5Version": "1.65.6",
    			"libs": {
    				"sap.ui.core": {},
    				"sap.m": {},
    				"sap.ui.layout": {}
    			}
    		},
    		"contentDensities": {
    			"compact": true,
    			"cozy": true
    		},
    		"models": {
    			"i18n": {
    				"type": "sap.ui.model.resource.ResourceModel",
    				"settings": {
    					"bundleName": "appui5.appui5.i18n.i18n"
    				}
    			}
    		},
    		"resources": {
    			"css": [{
    				"uri": "css/style.css"
    			}]
    		},
    		"handleValidation": false,
    		"routing": {
    			"config": {
    				"routerClass": "sap.m.routing.Router",
    				"viewType": "XML",
    				"async": true,
    				"viewPath": "appui5.appui5.view",
    				"controlAggregation": "pages",
    				"controlId": "app",
    				"clearControlAggregation": false
    			},
    			"routes": [{
    				"name": "RouteApp",
    				"pattern": "RouteApp",
    				"target": ["TargetApp"]
    			}],
    			"targets": {
    				"TargetApp": {
    					"viewType": "XML",
    					"transition": "slide",
    					"clearControlAggregation": false,
    					"viewId": "App",
    					"viewName": "App"
    				}
    			}
    		}
    	}
    }

    business-apps

    {
    	"_version": "3.0.0",
    	"identification": {
    		"id": "businesscustomapp",
    		"entityType": "businessapp",
    		"i18n": "i18n/businessApps.properties"
    	},
    	
    	"payload": {
    
    		"visualizations": {
    
    			"products-displayTocustom": {
    				"vizType": "appui5.appui5",
    				
    				"vizConfig": {
    					"sap.app": {
    						"subTitle": "CustomApp"
    					},
    					"sap.flp": {
    						"target": {
    							"inboundId": "products-display"
    						}
    
    					}
    
    				}
    
    			}
    
    		}
    
    	}
    
    }

     

    CMD file.json

     

    {
    	"_version": "3.0.0",
    	"identification": {
    		"id": "a9c311ca-997a-4d7b-ba57-fbec6c8c0002-1585300229069",
    		"entityType": "bundle"
    	},
    	"payload": {
    		"catalogs": [{
    			"_version": "3.0.0",
    			"identification": {
    				"id": "defaultCatalogId",
    				"title": "{{title}}",
    				"entityType": "catalog",
    				"i18n": "i18n/defaultCatalogId.properties"
    			},
    			"payload": {
    				"viz": [{
    					"id": "businesscustomapp",
    					"vizId": "products-displayTocustom"
    				}]
    			}
    		}],
    		"groups": [{
    			"_version": "3.0.0",
    			"identification": {
    				"id": "defaultGroupId",
    				"title": "{{title}}",
    				"entityType": "group",
    				"i18n": "i18n/defaultGroupId.properties"
    			},
    			"payload": {
    				"viz": [{
    					"id": "businesscustomapp-0-1573035031268",
    					"appId": "businesscustomapp",
    					"vizId": "products-displayTocustom"
    				}]
    			}
    		}],
    		"sites": [{
    			"_version": "3.0.0",
    			"identification": {
    				"id": "504f54af-cda5-47eb-bb3d-05072e32db70-1585300229069",
    				"entityType": "site",
    				"title": "SAP Fiori launchpad site on Cloud Foundry",
    				"description": "SAP Fiori launchpad site on Cloud Foundry, deployed from SAP Web IDE"
    			},
    			"payload": {
    				"config": {
    					"ushellConfig": {
    						"renderers": {
    							"fiori2": {
    								"componentData": {
    									"config": {
    										"applications": {
    											"Shell-home": {}
    										}
    									}
    								}
    							}
    						}
    					}
    				},
    				"groupsOrder": ["defaultGroupId"],
    				"sap.cloud.portal": {
    					"config": {
    						"theme.id": "sap_fiori_3",
    						"theme.active": ["sap_fiori_3", "sap_belize_hcb", "sap_belize_hcw"]
    					}
    				}
    			}
    		}]
    	}
    }

     

    undefined Error : “Failed to search all appHostIds, missing apps: businesscustomapp””

     

     

    • I think there isn’t an UI5 app ‘businesscustomapp‘ deployed in your space/HTML5 repository. The app appui5.appui5 is only the UI5 app for the visualisation of the custom tile. The target app is also needed.

    • Hello CHARANRAJ THARIGONDA, What I could recommend and what in my case solved my problem is that for example in your business-apps.json you put as id not businesscustomapp but something like businesscustomapp.app (keep in mind that you must update the rest and change businesscustomapp by businesscustomapp.app) and after this change create an HTML5 Module where the name of your application would be app and its namespace would be businesscustomapp, after making all these changes make your build and deploy.

      Regards.