Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
DanielKawkab
Explorer

Motivation


I really like SAPUI5 for its large control library, strong OData integration and the possibility to load other SAPUI5 apps from anywhere at any time, which creates a great time-to-value. These apps are typically running in a SAP managed environment, like a FLP, be it cloud or on-premise or the Portal service.

But what if we would be able to run SAPUI5 anywhere, even at some places like a customers website, where style guidelines play a huge role. And what if the integration of the SAPUI5 app would be nothing but two lines of HTML?

An approach to create this scenario is exactly what will be created throughout this blog post.

Maybe one more thing about the integration of SAPUI5 applications in style-dependent environments:
UI5 comes with an out-of-the-box set of themes you can use right away. Even though i like themes like Horizon and Fiori very much, I don't think these will resemble most customers style guidelines. And this is okay, since the said SAP-managed environments are mostly for internal use (not a 100% true for the Portal Service). If you would like to place your app in a non-internal environment, you would need to take care of the styling yourself, like with for example creating a custom theme.

The fundamental idea


A UI5 Web Component can be considered as a kind of SAPUI5 control, just without being connected to the whole SAPUI5 ecosystem and lifecycle. Web Components are lightweight, modular and come with lots of features as i18n-support, theming and a wide range of existing components, that you can use right away. They are based on web standards and use the browser's Custom Elements API to create custom HTML tags that reflect a web component.

As mentioned, web components are usually something like a buttons, card, or an input field. They are lightweight components that you can embed on any website in the form of an HTML tag.

Example:
To embed a button, you just need to add the HTML tag "<ui5-button>" to the HTML code of your web page.

The idea behind this approach is to display not just a single "control" by adding the HTML tag to your page, but to display an entire SAPUI5 application by doing so. For this, a custom UI5 Web Component must be created, which internally will load the SAPUI5 app and display it on the page.

So we will not use one of the existing Web Components that are already provided, but create a fresh one we can fill with logic ourselves.

Note


Since I only want to present the said scenario, I will limit myself technically to the most necessary. Therefore I will not use Typescript and PNPM in this blog post.

What you need:



1. Create a SAPUI5 application


To embed an UI5 app, we first of all need an UI5 app. Fortunately we can use the Yeoman easy-ui5-generator to speed things up:

  • Install easy-ui5-generator via your terminal => npm install -g yo generator-easy-ui5

  • Navigate to the folder your project will be contained in

  • yo easy-ui5 app


Select the following options: 



After this you will have a pretty empty, but working UI5 application.

2. Create a UI5 Web Component


In our scenario we do not want to use the already existing Web Components, but we want to create a custom one. For this we have to initialize a UI5 Web Components project:

  • Via your terminal navigate to your target folder

  • Run "npm init @ui5/webcomponents-package@1.15.1"


Select the following options: 


You can now follow the command displayed in your terminal to start up your custom Web Component and check what i looks like.



3. Prepare your SAPUI5 app for consumption


Since this blog is all about showing the integration of our SAPUI5 app in our UI5 web component, we do not need to change or adjust our SAPUI5 app. One thing we need to do, though, is take care of CORS, since the UI5 Tooling server won't enable us to respond with custom headers.

For this we create an approuter, which will simply server our static files. In your SAPUI5 applications root folder create a folder approuter and add the following files to it:

package.json


{
"name": "ui5wc-with-ui5-ui5-ar",
"version": "0.0.1",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@sap/approuter": "^14.0.0"
}
}

Here we add "@sap/approuter" as a dependency, since we want to use the routing capabilities of the approuter later on. The "start"-script will not just start the approuter from the installed package folder, but start the index.js-file, which we will create now.

index.js


const approuter = require("@sap/approuter");
const ar = approuter();

process.env.PORT = process.env.PORT || 3000;

ar.beforeRequestHandler.use(function myMiddleware(req, res, next) {
res.setHeader("access-control-allow-origin", "*");
next();
});

ar.start();

