Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
matthias_schmalz
Product and Topic Expert
Product and Topic Expert
16,441

⚠️This blog post is outdated.⚠️
The technology has evolved and I have reworked this blog series. Please find the new version here: https://community.sap.com/t5/technology-blogs-by-sap/designing-ui5-apps-as-business-solution-for-sap... 

 

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

Part 4: Splitting the Destination Service instance

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...

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...
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...


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.

22 Comments