@sap-ux/fiori-freestyle-writer 0.12.9 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/defaults.d.ts +1 -0
- package/dist/defaults.js +4 -1
- package/dist/index.js +14 -10
- package/package.json +2 -2
- package/templates/basic/add/webapp/Component.ts +28 -0
- package/templates/basic/add/webapp/controller/App.controller.ts +11 -0
- package/templates/basic/custom/Controller.ts +11 -0
- package/templates/common/add/webapp/model/models.ts +8 -0
- package/templates/common/add/webapp/test/flpSandbox.html +12 -7
- package/templates/listdetail/add/webapp/Component.ts +68 -0
- package/templates/listdetail/add/webapp/controller/App.controller.ts +39 -0
- package/templates/listdetail/add/webapp/controller/BaseController.ts +82 -0
- package/templates/listdetail/add/webapp/controller/Detail.controller.ts +184 -0
- package/templates/listdetail/add/webapp/controller/DetailObjectNotFound.controller.ts +8 -0
- package/templates/listdetail/add/webapp/controller/ErrorHandler.ts +61 -0
- package/templates/listdetail/add/webapp/controller/List.controller.ts +331 -0
- package/templates/listdetail/add/webapp/controller/ListSelector.ts +98 -0
- package/templates/listdetail/add/webapp/controller/NotFound.controller.ts +16 -0
- package/templates/listdetail/add/webapp/model/formatter.ts +13 -0
- package/templates/worklist/add/webapp/Component.ts +64 -0
- package/templates/worklist/add/webapp/controller/App.controller.ts +12 -0
- package/templates/worklist/add/webapp/controller/BaseController.ts +96 -0
- package/templates/worklist/add/webapp/controller/ErrorHandler.ts +74 -0
- package/templates/worklist/add/webapp/controller/NotFound.controller.ts +15 -0
- package/templates/worklist/add/webapp/controller/Object.controller.ts +79 -0
- package/templates/worklist/add/webapp/controller/Worklist.controller.ts +115 -0
- package/templates/worklist/add/webapp/model/formatter.ts +13 -0
- package/templates/listdetail/add/webapp/controller/DetailObjectNotFound.js +0 -7
package/dist/defaults.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ import type { FreestyleApp } from './types';
|
|
|
6
6
|
* @param ffApp full config object used by the generate method
|
|
7
7
|
*/
|
|
8
8
|
export declare function setDefaults(ffApp: FreestyleApp<unknown>): void;
|
|
9
|
+
export declare const escapeFLPText: (s: string) => string;
|
|
9
10
|
//# sourceMappingURL=defaults.d.ts.map
|
package/dist/defaults.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.setDefaults = void 0;
|
|
6
|
+
exports.escapeFLPText = exports.setDefaults = void 0;
|
|
7
7
|
const read_pkg_up_1 = __importDefault(require("read-pkg-up"));
|
|
8
8
|
const types_1 = require("./types");
|
|
9
9
|
/**
|
|
@@ -48,4 +48,7 @@ function setDefaults(ffApp) {
|
|
|
48
48
|
ffApp.appOptions = Object.assign({ loadReuseLibs: true }, ffApp.appOptions);
|
|
49
49
|
}
|
|
50
50
|
exports.setDefaults = setDefaults;
|
|
51
|
+
// Specific escaping is required for FLP texts in flpSandbox.html template file
|
|
52
|
+
// Escapes '\' with '\\\\' and '"' with '\"' to correctly render inputs in a secure way
|
|
53
|
+
exports.escapeFLPText = (s) => s.replace(/\\/g, '\\\\').replace(/(")/g, '\\$&');
|
|
51
54
|
//# sourceMappingURL=defaults.js.map
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ const defaults_1 = require("./defaults");
|
|
|
30
30
|
* @returns Reference to a mem-fs-editor
|
|
31
31
|
*/
|
|
32
32
|
function generate(basePath, data, fs) {
|
|
33
|
-
var _a, _b, _c, _d, _e;
|
|
33
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
34
34
|
return __awaiter(this, void 0, void 0, function* () {
|
|
35
35
|
// Clone rather than modifying callers refs
|
|
36
36
|
const ffApp = cloneDeep_1.default(data);
|
|
@@ -40,14 +40,18 @@ function generate(basePath, data, fs) {
|
|
|
40
40
|
// add new and overwrite files from templates e.g.
|
|
41
41
|
const tmplPath = path_1.join(__dirname, '..', 'templates');
|
|
42
42
|
// Common files
|
|
43
|
-
|
|
44
|
-
fs.copyTpl(path_1.join(tmplPath,
|
|
43
|
+
const ignore = [((_a = ffApp.appOptions) === null || _a === void 0 ? void 0 : _a.typescript) ? '**/*.js' : '**/*.ts'];
|
|
44
|
+
fs.copyTpl(path_1.join(tmplPath, 'common', 'add'), basePath, Object.assign(Object.assign({}, ffApp), { escapeFLPText: defaults_1.escapeFLPText }), undefined, {
|
|
45
|
+
globOptions: { ignore }
|
|
46
|
+
});
|
|
47
|
+
fs.copyTpl(path_1.join(tmplPath, ffApp.template.type, 'add'), basePath, ffApp, undefined, { globOptions: { ignore } });
|
|
45
48
|
if (ffApp.template.type === types_1.TemplateType.Basic) {
|
|
46
49
|
const viewName = ffApp.template.settings.viewName;
|
|
47
50
|
const viewTarget = path_1.join(basePath, 'webapp', 'view', `${viewName}.view.xml`);
|
|
48
51
|
fs.copyTpl(path_1.join(tmplPath, ffApp.template.type, 'custom/View.xml'), viewTarget, ffApp);
|
|
49
|
-
const
|
|
50
|
-
|
|
52
|
+
const ext = ((_b = ffApp.appOptions) === null || _b === void 0 ? void 0 : _b.typescript) ? 'ts' : 'js';
|
|
53
|
+
const controllerTarget = path_1.join(basePath, `webapp/controller/${viewName}.controller.${ext}`);
|
|
54
|
+
fs.copyTpl(path_1.join(tmplPath, ffApp.template.type, `custom/Controller.${ext}`), controllerTarget, ffApp);
|
|
51
55
|
}
|
|
52
56
|
// Add template specific manifest settings
|
|
53
57
|
const manifestPath = path_1.join(basePath, 'webapp', 'manifest.json');
|
|
@@ -60,12 +64,12 @@ function generate(basePath, data, fs) {
|
|
|
60
64
|
fs.extendJSON(packagePath, JSON.parse(ejs_1.render(fs.read(path_1.join(tmplPath, 'common', 'extend', 'package.json')), ffApp, {})));
|
|
61
65
|
const packageJson = JSON.parse(fs.read(packagePath));
|
|
62
66
|
packageJson.scripts = Object.assign(packageJson.scripts, Object.assign({}, packageConfig_1.getPackageJsonTasks({
|
|
63
|
-
localOnly: !((
|
|
64
|
-
addMock: !!((
|
|
65
|
-
sapClient: (
|
|
67
|
+
localOnly: !((_c = ffApp.service) === null || _c === void 0 ? void 0 : _c.url),
|
|
68
|
+
addMock: !!((_d = ffApp.service) === null || _d === void 0 ? void 0 : _d.metadata),
|
|
69
|
+
sapClient: (_e = ffApp.service) === null || _e === void 0 ? void 0 : _e.client,
|
|
66
70
|
flpAppId: ffApp.app.flpAppId,
|
|
67
|
-
startFile: (
|
|
68
|
-
localStartFile: (
|
|
71
|
+
startFile: (_f = data === null || data === void 0 ? void 0 : data.app) === null || _f === void 0 ? void 0 : _f.startFile,
|
|
72
|
+
localStartFile: (_g = data === null || data === void 0 ? void 0 : data.app) === null || _g === void 0 ? void 0 : _g.localStartFile
|
|
69
73
|
})));
|
|
70
74
|
fs.writeJSON(packagePath, packageJson);
|
|
71
75
|
// Add service to the project if provided
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap-ux/fiori-freestyle-writer",
|
|
3
3
|
"description": "SAP Fiori freestyle application writer",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.2",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/SAP/open-ux-tools.git",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@sap-ux/odata-service-writer": "0.14.6",
|
|
24
|
-
"@sap-ux/ui5-application-writer": "0.
|
|
24
|
+
"@sap-ux/ui5-application-writer": "0.15.1",
|
|
25
25
|
"@sap-ux/ui5-config": "0.14.5",
|
|
26
26
|
"ejs": "3.1.7",
|
|
27
27
|
"i18next": "20.3.2",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import BaseComponent from "<%- app.baseComponent %>";
|
|
2
|
+
import { createDeviceModel } from "<%=app.id.replace(/\./g, '/')%>/model/models";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @namespace <%- app.id %>
|
|
6
|
+
*/
|
|
7
|
+
export default class Component extends BaseComponent {
|
|
8
|
+
|
|
9
|
+
public static metadata = {
|
|
10
|
+
manifest: "json"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
|
|
15
|
+
* @public
|
|
16
|
+
* @override
|
|
17
|
+
*/
|
|
18
|
+
public init() : void {
|
|
19
|
+
// call the base component's init function
|
|
20
|
+
super.init();
|
|
21
|
+
|
|
22
|
+
// enable routing
|
|
23
|
+
this.getRouter().initialize();
|
|
24
|
+
|
|
25
|
+
// set the device model
|
|
26
|
+
this.setModel(createDeviceModel(), "device");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
},
|
|
45
45
|
applications: {
|
|
46
46
|
"<%- app.flpAppId %>": {
|
|
47
|
-
title: "<%- app.title %>",
|
|
48
|
-
description: "<%- app.description %>",
|
|
47
|
+
title: "<%- escapeFLPText(app.title) %>",
|
|
48
|
+
description: "<%- escapeFLPText(app.description) %>",
|
|
49
49
|
additionalInformation: "SAPUI5.Component=<%- app.id %>",
|
|
50
50
|
applicationType: "URL",
|
|
51
51
|
url: "../"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
</script>
|
|
56
56
|
|
|
57
57
|
<script src="../test-resources/sap/ushell/bootstrap/sandbox.js" id="sap-ushell-bootstrap"></script>
|
|
58
|
-
<!-- Bootstrap the UI5 core library -->
|
|
58
|
+
<!-- Bootstrap the UI5 core library. 'data-sap-ui-frameOptions="allow"'' is a NON-SECURE setting for test environments -->
|
|
59
59
|
<script id="sap-ui-bootstrap"
|
|
60
60
|
src="../resources/sap-ui-core.js"
|
|
61
61
|
data-sap-ui-libs="<%- ui5.ui5Libs %>"
|
|
@@ -65,10 +65,15 @@
|
|
|
65
65
|
data-sap-ui-compatVersion="edge"
|
|
66
66
|
data-sap-ui-language="en"
|
|
67
67
|
data-sap-ui-resourceroots='{"<%- app.id %>": "../"}'
|
|
68
|
-
data-sap-ui-frameOptions="allow">
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
data-sap-ui-frameOptions="allow">
|
|
69
|
+
</script><% if (appOptions.loadReuseLibs) { %>
|
|
70
|
+
<script id="locate-reuse-libs" src="../utils/locate-reuse-libs.js" data-sap-ui-manifest-uri="../manifest.json">
|
|
71
|
+
</script><% } else { %>
|
|
72
|
+
<script>
|
|
73
|
+
sap.ui.getCore().attachInit(function () {
|
|
74
|
+
sap.ushell.Container.createRenderer().placeAt("content");
|
|
75
|
+
});
|
|
76
|
+
</script><% } %>
|
|
72
77
|
</head>
|
|
73
78
|
|
|
74
79
|
<!-- UI Content -->
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import UIComponent from "sap/ui/core/UIComponent";
|
|
2
|
+
import { support } from "sap/ui/Device";
|
|
3
|
+
import ErrorHandler from "./controller/ErrorHandler";
|
|
4
|
+
import ListSelector from "./controller/ListSelector";
|
|
5
|
+
import { createDeviceModel } from "./model/models";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @namespace <%- app.id %>
|
|
9
|
+
*/
|
|
10
|
+
export default class Component extends UIComponent {
|
|
11
|
+
|
|
12
|
+
public static metadata = {
|
|
13
|
+
manifest: "json"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
public listSelector: ListSelector;
|
|
17
|
+
private errorHandler: ErrorHandler;
|
|
18
|
+
private contentDensityClass: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
|
|
22
|
+
*/
|
|
23
|
+
public init(): void {
|
|
24
|
+
// call the base component's init function
|
|
25
|
+
super.init();
|
|
26
|
+
|
|
27
|
+
this.listSelector = new ListSelector();
|
|
28
|
+
this.errorHandler = new ErrorHandler(this);
|
|
29
|
+
|
|
30
|
+
// enable routing
|
|
31
|
+
this.getRouter().initialize();
|
|
32
|
+
|
|
33
|
+
// set the device model
|
|
34
|
+
this.setModel(createDeviceModel(), "device");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The component is destroyed by UI5 automatically.
|
|
39
|
+
* In this method, the ListSelector and ErrorHandler are destroyed.
|
|
40
|
+
*/
|
|
41
|
+
public destroy() {
|
|
42
|
+
this.listSelector.destroy();
|
|
43
|
+
this.errorHandler.destroy();
|
|
44
|
+
// call the base component's destroy function
|
|
45
|
+
super.destroy();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
|
|
50
|
+
* design mode class should be set, which influences the size appearance of some controls.
|
|
51
|
+
* @return css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
|
|
52
|
+
*/
|
|
53
|
+
public getContentDensityClass(): string {
|
|
54
|
+
if (this.contentDensityClass === undefined) {
|
|
55
|
+
// check whether FLP has already set the content density class; do nothing in this case
|
|
56
|
+
// eslint-disable-next-line sap-no-proprietary-browser-api
|
|
57
|
+
if (document.body.classList.contains("sapUiSizeCozy") || document.body.classList.contains("sapUiSizeCompact")) {
|
|
58
|
+
this.contentDensityClass = "";
|
|
59
|
+
} else if (!support.touch) { // apply "compact" mode if touch is not supported
|
|
60
|
+
this.contentDensityClass = "sapUiSizeCompact";
|
|
61
|
+
} else {
|
|
62
|
+
// "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table
|
|
63
|
+
this.contentDensityClass = "sapUiSizeCozy";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return this.contentDensityClass;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import JSONModel from "sap/ui/model/json/JSONModel";
|
|
2
|
+
import ODataModel from "sap/ui/model/odata/v2/ODataModel";
|
|
3
|
+
import BaseController from "./BaseController";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @namespace <%- app.id %>
|
|
7
|
+
*/
|
|
8
|
+
export default class App extends BaseController {
|
|
9
|
+
|
|
10
|
+
public onInit(): void {
|
|
11
|
+
const originalBusyDelay = this.getView()!.getBusyIndicatorDelay();
|
|
12
|
+
|
|
13
|
+
const viewModel = new JSONModel({
|
|
14
|
+
busy : true,
|
|
15
|
+
delay : 0,
|
|
16
|
+
layout : "OneColumn",
|
|
17
|
+
previousLayout : "",
|
|
18
|
+
actionButtonsInfo : {
|
|
19
|
+
midColumn : {
|
|
20
|
+
fullScreen : false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
this.setModel(viewModel, "appView");
|
|
25
|
+
|
|
26
|
+
const fnSetAppNotBusy = function() {
|
|
27
|
+
viewModel.setProperty("/busy", false);
|
|
28
|
+
viewModel.setProperty("/delay", originalBusyDelay);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// since then() has no "reject"-path attach to the MetadataFailed-Event to disable the busy indicator in case of an error
|
|
32
|
+
const mainModel: ODataModel = this.getUIComponent().getModel() as ODataModel;
|
|
33
|
+
mainModel.metadataLoaded().then(fnSetAppNotBusy);
|
|
34
|
+
mainModel.attachMetadataFailed(fnSetAppNotBusy);
|
|
35
|
+
|
|
36
|
+
// apply content density mode to root view
|
|
37
|
+
this.getView()!.addStyleClass(this.getUIComponent().getContentDensityClass());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import ResourceBundle from "sap/base/i18n/ResourceBundle";
|
|
2
|
+
import Model from "sap/ui/model/Model";
|
|
3
|
+
import ResourceModel from "sap/ui/model/resource/ResourceModel";
|
|
4
|
+
import Controller from "sap/ui/core/mvc/Controller";
|
|
5
|
+
import View from "sap/ui/core/mvc/View";
|
|
6
|
+
import History from "sap/ui/core/routing/History";
|
|
7
|
+
import Router from "sap/ui/core/routing/Router";
|
|
8
|
+
import AppComponent from "../Component";
|
|
9
|
+
import { currencyValue } from "../model/formatter";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @namespace <%- app.id %>
|
|
13
|
+
*/
|
|
14
|
+
export default class BaseController extends Controller {
|
|
15
|
+
|
|
16
|
+
public readonly formatter = {
|
|
17
|
+
currencyValue
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convenience method for accessing the owner component.
|
|
22
|
+
*
|
|
23
|
+
* @returns the owner component
|
|
24
|
+
*/
|
|
25
|
+
protected getUIComponent(): AppComponent {
|
|
26
|
+
return super.getOwnerComponent() as AppComponent;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convenience method for accessing the router in every controller of the application.
|
|
31
|
+
*
|
|
32
|
+
* @returns the router for this component
|
|
33
|
+
*/
|
|
34
|
+
protected getRouter(): Router {
|
|
35
|
+
return this.getUIComponent().getRouter();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convenience method for getting the view model by name in every controller of the application.
|
|
40
|
+
*
|
|
41
|
+
* @param name the model name
|
|
42
|
+
* @returns the model instance
|
|
43
|
+
*/
|
|
44
|
+
protected getModel<T extends Model>(name?: string): T {
|
|
45
|
+
return this.getView()!.getModel(name) as T;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convenience method for setting the view model in every controller of the application.
|
|
50
|
+
*
|
|
51
|
+
* @param model the model instance
|
|
52
|
+
* @param name the model name
|
|
53
|
+
* @returns the view instance
|
|
54
|
+
*/
|
|
55
|
+
protected setModel(model: Model, name: string): View {
|
|
56
|
+
return this.getView()!.setModel(model, name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Convenience method for getting the resource bundle.
|
|
61
|
+
*
|
|
62
|
+
* @returns the resourceBundle of the component
|
|
63
|
+
*/
|
|
64
|
+
protected getResourceBundle(): ResourceBundle {
|
|
65
|
+
return (this.getUIComponent().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Event handler for navigating back.
|
|
70
|
+
* It there is a history entry we go one step back in the browser history
|
|
71
|
+
* If not, it will replace the current entry of the browser history with the list route.
|
|
72
|
+
*
|
|
73
|
+
*/
|
|
74
|
+
protected onNavBack() {
|
|
75
|
+
if (History.getInstance().getPreviousHash() !== undefined) {
|
|
76
|
+
// eslint-disable-next-line sap-no-history-manipulation
|
|
77
|
+
history.go(-1);
|
|
78
|
+
} else {
|
|
79
|
+
this.getRouter().navTo("list", {});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { URLHelper } from "sap/m/library";<%if (template.settings.lineItem.name) {%>
|
|
2
|
+
import Table from "sap/m/Table";<%}%>;
|
|
3
|
+
import Event from "sap/ui/base/Event";
|
|
4
|
+
import JSONModel from "sap/ui/model/json/JSONModel";
|
|
5
|
+
import ListBinding from "sap/ui/model/ListBinding";
|
|
6
|
+
import ODataModel from "sap/ui/model/odata/v2/ODataModel";
|
|
7
|
+
import BaseController from "./BaseController";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @namespace <%- app.id %>
|
|
11
|
+
*/
|
|
12
|
+
export default class Detail extends BaseController {
|
|
13
|
+
|
|
14
|
+
public onInit(): void {
|
|
15
|
+
// Model used to manipulate control states. The chosen values make sure,
|
|
16
|
+
// detail page is busy indication immediately so there is no break in
|
|
17
|
+
// between the busy indication for loading the view's meta data
|
|
18
|
+
const viewModel = new JSONModel({
|
|
19
|
+
busy: false,
|
|
20
|
+
delay: 0<%if (template.settings.lineItem.name) {%>,
|
|
21
|
+
lineItemListTitle: this.getResourceBundle().getText("detailLineItemTableHeading")<%}%>
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.getRouter().getRoute("object")!.attachPatternMatched(this.onObjectMatched, this);
|
|
25
|
+
|
|
26
|
+
this.setModel(viewModel, "detailView");
|
|
27
|
+
|
|
28
|
+
(this.getUIComponent().getModel() as ODataModel).metadataLoaded().then(this.onMetadataLoaded.bind(this));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Event handler when the share by E-Mail button has been clicked
|
|
33
|
+
*/
|
|
34
|
+
public onSendEmailPress() {
|
|
35
|
+
const viewModel = this.getModel("detailView");
|
|
36
|
+
|
|
37
|
+
URLHelper.triggerEmail(
|
|
38
|
+
undefined,
|
|
39
|
+
viewModel.getProperty("/shareSendEmailSubject"),
|
|
40
|
+
viewModel.getProperty("/shareSendEmailMessage")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
<%if (template.settings.lineItem.name) {%>
|
|
45
|
+
/**
|
|
46
|
+
* Updates the item count within the line item table's header
|
|
47
|
+
* @param event an event containing the total number of items in the list
|
|
48
|
+
*/
|
|
49
|
+
public onListUpdateFinished(event: Event) {
|
|
50
|
+
const viewModel = this.getModel<JSONModel>("detailView");
|
|
51
|
+
const totalItems = event.getParameter("total") as number;
|
|
52
|
+
let title: string;
|
|
53
|
+
// only update the counter if the length is final
|
|
54
|
+
if ((this.byId("lineItemsList")!.getBinding("items") as ListBinding).isLengthFinal()) {
|
|
55
|
+
if (totalItems) {
|
|
56
|
+
title = this.getResourceBundle().getText("detailLineItemTableHeadingCount", [totalItems]);
|
|
57
|
+
} else {
|
|
58
|
+
//Display 'Line Items' instead of 'Line items (0)'
|
|
59
|
+
title = this.getResourceBundle().getText("detailLineItemTableHeading");
|
|
60
|
+
}
|
|
61
|
+
viewModel.setProperty("/lineItemListTitle", title);
|
|
62
|
+
}
|
|
63
|
+
}<%}%>
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Binds the view to the object path and expands the aggregated line items.
|
|
67
|
+
* @function
|
|
68
|
+
* @param event pattern match event in route 'object'
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
private onObjectMatched(event: Event) {
|
|
72
|
+
const objectId = event.getParameter("arguments").objectId;
|
|
73
|
+
this.getModel<JSONModel>("appView").setProperty("/layout", "TwoColumnsMidExpanded");
|
|
74
|
+
this.getModel<ODataModel>().metadataLoaded().then(function (this: Detail) {
|
|
75
|
+
const objectPath = this.getModel<ODataModel>().createKey("Suppliers", {
|
|
76
|
+
SupplierID: objectId
|
|
77
|
+
});
|
|
78
|
+
this.bindView("/" + objectPath);
|
|
79
|
+
}.bind(this));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Binds the view to the object path. Makes sure that detail view displays
|
|
84
|
+
* a busy indicator while data for the corresponding element binding is loaded.
|
|
85
|
+
* @function
|
|
86
|
+
* @param objectPath path to the object to be bound to the view.
|
|
87
|
+
*/
|
|
88
|
+
private bindView(objectPath: string) {
|
|
89
|
+
// Set busy indicator during view binding
|
|
90
|
+
const viewModel = this.getModel<JSONModel>("detailView");
|
|
91
|
+
|
|
92
|
+
// If the view was not bound yet its not busy, only if the binding requests data it is set to busy again
|
|
93
|
+
viewModel.setProperty("/busy", false);
|
|
94
|
+
|
|
95
|
+
this.getView()!.bindElement({
|
|
96
|
+
path: objectPath,
|
|
97
|
+
events: {
|
|
98
|
+
change: this.onBindingChange.bind(this),
|
|
99
|
+
dataRequested: function () {
|
|
100
|
+
viewModel.setProperty("/busy", true);
|
|
101
|
+
},
|
|
102
|
+
dataReceived: function () {
|
|
103
|
+
viewModel.setProperty("/busy", false);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private onBindingChange() {
|
|
110
|
+
const view = this.getView()!;
|
|
111
|
+
const elementBinding = view.getElementBinding();
|
|
112
|
+
|
|
113
|
+
// No data for the binding
|
|
114
|
+
if (!elementBinding.getBoundContext()) {
|
|
115
|
+
this.getRouter().getTargets()!.display("detailObjectNotFound");
|
|
116
|
+
// if object could not be found, the selection in the list
|
|
117
|
+
// does not make sense anymore.
|
|
118
|
+
this.getUIComponent().listSelector.clearListSelection();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const path = elementBinding.getPath();
|
|
123
|
+
const resourceBundle = this.getResourceBundle();
|
|
124
|
+
const detailObject = view.getModel().getObject(path);
|
|
125
|
+
const viewModel = this.getModel<JSONModel>("detailView");
|
|
126
|
+
|
|
127
|
+
this.getUIComponent().listSelector.selectAListItem(path);
|
|
128
|
+
|
|
129
|
+
viewModel.setProperty("/shareSendEmailSubject",
|
|
130
|
+
resourceBundle.getText("shareSendEmailObjectSubject", [detailObject.<%=template.settings.entity.key%>]));
|
|
131
|
+
viewModel.setProperty("/shareSendEmailMessage",
|
|
132
|
+
resourceBundle.getText("shareSendEmailObjectMessage", [detailObject.<%=template.settings.entity.idProperty%>, detailObject.<%=template.settings.entity.key%>, location.href]));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected onMetadataLoaded() {
|
|
136
|
+
// Store original busy indicator delay for the detail view
|
|
137
|
+
const originalViewBusyDelay = this.getView()!.getBusyIndicatorDelay();
|
|
138
|
+
const viewModel = this.getModel<JSONModel>("detailView");<%if (template.settings.lineItem.name) {%>
|
|
139
|
+
const lineItemTable = this.byId("lineItemsList") as Table;
|
|
140
|
+
const originalLineItemTableBusyDelay = lineItemTable.getBusyIndicatorDelay();<%}%>;
|
|
141
|
+
|
|
142
|
+
// Make sure busy indicator is displayed immediately when
|
|
143
|
+
// detail view is displayed for the first time
|
|
144
|
+
viewModel.setProperty("/delay", 0);<%if (template.settings.lineItem.name) {%>
|
|
145
|
+
viewModel.setProperty("/lineItemTableDelay", 0);
|
|
146
|
+
|
|
147
|
+
lineItemTable.attachEventOnce("updateFinished", function () {
|
|
148
|
+
// Restore original busy indicator delay for line item table
|
|
149
|
+
viewModel.setProperty("/lineItemTableDelay", originalLineItemTableBusyDelay);
|
|
150
|
+
});<%}%>
|
|
151
|
+
|
|
152
|
+
// Binding the view will set it to not busy - so the view is always busy if it is not bound
|
|
153
|
+
viewModel.setProperty("/busy", true);
|
|
154
|
+
// Restore original busy indicator delay for the detail view
|
|
155
|
+
viewModel.setProperty("/delay", originalViewBusyDelay);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Set the full screen mode to false and navigate to list page
|
|
160
|
+
*/
|
|
161
|
+
protected onCloseDetailPress() {
|
|
162
|
+
this.getModel<JSONModel>("appView").setProperty("/actionButtonsInfo/midColumn/fullScreen", false);
|
|
163
|
+
// No item should be selected on list after detail page is closed
|
|
164
|
+
this.getUIComponent().listSelector.clearListSelection();
|
|
165
|
+
this.getRouter().navTo("list");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Toggle between full and non full screen mode.
|
|
170
|
+
*/
|
|
171
|
+
protected toggleFullScreen() {
|
|
172
|
+
const viewModel = this.getModel<JSONModel>("appView");
|
|
173
|
+
const fullScreen = viewModel.getProperty("/actionButtonsInfo/midColumn/fullScreen");
|
|
174
|
+
viewModel.setProperty("/actionButtonsInfo/midColumn/fullScreen", !fullScreen);
|
|
175
|
+
if (!fullScreen) {
|
|
176
|
+
// store current layout and go full screen
|
|
177
|
+
viewModel.setProperty("/previousLayout", viewModel.getProperty("/layout"));
|
|
178
|
+
viewModel.setProperty("/layout", "MidColumnFullScreen");
|
|
179
|
+
} else {
|
|
180
|
+
// reset to previous layout
|
|
181
|
+
viewModel.setProperty("/layout", viewModel.getProperty("/previousLayout"));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import MessageBox, { Action } from "sap/m/MessageBox";
|
|
2
|
+
import UI5Object from "sap/ui/base/Object";
|
|
3
|
+
import Event from "sap/ui/base/Event";
|
|
4
|
+
import ODataModel from "sap/ui/model/odata/v2/ODataModel";
|
|
5
|
+
import AppComponent from "../Component";
|
|
6
|
+
import ResourceModel from "sap/ui/model/resource/ResourceModel";
|
|
7
|
+
import ResourceBundle from "sap/base/i18n/ResourceBundle";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @namespace <%- app.id %>
|
|
11
|
+
*/
|
|
12
|
+
export default class ErrorHandler extends UI5Object {
|
|
13
|
+
|
|
14
|
+
protected readonly component: AppComponent;
|
|
15
|
+
protected messageOpen = false;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handles application errors by automatically attaching to the model events and displaying errors when needed.
|
|
19
|
+
* @param component reference to the app's component
|
|
20
|
+
*/
|
|
21
|
+
public constructor(component: AppComponent) {
|
|
22
|
+
super();
|
|
23
|
+
this.component = component;
|
|
24
|
+
const model = component.getModel() as ODataModel;
|
|
25
|
+
model.attachMetadataFailed(this.showServiceError);
|
|
26
|
+
model.attachRequestFailed(function (this: ErrorHandler, event: Event) {
|
|
27
|
+
const params = event.getParameters() as { response: XMLHttpRequest['response'] };
|
|
28
|
+
// An entity that was not found in the service is also throwing a 404 error in oData.
|
|
29
|
+
// We already cover this case with a notFound target so we skip it here.
|
|
30
|
+
// A request that cannot be sent to the server is a technical error that we have to handle though
|
|
31
|
+
if (params.response.statusCode !== "404" || (params.response.statusCode === 404 && params.response.responseText.indexOf("Cannot POST") === 0)) {
|
|
32
|
+
this.showServiceError(event);
|
|
33
|
+
}
|
|
34
|
+
}, this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shows a {@link sap.m.MessageBox} when a service call has failed.
|
|
39
|
+
* Only the first error message will be display.
|
|
40
|
+
* @param {string} sDetails a technical error to be displayed on request
|
|
41
|
+
*/
|
|
42
|
+
private showServiceError(event: Event) {
|
|
43
|
+
if (this.messageOpen) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.messageOpen = true;
|
|
47
|
+
MessageBox.error(
|
|
48
|
+
((this.component.getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle).getText("errorText"),
|
|
49
|
+
{
|
|
50
|
+
id: "serviceErrorMessageBox",
|
|
51
|
+
details: (event.getParameters() as XMLHttpRequest['response']).response,
|
|
52
|
+
styleClass: this.component.getContentDensityClass(),
|
|
53
|
+
actions: [Action.CLOSE],
|
|
54
|
+
onClose: function (this: ErrorHandler) {
|
|
55
|
+
this.messageOpen = false;
|
|
56
|
+
}.bind(this)
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|