This file will be started by our "start"-script. Since this is supposed to be an approuter we must require the approuter package and start it manually. The important thing here is, that we insert a custom middleware, in which we can set custom headers. For our scenario we set the "access-control-allow-origin"-header to handle CORS.

xs-app.json


{
"authenticationMethod": "none",
"routes": [
{
"source": "^/ui/(.*)$",
"target": "$1",
"authenticationType": "none",
"localDir": "ui"
}
]
}

The routing configuration of the approuter. In this file you typically refer to destinations or services. All we want to do, to make our scenario work, is serving static resources. Whenever the approuters "/ui"-path is requested, the approuter will respond with the content of it's local "ui"-directory.

Edit the package.json of your SAPUI5 app


Adjust the build-script so that the built resources are placed in approuter/ui .
"build": "ui5 build --clean-dest --dest approuter/ui",

Run the script to create the ui folder including the UI5 resources.

Now open a terminal, navigate to the newly created approuter folder and install the dependencies by executing npm install in your terminal.

4. Consume the SAPUI5 app in your Web Component


Since our SAPUI5 app is fully prepared to be used and not even CORS can harm us anymore, we can start consuming it. Let's edit/add some files in our UI5 Web Components project to do so.

src/MyComponent.hbs


<div id="demo-component-container">
{{loadComponent}}
</div>

The .hbs file is a template file where we can call functions from the associated MyComponent.js file. We can remove everything we don't need for now and add only a single div element that contains a function that initializes the SAPUI5 app.

src/MyComponent.js


import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import ComponentLoader from "./util/ComponentLoader.js"
// Template
import MyComponentTemplate from "./generated/templates/MyComponentTemplate.lit.js";

/**
* @public
*/
const metadata = {
tag: "my-component",
properties: /** @lends demo.MyComponent.prototype */ {
/**
* Defines the count of the component.
* @type { sap.ui.webc.base.types.Integer }
* @defaultvalue 0
* @public
*/
count: {
type: Integer,
defaultValue: 0,
},
},
slots: {
},
events: {
},
};

/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
*
* The <code>my-component</code> component is a demo component that displays some text.
*
* @constructor
* @alias demo.MyComponent
* @extends sap.ui.webc.base.UI5Element
* @tagname my-component
* @public
*/
class MyComponent extends UI5Element {
static get metadata() {
return metadata;
}

static get render() {
return litRender;
}

static get template() {
return MyComponentTemplate;
}

get loadComponent() {
ComponentLoader.load();
}
}

MyComponent.define();

export default MyComponent;

This file configures the metadata of the Web Component and provides functions that can be called in the associated .hbs file.

For example, in the metadata we define the html tag that can be used to include the Web Component. We add the loadComponent function that will call the load function of our ComponentLoader. Don't worry, we will create the ComponentLoader soon.

test/pages/index.html


<script src="../../bundle.esm.js" type="module"></script>
<my-component id="myFirstComponent"></my-component>

The test/pages/index.html file is started whenever we start our Web Component locally. We simply delete everything we don't need in our scenario. What's left is a script that loads the bundle file, which boots everything we need in the background, and of course our custom Web Component tag with an id property that we'll need later.

By the way:
If you want to embed your web component on a real web page after you have deployed it to the platform of your choice, the integration scenario looks exactly like this. Except that the path to your bundle file must point to the deployed resource.

src/util/UI5Initializer.js


let instance;
class UI5Initializer {
constructor() { }
initialize() {
return new Promise(function (resolve, reject) {
let head = document.getElementsByTagName("head")[0];
let script = document.createElement("script");
script.addEventListener("load", function () { resolve(); });
script.src = `https://sapui5.hana.ondemand.com/resources/sap-ui-core.js`;
script.id = "sap-ui-bootstrap";
script.setAttribute("data-sap-ui-compatVersion", "edge");
script.setAttribute("data-sap-ui-resourceroots", '{ "demo.app" : "http://localhost:3000/ui" }');
script.setAttribute("data-sap-ui-theme", "sap_fiori_3_dark");
script.setAttribute("data-sap-ui-noConflict", "true");
script.setAttribute("data-sap-ui-frameOptions", "trusted");
head.appendChild(script);
});
}
}

