Skip to Content
Technical Articles
Author's profile photo Matthias Schmalz

Designing UI5 Apps for SAP Launchpad Service – Part 1

Content Navigation

Part 1: A simple UI5 app that runs in the SAP Launchpad Service (Current post)

Part 2: Multiple Apps with a Shared Reuse Library

Part 3: Splitting bigger projects

Introduction

With this guide, we want to give you technical insights into UI5 apps in the SAP BTP that run in the launchpad service.
You will learn how to structure complex UI5 applications to avoid common pitfalls.
You should already be familiar with:

This guide will not go into details about the following:
– Templating tools for UI5 projects and MTA
– Developing service backends e.g. with CAP
– Configuration of launchpad sites
– OData and SAP Fiori elements

All discussed samples are published in a Github repository.

Related Blogs

A Simple UI5 app that runs in the SAP Launchpad Service

This first post will focus on a simple deployment which only contains one standalone UI5 app, that fetches data from a backend. This app can run in the launchpad or standalone.

The sample code can be found in the Github repository.

Deployed Content

In the beginning let’s have a look at all the parts which form a deployment of an app.
There are mainly 3 service instances that you can find in your Cloud Foundry space after deploying it:

  • An HTML5 repository app-host containing an HTML5 app. The HTML5 repository takes care of storing and serving all the static content of your app, i.e. all js, html and other files that form the runnable source code of your app. An app-host is a container for an atomic deployment and can contain multiple apps.
    In addition to the app sources it contains:

    • A manifest.json as part of the UI5 app, which describes the app and specifies, e.g. the app ID
    • An xs-app.json which defines routes of your app e.g. to connect to your backend
  • An XSUAA instance that controls security-relevant settings and could define authorization scopes for the application backend. It also controls the authentication to your app.
  • A destination service instance with destinations to the app-host and the XSUAA. This is needed to connect the service instances to the launchpad service

MTA Project Structure

The deployment of this is described in the mta.yaml of the project. It is structured as follows:

 

  • 3 resources that represent the created service instances. They are all created by this MTA and are therefore managed services.
  • A module for the UI5 app. Most build steps are defined in the package.json. As a result it produces a zip file.
  • A module that deploys the app to the app-host. It declares a build dependency to the UI5 app to include the produced zip and requires the app-host resource as the deploy target.
  • A module that creates the destinations. It requires the destination service resource as content target and the app-host and XSUAA instance to create destinations for them.
    The configuration defines the destinations and adds some additional properties.

The configuration of the destination resource enables it to use the managed app router and already creates a hardcoded destination for the simple OData service (northwind):

- name: btp-samples-simple-app-dest-srv
  type: org.cloudfoundry.managed-service
  parameters:
    config:
      HTML5Runtime_enabled: true
      init_data:
        instance:
          destinations:
          - Name: northwind
            Authentication: NoAuthentication
            ProxyType: Internet
            Type: HTTP
            URL: https://services.odata.org/V3/Northwind/Northwind.svc/
          existing_destinations_policy: update
      version: 1.0.0
    service: destination
    service-name: btp-samples-simple-app-dest-srv
    service-plan: lite
The XSUAA resource includes the xs-security.json, which could define authorization scopes and roles:
- name: btp-samples-simple-app-uaa
  type: org.cloudfoundry.managed-service
  parameters:
    path: ./xs-security.json
    service: xsuaa
    service-plan: application
    service-name: btp-samples-simple-app-xsuaa-srv

 

The destination content module requires all 3 instances. It specifies to create service keys which will provide the credentials.

- name: btp-samples-simple-app-dest-content
  type: com.sap.application.content
  requires:
  - name: btp-samples-simple-app-dest-srv
    parameters:
      content-target: true
  - name: btp-samples-simple-app-repo-host
    parameters:
      service-key:
        name: btp-samples-simple-app-repo-host-key
  - name: btp-samples-simple-app-uaa
    parameters:
      service-key:
        name: btp-samples-simple-app-uaa-key

Then it defines the destinations to be created. Here the values of the ServiceInstanceName properties are the instance names and not the MTA resource names as it is the case above.
It adds some additional properties. First it specifies the same service name for XSUAA and app-host. This way they are bound together.
Second, it defines the authentication method for the app-host for further routings to use OAuth2UserTokenExchange via the XSUAA:

  parameters:
    content:
      instance:
        destinations:
        - Name: btp-samples-simple-app_repo_host
          ServiceInstanceName: btp-samples-simple-app-html5-srv
          ServiceKeyName: btp-samples-simple-app-repo-host-key
          sap.cloud.service: btp.samples.simple.app
        - Name: btp-samples-simple-app_uaa
          ServiceInstanceName: btp-samples-simple-app-xsuaa-srv
          ServiceKeyName: btp-samples-simple-app-uaa-key
          sap.cloud.service: btp.samples.simple.app
          Authentication: OAuth2UserTokenExchange
        existing_destinations_policy: ignore

 

