Recently, after digging in UI5 manuals, I've refactored the legacy codebase with the login fragment functionality using OpenUI5 1.86+ and the modern, strict JS (async
/await
, arrow functions, object shorthand, asynchronous UI5 loading, etc.) where it is possible in accordance with the UI5 code reuse best practices. Finally, I can share my completely tested, working, and ready-to-use turn-key outcomes.
index.html
A bootstrapping using the Content-Security-Policy (CSP):
<!DOCTYPE html>
<html lang = "en">
<head>
<meta content = "default-src https: 'self' https://*.hana.ondemand.com 'unsafe-eval' 'unsafe-inline'; child-src 'none'; object-src 'none';"
http-equiv = "Content-Security-Policy" />
<meta charset = "utf-8" />
<title>Certfys TMS</title>
<!--suppress JSUnresolvedLibraryURL -->
<script data-sap-ui-appCachebuster="./"
data-sap-ui-async = "true"
data-sap-ui-compatVersion = "edge"
data-sap-ui-excludejquerycompat = "true"
data-sap-ui-onInit = "module:sap/ui/core/ComponentSupport"
data-sap-ui-resourceroots = '{"webapp": "./"}'
data-sap-ui-theme = "sap_fiori_3"
data-sap-ui-xx-componentpreload = "off"
id = "sap-ui-bootstrap"
src = "https://openui5nightly.hana.ondemand.com/resources/sap-ui-core.js">
</script>
</head>
<body class = "sapUiBody"
id = "content">
<div data-id = "rootComponentContainer"
data-name = "webapp"
data-sap-ui-component
data-settings = '{"id" : "webapp"}'></div>
</body>
</html>
Component.js
// eslint-disable-next-line strict
"use strict";
// eslint-disable-next-line no-undef
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"./controller/AuthDialog"
// eslint-disable-next-line max-params
], (UIComponent, Device, AuthDialog) => UIComponent.extend("webapp.Component", {
exit() {
this._authDialog.destroy();
delete this._authDialog;
},
getContentDensityClass() {
if (!this._sContentDensityClass) {
if (Device.support.touch) {
this._sContentDensityClass = "sapUiSizeCozy";
} else {
this._sContentDensityClass = "sapUiSizeCompact";
}
}
return this._sContentDensityClass;
},
init(...args) {
UIComponent.prototype.init.apply(this, args);
this.getRouter().initialize();
this._authDialog = new AuthDialog(this.getRootControl(0));
},
metadata: {
manifest: "json"
},
openAuthDialog() {
this._authDialog.open();
}
}));
App.controller.js
// eslint-disable-next-line strict
"use strict";
// eslint-disable-next-line no-undef
sap.ui.define([
"sap/ui/core/mvc/Controller"
// eslint-disable-next-line max-params
], (Controller) => Controller.extend("webapp.controller.App", {
onInit() {
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
}
}));
Login.view.xml
<mvc:View
xmlns:mvc = "sap.ui.core.mvc"
controllerName = "webapp.controller.Login">
</mvc:View>
Login.controller.js
// eslint-disable-next-line strict
"use strict";
// eslint-disable-next-line no-undef
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/UIComponent",
"webapp/controller/BaseController"
// eslint-disable-next-line max-params
], (Controller, UIComponent, BaseController) => Controller.extend("webapp.controller.login", {
async onInit() {
const session = {
sessionID: sessionStorage.getItem("SessionId"),
userID: sessionStorage.getItem("UserId")
};
const isAuthorized = await BaseController.isAuthorized(session);
if (isAuthorized) {
const oRouter = UIComponent.getRouterFor(this);
oRouter.navTo("overview");
} else {
this.getOwnerComponent().openAuthDialog();
}
}
}));
AuthDialog.fragment.xml
<core:FragmentDefinition
xmlns:core = "sap.ui.core"
xmlns = "sap.m">
<Dialog
id = "authDialog"
title = "{i18n>AUTH_DIALOG_DIALOG_TITLE}"
type = "Message"
escapeHandler = ".escapeHandler">
<Label
labelFor = "username"
text = "{i18n>AUTH_DIALOG_LAB_USERNAME}" />
<Input
id = "username"
liveChange = ".onLiveChange"
placeholder = "{i18n>AUTH_DIALOG_PH_USERNAME}"
type = "Text" />
<Label
labelFor = "password"
text = "{i18n>AUTH_DIALOG_LAB_PASSWORD}" />
<Input
id = "password"
liveChange = ".onLiveChange"
placeholder = "{i18n>AUTH_DIALOG_PH_PASSWORD}"
type = "Password" />
<beginButton>
<Button
enabled = "false"
id = "btnLogin"
press = ".onPressLogin"
text = "{i18n>AUTH_DIALOG_BTN_SUBMIT}"
type = "Emphasized" />
</beginButton>
</Dialog>
</core:FragmentDefinition>
AuthDialog.js
// eslint-disable-next-line strict
"use strict";
const _resetForm = function _resetForm(oView) {
oView.byId("username").setValue("");
oView.byId("password").setValue("");
oView.byId("btnLogin").setEnabled(false);
};
const _loginUser = async function _loginUser(oView, httpProtocol) {
const userCredentials = {
password: oView.byId("password").getValue(),
username: oView.byId("username").getValue()
};
let authData;
try {
const authResponse = await fetch(`${httpProtocol}://${location.host}/login`, {
body: JSON.stringify(userCredentials),
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json"
},
method: "POST",
mode: "same-origin",
redirect: "follow",
referrerPolicy: "no-referrer"
});
authData = await authResponse.json();
} catch (err) {
authData = {
message: err.message,
result: false
};
}
return authData;
};
const _authUser = async function _authUser(UIComponent, MessageToast, oView, httpProtocol) {
const authData = await _loginUser(oView, httpProtocol);
sessionStorage.setItem("SessionId", authData.sessionID);
sessionStorage.setItem("UserId", authData.userID);
if (authData.result) {
const oRouter = UIComponent.getRouterFor(oView);
oRouter.navTo("overview");
} else {
const oBundle = oView.getModel("i18n").getResourceBundle();
const sMsg = oBundle.getText(authData.message);
MessageToast.show(sMsg);
_resetForm(oView);
}
};
// eslint-disable-next-line no-undef
sap.ui.define([
"sap/ui/base/ManagedObject",
"sap/ui/core/Fragment",
"sap/ui/core/UIComponent",
"sap/m/MessageToast",
"webapp/controller/BaseController"
// eslint-disable-next-line max-params
], (ManagedObject, Fragment, UIComponent, MessageToast, BaseController) => ManagedObject.extend("webapp.controller.AuthDialog", {
constructor: function constructor(oView) {
this._oView = oView;
},
exit() {
delete this._oView;
},
async open() {
const oView = this._oView;
if (!this._oDialog) {
// noinspection JSUnusedGlobalSymbols
const fragmentController = {
escapeHandler(pEscapeHandler) {
pEscapeHandler.reject();
},
onLiveChange() {
const minRequiredLen = 3;
const username = oView.byId("username").getValue();
const password = oView.byId("password").getValue();
oView.byId("btnLogin").setEnabled((minRequiredLen <= username.length) && (minRequiredLen <= password.length));
},
async onPressLogin() {
const httpProtocol = BaseController.getHTTPProtocol();
await _authUser(UIComponent, MessageToast, oView, httpProtocol);
}
};
this._oDialog = await Fragment.load({
controller: fragmentController,
id: oView.getId(),
name: "webapp.view.AuthDialog"
});
this._oDialog.onsapenter = (async () => {
if (oView.byId("btnLogin").getEnabled()) {
const httpProtocol = BaseController.getHTTPProtocol();
await _authUser(UIComponent, MessageToast, oView, httpProtocol);
}
});
oView.addDependent(this._oDialog);
}
_resetForm(oView);
this._oDialog.open();
}
}));
BaseController.js
// eslint-disable-next-line strict
"use strict";
// eslint-disable-next-line no-undef
sap.ui.define([], () => ({
getHTTPProtocol() {
return ("localhost" === location.hostname)
? "http"
: "https";
},
async isAuthorized(session) {
const httpProtocol = this.getHTTPProtocol();
let authData;
try {
const authResponse = await fetch(`${httpProtocol}://${location.host}/checkSession`, {
body: JSON.stringify(session),
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json"
},
method: "POST",
mode: "same-origin",
redirect: "follow",
referrerPolicy: "no-referrer"
});
authData = await authResponse.json();
} catch (err) {
authData = {
message: err.message,
result: false
};
}
return authData.result;
}
}));
manifest.json
The relevant manifest fragment:
"routes": [
{
"name": "login",
"pattern": "",
"target": "login"
}
],
"targets": {
"login": {
"viewId": "login",
"viewName": "Login"
}
}
The final result:
Hope, it will help someone to grasp the way the modern UI5 is working with the fragments.