SAP Tutorial: Complete CAP Java Part 3

Brian Heise
Nerd For Tech
Published in
12 min readJun 17, 2021

Setting up a Fiori Launchpad and a Fiori Elements List Report

Photo by Clément Hélardot on Unsplash

Contents

Welcome to the Complete CAP Java Part 3, where I show you how to build a clone of SAP’s bookshop example step by step. Be sure to check out the earlier parts to see what we’ve been working on up until now, and of course check out the Git repo to see the code all in one place.

In this section we’re going to be focusing on setting up a Fiori UI, complete with our own Fiori Launchpad. Let’s get to it!

Step 1: Bootstrap a Fiori Launchpad

In Part 2 of this series we made a folder called app in the project root and inside it created file called fiori.html with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
<title>Bookshop</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

Let’s turn this file into a Fiori Launchpad. First, add these meta tags inside the page’s head tag:

<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

After that, add in the following two scripts to bootstrap Fiori Launchpad and SAPUI5:

<script
src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js">
</script>
<script
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-frameOptions="allow">
</script>

With these two scripts our app will load all the files we need to render our UI from SAP’s SAPUI5 Content Delivery Network. Now we need to provide an element in the body to receive the content and a script to inject it.

First, our body element. Delete the whole body element as it currently exists and replace it with this:

<body class="sapUiBody" id="content"></body>

Finally, add the injection script at the bottom of the head tag:

<script>
sap.ui
.getCore()
.attachInit(() =>
sap.ushell.Container.createRenderer().placeAt("content")
);
</script>

Now, one last step to get our launchpad up and running — we need a fioriSandboxConfig.json. The Fiori Sandbox that we boostrapped by default looks for this in a folder called appconfig so let’s create that folder and inside it create fioriSandboxConfig.json, as shown below:

Inside the file, add the following code:

{
"defaultRenderer": "fiori2",
"applications": {
"browse-books": {
"title": "Browse Books",
"description": "Find your favorite book",
"additionalInformation": "SAPUI5.Component=bookshop",
"applicationType": "URL",
"url": "/browse/webapp",
"navigationMode": "embedded"
}
}
}

Now try booking up the app and opening http://localhost:8080/fiori.html. You should see the following:

Very nice, huh? Inside the applications object we can provide a unique app id, in our case “browse-books”. You can add as many of these as you like, and indeed as we continue building out this app we’ll add several more. Each one will render as a separate tile in the launchpad.

Inside each app object, we have several properties to configure. Title and description are what is displayed on the tile itself. Additional information provides the name of the SAPUI Component that this app corresponds to. We haven’t made this component yet, though. The next two lines, applicationType and URL tell the app where to find the app component — it’ll be in the folder /browse/webapp. We’ll make this next.

Step 2: Configuring a List Report App

If you click on the tile that we made in the previous step, it’ll result in an error since we pointed the app to look for a UI component that doesn’t exist yet. Let’s go ahead and make it. Note that it is possible to use Fiori Tools to generate a Fiori Elements app, but I want you to understand in more detail how the configuration files for the app work, so in this tutorial we’ll build the configuration from scratch.

First, create a folder called browse inside the app folder, which we are naming to match the URL of our API that this UI will be devoted to displaying. Keep in mind, though, that this can in fact be named anything you want — it has no effect on the functionality of the app. We’re just using this naming convention for clarity’s sake — one oData API corresponds to one app and both get the same name.

Within the browse folder make another folder called webapp. Inside that folder make a file called Component.js. This file’s name is important — make sure it’s capitalized. The result should look like this:

Inside the file, add the following code:

sap.ui.define(["sap/fe/core/AppComponent"], (ac) =>
ac.extend("bookshop.Component", {
metadata: { manifest: "json" },
})
);

For those of us who are new to SAPUI5, let’s break down what’s happening here (be sure to check out the SAPUI5 documentation to learn more about the framework).