The app-content module collects the zip of the UI5 app module at build time.
Later it deploys them to the app-host.

- name: btp-samples-simple-app-app-content
  type: com.sap.application.content
  path: .
  requires:
  - name: btp-samples-simple-app-repo-host
    parameters:
      content-target: true
  build-parameters:
    build-result: resources
    requires:
    - name: btpsamplessimpleapp
      artifacts:
      - btpsamplessimpleapp.zip
      target-path: resources/

 

The module of the app itself configure the build via node.js commands.

- name: btpsamplessimpleapp
  type: html5
  path: simple.app
  build-parameters:
    build-result: dist
    builder: custom
    commands:
    - npm install
    - npm run build:cf
    supported-platforms: []

The UI5 App

Build

The UI5 app is built with UI5 Tooling. The build is controlled via the package.json and the ui5-deploy.yaml and produces a zip for the content module packager.

Routings

There is an xs-app.json file that defines routings to backend services.
Here the simple OData service path is forwarded via the northwind destination, which is created by the MTA, without any authentication.
Usually you would connect it to a BTP cloud service and define an additional authentication flow like OAuth2UserTokenExchange.

{
  "welcomeFile": "/index.html",
  "authenticationMethod": "route",
  "logout": {
    "logoutEndpoint": "/logout",
    "logoutPage": "/logout-page.html"
  },
  "routes": [
    {
      "source": "^/northwind/(.*)$",
      "target": "/$1",
      "authenticationType": "none",
      "destination": "northwind"
    },
    {
        "source": "^/index.html",
        "service": "html5-apps-repo-rt",
        "cacheControl": "no-cache, no-store, must-revalidate"
    },  
    {
        "source": "^/logout-page.html$",
        "service": "html5-apps-repo-rt",
        "authenticationType": "none"
    },  
    {
      "source": "^(.*)$",
      "target": "$1",
      "service": "html5-apps-repo-rt",
      "authenticationType": "xsuaa"
    }
  ]
}

There is a special route for the index.html which is also specified as welcomeFile. This route disables browser caching to ensure the page is reloaded and an authentication flow can be triggered if necessary.
Another special route is for the logout-page.html which is also used in the logout configuration. It disables authentication for the page to prevent that another login flow is triggered after logout.

You can test the app locally via the UI5 Tooling. It provides a local middleware which is configured in ui5.yaml.
Here similar routings are configured e.g. the same OData service is connected. See Use Custom Middlewares  for more details.

server:
  customMiddleware:
  - name: fiori-tools-proxy
    afterMiddleware: compression
    configuration:
      ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted
      backend:
      - path: /northwind
        pathPrefix: /
        url: https://services.odata.org/V3/Northwind/Northwind.svc/
      ui5:
        path: 
        - /resources
        - /test-resources
        url: https://ui5.sap.com
        version:  # The UI5 version, for instance, 1.78.1. Empty means latest version

To start the local test just run the start script in the package.json.
There is also a start-local script which runs the app in a local FLP.

App Descriptor

It has a manifest.json file that defines a unique sap.app/id and application sap.app/type for it.
Having unique Ids for all apps in your subaccount is important to avoid conflicts.

   "sap.app": {
        "id": "btp.samples.simple.app",
        "type": "application",
        "i18n": "i18n/i18n.properties",
        "title": "{{appTitle}}",
        "description": "{{appDescription}}",
        "applicationVersion": {
            "version": "1.0.0"
        }
}

An entry under sap.app/crossNavigation/inbound defines the tile and navigation for the launchpad service. It is important to use unique values here to avoid conflicts with other apps which are deployed in the same subaccount.

        "crossNavigation": {
            "inbounds": {
                "btp-samples-simple-app-inbound": {
                    "signature": {
                        "parameters": {},
                        "additionalParameters": "allowed"
                    },
                    "semanticObject": "simpleApp",
                    "action": "display",
                    "title": "{{flpTitle}}",
                    "subTitle": "{{flpSubtitle}}",
                    "icon": ""
                }
            }
        }

The OData service URL is specified under sap.app/dataSources. It points to the routed source defined in xs-app.json. Please note that this is a relative path, which is resolved relative to the app. The reason for this is explained below.

"dataSources": {
    "mainService": {
        "uri": "northwind",
        "type": "OData"
    }
}

It also specifies sap.cloud/service with the same service name as specified for the destinations in the mta.yaml. Note that this has a different meaning than the sap.app/id (although it has the same value in this sample).