instance ??= new UI5Initializer;
export default instance;

We need the UI5Initializer to do just one simple thing: Load the SAPUI5 resources and resolve a Promise as soon as it's done with it. That is exactly what happens in the initialize function. Especially important is the property "data-sap-ui-resourceroots", via which we map a SAPUI5 namespace to a source resource. We need this mapping to find our SAPUI5 application when we create it in the ComponentLoader.

src/util/ComponentLoader.js


import UI5Initializer from "./UI5Initializer";

let instance;

class Component {
constructor() {}

async load() {
await UI5Initializer.initialize();
sap.ui.require([], async () => {
const component = await sap.ui.core.Component.create({
name: "demo.app",
url: sap.ui.require.toUrl("demo/app"),
});

//Create a new container since shadow DOM won't work
const tag = document.querySelector('#myFirstComponent')
const container = document.createElement("div");
tag.parentNode.insertBefore(container, tag);

new sap.ui.core.ComponentContainer({
component
}).placeAt(container);
});
}
}

instance ??= new Component();
export default instance;

All the necessary preparation is done. We can finally load our SAPUI5 app and display it via our Web Component. In the load function we first of all wait until the UI5Initializer loaded the SAPUI5 resources, so we have access to the sap.ui object. After this we can create our SAPUI5 apps component. Noticed that the url of it is "demo/app"? By using the toUrl function and the "data-sap-ui-resourceroots" that we defined in the UI5Initializer, the source of the SAPUI5 app can be resolved.

Since embedding our SAPUI5 app in the shadow DOM doesn't work completely, we have to implement a small workaround.

We create a custom container for the SAPUI5 component and place it in the DOM right next to the Web Component element. I had not been able to test the insertion of the SAPUI5 component into the shadow DOM completely successfully, because the styles did not apply. This makes sense, because the shadow DOM is there for exactly this purpose. To encapsulate elements inside of it from elements outside of it. And this is also true for styles and DOM selection.

5. Start up everything



  1. Start your SAPUI5 app by running the start script in the approuter folder of the project. We have previously created and filled the ui folder using the SAPUI5 app's build script.

  2. Start your UI5 web component by running the start script in the project folder.


 

Congratulations, your Web Component is running and displaying your SAPUI5 app!


We can see the HTML tag (green frame) that we just created ourselves. Right next to the tag is our SAPUI5 component (blue frame), just as we defined it in the ComponentLoader.

Last but not least, let's summarize the pros and cons.


 

Pros



  • Easy to use, since it's only two lines of code.
    Even semi-technical employees or CMS users can integrate SAPUI5 apps on their sites.

  • You can check specific things before initializing SAPUI5.
    Incompatible browser versions and anything else that might be relevant for your SAPUI5 application can be checked before it is too late. This way, you can, for example, display an information page that indicates an outdated version before SAPUI5 does so and overwrites the DOM with it.

  • Configure the SAPUI5 app via the properties of your Web Component.
    You can add properties to your Web Component, which can be configured on your Web Components html-tag. Read these properties before you load the SAPUI5 component to start it with a different parameterization,

  • Centralization of the configuration of your SAPUI5 application
    There are only two lines of code on your embedding web page. Everything else is managed centrally in the UI5 Web Component. This allows you not only to change the configuration, such as the SAPUI5 version, but also to centrally deliver updates for all SAPUI5 apps that you have embedded on web pages, instead of doing this individually for the integration of each SAPUI5 app.


Cons



  • No shadow DOM support
    No shadow DOM means that your SAPUI5 application will exist in the same DOM as all the surrounding elements of the embedding site. This may lead to some style clashes.


Thank you for sticking with me this far! I would be very interested to hear what you think about this approach. You can find everything I did in this repository.

6 Comments
Labels in this area