We start out by calling the method define (in the namespace sap.ui). It’s purpose is to define a new SAPUI5 module, in this case our Fiori Elements application. The define method’s first argument is an array of SAPUI5 dependencies. In our case, we just have one dependency, AppComponent (it must be prefaced by it’s complete namespace). AppComponent is the component which must be extended to make any Fiori Elements application. The next argument is an anonymous function which takes as its argument each dependency in the dependency array in the order that they were passed in. In our case, then, this function takes one argument, the AppComponent, abbreviated as ac. We then call the extend method of the AppComponent class, which takes as it’s first argument a string, the name of our component and it’s second argument an object, which contains the configuration of the component. The object that we passed in simply tells the component to look for its configuration (metadata) in a file called manifest.json, which we’ll make next.

Create manifest.json in the same folder as Component.js and add the following code:

{
"_version": "1.8.0",
"sap.app": {
"id": "bookshop",
"type": "application",
"title": "Browse Books",
"description": "Find your favorite book",
"dataSources": {
"CatalogService": {
"uri": "/api/browse/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
}
},
"sap.ui5": {
"dependencies": {
"libs": {
"sap.fe.templates": {}
}
},
"models": {
"": {
"dataSource": "CatalogService",
"settings": {
"synchronizationMode": "None",
"operationMode": "Server",
"autoExpandSelect": true,
"earlyRequests": true,
"groupProperties": {
"default": {
"submit": "Auto"
}
}
}
}
},
"routing": {
"routes": [
{
"pattern": ":?query:",
"name": "BooksList",
"target": "BooksList"
}
],
"targets": {
"BooksList": {
"type": "Component",
"id": "BooksList",
"name": "sap.fe.templates.ListReport",
"options": {
"settings": {
"entitySet": "Books"
}
}
}
}
}
},
"sap.ui": {
"technology": "UI5",
"fullWidth": false
}
}

That’s a doozy of a file. I stripped down all but the essentials needed to get the page to load, but there’s still a ton to go over. Let’s take some time to go through this line by line and digest it. If you want to know more about this descriptor and what it does, check out this documentation.

No Namespace

  1. “_version” indicates the version of our metadata file.

sap.app

"sap.app": {
"id": "bookshop",
"type": "application",
"title": "Browse Books",
"description": "Find your favorite book",
"dataSources": {
"CatalogService": {
"uri": "/api/browse/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
}
},
  1. id: The same as we defined in our Component.js (the bookshop in bookshop.Component). Our fioriLaunchpadConfig.json also must match this value. This value must be unique among the various components of our frontend.
  2. type: basically specifies whether our app is a standalone application or another category of SAPUI5 component. This counts as a standalone app.
  3. title: Displays on the left side of the header bar in the UI.
  4. description: A human readable description for the app.
  5. datasources: this is where we tell our app how to interact with our oData service.

sap.app/datasources/

"dataSources": {
"CatalogService": {
"uri": "/api/browse/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
}

Here you list the metadata for each datasource your app uses. In a Fiori Elements app it’s typically just one datasource, in our case our CatalogService that we defined in an earlier part of this series. Note that you can name each datasource whatever you want. This key is simply used as an identifier in other parts of our manifest file, but it doesn’t technically need to be the same of our service; we simply name it that for clarity.

  • Each datasource needs a URI. This should be what you see at the top of your service when you open http://localhost:8080, as shown below.
  • The type is, of course, oData. We can also check this at localhost:8080.
  • And, of course, under settings we designate it as “4.0”, which is the default version of oData that CAP serves. You can verify this in the metadata document found at http://localhost:8080/api/browse/$metadata

sap.ui5

Here we just declare the dependencies, the sap.fe.templates library, which contains Fiori Elements.

models

"models": {
"": {
"dataSource": "CatalogService",
"settings": {
"synchronizationMode": "None",
"operationMode": "Server",
"autoExpandSelect": true,
"earlyRequests": true,
"groupProperties": {
"default": {
"submit": "Auto"
}
}
}
}
},

Now to the models. SAPUI5 is going to automatically generate the data models that will be used in our Fiori Elements app based on what we define here.

We first start with an empty key. This tells the SAPUI5 framework that this is our default datasource, meaning that it doesn’t need to be referenced with a specific keyword. In a Fiori Elements app, since we don’t actually write any source code for the UI outside of this config file, you must have your datasource defined as the default source.

Next, under datasource we reference the datasource that we defined in sap.app. From that datasource SAPUI5 can recognize that we’re dealing with an oData V4 and therefore select the correct constructor to build the model. It will also automatically read in and parse the metadata document from the backend to get most of the information it needs. We just need to pass in a simple settings object for a little additional configuration. To check out all the options for this object, check out this documentation.

First, we provide synchronizationMode as None. According to the documentation, this must always be set to None.

Next we define the operationMode. Operation mode refers to how filtering and sorting are handled. Setting this to server means that Fiori Elements will append $filter and $orderby parameters to requests sent to our backend and the backend will handle all filtering and sorting. You can check out the other options here.

Next is autoExpandSelect, which should be read as meaning Auto Expand and Auto Select. This means that FioriElements will automatically generate $expand and $select parameters when sending requests to the backend.

Next is earlyRequests, which we set to true. This tells Fiori Elements to get the metadata document and security token from the backend as soon as possible.

Finally, group properties. This controls the use of batch requests (bundling multiple requests to the backend into a single request) for application groups. Here a map of application group IDs should be passed in with a single property, submit. In our case, we provide default, so all application IDs will have the same behavior, auto. You can check out more about submit modes in this documentation.

routing

"routing": {
"routes": [
{
"pattern": ":?query:",
"name": "BooksList",
"target": "BooksList"
}
],
"targets": {
"BooksList": {
"type": "Component",
"id": "BooksList",
"name": "sap.fe.templates.ListReport",
"options": {
"settings": {
"entitySet": "Books"
}
}
}
}
}
},

Routing is probably the most important part of our config file as it’s how we tell our app to move from page to page. If we get this wrong, not only will page navigation not work — even the links that would let us navigate to another page won’t display. Let’s go through each field step by step to see what they do. For more information on SAPUI5 routing, check this documentation.

The first key we have is routes, which contains an array of route objects. Each route object contains a pattern, a name, and a target. The pattern is the URL pattern that activates the route. The name is an identifier for this specific route and the target is an identifier for the page that will be generated, whose specific settings are found in the targets object below.

In our case, we currently have one route that simply contains “:?query:”. ?query indicates that this route accepts query parameters, for example filter or select. If you’re new to oData, check out this documentation for more info on oData query parameters. Since there’s nothing else contained in the route, this route directs to our Fiori App’s root page.

Our name and target fields both contain BooksList. I would consider it best practice to always keep these names identical. The value for each can be whatever we want, but it’s best practice for a List Report Fiori Elements page, which is what we’re making, to name the route and target by suffixing the name of the entity displayed by the page with List. Since our entity is Books, we call it BooksList.

Next, the targets section. This contains configurations specific to the routes defined above. Within targets we define an object whose key is the target of our route: BooksList. This key must be correct or the SAPUI5 runtime won’t be able to locate the configuration for the route.

In the BooksList object we specify the type as Component, which will be the case for all Fiori Elements applications. Next we specify a unique id, which for simplicity’s sake we give the same name as the object itself, BookList. Next, under the name key we specify the name of the component to be rendered using the fully qualified name. Since we’re making a List Report app, we use sap.fe.templates.ListReport. Finally, we provide an options object which contains a settings object. This object contains a single field, entitySet, in which we specify which entity from our oData service that we want this page to display. Naturally, as we only have one entity, we’ll use that: Books.

Now, if we start our application with mvn spring-boot:run, open the app at http://localhost:8080/fiori.html, then click on our only tile, we’ll see this:

No data is displayed yet because we haven’t yet designated what fields should be automatically displayed by default (we’ll learn how to do that in the next installment of this series). For now, though, we can verify that our app can display our data by clicking the gear icon and selecting some fields to display.

Conclusion

In this installment in the Complete CAP Java tutorial we learned how to set up a basic Fiori Elements UI for our bookshop application, including taking a deep dive in the details of a SAPUI5 manifest.json file. In the next installment of this series we’ll set the standard fields to display in the table, how to prevent the display of certain fields, and how to implement filtering.

Was anything unclear in this tutorial? Leave a question below and I’ll get back to you as soon as possible. Was anything incorrect? Please leave a comment below and let me know (a source for the correction would be most helpful). Thanks for your comments!

Support

Did you like this blog? Want to make sure I can keep creating them? Then consider subscribing on Patreon!

--

--

Brian Heise
Nerd For Tech

Full Stack web developer employed at Liferay Japan