"sap.cloud": {
    "public": true,
    "service": "btp.samples.simple.app"
}

Running the Deployed App

Deploying the App

To deploy the app you have to build the MTA and then deploy it to your Cloud Foundry space.
SAP Business Application Studio simplifies this for you, because the build tool and the deployer are already set up.
The deployment itself will run quite fast because only service instances and content are deployed. There is no CF application at all.

Running Standalone

After the deployment the app can be run standalone from its index.html via the managed approuter. You can find it in the SAP BTP cockpit on the HTML5 Applications page under your subaccount. Its URL will look like this:

https://<tenant subdomain>.launchpad.cfapps.<landscape host>/<destination instance guid>.<service name>.<sap.app/Id>-/index.html
Service name and sap.app/id have any dots removed. Example for a URL:
https://xyz.launchpad.cfapps.eu10.hana.ondemand.com/8b1f65d4-0445-446f-a4b5-90627416789e.btp-samples-simple-app.btpsamplessimpleapp-1.0.0/index.html

All routings in xs-app.json are relative to this. For example, the OData service is under
https://xyz.launchpad.cfapps.eu10.hana.ondemand.com/8b1f65d4-0445-446f-a4b5-90627416789e.btp-samples-simple-app.btpsamplessimpleapp-1.0.0/northwind/
There is no way to install a routing directly in the app router root. Therefore, it is important that you always use paths that are relative to the app location.

The index.html which is located next to the app defines the resource mapping for it and points to the same folder:

<script id="sap-ui-bootstrap"
    src="https://ui5.sap.com/resources/sap-ui-cachebuster/sap-ui-core.js"
    data-sap-ui-resourceroots='{
        "btp.samples.simple.app": "./"
    }'
    data-sap-ui-libraries="sap.m" >
</script>

Inside the manifest.json the data source path is automatically resolved relative to the app.

Beware that there are other locations, where this automatic resolution does not happen.
For example this applies when you do an AJAX request via coding or specify an URL in a control property like the src of an image.
Our app contains an image control in the header area that shows a logo from a local file to demonstrate this.
Here the browser would resolve an URL relative to the document location which is the index.html. Using simple hardcoded URLs only works, as long as the app is located next to the index.html.
Why this can cause issues and how to solve this will be explained below.

 

The index.html loads UI5 via a public CDN URL which has cachebuster enabled. This ensures the best startup performance, because the resources will be fetched from the closest location and cached in the browser cache.

Running in the Launchpad Service

In order to run the app in the Launchpad Service, you have to configure it first and assign it to a user role. You can find more details on this here: Integrate Your SAPUI5 App into Your Launchpad Site

After everything is set up the app could be launched directly via an URL with this pattern:
https://<tenant subdomain>.launchpad.cfapps.<landscape host>/site?siteId=<site ID>#<semantic object>-<action>, for example:
https://xyz.launchpad.cfapps.eu10.hana.ondemand.com/site?siteId=df526ffe-2a32-464e-8fc5-5d5db908334e#simpleApp-display

As you can see, the URL points to a completely different path than where the app can be found. Also the startup HTML page does not contain the resource mapping as in the standalone case.
In order to launch the app, the platform provides all needed URLs. This information is retrieved via a request which looks like
/comsapfdc/fdcCache_<subaccountid (- replaced by _)>/~<cache token>~/apps/<sap.app/id>/ui5AppInfo.json
You can find this in the network trace when starting the app.
In this data you can find a similar URL as when running the app standalone, but it has an additional cache token, which is the date of the last change.

{
	"name": "btp.samples.simple.app",
	"url": "/8b1f65d4-0445-446f-a4b5-90627416789e.btp-samples-simple-app.btpsamplessimpleapp/~211021122629+0000~/",
	"manifest": true,
	"asyncHints": {
		"libs": [{
			"name": "sap.f",
			"lazy": false
		}, {
			"name": "sap.m",
			"lazy": false
		}, {
			"name": "sap.ui.fl",
			"lazy": false
		}],
		"components": []
	},
	"messages": [],
	"version": "1.0.0"
}

As mentioned before, some hardcoded URLs would be resolved relative to the document location. In this case this would not result in the correct URL for fetching the logo image.
Therefore the URL is calculated in the Master.controller.js as follows:

sap.ui.require.toUrl("btp/samples/simple/app/images/logo_ui5.png")

The same applies to any other call that you do for example any REST call that you trigger from your coding.
This way your app is independent of the location from where it is launched.

Next Steps

Now you are ready to plan your first simple app project.
However, usually you need more apps and reuse components.
Please check out part 2 of this blog for this.

Assigned Tags

      10 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Fabien HENIQUE
      Fabien HENIQUE

      Hello Matthias,

      This really well explained and I’m already waiting for next parts 🙂

      I only have one thing I’m trying to figure out, do you know how to use a fixed version of UI5? I put a fixed version in index.html and deploy-yaml, but UI5 version is always the latest one.

      Author's profile photo John Long
      John Long

      To update the ui5 version used on SAP Launchpad Site (FLP)

      https://help.sap.com/viewer/ad4b9f0b14b0458cad9bd27bf435637d/Cloud/en-US/3a0e6d6b791c4c2189f6a0a424188362.html

      Add to the app manifest.json file

      "sap.platform.cf": {
      	   "ui5VersionNumber": "1.89.x"
          },
      "sap.ui5": {
              ...
              },

      Once the app is deployed and rendering on cFLP, you can validate it by opening developer tools, sources, expanding out your application as shown in the attached screen shot. The Fiori app is using a different version to the default version supplied by Fiori Launchpad. Remember, the Launchpad site is using the latest to render the tiles/ settings etc but your app is using its own version.

      Author's profile photo Matthias Schmalz
      Matthias Schmalz
      Blog Post Author

      Hi all,

      finally the Github repository with the samples has been published: https://github.com/SAP-samples/btp-launchpad-ui-samples

      I'm sorry for the delay. Currently I am working on part 2.

      Best regards
      Matthias

      Author's profile photo Matthias Schmalz
      Matthias Schmalz
      Blog Post Author

      Hi all,

      I have just published Part 2 of the blog for having a shared reuse library.
      Have fun reading.

      Best regards
      Matthias

      Author's profile photo Matthias Schmalz
      Matthias Schmalz
      Blog Post Author

      Hi all,

      Part 3 has now been published as well.

      Best regards
      Matthias

      Author's profile photo Oleksii Konchyts
      Oleksii Konchyts

      Hi Matthias Schmalz

      thank you , very helpful blog. You use in your example static(hardcode) image. What about dynamic... I try to use the url as property for Avatar for example:

      "imageUrl":"master/ProductImages('HT-1000')/image

      we don’t need extra route because it the same as for OData

      manifest.json:

      "dataSources": {
             "mainService": {
                   "uri": "master/",
                   "type": "OData",
                   "settings": {
                         "annotations": [],
                         "localUri": "localService/metadata.xml",
                         "odataVersion": "4.0"
                   }
             }
      },

      xs-app.json:

      "routes": [
             {
                   "source": "^/master/(.*)$",
                   "target": "/master/$1",
                   "destination": "backend-srv-api",
                   "authenticationType": "xsuaa",
                   "csrfProtection": false
              },
      ...
      ]
      So the issue is that in Launchpad service the error is occurred
      Request URL:
      https://70d743detrial.launchpad.cfapps.us10.hana.ondemand.com/cp.portal/master/ProductImages('HT-1040')/image
      Request Method: GET
      Status Code: 404 Not Found
      it looks like Launchpad Service couldn’t fine the route, 🙁
      from%20Launchpad%20service
      But if the app will be run from sap btp cockpit>HTML5 Applications everything is fine . Routing is working and images for items are uploaded .
      from%20SAP%20BTP%20Cockpit%20HTML5Application
      Please help me to solve the issue. What is missing?
      Thank you in advance .
      Kind regards
      Oleksii
      Author's profile photo Matthias Schmalz
      Matthias Schmalz
      Blog Post Author

      Hi Oleksii,

      even for dynamic URLs you have to do the same resolution as in my sample. Otherwise the URL is relative to the portal index page and not to your app.

      Just call the same method with your calculated relative URL.

      Best regards

      Matthias

      Author's profile photo Sachin Bennur
      Sachin Bennur

      Hi Matthias Schmalz,

       

      Thanks for the detailed explanation.

      When Running Standalone , index.html is the place from where the app can be accessed .

      https://<tenant subdomain>.launchpad.cfapps.<landscape host>/<destination instance guid>.<service name>.<sap.app/Id>-/index.html

      But when Running in Launchpad Service the app has to be accessed in different way .

      https://<tenant subdomain>.launchpad.cfapps.<landscape host>/site?siteId=<site ID>#<semantic object>-<action>

      Let's say i have added javascript source inside a script tag in the index.html which enables some kind of integration with the App .

      How do i achieve this when opening the app from Launchpad service ?

      Thanks,

      Sachin

      Author's profile photo Matthias Schmalz
      Matthias Schmalz
      Blog Post Author

      Hi Sachin,

      since you are not able to provide the script via an own index.html you only have 2 options:

      Best regards
      Matthias

      Author's profile photo Sachin Bennur
      Sachin Bennur

      Thanks Matthias , I will try this out .