@open-rap/compiler 0.0.0-stage → 0.1.0

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.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -2
  3. package/dist/agent/server.d.ts +35 -0
  4. package/dist/agent/server.js +408 -0
  5. package/dist/agent/server.js.map +1 -0
  6. package/dist/authoring/abapgit.d.ts +38 -0
  7. package/dist/authoring/abapgit.js +242 -0
  8. package/dist/authoring/abapgit.js.map +1 -0
  9. package/dist/authoring/catalog.d.ts +48 -0
  10. package/dist/authoring/catalog.js +117 -0
  11. package/dist/authoring/catalog.js.map +1 -0
  12. package/dist/authoring/floorplan.d.ts +28 -0
  13. package/dist/authoring/floorplan.js +31 -0
  14. package/dist/authoring/floorplan.js.map +1 -0
  15. package/dist/authoring/generate.d.ts +57 -0
  16. package/dist/authoring/generate.js +630 -0
  17. package/dist/authoring/generate.js.map +1 -0
  18. package/dist/authoring/mcp.d.ts +15 -0
  19. package/dist/authoring/mcp.js +52 -0
  20. package/dist/authoring/mcp.js.map +1 -0
  21. package/dist/authoring/new.d.ts +35 -0
  22. package/dist/authoring/new.js +389 -0
  23. package/dist/authoring/new.js.map +1 -0
  24. package/dist/bdef/ast.d.ts +249 -0
  25. package/dist/bdef/ast.js +3 -0
  26. package/dist/bdef/ast.js.map +1 -0
  27. package/dist/bdef/lexer.d.ts +105 -0
  28. package/dist/bdef/lexer.js +148 -0
  29. package/dist/bdef/lexer.js.map +1 -0
  30. package/dist/bdef/parser.d.ts +22 -0
  31. package/dist/bdef/parser.js +1012 -0
  32. package/dist/bdef/parser.js.map +1 -0
  33. package/dist/build.d.ts +26 -0
  34. package/dist/build.js +62 -0
  35. package/dist/build.js.map +1 -0
  36. package/dist/cds/adapter.d.ts +91 -0
  37. package/dist/cds/adapter.js +381 -0
  38. package/dist/cds/adapter.js.map +1 -0
  39. package/dist/cds/expression-ast.d.ts +65 -0
  40. package/dist/cds/expression-ast.js +330 -0
  41. package/dist/cds/expression-ast.js.map +1 -0
  42. package/dist/cli.d.ts +9 -0
  43. package/dist/cli.js +485 -0
  44. package/dist/cli.js.map +1 -0
  45. package/dist/dcl/parser.d.ts +59 -0
  46. package/dist/dcl/parser.js +338 -0
  47. package/dist/dcl/parser.js.map +1 -0
  48. package/dist/ddic/catalog.d.ts +17 -0
  49. package/dist/ddic/catalog.js +211 -0
  50. package/dist/ddic/catalog.js.map +1 -0
  51. package/dist/ddic/cds-types.d.ts +47 -0
  52. package/dist/ddic/cds-types.js +131 -0
  53. package/dist/ddic/cds-types.js.map +1 -0
  54. package/dist/ddic/table-source.d.ts +70 -0
  55. package/dist/ddic/table-source.js +500 -0
  56. package/dist/ddic/table-source.js.map +1 -0
  57. package/dist/ddic/xml.d.ts +65 -0
  58. package/dist/ddic/xml.js +201 -0
  59. package/dist/ddic/xml.js.map +1 -0
  60. package/dist/dev.d.ts +128 -0
  61. package/dist/dev.js +434 -0
  62. package/dist/dev.js.map +1 -0
  63. package/dist/diagnostics.d.ts +33 -0
  64. package/dist/diagnostics.js +53 -0
  65. package/dist/diagnostics.js.map +1 -0
  66. package/dist/gen/abap/derived-types.d.ts +26 -0
  67. package/dist/gen/abap/derived-types.js +323 -0
  68. package/dist/gen/abap/derived-types.js.map +1 -0
  69. package/dist/gen/abap/eml.d.ts +118 -0
  70. package/dist/gen/abap/eml.js +362 -0
  71. package/dist/gen/abap/eml.js.map +1 -0
  72. package/dist/gen/abap/enums.d.ts +13 -0
  73. package/dist/gen/abap/enums.js +74 -0
  74. package/dist/gen/abap/enums.js.map +1 -0
  75. package/dist/gen/abap/framework-http.d.ts +14 -0
  76. package/dist/gen/abap/framework-http.js +1049 -0
  77. package/dist/gen/abap/framework-http.js.map +1 -0
  78. package/dist/gen/abap/framework-query.d.ts +14 -0
  79. package/dist/gen/abap/framework-query.js +225 -0
  80. package/dist/gen/abap/framework-query.js.map +1 -0
  81. package/dist/gen/abap/framework-utils.d.ts +29 -0
  82. package/dist/gen/abap/framework-utils.js +3707 -0
  83. package/dist/gen/abap/framework-utils.js.map +1 -0
  84. package/dist/gen/abap/framework.d.ts +12 -0
  85. package/dist/gen/abap/framework.js +480 -0
  86. package/dist/gen/abap/framework.js.map +1 -0
  87. package/dist/gen/abap/generate.d.ts +40 -0
  88. package/dist/gen/abap/generate.js +238 -0
  89. package/dist/gen/abap/generate.js.map +1 -0
  90. package/dist/gen/abap/rewrite.d.ts +82 -0
  91. package/dist/gen/abap/rewrite.js +957 -0
  92. package/dist/gen/abap/rewrite.js.map +1 -0
  93. package/dist/gen/abap/shadow.d.ts +25 -0
  94. package/dist/gen/abap/shadow.js +175 -0
  95. package/dist/gen/abap/shadow.js.map +1 -0
  96. package/dist/gen/abap/tokens.d.ts +33 -0
  97. package/dist/gen/abap/tokens.js +87 -0
  98. package/dist/gen/abap/tokens.js.map +1 -0
  99. package/dist/gen/app/index.d.ts +25 -0
  100. package/dist/gen/app/index.js +322 -0
  101. package/dist/gen/app/index.js.map +1 -0
  102. package/dist/gen/csdl.d.ts +11 -0
  103. package/dist/gen/csdl.js +562 -0
  104. package/dist/gen/csdl.js.map +1 -0
  105. package/dist/gen/deploy.d.ts +12 -0
  106. package/dist/gen/deploy.js +53 -0
  107. package/dist/gen/deploy.js.map +1 -0
  108. package/dist/gen/docs/index.d.ts +35 -0
  109. package/dist/gen/docs/index.js +267 -0
  110. package/dist/gen/docs/index.js.map +1 -0
  111. package/dist/gen/docs/markdown.d.ts +23 -0
  112. package/dist/gen/docs/markdown.js +187 -0
  113. package/dist/gen/docs/markdown.js.map +1 -0
  114. package/dist/gen/docs/site.d.ts +17 -0
  115. package/dist/gen/docs/site.js +106 -0
  116. package/dist/gen/docs/site.js.map +1 -0
  117. package/dist/gen/keycloak.d.ts +21 -0
  118. package/dist/gen/keycloak.js +90 -0
  119. package/dist/gen/keycloak.js.map +1 -0
  120. package/dist/gen/launchpad/index.d.ts +48 -0
  121. package/dist/gen/launchpad/index.js +302 -0
  122. package/dist/gen/launchpad/index.js.map +1 -0
  123. package/dist/gen/launchpad/ui.d.ts +14 -0
  124. package/dist/gen/launchpad/ui.js +350 -0
  125. package/dist/gen/launchpad/ui.js.map +1 -0
  126. package/dist/gen/mcp.d.ts +23 -0
  127. package/dist/gen/mcp.js +244 -0
  128. package/dist/gen/mcp.js.map +1 -0
  129. package/dist/gen/sql-expressions.d.ts +12 -0
  130. package/dist/gen/sql-expressions.js +136 -0
  131. package/dist/gen/sql-expressions.js.map +1 -0
  132. package/dist/gen/sql.d.ts +38 -0
  133. package/dist/gen/sql.js +612 -0
  134. package/dist/gen/sql.js.map +1 -0
  135. package/dist/gen/ui/index.d.ts +7 -0
  136. package/dist/gen/ui/index.js +27 -0
  137. package/dist/gen/ui/index.js.map +1 -0
  138. package/dist/gen/ui/layout.d.ts +230 -0
  139. package/dist/gen/ui/layout.js +511 -0
  140. package/dist/gen/ui/layout.js.map +1 -0
  141. package/dist/gen/ui/static.d.ts +55 -0
  142. package/dist/gen/ui/static.js +2901 -0
  143. package/dist/gen/ui/static.js.map +1 -0
  144. package/dist/gen/ui/views.d.ts +10 -0
  145. package/dist/gen/ui/views.js +511 -0
  146. package/dist/gen/ui/views.js.map +1 -0
  147. package/dist/gen/vendor.d.ts +23 -0
  148. package/dist/gen/vendor.js +68 -0
  149. package/dist/gen/vendor.js.map +1 -0
  150. package/dist/gen/xml.d.ts +9 -0
  151. package/dist/gen/xml.js +42 -0
  152. package/dist/gen/xml.js.map +1 -0
  153. package/dist/index.d.ts +20 -0
  154. package/dist/index.js +20 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/input/project.d.ts +50 -0
  157. package/dist/input/project.js +128 -0
  158. package/dist/input/project.js.map +1 -0
  159. package/dist/ir/access-controls.d.ts +11 -0
  160. package/dist/ir/access-controls.js +148 -0
  161. package/dist/ir/access-controls.js.map +1 -0
  162. package/dist/ir/authorizations.d.ts +25 -0
  163. package/dist/ir/authorizations.js +178 -0
  164. package/dist/ir/authorizations.js.map +1 -0
  165. package/dist/ir/behaviors.d.ts +16 -0
  166. package/dist/ir/behaviors.js +548 -0
  167. package/dist/ir/behaviors.js.map +1 -0
  168. package/dist/ir/builder.d.ts +20 -0
  169. package/dist/ir/builder.js +505 -0
  170. package/dist/ir/builder.js.map +1 -0
  171. package/dist/ir/config.d.ts +25 -0
  172. package/dist/ir/config.js +130 -0
  173. package/dist/ir/config.js.map +1 -0
  174. package/dist/ir/dictionary.d.ts +54 -0
  175. package/dist/ir/dictionary.js +328 -0
  176. package/dist/ir/dictionary.js.map +1 -0
  177. package/dist/ir/entities.d.ts +58 -0
  178. package/dist/ir/entities.js +676 -0
  179. package/dist/ir/entities.js.map +1 -0
  180. package/dist/ir/expressions.d.ts +33 -0
  181. package/dist/ir/expressions.js +327 -0
  182. package/dist/ir/expressions.js.map +1 -0
  183. package/dist/ir/handlers.d.ts +63 -0
  184. package/dist/ir/handlers.js +398 -0
  185. package/dist/ir/handlers.js.map +1 -0
  186. package/dist/ir/number-ranges.d.ts +24 -0
  187. package/dist/ir/number-ranges.js +74 -0
  188. package/dist/ir/number-ranges.js.map +1 -0
  189. package/dist/ir/seed.d.ts +23 -0
  190. package/dist/ir/seed.js +78 -0
  191. package/dist/ir/seed.js.map +1 -0
  192. package/dist/ir/service.d.ts +21 -0
  193. package/dist/ir/service.js +96 -0
  194. package/dist/ir/service.js.map +1 -0
  195. package/dist/launchpad/config.d.ts +47 -0
  196. package/dist/launchpad/config.js +321 -0
  197. package/dist/launchpad/config.js.map +1 -0
  198. package/dist/launchpad/workspace.d.ts +48 -0
  199. package/dist/launchpad/workspace.js +166 -0
  200. package/dist/launchpad/workspace.js.map +1 -0
  201. package/dist/srvd/parser.d.ts +36 -0
  202. package/dist/srvd/parser.js +193 -0
  203. package/dist/srvd/parser.js.map +1 -0
  204. package/dist/standard/data.d.ts +18 -0
  205. package/dist/standard/data.js +534 -0
  206. package/dist/standard/data.js.map +1 -0
  207. package/dist/standard/index.d.ts +36 -0
  208. package/dist/standard/index.js +507 -0
  209. package/dist/standard/index.js.map +1 -0
  210. package/dist/version.d.ts +2 -0
  211. package/dist/version.js +4 -0
  212. package/dist/version.js.map +1 -0
  213. package/docs/AGENTS.md +79 -0
  214. package/docs/ANNOTATIONS.md +76 -0
  215. package/docs/DECISIONS.md +1526 -0
  216. package/docs/DEPLOYMENT.md +113 -0
  217. package/docs/DIAGNOSTICS.md +116 -0
  218. package/docs/FEATURES.md +287 -0
  219. package/docs/GUIDE.md +560 -0
  220. package/docs/KNOWN_GAPS.md +216 -0
  221. package/docs/MCP.md +84 -0
  222. package/docs/PLAN.md +690 -0
  223. package/docs/README.md +148 -0
  224. package/docs/REMOTE_APIS.md +121 -0
  225. package/docs/ROADMAP.md +35 -0
  226. package/docs/UTILITIES.md +95 -0
  227. package/package.json +49 -3
@@ -0,0 +1,2901 @@
1
+ /**
2
+ * The fixed files of the generated UI: bootstrap, component, manifest, and the generic
3
+ * controllers of the List Report and the Object Page, driven by `model/layout.json`.
4
+ * Plain OpenUI5 1.136 (sap.m, sap.f, sap.uxap, sap.ui.layout), ODataModel V4.
5
+ */
6
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7
+ export function indexHtml(layout) {
8
+ return `<!DOCTYPE html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
+ <title>${esc(layout.title)}</title>
14
+ <script id="sap-ui-bootstrap" src="../resources/sap-ui-core.js"
15
+ data-sap-ui-theme="sap_horizon"
16
+ data-sap-ui-async="true"
17
+ data-sap-ui-compat-version="edge"
18
+ data-sap-ui-resource-roots='{"rapt.app": "./"}'
19
+ data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"></script>
20
+ <style>html, body { height: 100%; margin: 0; }</style>
21
+ <link rel="stylesheet" href="css/style.css">
22
+ </head>
23
+ <body class="sapUiBody" id="content">
24
+ <div data-sap-ui-component data-name="rapt.app" data-id="container" data-height="100%" data-settings='{"id": "rapt"}' style="height: 100%"></div>
25
+ </body>
26
+ </html>
27
+ `;
28
+ }
29
+ /**
30
+ * The content of the shell page sits between the shell bar and the bottom of the window: the
31
+ * inline `height: 100%` that sap.m.App writes on its ancestors would push it one shell bar below
32
+ * the window, and the document would scroll as well as the page.
33
+ */
34
+ export const SHELL_STYLE = `.raptShell > section { height: auto !important; }
35
+ `;
36
+ /** Filter bar of the list report: fields and buttons aligned on the bottom of the inputs. */
37
+ export const STYLE = `${SHELL_STYLE}.raptFilterBar { column-gap: 1rem; row-gap: 0.5rem; }
38
+ .raptFilterBar .raptFilterItem { margin: 0; }
39
+ .raptFilterBar .raptFilterButtons { margin-left: auto; align-items: flex-end; }
40
+ .raptChartArea { padding: 0 0 1rem 0; }
41
+ .raptChart { width: 100%; box-sizing: border-box; padding: 0.5rem 0; }
42
+ .raptChart svg { display: block; }
43
+ .raptChartLabel { font-family: var(--sapFontFamily); font-size: 11px; fill: var(--sapContent_LabelColor, #556b82); }
44
+ .raptChartGrid { stroke: var(--sapList_BorderColor, #e5e5e5); stroke-width: 1; }
45
+ .raptChartItem { cursor: pointer; opacity: 0.9; }
46
+ .raptChartItem:hover { opacity: 1; }
47
+ .raptChartSelected { stroke: var(--sapContent_FocusColor, #0070f2); stroke-width: 3; opacity: 1; }
48
+ .raptChartHole { fill: var(--sapTile_Background, #fff); }
49
+ .raptChartEmpty { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--sapContent_LabelColor); }
50
+ .raptCards { gap: 1rem; padding-top: 1rem; }
51
+ .raptCards .raptCard { max-width: 100%; }
52
+ `;
53
+ export function manifest(layout) {
54
+ const routes = [];
55
+ const targets = {};
56
+ // the start page (D53): an Overview Page, a form to create (form entry), or a list report
57
+ const startPage = layout.pages.find((p) => p.set === layout.start);
58
+ if (layout.overview !== undefined) {
59
+ routes.push({ pattern: "", name: "Overview", target: "Overview" });
60
+ targets["Overview"] = { name: "Overview", id: "Overview", level: 0 };
61
+ }
62
+ else if (startPage?.floorplan === "formEntry") {
63
+ routes.push({ pattern: "", name: `${startPage.set}FormEntry`, target: `${startPage.set}Object` });
64
+ }
65
+ for (const page of layout.pages) {
66
+ if (page.list !== undefined) {
67
+ const start = page.set === layout.start && layout.overview === undefined && page.floorplan !== "formEntry";
68
+ routes.push({ pattern: start ? "" : `${page.set}`, name: `${page.set}List`, target: `${page.set}List` });
69
+ targets[`${page.set}List`] = { name: `${page.set}List`, id: `${page.set}List`, level: 1 };
70
+ }
71
+ if (page.insertable) {
72
+ routes.push({ pattern: `${page.set}/new`, name: `${page.set}New`, target: `${page.set}Object` });
73
+ }
74
+ routes.push({ pattern: `${page.set}({key})`, name: `${page.set}Object`, target: `${page.set}Object` });
75
+ targets[`${page.set}Object`] = { name: `${page.set}Object`, id: `${page.set}Object`, level: page.list === undefined ? 3 : 2 };
76
+ }
77
+ return `${JSON.stringify({
78
+ "_version": "1.65.0",
79
+ "sap.app": {
80
+ id: "rapt.app",
81
+ type: "application",
82
+ title: layout.title,
83
+ applicationVersion: { version: "1.0.0" },
84
+ dataSources: {
85
+ mainService: { uri: layout.servicePath, type: "OData", settings: { odataVersion: "4.0" } },
86
+ layout: { uri: "model/layout.json", type: "JSON" },
87
+ },
88
+ },
89
+ "sap.ui": { technology: "UI5", deviceTypes: { desktop: true, tablet: true, phone: true } },
90
+ "sap.ui5": {
91
+ rootView: { viewName: "rapt.app.view.App", type: "XML", id: "appView" },
92
+ dependencies: { minUI5Version: "1.136.0", libs: { "sap.m": {}, "sap.f": {}, "sap.uxap": {}, "sap.ui.layout": {} } },
93
+ contentDensities: { compact: true, cozy: true },
94
+ models: {
95
+ "": { dataSource: "mainService", preload: true, settings: { autoExpandSelect: true, earlyRequests: true, operationMode: "Server" } },
96
+ layout: { dataSource: "layout", type: "sap.ui.model.json.JSONModel" },
97
+ },
98
+ routing: {
99
+ config: { routerClass: "sap.m.routing.Router", type: "View", viewType: "XML", path: "rapt.app.view", controlId: "app", controlAggregation: "pages", async: true },
100
+ routes,
101
+ targets,
102
+ },
103
+ },
104
+ }, null, 2)}\n`;
105
+ }
106
+ /**
107
+ * The root view: the pages of the application in an App; above them the shell bar of the
108
+ * launchpad (D37) when the application is served behind one (home, user, logout).
109
+ */
110
+ export const APP_VIEW = `<mvc:View controllerName="rapt.app.controller.App" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:f="sap.f" displayBlock="true" height="100%">
111
+ <Page id="shell" showHeader="{= !!\${ui>/shell} }" enableScrolling="false" class="raptShell">
112
+ <customHeader>
113
+ <f:ShellBar id="shellBar" title="{ui>/shell/title}" secondTitle="{ui>/shell/launchpad}" homeIcon="{ui>/shell/logo}" homeIconTooltip="Home" homeIconPressed=".onHome" avatarPressed=".onAvatarPressed">
114
+ <f:profile>
115
+ <Avatar id="avatar" initials="{ui>/shell/user/initials}" tooltip="{ui>/shell/user/name}"/>
116
+ </f:profile>
117
+ </f:ShellBar>
118
+ </customHeader>
119
+ <content>
120
+ <App id="app"/>
121
+ </content>
122
+ </Page>
123
+ </mvc:View>
124
+ `;
125
+ export const APP_CONTROLLER = `sap.ui.define([
126
+ "sap/ui/core/mvc/Controller",
127
+ "sap/m/ResponsivePopover",
128
+ "sap/m/VBox",
129
+ "sap/m/Text",
130
+ "sap/m/Button"
131
+ ], function (Controller, ResponsivePopover, VBox, Text, Button) {
132
+ "use strict";
133
+
134
+ /** The shell bar of the launchpad the application is served behind (D37). */
135
+ return Controller.extend("rapt.app.controller.App", {
136
+ onHome: function () {
137
+ window.location.assign(this.getView().getModel("ui").getProperty("/shell/home") || "/");
138
+ },
139
+
140
+ onAvatarPressed: function (oEvent) {
141
+ if (!this._oUserMenu) {
142
+ var oShell = this.getView().getModel("ui").getProperty("/shell");
143
+ var aItems = [];
144
+ if (oShell.user.id !== oShell.user.name) {
145
+ aItems.push(new Text({ text: oShell.user.id }));
146
+ }
147
+ if (!oShell.logout) {
148
+ aItems.push(new Text({ text: "Development user (no login)" }));
149
+ }
150
+ this._oUserMenu = new ResponsivePopover(this.createId("userMenu"), { title: oShell.user.name, placement: "Bottom", contentMinWidth: "12rem" });
151
+ if (aItems.length > 0) {
152
+ this._oUserMenu.addContent(new VBox({ items: aItems }).addStyleClass("sapUiSmallMargin"));
153
+ }
154
+ if (oShell.logout) {
155
+ this._oUserMenu.setBeginButton(new Button(this.createId("logout"), {
156
+ text: "Sign Out",
157
+ icon: "sap-icon://log",
158
+ press: function () {
159
+ window.location.assign(oShell.logout);
160
+ }
161
+ }));
162
+ }
163
+ this.getView().addDependent(this._oUserMenu);
164
+ }
165
+ this._oUserMenu.openBy(oEvent.getParameter("avatar"));
166
+ }
167
+ });
168
+ });
169
+ `;
170
+ export const COMPONENT = `sap.ui.define([
171
+ "sap/ui/core/UIComponent",
172
+ "sap/ui/model/json/JSONModel",
173
+ "sap/ui/core/Messaging"
174
+ ], function (UIComponent, JSONModel, Messaging) {
175
+ "use strict";
176
+
177
+ return UIComponent.extend("rapt.app.Component", {
178
+ metadata: { manifest: "json", interfaces: ["sap.ui.core.IAsyncContentCreation"] },
179
+
180
+ init: function () {
181
+ UIComponent.prototype.init.apply(this, arguments);
182
+ var oUi = new JSONModel({ editMode: false, createMode: false, draft: false, busy: false, tableSelected: false, childSelected: false, permissions: {}, user: undefined, collab: { users: [] } });
183
+ var that = this;
184
+ this._aUserCallbacks = [];
185
+ this.setModel(oUi, "ui");
186
+ this.setModel(Messaging.getMessageModel(), "message");
187
+ // the creations the user may perform (global authorization): Create is hidden otherwise
188
+ var sService = this.getManifestEntry("/sap.app/dataSources/mainService/uri");
189
+ fetch(sService + "$permissions", { credentials: "same-origin", headers: { accept: "application/json" } })
190
+ .then(function (oResponse) { return oResponse.ok ? oResponse.json() : {}; })
191
+ .then(function (oPermissions) {
192
+ oUi.setProperty("/permissions", oPermissions);
193
+ // the user, for the draft markers (D58)
194
+ oUi.setProperty("/user", oPermissions.$user || "");
195
+ that._fireUser();
196
+ })
197
+ .catch(function () { /* the permissions stay unknown: every button is shown */ oUi.setProperty("/user", ""); that._fireUser(); });
198
+ // behind a launchpad (D37): its shell bar above the pages; standalone, the request fails
199
+ var sTitle = this.getManifestEntry("/sap.app/title");
200
+ fetch("/launchpad/config", { credentials: "same-origin", headers: { accept: "application/json" } })
201
+ .then(function (oResponse) { return oResponse.ok ? oResponse.json() : undefined; })
202
+ .then(function (oLaunchpad) {
203
+ if (oLaunchpad && oLaunchpad.user) {
204
+ oUi.setProperty("/shell", { title: sTitle, launchpad: oLaunchpad.title, home: oLaunchpad.home || "/", logo: oLaunchpad.logo || "", user: oLaunchpad.user, logout: oLaunchpad.logout || "" });
205
+ }
206
+ })
207
+ .catch(function () { /* not behind a launchpad */ });
208
+ this.getRouter().initialize();
209
+ },
210
+
211
+ /** Calls back with the user once known (from $permissions). */
212
+ whenUser: function (fnCallback) {
213
+ var sUser = this.getModel("ui").getProperty("/user");
214
+ if (sUser !== undefined) {
215
+ fnCallback(sUser);
216
+ } else {
217
+ this._aUserCallbacks.push(fnCallback);
218
+ }
219
+ },
220
+
221
+ _fireUser: function () {
222
+ var sUser = this.getModel("ui").getProperty("/user");
223
+ (this._aUserCallbacks || []).splice(0).forEach(function (fn) { fn(sUser); });
224
+ },
225
+
226
+ /** A change was saved: the lists show it when the user comes back to them. */
227
+ markStale: function () {
228
+ this._iVersion = (this._iVersion || 0) + 1;
229
+ },
230
+
231
+ /** Whether the list of an entity set must be refreshed (a change since it was last shown). */
232
+ takeStale: function (sSet) {
233
+ this._mSeen = this._mSeen || {};
234
+ var bStale = (this._mSeen[sSet] || 0) !== (this._iVersion || 0);
235
+ this._mSeen[sSet] = this._iVersion || 0;
236
+ return bStale;
237
+ },
238
+
239
+ /** The transient context of an instance being created (List Report → Object Page). */
240
+ setTransient: function (oContext) {
241
+ this._oTransient = oContext;
242
+ },
243
+
244
+ getTransient: function () {
245
+ return this._oTransient;
246
+ }
247
+ });
248
+ });
249
+ `;
250
+ /** Helpers shared by the controllers: layout lookups, key predicates, actions. */
251
+ export const SUPPORT = `sap.ui.define([
252
+ "sap/m/Dialog",
253
+ "sap/m/Button",
254
+ "sap/m/Label",
255
+ "sap/m/Input",
256
+ "sap/m/DatePicker",
257
+ "sap/m/CheckBox",
258
+ "sap/ui/layout/form/SimpleForm",
259
+ "sap/ui/core/Messaging",
260
+ "sap/m/TableSelectDialog",
261
+ "sap/m/Column",
262
+ "sap/m/ColumnListItem",
263
+ "sap/m/Text",
264
+ "sap/ui/model/Filter",
265
+ "sap/m/Select",
266
+ "sap/ui/core/Item"
267
+ ], function (Dialog, Button, Label, Input, DatePicker, CheckBox, SimpleForm, Messaging, TableSelectDialog, Column, ColumnListItem, Text, Filter, Select, Item) {
268
+ "use strict";
269
+
270
+ function pad(n) {
271
+ return String(n).padStart(2, "0");
272
+ }
273
+
274
+ var support = {
275
+ page: function (oController, sSet) {
276
+ return oController.getOwnerComponent().getModel("layout").getProperty("/pages").find(function (p) {
277
+ return p.set === sSet;
278
+ });
279
+ },
280
+
281
+ action: function (oPage, sName, aActions) {
282
+ return (aActions || oPage.actions).find(function (a) {
283
+ return a.name === sName;
284
+ });
285
+ },
286
+
287
+ /** The key of an instance as written in the URL: \`3f25…\` or \`A='x',B=1\`. */
288
+ keyOf: function (sPath) {
289
+ var iOpen = sPath.lastIndexOf("(");
290
+ return sPath.slice(iOpen + 1, sPath.length - 1);
291
+ },
292
+
293
+ keyPredicate: function (oPage, oData) {
294
+ var aKeys = oPage.keys.map(function (sKey) {
295
+ var vValue = oData[sKey];
296
+ var sEdm = oPage.fields[sKey] ? oPage.fields[sKey].edm : "Edm.String";
297
+ var sText = sEdm === "Edm.String" ? "'" + String(vValue).replace(/'/g, "''") + "'" : String(vValue);
298
+ return { key: sKey, text: sText };
299
+ });
300
+ return aKeys.length === 1 ? aKeys[0].text : aKeys.map(function (k) { return k.key + "=" + k.text; }).join(",");
301
+ },
302
+
303
+ dateText: function (oDate) {
304
+ return oDate.getFullYear() + "-" + pad(oDate.getMonth() + 1) + "-" + pad(oDate.getDate());
305
+ },
306
+
307
+ /** Asks for the parameters of an action; resolves null when cancelled. */
308
+ askParameters: function (oView, oAction) {
309
+ if (oAction.parameters.length === 0) {
310
+ return Promise.resolve({});
311
+ }
312
+ return new Promise(function (resolve) {
313
+ var aControls = [];
314
+ var aContent = [];
315
+ oAction.parameters.forEach(function (oParameter) {
316
+ var oControl;
317
+ if (oParameter.kind === "date") {
318
+ oControl = new DatePicker({ valueFormat: "yyyy-MM-dd" });
319
+ } else if (oParameter.kind === "boolean") {
320
+ oControl = new CheckBox();
321
+ } else if (oParameter.kind === "enum") {
322
+ oControl = new Select({ forceSelection: true, items: oParameter.options.map(function (o) { return new Item({ key: o.key, text: o.text }); }) });
323
+ } else if (oParameter.valueHelp) {
324
+ oControl = new Input({ showValueHelp: true });
325
+ oControl.attachValueHelpRequest(function () {
326
+ var oInput = this;
327
+ support.valueHelp(oView, oParameter.valueHelp, false).then(function (aValues) {
328
+ if (aValues.length > 0) {
329
+ oInput.setValue(aValues[0]);
330
+ }
331
+ });
332
+ }, oControl);
333
+ } else {
334
+ oControl = new Input({ type: oParameter.kind === "integer" || oParameter.kind === "decimal" ? "Number" : "Text" });
335
+ }
336
+ aControls.push({ parameter: oParameter, control: oControl });
337
+ aContent.push(new Label({ text: oParameter.label, labelFor: oControl }), oControl);
338
+ });
339
+ var oDialog = new Dialog({
340
+ title: oAction.label,
341
+ content: [new SimpleForm({ editable: true, content: aContent })],
342
+ beginButton: new Button({
343
+ text: oAction.label,
344
+ type: "Emphasized",
345
+ press: function () {
346
+ var oValues = {};
347
+ aControls.forEach(function (o) {
348
+ var p = o.parameter;
349
+ if (p.kind === "boolean") {
350
+ oValues[p.name] = o.control.getSelected();
351
+ } else if (p.kind === "enum") {
352
+ oValues[p.name] = o.control.getSelectedKey();
353
+ } else if (p.kind === "integer") {
354
+ oValues[p.name] = o.control.getValue() === "" ? 0 : Number(o.control.getValue());
355
+ } else {
356
+ oValues[p.name] = o.control.getValue();
357
+ }
358
+ });
359
+ oDialog.close();
360
+ resolve(oValues);
361
+ }
362
+ }),
363
+ endButton: new Button({
364
+ text: "Cancel",
365
+ press: function () {
366
+ oDialog.close();
367
+ resolve(null);
368
+ }
369
+ }),
370
+ afterClose: function () {
371
+ oDialog.destroy();
372
+ }
373
+ });
374
+ oView.addDependent(oDialog);
375
+ oDialog.open();
376
+ });
377
+ },
378
+
379
+ /** Calls an action (or a function); resolves its return value context, or null when cancelled. */
380
+ invoke: function (oView, oPage, oAction, oContext) {
381
+ return support.askParameters(oView, oAction).then(function (oValues) {
382
+ if (oValues === null) {
383
+ return null;
384
+ }
385
+ Messaging.removeAllMessages();
386
+ var oModel = oView.getModel();
387
+ var oOperation = oAction.static
388
+ ? oModel.bindContext("/" + oPage.set + "/" + oAction.qualified + "(...)")
389
+ : oModel.bindContext(oAction.qualified + "(...)", oContext, { $$inheritExpandSelect: true });
390
+ Object.keys(oValues).forEach(function (sName) {
391
+ oOperation.setParameter(sName, oValues[sName]);
392
+ });
393
+ return oOperation.invoke();
394
+ });
395
+ },
396
+
397
+ /** The descriptions of the values of a value help (text entity), by value: { EUR: "Euro" }. */
398
+ valueHelpTexts: function (oView, oHelp) {
399
+ if (!oHelp.text) {
400
+ return Promise.resolve({});
401
+ }
402
+ var sService = oView.getModel().getServiceUrl();
403
+ var sLanguage = (navigator.language || "en").slice(0, 2).toUpperCase();
404
+ var sKey = { EN: "E", DE: "D", FR: "F", ES: "S", IT: "I", PT: "P", NL: "N", JA: "J", ZH: "1", RU: "R", PL: "L" }[sLanguage] || "E";
405
+ var oText = oHelp.text;
406
+ var sFilter = oText.language ? "&$filter=" + encodeURIComponent(oText.language + " eq '" + sKey + "' or " + oText.language + " eq 'E'") : "";
407
+ return fetch(sService + oText.set + "?$select=" + [oText.key, oText.element].concat(oText.language ? [oText.language] : []).join(",") + sFilter, {
408
+ credentials: "same-origin",
409
+ headers: { accept: "application/json" }
410
+ }).then(function (oResponse) {
411
+ return oResponse.ok ? oResponse.json() : { value: [] };
412
+ }).then(function (oData) {
413
+ var mTexts = {};
414
+ (oData.value || []).forEach(function (oRow) {
415
+ var sValue = String(oRow[oText.key]);
416
+ if (mTexts[sValue] === undefined || (oText.language && oRow[oText.language] === sKey)) {
417
+ mTexts[sValue] = oRow[oText.element];
418
+ }
419
+ });
420
+ return mTexts;
421
+ }).catch(function () {
422
+ return {};
423
+ });
424
+ },
425
+
426
+ /**
427
+ * Opens the value help of a field; resolves the chosen values (none when cancelled). With the
428
+ * context of the instance, the bindings of usage filter restrict the list, and the chosen rows
429
+ * are given as second argument (results of the additional bindings).
430
+ */
431
+ valueHelp: function (oView, oHelp, bMulti, oContext) {
432
+ return support.valueHelpTexts(oView, oHelp).then(function (mTexts) {
433
+ return support._valueHelpDialog(oView, oHelp, bMulti, mTexts, oContext);
434
+ });
435
+ },
436
+
437
+ /** The filters of the additional bindings of usage filter, from the values of the instance. */
438
+ valueHelpFilters: function (oHelp, oContext) {
439
+ if (!oContext || !oHelp.bindings) {
440
+ return [];
441
+ }
442
+ return oHelp.bindings.filter(function (b) {
443
+ var vValue = oContext.getProperty(b.local);
444
+ return b.usage !== "result" && vValue !== undefined && vValue !== null && vValue !== "";
445
+ }).map(function (b) {
446
+ return new Filter(b.element, "EQ", oContext.getProperty(b.local));
447
+ });
448
+ },
449
+
450
+ /** Copies the elements of a chosen row to the local elements (additional bindings of usage result). */
451
+ applyValueHelpResults: function (oHelp, oContext, oRow) {
452
+ (oHelp.bindings || []).forEach(function (b) {
453
+ if (b.usage !== "filter" && oRow && oRow[b.element] !== undefined) {
454
+ oContext.setProperty(b.local, oRow[b.element]);
455
+ }
456
+ });
457
+ },
458
+
459
+ /**
460
+ * Checks a typed value against the value help (useForValidation) and fills the additional
461
+ * bindings of usage result; resolves false when the value is not in the list.
462
+ */
463
+ checkValueHelp: function (oView, oHelp, oContext, sValue) {
464
+ if (sValue === "" || sValue === null || sValue === undefined) {
465
+ return Promise.resolve(true);
466
+ }
467
+ var aConditions = [oHelp.element + " eq '" + String(sValue).replace(/'/g, "''") + "'"];
468
+ support.valueHelpFilters(oHelp, oContext).forEach(function (oFilter) {
469
+ aConditions.push(oFilter.getPath() + " eq '" + String(oFilter.getValue1()).replace(/'/g, "''") + "'");
470
+ });
471
+ return fetch(oView.getModel().getServiceUrl() + oHelp.set + "?$top=1&$filter=" + encodeURIComponent(aConditions.join(" and ")), {
472
+ credentials: "same-origin",
473
+ headers: { accept: "application/json" }
474
+ }).then(function (oResponse) {
475
+ return oResponse.ok ? oResponse.json() : { value: [] };
476
+ }).then(function (oData) {
477
+ var oRow = (oData.value || [])[0];
478
+ if (oRow) {
479
+ support.applyValueHelpResults(oHelp, oContext, oRow);
480
+ }
481
+ return oRow !== undefined;
482
+ }).catch(function () {
483
+ return true;
484
+ });
485
+ },
486
+
487
+ /** The columns of a value help: its elements, and the description of the text entity after the value. */
488
+ valueHelpColumns: function (oHelp, mTexts) {
489
+ var aColumns = oHelp.columns.slice();
490
+ if (Object.keys(mTexts).length > 0) {
491
+ aColumns.splice(1, 0, { property: oHelp.element, label: oHelp.text.label, string: false, text: true });
492
+ }
493
+ return aColumns;
494
+ },
495
+
496
+ /** The binding of the rows of a value help: the elements of the columns and of the additional bindings. */
497
+ valueHelpItems: function (oHelp, aColumns, mTexts, aBase) {
498
+ return {
499
+ path: "/" + oHelp.set,
500
+ parameters: { $count: true, $select: aColumns.filter(function (c) { return !c.text; }).map(function (c) { return c.property; }).concat((oHelp.bindings || []).map(function (b) { return b.element; })).filter(function (p, i, a) { return a.indexOf(p) === i; }).join(",") },
501
+ filters: aBase.length === 0 ? undefined : new Filter({ filters: aBase, and: true }),
502
+ template: new ColumnListItem({
503
+ cells: aColumns.map(function (c) {
504
+ return c.text
505
+ ? new Text({ text: { path: c.property, formatter: function (v) { return mTexts[v] || ""; } } })
506
+ : new Text({ text: "{" + c.property + "}" });
507
+ })
508
+ })
509
+ };
510
+ },
511
+
512
+ /** The filter of a search in a value help: a text column or a description contains the value. */
513
+ valueHelpSearch: function (oHelp, mTexts, sSearch, aBase) {
514
+ var sValue = (sSearch || "").trim();
515
+ var aFilters = oHelp.columns.filter(function (c) { return c.string; }).map(function (c) {
516
+ return new Filter({ path: c.property, operator: "Contains", value1: sValue, caseSensitive: false });
517
+ });
518
+ // values whose description matches
519
+ var sLower = sValue.toLowerCase();
520
+ Object.keys(mTexts).filter(function (k) {
521
+ return String(mTexts[k]).toLowerCase().indexOf(sLower) >= 0;
522
+ }).slice(0, 30).forEach(function (k) {
523
+ aFilters.push(new Filter(oHelp.element, "EQ", k));
524
+ });
525
+ var aAll = aBase.slice();
526
+ if (sValue !== "" && aFilters.length > 0) {
527
+ aAll.push(new Filter({ filters: aFilters, and: false }));
528
+ }
529
+ return aAll.length === 0 ? [] : new Filter({ filters: aAll, and: true });
530
+ },
531
+
532
+ _valueHelpDialog: function (oView, oHelp, bMulti, mTexts, oContext) {
533
+ var aColumns = support.valueHelpColumns(oHelp, mTexts);
534
+ return new Promise(function (resolve) {
535
+ var bDone = false;
536
+ var aBase = support.valueHelpFilters(oHelp, oContext);
537
+ var done = function (aValues, aRows) {
538
+ if (!bDone) {
539
+ bDone = true;
540
+ resolve(aValues);
541
+ if (aRows && aRows.length > 0 && oContext) {
542
+ support.applyValueHelpResults(oHelp, oContext, aRows[0]);
543
+ }
544
+ }
545
+ };
546
+ var oDialog = new TableSelectDialog({
547
+ title: oHelp.label,
548
+ multiSelect: bMulti,
549
+ growing: true,
550
+ growingThreshold: 50,
551
+ columns: aColumns.map(function (c) {
552
+ return new Column({ header: new Text({ text: c.label }) });
553
+ }),
554
+ search: function (oEvent) {
555
+ oEvent.getSource().getBinding("items").filter(support.valueHelpSearch(oHelp, mTexts, oEvent.getParameter("value"), aBase));
556
+ },
557
+ confirm: function (oEvent) {
558
+ var aItems = oEvent.getParameter("selectedItems") || [];
559
+ done(aItems.map(function (oItem) {
560
+ return String(oItem.getBindingContext().getProperty(oHelp.element));
561
+ }), aItems.map(function (oItem) {
562
+ return oItem.getBindingContext().getObject();
563
+ }));
564
+ },
565
+ cancel: function () {
566
+ done([]);
567
+ },
568
+ afterClose: function () {
569
+ done([]);
570
+ oDialog.destroy();
571
+ }
572
+ });
573
+ oDialog.bindAggregation("items", support.valueHelpItems(oHelp, aColumns, mTexts, aBase));
574
+ oView.addDependent(oDialog);
575
+ oDialog.open();
576
+ });
577
+ },
578
+
579
+ errorText: function (oError) {
580
+ var aMessages = Messaging.getMessageModel().getData().filter(function (m) {
581
+ return m.getType() === "Error";
582
+ });
583
+ if (aMessages.length > 0) {
584
+ return aMessages.map(function (m) { return m.getMessage(); }).join("\\n");
585
+ }
586
+ return oError && oError.message ? oError.message : "The request failed";
587
+ }
588
+ };
589
+ return support;
590
+ });
591
+ `;
592
+ /**
593
+ * The conditions of the filter fields (D51, D68), written as in SAP Fiori elements: `=x`, `!=x`,
594
+ * `>x`, `>=x`, `<x`, `<=x`, `a...b`, `*x*`, `x*`, `*x`, `<empty>`, and the exclusions `!(…)`; a
595
+ * value alone is "contains" for a free text, "equal" otherwise. The operators of a field depend on
596
+ * its type and on `@Consumption.filter.selectionType`. Pure functions (no control): unit tested in
597
+ * Node.js with a stub of `sap.ui.define`.
598
+ */
599
+ export const FILTERS = `sap.ui.define([
600
+ "sap/ui/model/Filter",
601
+ "sap/ui/model/FilterOperator",
602
+ "sap/ui/core/format/DateFormat"
603
+ ], function (Filter, FilterOperator, DateFormat) {
604
+ "use strict";
605
+
606
+ var NUMERIC = { integer: true, decimal: true, amount: true };
607
+ var DAYS = { date: true, datetime: true };
608
+ var PREFIXES = [["!=", "NE"], [">=", "GE"], ["<=", "LE"], [">", "GT"], ["<", "LT"], ["=", "EQ"]];
609
+ var SIGNS = { EQ: "=", NE: "!=", GT: ">", GE: ">=", LT: "<", LE: "<=" };
610
+ var EMPTY = "<empty>";
611
+ /** The operators of "Define Conditions"; an exclusion names the operator it negates. */
612
+ var OPERATORS = {
613
+ EQ: { text: "equal to", values: 1 },
614
+ Contains: { text: "contains", values: 1 },
615
+ StartsWith: { text: "starts with", values: 1 },
616
+ EndsWith: { text: "ends with", values: 1 },
617
+ BT: { text: "between", values: 2 },
618
+ LT: { text: "less than", values: 1 },
619
+ LE: { text: "less than or equal to", values: 1 },
620
+ GT: { text: "greater than", values: 1 },
621
+ GE: { text: "greater than or equal to", values: 1 },
622
+ Empty: { text: "empty", values: 0 },
623
+ NE: { text: "not equal to", values: 1, exclude: "EQ" },
624
+ NotContains: { text: "does not contain", values: 1, exclude: "Contains" },
625
+ NotStartsWith: { text: "does not start with", values: 1, exclude: "StartsWith" },
626
+ NotEndsWith: { text: "does not end with", values: 1, exclude: "EndsWith" },
627
+ NB: { text: "not between", values: 2, exclude: "BT" },
628
+ NOTLT: { text: "not less than", values: 1, exclude: "LT" },
629
+ NOTLE: { text: "not less than or equal to", values: 1, exclude: "LE" },
630
+ NOTGT: { text: "not greater than", values: 1, exclude: "GT" },
631
+ NOTGE: { text: "not greater than or equal to", values: 1, exclude: "GE" },
632
+ NotEmpty: { text: "not empty", values: 0, exclude: "Empty" }
633
+ };
634
+ var COMPARISONS = ["EQ", "BT", "LT", "LE", "GT", "GE"];
635
+ var PATTERNS = { Contains: true, StartsWith: true, EndsWith: true, NotContains: true, NotStartsWith: true, NotEndsWith: true };
636
+ /** The exclusions of comparisons as the opposite comparison: not less than 5 is 5 or more. */
637
+ var OPPOSITE = { NOTLT: "GE", NOTLE: "GT", NOTGT: "LE", NOTGE: "LT" };
638
+
639
+ function negation(sOp) {
640
+ return Object.keys(OPERATORS).find(function (k) { return OPERATORS[k].exclude === sOp; });
641
+ }
642
+
643
+ function pad(n) {
644
+ return String(n).padStart(2, "0");
645
+ }
646
+
647
+ /** A date typed or picked: ISO (yyyy-MM-dd), or in the format of the locale. */
648
+ function day(sValue) {
649
+ var aIso = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(sValue);
650
+ if (aIso) {
651
+ var oDate = new Date(Date.UTC(Number(aIso[1]), Number(aIso[2]) - 1, Number(aIso[3])));
652
+ return oDate.getUTCMonth() === Number(aIso[2]) - 1 && oDate.getUTCDate() === Number(aIso[3]) ? sValue : null;
653
+ }
654
+ if (!DateFormat) {
655
+ return null;
656
+ }
657
+ var oParsed = ["medium", "short", "long"].map(function (sStyle) {
658
+ return DateFormat.getDateInstance({ style: sStyle }).parse(sValue);
659
+ }).find(function (o) { return o && !isNaN(o.getTime()); });
660
+ return oParsed ? oParsed.getFullYear() + "-" + pad(oParsed.getMonth() + 1) + "-" + pad(oParsed.getDate()) : null;
661
+ }
662
+
663
+ /** The value of a condition as kept in the token key: dates in ISO, times as HH:mm:ss; null when invalid. */
664
+ function normalize(oField, sValue) {
665
+ var s = String(sValue === null || sValue === undefined ? "" : sValue).trim();
666
+ if (DAYS[oField.kind]) {
667
+ return s === "" ? null : day(s);
668
+ }
669
+ if (oField.kind === "time") {
670
+ var aTime = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/.exec(s);
671
+ return aTime && Number(aTime[1]) < 24 && Number(aTime[2]) < 60 ? pad(aTime[1]) + ":" + aTime[2] + ":" + (aTime[3] || "00") : null;
672
+ }
673
+ return valid(oField, s) ? s : null;
674
+ }
675
+
676
+ function valid(oField, sValue) {
677
+ if (sValue === "") {
678
+ return false;
679
+ }
680
+ if (NUMERIC[oField.kind]) {
681
+ return /^[-+]?\\d*[.,]?\\d+$/.test(sValue);
682
+ }
683
+ if (oField.kind === "digits") {
684
+ return /^\\d+$/.test(sValue) && (!oField.maxLength || sValue.length <= oField.maxLength);
685
+ }
686
+ if (oField.kind === "uuid") {
687
+ return /^[0-9a-fA-F-]+$/.test(sValue);
688
+ }
689
+ return true;
690
+ }
691
+
692
+ var filters = {
693
+ /** The operators allowed for a field: its type, then \`@Consumption.filter.selectionType\` (FilterExpressionRestrictions). */
694
+ operators: function (oField) {
695
+ var aInclude;
696
+ if (oField.kind === "boolean" || oField.kind === "enum") {
697
+ return ["EQ"];
698
+ }
699
+ if (oField.kind === "uuid") {
700
+ aInclude = ["EQ"];
701
+ } else if (oField.kind === "text") {
702
+ aInclude = ["EQ", "Contains", "StartsWith", "EndsWith", "BT", "LT", "LE", "GT", "GE", "Empty"];
703
+ } else if (DAYS[oField.kind] || oField.kind === "time") {
704
+ aInclude = COMPARISONS.concat(["Empty"]);
705
+ } else {
706
+ aInclude = COMPARISONS.slice();
707
+ }
708
+ var sAllowed = oField.filter && oField.filter.expressions;
709
+ if (sAllowed === "SingleValue") {
710
+ return ["EQ"];
711
+ }
712
+ if (sAllowed === "SingleRange" || sAllowed === "MultiRange") {
713
+ aInclude = aInclude.filter(function (op) { return COMPARISONS.indexOf(op) >= 0; });
714
+ }
715
+ // one range: no exclusion
716
+ return sAllowed === "SingleRange" ? aInclude : aInclude.concat(aInclude.map(negation).filter(Boolean));
717
+ },
718
+
719
+ /** The label of an operator ("equal to"), and the number of its values (0, 1 or 2). */
720
+ label: function (sOp) {
721
+ return OPERATORS[sOp] ? OPERATORS[sOp].text : sOp;
722
+ },
723
+
724
+ valueCount: function (sOp) {
725
+ return OPERATORS[sOp] ? OPERATORS[sOp].values : 1;
726
+ },
727
+
728
+ /** An exclusion: and'ed with the other conditions of the field (the included ones are or'ed). */
729
+ isExclude: function (sOp) {
730
+ return !!(OPERATORS[sOp] && OPERATORS[sOp].exclude);
731
+ },
732
+
733
+ /** The operator of a value typed alone: "contains" for a free text, "equal to" for a key or a value help. */
734
+ defaultOperator: function (oField) {
735
+ return oField.kind === "text" && !oField.key && !oField.valueHelp && filters.operators(oField).indexOf("Contains") >= 0 ? "Contains" : "EQ";
736
+ },
737
+
738
+ /** The condition of an operator and its values (the rows of "Define Conditions"); null when not valid for the field. */
739
+ condition: function (oField, sOp, aValues) {
740
+ if (!OPERATORS[sOp] || filters.operators(oField).indexOf(sOp) < 0) {
741
+ return null;
742
+ }
743
+ var aNormalized = (aValues || []).slice(0, OPERATORS[sOp].values).map(function (v) { return normalize(oField, v); });
744
+ if (aNormalized.length < OPERATORS[sOp].values || aNormalized.some(function (v) { return v === null; })) {
745
+ return null;
746
+ }
747
+ return { op: sOp, values: aNormalized };
748
+ },
749
+
750
+ /** The condition of a text typed in a filter field (or of a token key); null when it is not valid for the field. */
751
+ parse: function (oField, sText) {
752
+ var oRead = filters._read(oField, String(sText).trim());
753
+ return oRead ? filters.condition(oField, oRead.op, oRead.values) : null;
754
+ },
755
+
756
+ _read: function (oField, sValue) {
757
+ if (sValue === "") {
758
+ return null;
759
+ }
760
+ if (sValue === EMPTY) {
761
+ return { op: "Empty", values: [] };
762
+ }
763
+ if (sValue.indexOf("!(") === 0 && sValue[sValue.length - 1] === ")") {
764
+ var oInner = filters._read(oField, sValue.slice(2, -1).trim());
765
+ var sNot = oInner && negation(oInner.op);
766
+ return sNot ? { op: sNot, values: oInner.values } : null;
767
+ }
768
+ var iRange = sValue.indexOf("...");
769
+ var aDays = DAYS[oField.kind] && iRange < 0 ? sValue.split(" - ") : [];
770
+ if (iRange > 0 && !/^[!<>=]/.test(sValue)) {
771
+ return { op: "BT", values: [sValue.slice(0, iRange).trim(), sValue.slice(iRange + 3).trim()] };
772
+ }
773
+ if (aDays.length === 2) {
774
+ return { op: "BT", values: [aDays[0].trim(), aDays[1].trim()] };
775
+ }
776
+ var aPrefix = PREFIXES.find(function (p) { return sValue.indexOf(p[0]) === 0; });
777
+ if (aPrefix) {
778
+ return { op: aPrefix[1], values: [sValue.slice(aPrefix[0].length).trim()] };
779
+ }
780
+ if (oField.kind === "text" && sValue.length > 1 && sValue[0] === "*" && sValue[sValue.length - 1] === "*") {
781
+ return { op: "Contains", values: [sValue.slice(1, -1)] };
782
+ }
783
+ if (oField.kind === "text" && sValue.length > 1 && sValue[sValue.length - 1] === "*") {
784
+ return { op: "StartsWith", values: [sValue.slice(0, -1)] };
785
+ }
786
+ if (oField.kind === "text" && sValue.length > 1 && sValue[0] === "*") {
787
+ return { op: "EndsWith", values: [sValue.slice(1)] };
788
+ }
789
+ return { op: filters.defaultOperator(oField), values: [sValue] };
790
+ },
791
+
792
+ /** The text of a condition, kept as the key of its token (parsed back by parse). */
793
+ text: function (oCondition) {
794
+ var v = oCondition.values;
795
+ switch (oCondition.op) {
796
+ case "Empty": return EMPTY;
797
+ case "BT": return v[0] + "..." + v[1];
798
+ case "Contains": return "*" + v[0] + "*";
799
+ case "StartsWith": return v[0] + "*";
800
+ case "EndsWith": return "*" + v[0];
801
+ default:
802
+ if (SIGNS[oCondition.op]) {
803
+ return SIGNS[oCondition.op] + v[0];
804
+ }
805
+ return "!(" + filters.text({ op: OPERATORS[oCondition.op].exclude, values: v }) + ")";
806
+ }
807
+ },
808
+
809
+ /**
810
+ * The text shown in the token, as SAP Fiori elements: "equal to" without its sign (with the
811
+ * description of a value help: "Euro (EUR)"), exclusions as \`!(…)\`, dates in the format of the locale.
812
+ */
813
+ display: function (oField, oCondition, sDescription) {
814
+ var aValues = oCondition.values.map(function (v) { return filters.format(oField, v); });
815
+ if (oCondition.op === "EQ") {
816
+ return sDescription ? sDescription + " (" + aValues[0] + ")" : aValues[0];
817
+ }
818
+ if (oCondition.op === "NE") {
819
+ return "!(=" + aValues[0] + ")";
820
+ }
821
+ return filters.text({ op: oCondition.op, values: aValues });
822
+ },
823
+
824
+ /** A value as shown to the user (a date in the medium format of the locale). */
825
+ format: function (oField, sValue) {
826
+ if (DAYS[oField.kind] && DateFormat && /^\\d{4}-\\d{2}-\\d{2}$/.test(sValue)) {
827
+ var a = sValue.split("-");
828
+ return DateFormat.getDateInstance({ style: "medium" }).format(new Date(Number(a[0]), Number(a[1]) - 1, Number(a[2])));
829
+ }
830
+ return sValue;
831
+ },
832
+
833
+ /** The value sent to the service: NUMC with its leading zeros, decimal point. */
834
+ value: function (oField, sValue) {
835
+ if (oField.kind === "digits" && oField.maxLength && /^\\d+$/.test(sValue)) {
836
+ return sValue.padStart(oField.maxLength, "0");
837
+ }
838
+ if (NUMERIC[oField.kind]) {
839
+ return sValue.replace(",", ".").replace(/^\\+/, "");
840
+ }
841
+ if (oField.kind === "uuid") {
842
+ return sValue.toLowerCase();
843
+ }
844
+ return sValue;
845
+ },
846
+
847
+ /** The filter of a condition. */
848
+ filter: function (oField, oCondition) {
849
+ var v = oCondition.values.map(function (s) { return filters.value(oField, s); });
850
+ var sPath = oField.property;
851
+ var sOp = OPPOSITE[oCondition.op] || oCondition.op;
852
+ if (sOp === "Empty" || sOp === "NotEmpty") {
853
+ // a text is empty when initial ('') or null; another type when null
854
+ var bNot = sOp === "NotEmpty";
855
+ var oNull = new Filter(sPath, bNot ? FilterOperator.NE : FilterOperator.EQ, null);
856
+ return oField.edm === "Edm.String" ? new Filter({ filters: [new Filter(sPath, bNot ? FilterOperator.NE : FilterOperator.EQ, ""), oNull], and: bNot }) : oNull;
857
+ }
858
+ if (oField.kind === "datetime") {
859
+ return filters._days(sPath, sOp, v);
860
+ }
861
+ if (sOp === "BT" || sOp === "NB") {
862
+ return new Filter({ path: sPath, operator: FilterOperator[sOp], value1: v[0], value2: v[1] });
863
+ }
864
+ if (PATTERNS[sOp]) {
865
+ return new Filter({ path: sPath, operator: FilterOperator[sOp], value1: v[0], caseSensitive: false });
866
+ }
867
+ return new Filter({ path: sPath, operator: FilterOperator[sOp], value1: v[0], caseSensitive: oField.kind === "text" ? true : undefined });
868
+ },
869
+
870
+ /** A condition on the days of a date-time (Edm.DateTimeOffset): whole days, UTC. */
871
+ _days: function (sPath, sOp, v) {
872
+ var start = function (s) { return s + "T00:00:00Z"; };
873
+ var next = function (s) {
874
+ var oNext = new Date(s + "T00:00:00Z");
875
+ oNext.setUTCDate(oNext.getUTCDate() + 1);
876
+ return oNext.toISOString().slice(0, 10) + "T00:00:00Z";
877
+ };
878
+ var range = function (sFrom, sTo, bNot) {
879
+ return new Filter({
880
+ filters: [
881
+ new Filter(sPath, bNot ? FilterOperator.LT : FilterOperator.GE, start(sFrom)),
882
+ new Filter(sPath, bNot ? FilterOperator.GE : FilterOperator.LT, next(sTo))
883
+ ],
884
+ and: !bNot
885
+ });
886
+ };
887
+ switch (sOp) {
888
+ case "EQ": return range(v[0], v[0], false);
889
+ case "NE": return range(v[0], v[0], true);
890
+ case "BT": return range(v[0], v[1], false);
891
+ case "NB": return range(v[0], v[1], true);
892
+ case "LT": return new Filter(sPath, FilterOperator.LT, start(v[0]));
893
+ case "LE": return new Filter(sPath, FilterOperator.LT, next(v[0]));
894
+ case "GT": return new Filter(sPath, FilterOperator.GE, next(v[0]));
895
+ default: return new Filter(sPath, FilterOperator.GE, start(v[0]));
896
+ }
897
+ },
898
+
899
+ /** The filter of the conditions of one field: included values or'ed, exclusions and'ed. */
900
+ fieldFilter: function (oField, aConditions) {
901
+ var aInclude = [];
902
+ var aExclude = [];
903
+ aConditions.forEach(function (c) {
904
+ (filters.isExclude(c.op) ? aExclude : aInclude).push(filters.filter(oField, c));
905
+ });
906
+ var aParts = [];
907
+ if (aInclude.length === 1) {
908
+ aParts.push(aInclude[0]);
909
+ } else if (aInclude.length > 1) {
910
+ aParts.push(new Filter({ filters: aInclude, and: false }));
911
+ }
912
+ aParts = aParts.concat(aExclude);
913
+ if (aParts.length === 0) {
914
+ return null;
915
+ }
916
+ return aParts.length === 1 ? aParts[0] : new Filter({ filters: aParts, and: true });
917
+ },
918
+
919
+ /**
920
+ * A filter as an OData $filter expression (the chart of the Analytical List Page and the counts
921
+ * of the worklist tabs send their own requests). mFields: property → field of the layout.
922
+ */
923
+ toOData: function (oFilter, mFields) {
924
+ if (!oFilter) {
925
+ return "";
926
+ }
927
+ var aInner = oFilter.aFilters || (oFilter.getFilters && oFilter.getFilters()) || (oFilter.spec && oFilter.spec.filters);
928
+ if (aInner && aInner.length) {
929
+ var bAnd = oFilter.bAnd !== undefined ? oFilter.bAnd : oFilter.spec ? !!oFilter.spec.and : !!(oFilter.isAnd && oFilter.isAnd());
930
+ return "(" + aInner.map(function (f) { return filters.toOData(f, mFields); }).join(bAnd ? " and " : " or ") + ")";
931
+ }
932
+ var sPath = oFilter.sPath || (oFilter.spec && oFilter.spec.path);
933
+ var sOp = oFilter.sOperator || (oFilter.spec && oFilter.spec.operator);
934
+ var v1 = oFilter.oValue1 !== undefined ? oFilter.oValue1 : oFilter.spec && oFilter.spec.value1;
935
+ var v2 = oFilter.oValue2 !== undefined ? oFilter.oValue2 : oFilter.spec && oFilter.spec.value2;
936
+ var bCaseInsensitive = oFilter.bCaseSensitive === false || (oFilter.spec && oFilter.spec.caseSensitive === false);
937
+ var oField = (mFields && mFields[sPath]) || { edm: "Edm.String" };
938
+ var literal = function (v) {
939
+ if (v === null || v === undefined) { return "null"; }
940
+ if (oField.edm === "Edm.String") { return "'" + String(v).replace(/'/g, "''") + "'"; }
941
+ return String(v);
942
+ };
943
+ var sLeft = bCaseInsensitive ? "tolower(" + sPath + ")" : sPath;
944
+ var lit = function (v) { return bCaseInsensitive ? "tolower(" + literal(v) + ")" : literal(v); };
945
+ switch (sOp) {
946
+ case "BT": return "(" + sPath + " ge " + literal(v1) + " and " + sPath + " le " + literal(v2) + ")";
947
+ case "NB": return "(" + sPath + " lt " + literal(v1) + " or " + sPath + " gt " + literal(v2) + ")";
948
+ case "Contains": return "contains(" + sLeft + "," + lit(v1) + ")";
949
+ case "StartsWith": return "startswith(" + sLeft + "," + lit(v1) + ")";
950
+ case "EndsWith": return "endswith(" + sLeft + "," + lit(v1) + ")";
951
+ case "NotContains": return "not contains(" + sLeft + "," + lit(v1) + ")";
952
+ case "NotStartsWith": return "not startswith(" + sLeft + "," + lit(v1) + ")";
953
+ case "NotEndsWith": return "not endswith(" + sLeft + "," + lit(v1) + ")";
954
+ default: return sPath + " " + String(sOp).toLowerCase() + " " + literal(v1);
955
+ }
956
+ }
957
+ };
958
+ return filters;
959
+ });
960
+ `;
961
+ /**
962
+ * The value help of a filter field (D68), as in SAP Fiori elements, built from sap.m (the value
963
+ * help dialog of sap.ui.comp / sap.ui.mdc is SAPUI5 only): "Search and Select" lists the values of
964
+ * the value help, "Define Conditions" has one operator and its values per row, and the selected
965
+ * items and conditions are tokens below.
966
+ */
967
+ export const CONDITIONS = `sap.ui.define([
968
+ "sap/m/Dialog",
969
+ "sap/m/Button",
970
+ "sap/m/IconTabBar",
971
+ "sap/m/IconTabFilter",
972
+ "sap/m/VBox",
973
+ "sap/m/HBox",
974
+ "sap/m/FlexItemData",
975
+ "sap/m/Select",
976
+ "sap/ui/core/Item",
977
+ "sap/m/Input",
978
+ "sap/m/DatePicker",
979
+ "sap/m/TimePicker",
980
+ "sap/m/SearchField",
981
+ "sap/m/Table",
982
+ "sap/m/Column",
983
+ "sap/m/Text",
984
+ "sap/m/Title",
985
+ "sap/m/Tokenizer",
986
+ "sap/m/Token",
987
+ "sap/m/ScrollContainer",
988
+ "sap/ui/model/json/JSONModel",
989
+ "rapt/app/controller/support",
990
+ "rapt/app/controller/filters"
991
+ ], function (Dialog, Button, IconTabBar, IconTabFilter, VBox, HBox, FlexItemData, Select, Item, Input, DatePicker, TimePicker, SearchField, Table, Column, Text, Title, Tokenizer, Token, ScrollContainer, JSONModel, support, filters) {
992
+ "use strict";
993
+
994
+ function blank(sOp) {
995
+ return { op: sOp, v1: "", v2: "", error: false };
996
+ }
997
+
998
+ function dialog(oView, oField, aInitial, mTexts) {
999
+ var oHelp = oField.valueHelp;
1000
+ var aOperators = filters.operators(oField);
1001
+ var bSingle = !!(oField.filter && oField.filter.single);
1002
+ var bSelect = !!oHelp;
1003
+ var bDefine = !(bSelect && aOperators.length === 1);
1004
+ var sDefault = filters.defaultOperator(oField);
1005
+ var oModel = new JSONModel({ items: [], rows: [] });
1006
+ var aItems = [];
1007
+ var aRows = [];
1008
+ aInitial.forEach(function (c) {
1009
+ if (bSelect && c.condition.op === "EQ") {
1010
+ aItems.push({ value: c.condition.values[0], text: c.text });
1011
+ } else if (bDefine) {
1012
+ aRows.push({ op: c.condition.op, v1: c.condition.values[0] || "", v2: c.condition.values[1] || "", error: false });
1013
+ }
1014
+ });
1015
+ if (bDefine && aRows.length === 0) {
1016
+ aRows.push(blank(sDefault));
1017
+ }
1018
+ oModel.setData({ items: aItems, rows: aRows });
1019
+
1020
+ var isBlank = function (r) {
1021
+ return filters.valueCount(r.op) > 0 && String(r.v1 || "").trim() === "" && String(r.v2 || "").trim() === "";
1022
+ };
1023
+ var rowCondition = function (r) {
1024
+ return isBlank(r) ? null : filters.condition(oField, r.op, [r.v1, r.v2]);
1025
+ };
1026
+ var itemText = function (sValue) {
1027
+ return filters.display(oField, { op: "EQ", values: [sValue] }, mTexts[sValue]);
1028
+ };
1029
+
1030
+ return new Promise(function (resolve) {
1031
+ var bDone = false;
1032
+ var done = function (aResult) {
1033
+ if (!bDone) {
1034
+ bDone = true;
1035
+ resolve(aResult);
1036
+ }
1037
+ };
1038
+ var oTable;
1039
+ var oTokenizer;
1040
+ var oTitle;
1041
+
1042
+ /** Selects the rows of the table that are chosen items. */
1043
+ var syncSelection = function () {
1044
+ if (!oTable) {
1045
+ return;
1046
+ }
1047
+ var aValues = oModel.getProperty("/items").map(function (it) { return filters.value(oField, String(it.value)); });
1048
+ oTable.getItems().forEach(function (oItem) {
1049
+ var oContext = oItem.getBindingContext();
1050
+ if (oContext) {
1051
+ oItem.setSelected(aValues.indexOf(filters.value(oField, String(oContext.getProperty(oHelp.element)))) >= 0);
1052
+ }
1053
+ });
1054
+ };
1055
+
1056
+ /** The tokens of the chosen items and of the valid conditions. */
1057
+ var refresh = function () {
1058
+ var aCurrentRows = oModel.getProperty("/rows");
1059
+ if (bSingle && aCurrentRows.some(rowCondition) && oModel.getProperty("/items").length > 0) {
1060
+ oModel.setProperty("/items", []);
1061
+ syncSelection();
1062
+ }
1063
+ oTokenizer.destroyTokens();
1064
+ oModel.getProperty("/items").forEach(function (it, i) {
1065
+ oTokenizer.addToken(new Token({ key: "i" + i, text: it.text }));
1066
+ });
1067
+ aCurrentRows.forEach(function (r, i) {
1068
+ var oCondition = rowCondition(r);
1069
+ if (oCondition || isBlank(r)) {
1070
+ oModel.setProperty("/rows/" + i + "/error", false);
1071
+ }
1072
+ if (oCondition) {
1073
+ oTokenizer.addToken(new Token({ key: "r" + i, text: filters.display(oField, oCondition) }));
1074
+ }
1075
+ });
1076
+ oTitle.setText("Selected Items and Conditions (" + oTokenizer.getTokens().length + ")");
1077
+ };
1078
+
1079
+ var removeRow = function (i) {
1080
+ var aList = oModel.getProperty("/rows").slice();
1081
+ aList.splice(i, 1);
1082
+ if (aList.length === 0) {
1083
+ aList.push(blank(sDefault));
1084
+ }
1085
+ oModel.setProperty("/rows", aList);
1086
+ };
1087
+
1088
+ var valueControl = function (sProperty, bSecond, oContext) {
1089
+ var oControl;
1090
+ if (oField.kind === "date" || oField.kind === "datetime") {
1091
+ oControl = new DatePicker({ valueFormat: "yyyy-MM-dd", value: "{cond>" + sProperty + "}" });
1092
+ } else if (oField.kind === "time") {
1093
+ oControl = new TimePicker({ valueFormat: "HH:mm:ss", value: "{cond>" + sProperty + "}" });
1094
+ } else {
1095
+ oControl = new Input({
1096
+ value: "{cond>" + sProperty + "}",
1097
+ showValueHelp: bSelect,
1098
+ valueHelpRequest: function () {
1099
+ support.valueHelp(oView, oHelp, false).then(function (aValues) {
1100
+ if (aValues.length > 0) {
1101
+ oModel.setProperty(oContext.getPath() + "/" + sProperty, aValues[0]);
1102
+ refresh();
1103
+ }
1104
+ });
1105
+ }
1106
+ });
1107
+ }
1108
+ oControl.bindProperty("visible", { path: "cond>op", formatter: function (sOp) { return filters.valueCount(sOp) > (bSecond ? 1 : 0); } });
1109
+ oControl.bindProperty("valueState", { path: "cond>error", formatter: function (bError) { return bError ? "Error" : "None"; } });
1110
+ oControl.setValueStateText("Enter a valid value");
1111
+ oControl.setLayoutData(new FlexItemData({ growFactor: 1, baseSize: "0" }));
1112
+ return oControl.addStyleClass("raptConditionValue");
1113
+ };
1114
+
1115
+ var oRows = new VBox(oView.createId("conditionsRows"), {
1116
+ items: {
1117
+ path: "cond>/rows",
1118
+ factory: function (sId, oContext) {
1119
+ return new HBox(sId, {
1120
+ alignItems: "Center",
1121
+ items: [
1122
+ new Select({
1123
+ width: "15rem",
1124
+ selectedKey: "{cond>op}",
1125
+ items: aOperators.map(function (op) { return new Item({ key: op, text: filters.label(op) }); }),
1126
+ change: refresh
1127
+ }).addStyleClass("raptConditionOperator"),
1128
+ new HBox({
1129
+ layoutData: new FlexItemData({ growFactor: 1, baseSize: "0" }),
1130
+ items: [valueControl("v1", false, oContext), valueControl("v2", true, oContext)]
1131
+ }),
1132
+ new Button({
1133
+ icon: "sap-icon://decline",
1134
+ type: "Transparent",
1135
+ tooltip: "Remove Condition",
1136
+ press: function () {
1137
+ removeRow(Number(oContext.getPath().split("/").pop()));
1138
+ refresh();
1139
+ }
1140
+ })
1141
+ ]
1142
+ }).addStyleClass("raptConditionRow");
1143
+ }
1144
+ }
1145
+ });
1146
+ var oAdd = new Button(oView.createId("conditionsAdd"), {
1147
+ text: "Add",
1148
+ icon: "sap-icon://add",
1149
+ visible: !bSingle,
1150
+ press: function () {
1151
+ oModel.setProperty("/rows", oModel.getProperty("/rows").concat([blank(sDefault)]));
1152
+ }
1153
+ });
1154
+ // with a value help, the tabs scroll and the tokens stay visible below them
1155
+ var scroll = function (oContent) {
1156
+ return bSelect ? new ScrollContainer({ height: "22rem", vertical: true, horizontal: false, content: [oContent] }) : oContent;
1157
+ };
1158
+ var oDefine = scroll(new VBox({ items: [oRows, new HBox({ justifyContent: "End", items: [oAdd] })] }).addStyleClass("sapUiTinyMarginTop"));
1159
+ var oSearch;
1160
+
1161
+ var oSelect;
1162
+ if (bSelect) {
1163
+ var aColumns = support.valueHelpColumns(oHelp, mTexts);
1164
+ oTable = new Table(oView.createId("conditionsTable"), {
1165
+ mode: bSingle ? "SingleSelectLeft" : "MultiSelect",
1166
+ includeItemInSelection: true,
1167
+ growing: true,
1168
+ growingThreshold: 20,
1169
+ columns: aColumns.map(function (c) { return new Column({ header: new Text({ text: c.label }) }); }),
1170
+ updateFinished: syncSelection,
1171
+ selectionChange: function (oEvent) {
1172
+ var bSelected = oEvent.getParameter("selected");
1173
+ var aList = bSingle && bSelected ? [] : oModel.getProperty("/items").slice();
1174
+ (oEvent.getParameter("listItems") || []).forEach(function (oItem) {
1175
+ var sValue = String(oItem.getBindingContext().getProperty(oHelp.element));
1176
+ var iIndex = aList.findIndex(function (it) { return filters.value(oField, String(it.value)) === filters.value(oField, sValue); });
1177
+ if (bSelected && iIndex < 0) {
1178
+ aList.push({ value: sValue, text: itemText(sValue) });
1179
+ } else if (!bSelected && iIndex >= 0) {
1180
+ aList.splice(iIndex, 1);
1181
+ }
1182
+ });
1183
+ oModel.setProperty("/items", aList);
1184
+ if (bSingle && bSelected && bDefine) {
1185
+ oModel.setProperty("/rows", [blank(sDefault)]);
1186
+ }
1187
+ refresh();
1188
+ }
1189
+ });
1190
+ oTable.bindAggregation("items", support.valueHelpItems(oHelp, aColumns, mTexts, []));
1191
+ oSearch = new SearchField(oView.createId("conditionsSearch"), {
1192
+ width: "100%",
1193
+ search: function (oEvent) {
1194
+ oTable.getBinding("items").filter(support.valueHelpSearch(oHelp, mTexts, oEvent.getParameter("query"), []));
1195
+ }
1196
+ });
1197
+ oSelect = new VBox({ items: [oSearch.addStyleClass("sapUiTinyMarginTop"), scroll(oTable)] });
1198
+ }
1199
+
1200
+ var oMain = oSelect && bDefine
1201
+ ? new IconTabBar(oView.createId("conditionsTabs"), {
1202
+ expandable: false,
1203
+ selectedKey: aItems.length === 0 && aInitial.length > 0 ? "define" : "select",
1204
+ items: [
1205
+ new IconTabFilter({ key: "select", text: "Search and Select", content: [oSelect] }),
1206
+ new IconTabFilter({ key: "define", text: "Define Conditions", content: [oDefine] })
1207
+ ]
1208
+ }).addStyleClass("sapUiNoContentPadding")
1209
+ : oSelect || oDefine;
1210
+ oTitle = new Title({ text: "", level: "H6" }).addStyleClass("sapUiSmallMarginTop sapUiTinyMarginBottom");
1211
+ oTokenizer = new Tokenizer(oView.createId("conditionsTokens"), {
1212
+ width: "100%",
1213
+ renderMode: "Loose",
1214
+ tokenDelete: function (oEvent) {
1215
+ var aKeys = (oEvent.getParameter("tokens") || []).map(function (t) { return t.getKey(); });
1216
+ var aRowIndexes = [];
1217
+ oModel.setProperty("/items", oModel.getProperty("/items").filter(function (_it, i) { return aKeys.indexOf("i" + i) < 0; }));
1218
+ aKeys.forEach(function (k) {
1219
+ if (k[0] === "r") {
1220
+ aRowIndexes.push(Number(k.slice(1)));
1221
+ }
1222
+ });
1223
+ aRowIndexes.sort(function (a, b) { return b - a; }).forEach(removeRow);
1224
+ syncSelection();
1225
+ refresh();
1226
+ }
1227
+ });
1228
+
1229
+ var oDialog = new Dialog(oView.createId("conditions"), {
1230
+ title: oField.label,
1231
+ contentWidth: bSelect ? "60rem" : "46rem",
1232
+ initialFocus: oSearch,
1233
+ resizable: true,
1234
+ draggable: true,
1235
+ content: [new VBox({ items: [oMain, oTitle, oTokenizer] }).addStyleClass("sapUiSmallMargin")],
1236
+ beginButton: new Button(oView.createId("conditionsOk"), {
1237
+ text: "OK",
1238
+ type: "Emphasized",
1239
+ press: function () {
1240
+ var bError = false;
1241
+ var aConditions = [];
1242
+ oModel.getProperty("/rows").forEach(function (r, i) {
1243
+ var oCondition = rowCondition(r);
1244
+ var bWrong = !oCondition && !isBlank(r);
1245
+ oModel.setProperty("/rows/" + i + "/error", bWrong);
1246
+ bError = bError || bWrong;
1247
+ if (oCondition) {
1248
+ aConditions.push({ condition: oCondition, text: filters.display(oField, oCondition) });
1249
+ }
1250
+ });
1251
+ if (bError) {
1252
+ if (oMain.setSelectedKey) {
1253
+ oMain.setSelectedKey("define");
1254
+ }
1255
+ return;
1256
+ }
1257
+ var aResult = oModel.getProperty("/items").map(function (it) {
1258
+ return { condition: { op: "EQ", values: [it.value] }, text: it.text };
1259
+ }).concat(aConditions).filter(function (c, i, a) {
1260
+ return a.findIndex(function (o) { return filters.text(o.condition) === filters.text(c.condition); }) === i;
1261
+ });
1262
+ done(bSingle ? aResult.slice(-1) : aResult);
1263
+ oDialog.close();
1264
+ }
1265
+ }),
1266
+ endButton: new Button(oView.createId("conditionsCancel"), {
1267
+ text: "Cancel",
1268
+ press: function () {
1269
+ oDialog.close();
1270
+ }
1271
+ }),
1272
+ afterClose: function () {
1273
+ done(null);
1274
+ oDialog.destroy();
1275
+ }
1276
+ });
1277
+ oDialog.setModel(oModel, "cond");
1278
+ oModel.attachPropertyChange(refresh);
1279
+ oView.addDependent(oDialog);
1280
+ refresh();
1281
+ oDialog.open();
1282
+ });
1283
+ }
1284
+
1285
+ return {
1286
+ /**
1287
+ * Opens the value help of a filter field. aConditions: the current conditions, [{ condition,
1288
+ * text }]; resolves the new ones, or null when cancelled.
1289
+ */
1290
+ open: function (oView, oField, aConditions) {
1291
+ var oTexts = oField.valueHelp ? support.valueHelpTexts(oView, oField.valueHelp) : Promise.resolve({});
1292
+ return oTexts.then(function (mTexts) {
1293
+ return dialog(oView, oField, aConditions, mTexts);
1294
+ });
1295
+ }
1296
+ };
1297
+ });
1298
+ `;
1299
+ /**
1300
+ * A chart of the Analytical List Page and of the cards (D53): an SVG drawn by the control itself —
1301
+ * columns, bars, line, pie, donut — from the groups of a `$apply` (OpenUI5 has no chart library:
1302
+ * sap.viz exists in SAPUI5 only). Clicking a column selects its dimension value (event select).
1303
+ */
1304
+ export const CHART = `sap.ui.define(["sap/ui/core/Control"], function (Control) {
1305
+ "use strict";
1306
+
1307
+ var COLORS = ["var(--sapChart_OrderedColor_1, #168eff)", "var(--sapChart_OrderedColor_2, #c87b00)", "var(--sapChart_OrderedColor_3, #75980b)", "var(--sapChart_OrderedColor_4, #df1278)", "var(--sapChart_OrderedColor_5, #8b47d7)", "var(--sapChart_OrderedColor_6, #049f9a)"];
1308
+
1309
+ function esc(s) {
1310
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1311
+ }
1312
+
1313
+ function fmt(n) {
1314
+ var a = Math.abs(n);
1315
+ if (a >= 1e9) { return (n / 1e9).toFixed(1) + "B"; }
1316
+ if (a >= 1e6) { return (n / 1e6).toFixed(1) + "M"; }
1317
+ if (a >= 1e4) { return (n / 1e3).toFixed(1) + "K"; }
1318
+ return Math.round(n * 100) / 100 + "";
1319
+ }
1320
+
1321
+ return Control.extend("rapt.app.controller.Chart", {
1322
+ metadata: {
1323
+ properties: {
1324
+ type: { type: "string", defaultValue: "column" },
1325
+ height: { type: "sap.ui.core.CSSSize", defaultValue: "16rem" },
1326
+ selected: { type: "string", defaultValue: "" },
1327
+ noDataText: { type: "string", defaultValue: "No data" }
1328
+ },
1329
+ events: { select: { parameters: { value: { type: "string" } } } }
1330
+ },
1331
+
1332
+ /** aRows: [{ key, label, values: [number…] }]; aMeasures: [label…]. */
1333
+ setData: function (aRows, aMeasures) {
1334
+ this._aRows = aRows || [];
1335
+ this._aMeasures = aMeasures || [];
1336
+ this.invalidate();
1337
+ return this;
1338
+ },
1339
+
1340
+ getData: function () {
1341
+ return this._aRows || [];
1342
+ },
1343
+
1344
+ renderer: {
1345
+ apiVersion: 2,
1346
+ render: function (rm, oChart) {
1347
+ rm.openStart("div", oChart);
1348
+ rm.class("raptChart");
1349
+ rm.style("height", oChart.getHeight());
1350
+ rm.openEnd();
1351
+ rm.unsafeHtml(oChart._svg());
1352
+ rm.close("div");
1353
+ }
1354
+ },
1355
+
1356
+ _svg: function () {
1357
+ var aRows = this._aRows || [];
1358
+ var aMeasures = this._aMeasures || [];
1359
+ var sType = this.getType();
1360
+ var sSelected = this.getSelected();
1361
+ if (aRows.length === 0) {
1362
+ return "<div class='raptChartEmpty'>" + esc(this.getNoDataText()) + "</div>";
1363
+ }
1364
+ var W = 640, H = 260, L = 56, B = 48, T = 12, R = 12;
1365
+ var aParts = ["<svg viewBox='0 0 " + W + " " + H + "' width='100%' height='100%' preserveAspectRatio='xMidYMid meet' role='img'>"];
1366
+ var legend = aMeasures.length > 1 ? aMeasures.map(function (m, i) {
1367
+ return "<rect x='" + (L + i * 140) + "' y='" + (H - 14) + "' width='10' height='10' fill='" + COLORS[i % COLORS.length] + "'/><text x='" + (L + i * 140 + 14) + "' y='" + (H - 5) + "' class='raptChartLabel'>" + esc(m) + "</text>";
1368
+ }).join("") : "";
1369
+ if (sType === "pie" || sType === "donut") {
1370
+ var total = aRows.reduce(function (t, r) { return t + Math.max(0, r.values[0] || 0); }, 0) || 1;
1371
+ var cx = 150, cy = H / 2, r = 100, a0 = -Math.PI / 2;
1372
+ aRows.forEach(function (row, i) {
1373
+ var v = Math.max(0, row.values[0] || 0);
1374
+ var a1 = a0 + (v / total) * Math.PI * 2;
1375
+ var large = a1 - a0 > Math.PI ? 1 : 0;
1376
+ var x0 = cx + r * Math.cos(a0), y0 = cy + r * Math.sin(a0), x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
1377
+ var path = aRows.length === 1 ? "M" + (cx - r) + "," + cy + "a" + r + "," + r + " 0 1,0 " + (2 * r) + ",0a" + r + "," + r + " 0 1,0 " + (-2 * r) + ",0" : "M" + cx + "," + cy + "L" + x0 + "," + y0 + "A" + r + "," + r + " 0 " + large + ",1 " + x1 + "," + y1 + "Z";
1378
+ aParts.push("<path d='" + path + "' fill='" + COLORS[i % COLORS.length] + "' data-value='" + esc(row.key) + "' class='raptChartItem" + (sSelected === row.key ? " raptChartSelected" : "") + "'><title>" + esc(row.label) + ": " + esc(fmt(v)) + "</title></path>");
1379
+ aParts.push("<rect x='300' y='" + (20 + i * 22) + "' width='12' height='12' fill='" + COLORS[i % COLORS.length] + "'/><text x='318' y='" + (31 + i * 22) + "' class='raptChartLabel'>" + esc(row.label) + " (" + esc(fmt(v)) + ")</text>");
1380
+ a0 = a1;
1381
+ });
1382
+ if (sType === "donut") {
1383
+ aParts.push("<circle cx='" + cx + "' cy='" + cy + "' r='55' class='raptChartHole'/>");
1384
+ }
1385
+ aParts.push("</svg>");
1386
+ return aParts.join("");
1387
+ }
1388
+ var max = 0, min = 0;
1389
+ aRows.forEach(function (row) {
1390
+ row.values.forEach(function (v) { max = Math.max(max, v || 0); min = Math.min(min, v || 0); });
1391
+ });
1392
+ if (max === min) { max = min + 1; }
1393
+ var plotW = W - L - R, plotH = H - T - B;
1394
+ var horizontal = sType === "bar";
1395
+ var scale = function (v) { return (v - min) / (max - min); };
1396
+ // axis and grid
1397
+ for (var g = 0; g <= 4; g++) {
1398
+ var gv = min + (max - min) * g / 4;
1399
+ if (horizontal) {
1400
+ var gx = L + plotW * g / 4;
1401
+ aParts.push("<line x1='" + gx + "' y1='" + T + "' x2='" + gx + "' y2='" + (T + plotH) + "' class='raptChartGrid'/><text x='" + gx + "' y='" + (T + plotH + 14) + "' text-anchor='middle' class='raptChartLabel'>" + esc(fmt(gv)) + "</text>");
1402
+ } else {
1403
+ var gy = T + plotH - plotH * g / 4;
1404
+ aParts.push("<line x1='" + L + "' y1='" + gy + "' x2='" + (L + plotW) + "' y2='" + gy + "' class='raptChartGrid'/><text x='" + (L - 6) + "' y='" + (gy + 4) + "' text-anchor='end' class='raptChartLabel'>" + esc(fmt(gv)) + "</text>");
1405
+ }
1406
+ }
1407
+ var n = aRows.length, m = Math.max(1, aMeasures.length);
1408
+ var band = (horizontal ? plotH : plotW) / n;
1409
+ if (sType === "line") {
1410
+ for (var k = 0; k < m; k++) {
1411
+ var pts = aRows.map(function (row, i) { return (L + band * (i + 0.5)) + "," + (T + plotH - plotH * scale(row.values[k] || 0)); });
1412
+ aParts.push("<polyline points='" + pts.join(" ") + "' fill='none' stroke='" + COLORS[k % COLORS.length] + "' stroke-width='2'/>");
1413
+ aRows.forEach(function (row, i) {
1414
+ aParts.push("<circle cx='" + (L + band * (i + 0.5)) + "' cy='" + (T + plotH - plotH * scale(row.values[k] || 0)) + "' r='5' fill='" + COLORS[k % COLORS.length] + "' data-value='" + esc(row.key) + "' class='raptChartItem" + (sSelected === row.key ? " raptChartSelected" : "") + "'><title>" + esc(row.label) + ": " + esc(fmt(row.values[k] || 0)) + "</title></circle>");
1415
+ });
1416
+ }
1417
+ } else {
1418
+ var bar = band * 0.7 / m;
1419
+ aRows.forEach(function (row, i) {
1420
+ for (var k = 0; k < m; k++) {
1421
+ var v = row.values[k] || 0;
1422
+ var size = (horizontal ? plotW : plotH) * Math.abs(scale(v) - scale(0));
1423
+ var start = band * i + band * 0.15 + bar * k;
1424
+ var cls = "raptChartItem" + (sSelected === row.key ? " raptChartSelected" : "");
1425
+ var tip = "<title>" + esc(row.label) + (m > 1 ? " – " + esc(aMeasures[k]) : "") + ": " + esc(fmt(v)) + "</title>";
1426
+ if (horizontal) {
1427
+ aParts.push("<rect x='" + (L + plotW * Math.min(scale(v), scale(0))) + "' y='" + (T + start) + "' width='" + size + "' height='" + bar + "' fill='" + COLORS[k % COLORS.length] + "' data-value='" + esc(row.key) + "' class='" + cls + "'>" + tip + "</rect>");
1428
+ } else {
1429
+ aParts.push("<rect x='" + (L + start) + "' y='" + (T + plotH - plotH * Math.max(scale(v), scale(0))) + "' width='" + bar + "' height='" + size + "' fill='" + COLORS[k % COLORS.length] + "' data-value='" + esc(row.key) + "' class='" + cls + "'>" + tip + "</rect>");
1430
+ }
1431
+ }
1432
+ });
1433
+ }
1434
+ // category labels
1435
+ aRows.forEach(function (row, i) {
1436
+ var label = String(row.label).length > 14 ? String(row.label).slice(0, 13) + "…" : String(row.label);
1437
+ if (horizontal) {
1438
+ aParts.push("<text x='" + (L - 6) + "' y='" + (T + band * (i + 0.5) + 4) + "' text-anchor='end' class='raptChartLabel'>" + esc(label) + "</text>");
1439
+ } else {
1440
+ aParts.push("<text x='" + (L + band * (i + 0.5)) + "' y='" + (T + plotH + 16) + "' text-anchor='middle' class='raptChartLabel'>" + esc(label) + "</text>");
1441
+ }
1442
+ });
1443
+ aParts.push(legend);
1444
+ aParts.push("</svg>");
1445
+ return aParts.join("");
1446
+ },
1447
+
1448
+ onclick: function (oEvent) {
1449
+ var oTarget = oEvent.target && oEvent.target.closest ? oEvent.target.closest("[data-value]") : null;
1450
+ if (!oTarget) {
1451
+ return;
1452
+ }
1453
+ var sValue = oTarget.getAttribute("data-value");
1454
+ this.setSelected(this.getSelected() === sValue ? "" : sValue);
1455
+ this.fireSelect({ value: this.getSelected() });
1456
+ }
1457
+ });
1458
+ });
1459
+ `;
1460
+ /** The Overview Page (D53): counts of the list cards, data of the chart and KPI cards, navigation. */
1461
+ export const OVERVIEW_CONTROLLER = `sap.ui.define([
1462
+ "sap/ui/core/mvc/Controller",
1463
+ "sap/ui/model/json/JSONModel",
1464
+ "sap/ui/core/format/NumberFormat",
1465
+ "rapt/app/controller/support"
1466
+ ], function (Controller, JSONModel, NumberFormat, support) {
1467
+ "use strict";
1468
+
1469
+ var oNumber = NumberFormat.getFloatInstance({ style: "short", maxFractionDigits: 1 });
1470
+
1471
+ return Controller.extend("rapt.app.controller.Overview", {
1472
+ onInit: function () {
1473
+ this._oOvp = new JSONModel({ counts: {}, kpi: {} });
1474
+ this.getView().setModel(this._oOvp, "ovp");
1475
+ this._bFirst = true;
1476
+ this.getOwnerComponent().getRouter().getRoute("Overview").attachPatternMatched(this._onMatched, this);
1477
+ },
1478
+
1479
+ _cards: function () {
1480
+ return this.getOwnerComponent().getModel("layout").getProperty("/overview/cards") || [];
1481
+ },
1482
+
1483
+ _service: function () {
1484
+ return this.getOwnerComponent().getManifestEntry("/sap.app/dataSources/mainService/uri");
1485
+ },
1486
+
1487
+ _onMatched: function () {
1488
+ var that = this;
1489
+ var bStale = this.getOwnerComponent().takeStale("Overview");
1490
+ this._cards().forEach(function (oCard) {
1491
+ if (oCard.type === "chart") {
1492
+ that._loadChart(oCard);
1493
+ } else if (oCard.type === "kpi") {
1494
+ that._loadKpi(oCard);
1495
+ } else if (bStale && !that._bFirst) {
1496
+ var oControl = that.byId(oCard.id + (oCard.type === "list" ? "_list" : "_table"));
1497
+ if (oControl && oControl.getBinding("items")) {
1498
+ oControl.getBinding("items").refresh();
1499
+ }
1500
+ }
1501
+ });
1502
+ this._bFirst = false;
1503
+ },
1504
+
1505
+ _get: function (oCard, sQuery) {
1506
+ return fetch(this._service() + oCard.set + "?" + sQuery, { credentials: "same-origin", headers: { accept: "application/json" } })
1507
+ .then(function (r) { return r.ok ? r.json() : { value: [] }; });
1508
+ },
1509
+
1510
+ _loadChart: function (oCard) {
1511
+ var oSpec = oCard.chart;
1512
+ var oChart = this.byId(oCard.id + "_chart");
1513
+ var sApply = (oCard.filter ? "filter(" + oCard.filter + ")/" : "") + "groupby((" + oSpec.dimension.property + "),aggregate(" + oSpec.measures.map(function (m) {
1514
+ return m.property + " with " + m.aggregation + " as " + m.property;
1515
+ }).join(",") + "))";
1516
+ this._get(oCard, "$apply=" + encodeURIComponent(sApply) + "&$orderby=" + encodeURIComponent(oSpec.dimension.property) + "&$top=20").then(function (oData) {
1517
+ oChart.setData((oData.value || []).map(function (row) {
1518
+ var v = row[oSpec.dimension.property];
1519
+ return { key: v === null ? "" : String(v), label: v === null || v === "" ? "(empty)" : String(v), values: oSpec.measures.map(function (m) { return Number(row[m.property]) || 0; }) };
1520
+ }), oSpec.measures.map(function (m) { return m.label; }));
1521
+ });
1522
+ },
1523
+
1524
+ /** A KPI: the aggregate of a measure (per currency or unit when it has one), or the count. */
1525
+ _loadKpi: function (oCard) {
1526
+ var oKpi = oCard.kpi;
1527
+ var that = this;
1528
+ var sAggregate = oKpi.aggregation === "count" || !oKpi.property ? "$count as Value" : oKpi.property + " with " + oKpi.aggregation + " as Value";
1529
+ var sApply = (oCard.filter ? "filter(" + oCard.filter + ")/" : "") + (oKpi.unit ? "groupby((" + oKpi.unit + "),aggregate(" + sAggregate + "))" : "aggregate(" + sAggregate + ")");
1530
+ this._get(oCard, "$apply=" + encodeURIComponent(sApply) + (oKpi.unit ? "&$orderby=Value%20desc" : "")).then(function (oData) {
1531
+ var aRows = oData.value || [];
1532
+ var oFirst = aRows[0] || { Value: 0 };
1533
+ that._oOvp.setProperty("/kpi/" + oCard.id, {
1534
+ number: oNumber.format(Number(oFirst.Value) || 0),
1535
+ unit: oKpi.unit ? (oFirst[oKpi.unit] || "") : "",
1536
+ details: oKpi.label + (oKpi.aggregation === "count" ? "" : " (" + oKpi.aggregation + ")"),
1537
+ text: aRows.length > 1 ? aRows.slice(1, 4).map(function (r) { return oNumber.format(Number(r.Value) || 0) + " " + (r[oKpi.unit] || ""); }).join(" · ") : ""
1538
+ });
1539
+ });
1540
+ },
1541
+
1542
+ _card: function (oControl) {
1543
+ var oParent = oControl;
1544
+ while (oParent && !(oParent.data && oParent.data("card"))) {
1545
+ oParent = oParent.getParent();
1546
+ }
1547
+ var sId = oParent ? oParent.data("card") : "";
1548
+ return this._cards().find(function (c) { return c.id === sId; });
1549
+ },
1550
+
1551
+ onCardUpdated: function (oEvent) {
1552
+ var oCard = this._card(oEvent.getSource());
1553
+ var oBinding = oEvent.getSource().getBinding("items");
1554
+ if (oCard && oBinding && oBinding.isLengthFinal()) {
1555
+ this._oOvp.setProperty("/counts/" + oCard.id, oEvent.getParameter("total") + "");
1556
+ }
1557
+ },
1558
+
1559
+ onCardHeader: function (oEvent) {
1560
+ var oCard = this._card(oEvent.getSource());
1561
+ if (oCard && oCard.navigable) {
1562
+ this.getOwnerComponent().getRouter().navTo(oCard.set + "List");
1563
+ }
1564
+ },
1565
+
1566
+ onCardChartSelect: function (oEvent) {
1567
+ this.onCardHeader(oEvent);
1568
+ },
1569
+
1570
+ onCardItem: function (oEvent) {
1571
+ var oCard = this._card(oEvent.getSource());
1572
+ var oContext = oEvent.getSource().getBindingContext();
1573
+ if (oCard && oContext) {
1574
+ this.getOwnerComponent().getRouter().navTo(oCard.set + "Object", { key: support.keyOf(oContext.getPath()) });
1575
+ }
1576
+ }
1577
+ });
1578
+ });
1579
+ `;
1580
+ export const LIST_CONTROLLER = `sap.ui.define([
1581
+ "sap/ui/core/mvc/Controller",
1582
+ "sap/ui/model/Filter",
1583
+ "sap/ui/model/Sorter",
1584
+ "sap/ui/model/json/JSONModel",
1585
+ "sap/m/MessageBox",
1586
+ "sap/m/MessageToast",
1587
+ "sap/m/Token",
1588
+ "sap/m/SelectDialog",
1589
+ "sap/m/StandardListItem",
1590
+ "sap/m/p13n/Popup",
1591
+ "sap/m/p13n/SelectionPanel",
1592
+ "sap/m/p13n/SortPanel",
1593
+ "rapt/app/controller/support",
1594
+ "rapt/app/controller/filters",
1595
+ "rapt/app/controller/conditions",
1596
+ "rapt/app/controller/variants"
1597
+ ], function (Controller, Filter, Sorter, JSONModel, MessageBox, MessageToast, Token, SelectDialog, StandardListItem, P13nPopup, SelectionPanel, SortPanel, support, filters, conditions, variants) {
1598
+ "use strict";
1599
+
1600
+ function id(sName) {
1601
+ return sName.replace(/[^A-Za-z0-9_]/g, "_");
1602
+ }
1603
+
1604
+ var STANDARD = "standard";
1605
+
1606
+ return Controller.extend("rapt.app.controller.List", {
1607
+ onInit: function () {
1608
+ this._sSet = this.byId("page").data("set");
1609
+ var oPage = this._page();
1610
+ var oVisible = {};
1611
+ oPage.list.filterFields.forEach(function (f) {
1612
+ oVisible[f.property] = oPage.list.selectionFields.some(function (s) { return s.property === f.property; });
1613
+ });
1614
+ this._oLr = new JSONModel({ visible: oVisible, columns: {}, sort: [], active: 0, summary: "Filtered By: None", tableTitle: oPage.typeNamePlural, count: undefined, variant: STANDARD, defaultVariant: STANDARD, modified: false, variants: [{ key: STANDARD, title: oPage.typeNamePlural, author: "", changeable: false, favorite: true }], view: "both", chartSelection: "", tab: "all", tabCounts: {} });
1615
+ this._aFilters = [];
1616
+ this.getView().setModel(this._oLr, "lr");
1617
+ this._oStandard = this._state();
1618
+ if (oPage.draft) {
1619
+ // the draft condition needs the user (D58)
1620
+ var oList = this;
1621
+ this.getOwnerComponent().whenUser(function () {
1622
+ // once the table is bound to the model
1623
+ var fnApply = function () {
1624
+ if (oList.byId("table").getBinding("items")) {
1625
+ oList._applyTableFilters();
1626
+ } else {
1627
+ setTimeout(fnApply, 50);
1628
+ }
1629
+ };
1630
+ fnApply();
1631
+ });
1632
+ }
1633
+ // the default values of the filters (@Consumption.filter.defaultValue, D57)
1634
+ var bDefaults = false;
1635
+ oPage.list.filterFields.forEach(function (f) {
1636
+ if (f.filter && f.filter.defaultValue !== undefined) {
1637
+ bDefaults = true;
1638
+ this._oStandard.filters[f.property] = f.kind === "boolean" || f.kind === "enum" ? f.filter.defaultValue : ["=" + f.filter.defaultValue];
1639
+ if (this._oStandard.visible.indexOf(f.property) < 0) {
1640
+ this._oStandard.visible.push(f.property);
1641
+ }
1642
+ }
1643
+ }, this);
1644
+ this._bFilterDefaults = bDefaults;
1645
+ // the bindings of other pages do not see the changes of this one: refresh when coming back
1646
+ var oRouter = this.getOwnerComponent().getRouter();
1647
+ var oRoute = oRouter.getRoute(this._sSet + "List");
1648
+ if (oRoute) {
1649
+ oRoute.attachPatternMatched(this._onListMatched, this);
1650
+ }
1651
+ var that = this;
1652
+ // the chart of an Analytical List Page, the counts of the tabs of a worklist (D53)
1653
+ this._loadChart();
1654
+ this._loadTabCounts();
1655
+ this._oStore = variants.store(this.getOwnerComponent(), this._sSet + "List");
1656
+ this._oStore.load().then(function (oData) {
1657
+ var aItems = [that._oLr.getProperty("/variants/0")].concat(oData.items.map(function (v) {
1658
+ return { key: v.key, title: v.title, author: "", changeable: true, favorite: v.favorite !== false, content: v.content };
1659
+ }));
1660
+ that._oLr.setProperty("/variants", aItems);
1661
+ var sDefault = aItems.some(function (v) { return v.key === oData.defaultKey; }) ? oData.defaultKey : STANDARD;
1662
+ that._oLr.setProperty("/defaultVariant", sDefault);
1663
+ if (sDefault !== STANDARD || that._bFilterDefaults) {
1664
+ that._selectVariant(sDefault);
1665
+ }
1666
+ });
1667
+ },
1668
+
1669
+ _page: function () {
1670
+ return support.page(this, this._sSet);
1671
+ },
1672
+
1673
+ _onListMatched: function () {
1674
+ if (this.getOwnerComponent().takeStale(this._sSet)) {
1675
+ this._loadChart();
1676
+ this._loadTabCounts();
1677
+ this.byId("table").getBinding("items").refresh();
1678
+ this.byId("table").removeSelections(true);
1679
+ this.getView().getModel("ui").setProperty("/tableSelected", false);
1680
+ }
1681
+ },
1682
+
1683
+ _field: function (sProperty) {
1684
+ return this._page().list.filterFields.find(function (f) { return f.property === sProperty; });
1685
+ },
1686
+
1687
+ /** The filters, the search, the visible filters and columns, the sort order: a variant. */
1688
+ _state: function () {
1689
+ var oState = { filters: {}, search: "", visible: [], hidden: [], sort: this._oLr.getProperty("/sort").slice() };
1690
+ var oVisible = this._oLr.getProperty("/visible");
1691
+ var oColumns = this._oLr.getProperty("/columns");
1692
+ this._page().list.filterFields.forEach(function (oField) {
1693
+ if (oVisible[oField.property]) {
1694
+ oState.visible.push(oField.property);
1695
+ }
1696
+ var oControl = this.byId("filter_" + id(oField.property));
1697
+ if (!oControl) {
1698
+ return;
1699
+ }
1700
+ if (oField.kind === "boolean" || oField.kind === "enum") {
1701
+ if (oControl.getSelectedKey() !== "") {
1702
+ oState.filters[oField.property] = oControl.getSelectedKey();
1703
+ }
1704
+ } else if (oControl.getTokens().length > 0) {
1705
+ // the conditions as texts (=x, *x*, a...b, !(…)): the keys of the tokens (D68)
1706
+ oState.filters[oField.property] = oControl.getTokens().map(function (t) { return t.getKey(); });
1707
+ }
1708
+ }, this);
1709
+ Object.keys(oColumns).forEach(function (sProperty) {
1710
+ if (oColumns[sProperty] === false) {
1711
+ oState.hidden.push(sProperty);
1712
+ }
1713
+ });
1714
+ if (this.byId("search")) {
1715
+ oState.search = this.byId("search").getValue();
1716
+ }
1717
+ return oState;
1718
+ },
1719
+
1720
+ _applyState: function (oState) {
1721
+ var oVisible = {};
1722
+ var oColumns = {};
1723
+ this._page().list.filterFields.forEach(function (oField) {
1724
+ oVisible[oField.property] = oState.visible.indexOf(oField.property) >= 0;
1725
+ var oControl = this.byId("filter_" + id(oField.property));
1726
+ var vValue = oState.filters[oField.property];
1727
+ if (!oControl) {
1728
+ return;
1729
+ }
1730
+ if (oField.kind === "boolean" || oField.kind === "enum") {
1731
+ oControl.setSelectedKey(vValue || "");
1732
+ } else {
1733
+ if (vValue && !Array.isArray(vValue)) {
1734
+ // a date range of a variant saved before D68: { from, to }
1735
+ vValue = vValue.from ? [vValue.to ? vValue.from + "..." + vValue.to : "=" + vValue.from] : [];
1736
+ }
1737
+ oControl.setValue("");
1738
+ oControl.setValueState("None");
1739
+ oControl.setTokens((vValue || []).map(function (sText) {
1740
+ var oCondition = filters.parse(oField, sText);
1741
+ return oCondition ? this._token(oField, oCondition) : null;
1742
+ }, this).filter(Boolean));
1743
+ this._describe(oControl, oField);
1744
+ }
1745
+ }, this);
1746
+ oState.hidden.forEach(function (sProperty) { oColumns[sProperty] = false; });
1747
+ this._oLr.setProperty("/visible", oVisible);
1748
+ this._oLr.setProperty("/columns", oColumns);
1749
+ this._oLr.setProperty("/sort", oState.sort.slice());
1750
+ if (this.byId("search")) {
1751
+ this.byId("search").setValue(oState.search || "");
1752
+ }
1753
+ this._applySort();
1754
+ this.onSearch();
1755
+ this._oLr.setProperty("/modified", false);
1756
+ },
1757
+
1758
+ _applySort: function () {
1759
+ var oBinding = this.byId("table").getBinding("items");
1760
+ oBinding.sort(this._oLr.getProperty("/sort").map(function (s) { return new Sorter(s.key, s.descending); }));
1761
+ },
1762
+
1763
+ /** Turns the text typed in a filter field into a token (condition). */
1764
+ _commitText: function (oControl) {
1765
+ var sText = oControl.getValue();
1766
+ if (sText.trim() === "") {
1767
+ oControl.setValueState("None");
1768
+ return true;
1769
+ }
1770
+ var oCondition = filters.parse(this._field(oControl.data("property")), sText);
1771
+ if (!oCondition) {
1772
+ oControl.setValueState("Error");
1773
+ oControl.setValueStateText("Enter a valid value or condition (=, !=, >, >=, <, <=, a...b, *x*)");
1774
+ return false;
1775
+ }
1776
+ var oField = this._field(oControl.data("property"));
1777
+ var sKey = filters.text(oCondition);
1778
+ if (oField.filter && oField.filter.single) {
1779
+ oControl.removeAllTokens();
1780
+ }
1781
+ if (!oControl.getTokens().some(function (t) { return t.getKey() === sKey; })) {
1782
+ oControl.addToken(this._token(oField, oCondition));
1783
+ this._describe(oControl, oField);
1784
+ }
1785
+ oControl.setValue("");
1786
+ oControl.setValueState("None");
1787
+ return true;
1788
+ },
1789
+
1790
+ /** The token of a condition: its text as key (parsed by onSearch, kept in variants), shown as in SAP Fiori elements. */
1791
+ _token: function (oField, oCondition, sText) {
1792
+ return new Token({ key: filters.text(oCondition), text: sText || filters.display(oField, oCondition) });
1793
+ },
1794
+
1795
+ /** Shows the descriptions of the values of a value help in the "equal to" tokens: "Euro (EUR)". */
1796
+ _describe: function (oControl, oField) {
1797
+ if (!oField.valueHelp || !oField.valueHelp.text) {
1798
+ return;
1799
+ }
1800
+ this._mTexts = this._mTexts || {};
1801
+ if (!this._mTexts[oField.property]) {
1802
+ this._mTexts[oField.property] = support.valueHelpTexts(this.getView(), oField.valueHelp);
1803
+ }
1804
+ this._mTexts[oField.property].then(function (mTexts) {
1805
+ oControl.getTokens().forEach(function (t) {
1806
+ var oCondition = filters.parse(oField, t.getKey());
1807
+ if (oCondition && oCondition.op === "EQ" && mTexts[oCondition.values[0]] !== undefined) {
1808
+ t.setText(filters.display(oField, oCondition, mTexts[oCondition.values[0]]));
1809
+ }
1810
+ });
1811
+ });
1812
+ },
1813
+
1814
+ onFilterSubmit: function (oEvent) {
1815
+ if (this._commitText(oEvent.getSource())) {
1816
+ this.onSearch();
1817
+ }
1818
+ },
1819
+
1820
+ onFilterChange: function () {
1821
+ this._oLr.setProperty("/modified", true);
1822
+ this._summarize();
1823
+ },
1824
+
1825
+ onSearch: function () {
1826
+ var oPage = this._page();
1827
+ var aFilters = [];
1828
+ var bValid = true;
1829
+ oPage.list.filterFields.forEach(function (oField) {
1830
+ var oControl = this.byId("filter_" + id(oField.property));
1831
+ if (!oControl) {
1832
+ return;
1833
+ }
1834
+ var oFilter = null;
1835
+ // a mandatory filter (@Consumption.filter.mandatory, D57)
1836
+ if (oField.filter && oField.filter.mandatory) {
1837
+ var bEmpty = oControl.getTokens ? oControl.getTokens().length === 0 && oControl.getValue() === "" : oControl.getSelectedKey() === "";
1838
+ oControl.setValueState(bEmpty ? "Error" : "None");
1839
+ if (bEmpty) {
1840
+ oControl.setValueStateText("Fill in this filter");
1841
+ bValid = false;
1842
+ }
1843
+ }
1844
+ if (oField.kind === "boolean") {
1845
+ var sKey = oControl.getSelectedKey();
1846
+ oFilter = sKey === "" ? null : new Filter(oField.property, "EQ", sKey === "true");
1847
+ } else if (oField.kind === "enum") {
1848
+ var sMember = oControl.getSelectedKey();
1849
+ oFilter = sMember === "" ? null : new Filter(oField.property, "EQ", sMember);
1850
+ } else {
1851
+ bValid = this._commitText(oControl) && bValid;
1852
+ var aConditions = oControl.getTokens().map(function (t) { return filters.parse(oField, t.getKey()); }).filter(Boolean);
1853
+ oFilter = filters.fieldFilter(oField, aConditions);
1854
+ }
1855
+ if (oFilter) {
1856
+ aFilters.push(oFilter);
1857
+ }
1858
+ }, this);
1859
+ if (!bValid) {
1860
+ return;
1861
+ }
1862
+ var oBinding = this.byId("table").getBinding("items");
1863
+ if (oPage.list.searchable && this.byId("search")) {
1864
+ var sSearch = this.byId("search").getValue().trim();
1865
+ oBinding.changeParameters({ $search: sSearch === "" ? undefined : sSearch });
1866
+ }
1867
+ this._aFilters = aFilters;
1868
+ this._applyTableFilters();
1869
+ this._loadChart();
1870
+ this._loadTabCounts();
1871
+ this._summarize();
1872
+ },
1873
+
1874
+ /** The filters of the filter bar, and the value selected in the chart (Analytical List Page). */
1875
+ _applyTableFilters: function () {
1876
+ var aFilters = this._aFilters.slice();
1877
+ var oChart = this._page().charts[0];
1878
+ var sSelection = this._oLr.getProperty("/chartSelection");
1879
+ if (this._page().floorplan === "analyticalListPage" && oChart && sSelection !== "") {
1880
+ aFilters.push(new Filter(oChart.dimension.property, "EQ", sSelection));
1881
+ }
1882
+ // the members of an enumeration type (D55): OpenUI5 does not write their literals, they go
1883
+ // to the $filter parameter of the binding (with the draft condition of the list)
1884
+ var oPage = this._page();
1885
+ var aEnum = [];
1886
+ aFilters = aFilters.filter(function (oFilter) {
1887
+ var oField = (oPage.fields || {})[oFilter.getPath()];
1888
+ if (oField && oField.kind === "enum" && !oFilter.getFilters()) {
1889
+ aEnum.push(oFilter.getPath() + " eq '" + String(oFilter.getValue1()).replace(/'/g, "''") + "'");
1890
+ return false;
1891
+ }
1892
+ return true;
1893
+ });
1894
+ var oBinding = this.byId("table").getBinding("items");
1895
+ if (!oBinding) {
1896
+ return;
1897
+ }
1898
+ var sDraft = this._draftClause();
1899
+ if (aEnum.length > 0 || this._bEnumFilter || sDraft !== "") {
1900
+ var aClauses = (sDraft === "" ? [] : ["(" + sDraft + ")"]).concat(aEnum);
1901
+ var sFilter = aClauses.length > 0 ? aClauses.join(" and ") : undefined;
1902
+ if (sFilter !== this._sParameterFilter) {
1903
+ this._sParameterFilter = sFilter;
1904
+ oBinding.changeParameters({ $filter: sFilter });
1905
+ }
1906
+ this._bEnumFilter = aEnum.length > 0;
1907
+ }
1908
+ oBinding.filter(aFilters.length > 0 ? new Filter({ filters: aFilters, and: true }) : []);
1909
+ },
1910
+
1911
+ /**
1912
+ * The draft condition of the list (D58), as Fiori Elements: all (the own drafts, and the
1913
+ * active instances without an own draft), own drafts, locked by another user, no changes.
1914
+ */
1915
+ _draftClause: function () {
1916
+ if (!this._page().draft) {
1917
+ return "";
1918
+ }
1919
+ var sMe = String(this.getOwnerComponent().getModel("ui").getProperty("/user") || "").replace(/'/g, "''");
1920
+ var oStatus = this.byId("filter_EditingStatus");
1921
+ var sStatus = oStatus ? oStatus.getSelectedKey() : "";
1922
+ if (sStatus === "own") {
1923
+ return "IsActiveEntity eq false";
1924
+ }
1925
+ if (sStatus === "locked") {
1926
+ return "IsActiveEntity eq true and HasDraftEntity eq true" + (sMe ? " and DraftAdministrativeData/InProcessByUser ne '" + sMe + "'" : "");
1927
+ }
1928
+ if (sStatus === "unchanged") {
1929
+ return "IsActiveEntity eq true and HasDraftEntity eq false";
1930
+ }
1931
+ return sMe ? "IsActiveEntity eq false or HasDraftEntity eq false or DraftAdministrativeData/InProcessByUser ne '" + sMe + "'" : "IsActiveEntity eq true or HasActiveEntity eq false";
1932
+ },
1933
+
1934
+ _service: function () {
1935
+ return this.getOwnerComponent().getManifestEntry("/sap.app/dataSources/mainService/uri");
1936
+ },
1937
+
1938
+ /** The $filter of the filter bar, for the requests of the chart and of the tab counts. */
1939
+ _odataFilter: function (sExtra) {
1940
+ var sFilter = this._aFilters.length > 0 ? filters.toOData(new Filter({ filters: this._aFilters, and: true }), this._page().fields) : "";
1941
+ return [sFilter, sExtra].filter(Boolean).join(" and ");
1942
+ },
1943
+
1944
+ _searchParameter: function () {
1945
+ var oSearch = this.byId("search");
1946
+ var sSearch = oSearch ? oSearch.getValue().trim() : "";
1947
+ return sSearch === "" ? "" : "&$search=" + encodeURIComponent(sSearch);
1948
+ },
1949
+
1950
+ /** The chart of the Analytical List Page: the groups of its dimension ($apply). */
1951
+ _loadChart: function () {
1952
+ var oPage = this._page();
1953
+ var oSpec = oPage.charts[0];
1954
+ var oChart = this.byId("chart");
1955
+ if (oPage.floorplan !== "analyticalListPage" || !oSpec || !oChart) {
1956
+ return;
1957
+ }
1958
+ var sFilter = this._odataFilter();
1959
+ var sApply = (sFilter ? "filter(" + sFilter + ")/" : "") + "groupby((" + oSpec.dimension.property + "),aggregate(" + oSpec.measures.map(function (m) {
1960
+ return m.property + " with " + m.aggregation + " as " + m.property;
1961
+ }).join(",") + "))";
1962
+ var that = this;
1963
+ fetch(this._service() + this._sSet + "?$apply=" + encodeURIComponent(sApply) + "&$orderby=" + encodeURIComponent(oSpec.dimension.property) + "&$top=50" + this._searchParameter(), { credentials: "same-origin", headers: { accept: "application/json" } })
1964
+ .then(function (r) { return r.ok ? r.json() : { value: [] }; })
1965
+ .then(function (oData) {
1966
+ oChart.setData((oData.value || []).map(function (row) {
1967
+ var v = row[oSpec.dimension.property];
1968
+ return { key: v === null ? "" : String(v), label: v === null || v === "" ? "(empty)" : String(v), values: oSpec.measures.map(function (m) { return Number(row[m.property]) || 0; }) };
1969
+ }), oSpec.measures.map(function (m) { return m.label; }));
1970
+ that._oChartRows = oData.value || [];
1971
+ })
1972
+ .catch(function () { oChart.setData([], []); });
1973
+ },
1974
+
1975
+ onChartSelect: function (oEvent) {
1976
+ this._oLr.setProperty("/chartSelection", oEvent.getParameter("value"));
1977
+ this._applyTableFilters();
1978
+ },
1979
+
1980
+ onViewSwitch: function (oEvent) {
1981
+ this._oLr.setProperty("/view", oEvent.getParameter("item").getKey());
1982
+ },
1983
+
1984
+ /** Worklist: a tab is a selection variant (@UI.selectionVariant), its filter a parameter of the list. */
1985
+ onWorklistTab: function (oEvent) {
1986
+ var sKey = oEvent.getParameter("key");
1987
+ var oVariant = this._page().selectionVariants.find(function (v) { return v.key === sKey; });
1988
+ this._oLr.setProperty("/tab", sKey);
1989
+ var sDraft = this._draftClause();
1990
+ var sFilter = [sDraft, oVariant && oVariant.filter ? oVariant.filter : ""].filter(Boolean).map(function (f) { return "(" + f + ")"; }).join(" and ");
1991
+ this.byId("table").getBinding("items").changeParameters({ $filter: sFilter || undefined });
1992
+ },
1993
+
1994
+ _loadTabCounts: function () {
1995
+ var oPage = this._page();
1996
+ if (oPage.floorplan !== "worklist" || oPage.selectionVariants.length === 0) {
1997
+ return;
1998
+ }
1999
+ var that = this;
2000
+ var aTabs = [{ key: "all" }].concat(oPage.selectionVariants);
2001
+ aTabs.forEach(function (oTab) {
2002
+ var sFilter = that._odataFilter(oTab.filter ? "(" + oTab.filter + ")" : "");
2003
+ fetch(that._service() + that._sSet + "/$count" + (sFilter ? "?$filter=" + encodeURIComponent(sFilter) : "?") + that._searchParameter(), { credentials: "same-origin" })
2004
+ .then(function (r) { return r.ok ? r.text() : ""; })
2005
+ .then(function (sCount) { that._oLr.setProperty("/tabCounts/" + oTab.key, sCount.trim()); })
2006
+ .catch(function () { /* no count */ });
2007
+ });
2008
+ },
2009
+
2010
+ /** "Filtered By: …" in the collapsed header, the count of "Adapt Filters (n)". */
2011
+ _summarize: function () {
2012
+ var oState = this._state();
2013
+ var aLabels = [];
2014
+ Object.keys(oState.filters).forEach(function (sProperty) {
2015
+ var oField = this._field(sProperty);
2016
+ var vValue = oState.filters[sProperty];
2017
+ aLabels.push(oField.label + (Array.isArray(vValue) && vValue.length > 1 ? " (" + vValue.length + ")" : ""));
2018
+ }, this);
2019
+ if (oState.search && oState.search.trim() !== "") {
2020
+ aLabels.unshift("Search");
2021
+ }
2022
+ this._oLr.setProperty("/active", aLabels.length);
2023
+ this._oLr.setProperty("/summary", "Filtered By: " + (aLabels.length === 0 ? "None" : aLabels.join(", ")));
2024
+ },
2025
+
2026
+ onClearFilters: function () {
2027
+ if (this.byId("filter_EditingStatus")) {
2028
+ this.byId("filter_EditingStatus").setSelectedKey("");
2029
+ }
2030
+ var oState = this._state();
2031
+ oState.filters = {};
2032
+ oState.search = "";
2033
+ this._applyState(oState);
2034
+ this._oLr.setProperty("/modified", true);
2035
+ },
2036
+
2037
+ onAdaptFilters: function () {
2038
+ var that = this;
2039
+ var oVisible = this._oLr.getProperty("/visible");
2040
+ var oDialog = new SelectDialog({
2041
+ title: "Adapt Filters",
2042
+ multiSelect: true,
2043
+ rememberSelections: false,
2044
+ items: this._page().list.filterFields.map(function (f) {
2045
+ return new StandardListItem({ title: f.label, description: f.property, selected: oVisible[f.property] === true });
2046
+ }),
2047
+ search: function (oEvent) {
2048
+ var sValue = (oEvent.getParameter("value") || "").toLowerCase();
2049
+ oEvent.getSource().getItems().forEach(function (oItem) {
2050
+ oItem.setVisible(oItem.getTitle().toLowerCase().indexOf(sValue) >= 0);
2051
+ });
2052
+ },
2053
+ confirm: function (oEvent) {
2054
+ var aSelected = (oEvent.getParameter("selectedItems") || []).map(function (oItem) { return oItem.getDescription(); });
2055
+ var oNew = {};
2056
+ that._page().list.filterFields.forEach(function (f) {
2057
+ oNew[f.property] = aSelected.indexOf(f.property) >= 0;
2058
+ });
2059
+ var oState = that._state();
2060
+ oState.visible = aSelected;
2061
+ // a hidden filter does not filter
2062
+ Object.keys(oState.filters).forEach(function (sProperty) {
2063
+ if (!oNew[sProperty]) {
2064
+ delete oState.filters[sProperty];
2065
+ }
2066
+ });
2067
+ that._applyState(oState);
2068
+ that._oLr.setProperty("/modified", true);
2069
+ },
2070
+ afterClose: function () {
2071
+ oDialog.destroy();
2072
+ }
2073
+ });
2074
+ this.getView().addDependent(oDialog);
2075
+ oDialog.open();
2076
+ },
2077
+
2078
+ onTableSettings: function (oEvent) {
2079
+ var that = this;
2080
+ var oTable = this._page().list.table;
2081
+ var oColumns = this._oLr.getProperty("/columns");
2082
+ var aSort = this._oLr.getProperty("/sort");
2083
+ var oSelection = new SelectionPanel({ title: "Columns", showHeader: true, enableCount: true, enableReorder: false });
2084
+ oSelection.setP13nData(oTable.columns.map(function (c) {
2085
+ return { key: c.property, label: c.label, visible: oColumns[c.property] !== false };
2086
+ }));
2087
+ var oSort = new SortPanel({ title: "Sort" });
2088
+ oSort.setP13nData(oTable.columns.map(function (c) {
2089
+ var oSorted = aSort.find(function (s) { return s.key === c.property; });
2090
+ return { key: c.property, label: c.label, sorted: !!oSorted, descending: oSorted ? oSorted.descending : false };
2091
+ }));
2092
+ var oPopup = new P13nPopup({
2093
+ title: "View Settings",
2094
+ panels: [oSelection, oSort],
2095
+ close: function (oClose) {
2096
+ if (oClose.getParameter("reason") === "Ok") {
2097
+ var oNew = {};
2098
+ oSelection.getP13nData().forEach(function (o) {
2099
+ if (!o.visible) {
2100
+ oNew[o.key] = false;
2101
+ }
2102
+ });
2103
+ that._oLr.setProperty("/columns", oNew);
2104
+ that._oLr.setProperty("/sort", oSort.getP13nData().filter(function (o) { return o.sorted; }).map(function (o) {
2105
+ return { key: o.key, descending: !!o.descending };
2106
+ }));
2107
+ that._applySort();
2108
+ that._oLr.setProperty("/modified", true);
2109
+ }
2110
+ oPopup.destroy();
2111
+ }
2112
+ });
2113
+ this.getView().addDependent(oPopup);
2114
+ oPopup.open(oEvent.getSource());
2115
+ },
2116
+
2117
+ /** The value help of a filter field: "Search and Select" (value help) and "Define Conditions" (D68). */
2118
+ onFilterValueHelp: function (oEvent) {
2119
+ var oControl = oEvent.getSource();
2120
+ var oField = this._field(oControl.data("property"));
2121
+ var that = this;
2122
+ if (!this._commitText(oControl)) {
2123
+ oControl.setValue("");
2124
+ oControl.setValueState("None");
2125
+ }
2126
+ var aCurrent = oControl.getTokens().map(function (t) {
2127
+ var oCondition = filters.parse(oField, t.getKey());
2128
+ return oCondition ? { condition: oCondition, text: t.getText() } : null;
2129
+ }).filter(Boolean);
2130
+ conditions.open(this.getView(), oField, aCurrent).then(function (aConditions) {
2131
+ if (!aConditions) {
2132
+ return;
2133
+ }
2134
+ oControl.setTokens(aConditions.map(function (c) { return that._token(oField, c.condition, c.text); }));
2135
+ oControl.setValueState("None");
2136
+ that.onFilterChange();
2137
+ });
2138
+ },
2139
+
2140
+ _items: function () {
2141
+ return this._oLr.getProperty("/variants");
2142
+ },
2143
+
2144
+ _selectVariant: function (sKey) {
2145
+ var oItem = this._items().find(function (v) { return v.key === sKey; });
2146
+ this._oLr.setProperty("/variant", oItem ? sKey : STANDARD);
2147
+ this._applyState(oItem && oItem.content ? oItem.content : this._oStandard);
2148
+ },
2149
+
2150
+ onVariantSelect: function (oEvent) {
2151
+ this._selectVariant(oEvent.getParameter("key"));
2152
+ },
2153
+
2154
+ _persist: function () {
2155
+ var sDefault = this._oLr.getProperty("/defaultVariant");
2156
+ this._oStore.save({
2157
+ defaultKey: sDefault,
2158
+ items: this._items().filter(function (v) { return v.key !== STANDARD; }).map(function (v) {
2159
+ return { key: v.key, title: v.title, favorite: v.favorite, content: v.content };
2160
+ })
2161
+ }).catch(function () {
2162
+ MessageToast.show("The view could not be saved");
2163
+ });
2164
+ },
2165
+
2166
+ onVariantSave: function (oEvent) {
2167
+ var sKey = oEvent.getParameter("key") || ("v" + Date.now());
2168
+ var aItems = this._items().slice();
2169
+ var oContent = this._state();
2170
+ if (oEvent.getParameter("overwrite")) {
2171
+ aItems = aItems.map(function (v) { return v.key === sKey ? Object.assign({}, v, { content: oContent }) : v; });
2172
+ } else {
2173
+ aItems.push({ key: sKey, title: oEvent.getParameter("name"), author: "", changeable: true, favorite: true, content: oContent });
2174
+ }
2175
+ this._oLr.setProperty("/variants", aItems);
2176
+ if (oEvent.getParameter("def")) {
2177
+ this._oLr.setProperty("/defaultVariant", sKey);
2178
+ }
2179
+ this._oLr.setProperty("/variant", sKey);
2180
+ this._oLr.setProperty("/modified", false);
2181
+ this._persist();
2182
+ },
2183
+
2184
+ onVariantManage: function (oEvent) {
2185
+ var aDeleted = oEvent.getParameter("deleted") || [];
2186
+ var aRenamed = oEvent.getParameter("renamed") || [];
2187
+ var aFavorites = oEvent.getParameter("fav") || [];
2188
+ var aItems = this._items().filter(function (v) { return aDeleted.indexOf(v.key) < 0; }).map(function (v) {
2189
+ var oRename = aRenamed.find(function (r) { return r.key === v.key; });
2190
+ var oFavorite = aFavorites.find(function (f) { return f.key === v.key; });
2191
+ return Object.assign({}, v, oRename ? { title: oRename.name } : {}, oFavorite ? { favorite: oFavorite.visible } : {});
2192
+ });
2193
+ this._oLr.setProperty("/variants", aItems);
2194
+ var sDefault = oEvent.getParameter("def");
2195
+ if (sDefault !== undefined) {
2196
+ this._oLr.setProperty("/defaultVariant", sDefault);
2197
+ }
2198
+ if (aDeleted.indexOf(this._oLr.getProperty("/defaultVariant")) >= 0) {
2199
+ this._oLr.setProperty("/defaultVariant", STANDARD);
2200
+ }
2201
+ if (aDeleted.indexOf(this._oLr.getProperty("/variant")) >= 0) {
2202
+ this._selectVariant(STANDARD);
2203
+ }
2204
+ this._persist();
2205
+ },
2206
+
2207
+ onUpdateFinished: function (oEvent) {
2208
+ var iTotal = oEvent.getParameter("total");
2209
+ var sPlural = this.byId("tableTitle").data("plural");
2210
+ var oBinding = this.byId("table").getBinding("items");
2211
+ var bKnown = oBinding && oBinding.isLengthFinal();
2212
+ this._oLr.setProperty("/tableTitle", bKnown ? sPlural + " (" + iTotal + ")" : sPlural);
2213
+ },
2214
+
2215
+ onSelectionChange: function () {
2216
+ this.getView().getModel("ui").setProperty("/tableSelected", !!this.byId("table").getSelectedItem());
2217
+ },
2218
+
2219
+ onItemPress: function (oEvent) {
2220
+ var oContext = oEvent.getSource().getBindingContext();
2221
+ this.getOwnerComponent().getRouter().navTo(this._sSet + "Object", { key: support.keyOf(oContext.getPath()) });
2222
+ },
2223
+
2224
+ onCreate: function () {
2225
+ var oModel = this.getView().getModel();
2226
+ var that = this;
2227
+ if (this._page().draft) {
2228
+ // draft (D54): a new draft at once, then its object page in edit mode
2229
+ var oDraft = oModel.bindList("/" + this._sSet, undefined, undefined, undefined, { $$updateGroupId: "$auto" }).create({});
2230
+ oDraft.created().then(function () {
2231
+ that.getOwnerComponent().markStale();
2232
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: support.keyOf(oDraft.getPath()) });
2233
+ }, function (oError) {
2234
+ MessageBox.error(support.errorText(oError));
2235
+ });
2236
+ return;
2237
+ }
2238
+ var oBinding = oModel.bindList("/" + this._sSet, undefined, undefined, undefined, { $$updateGroupId: "changes" });
2239
+ this.getOwnerComponent().setTransient(oBinding.create({}));
2240
+ this.getOwnerComponent().getRouter().navTo(this._sSet + "New");
2241
+ },
2242
+
2243
+ onDelete: function () {
2244
+ var oItem = this.byId("table").getSelectedItem();
2245
+ if (!oItem) {
2246
+ return;
2247
+ }
2248
+ var oContext = oItem.getBindingContext();
2249
+ var that = this;
2250
+ MessageBox.confirm("Delete this object?", {
2251
+ onClose: function (sAction) {
2252
+ if (sAction !== MessageBox.Action.OK) {
2253
+ return;
2254
+ }
2255
+ oContext.delete("$auto").then(function () {
2256
+ that.getView().getModel("ui").setProperty("/tableSelected", false);
2257
+ MessageToast.show("Object deleted");
2258
+ }, function (oError) {
2259
+ MessageBox.error(support.errorText(oError));
2260
+ });
2261
+ }
2262
+ });
2263
+ },
2264
+
2265
+ onAction: function (oEvent) {
2266
+ var oButton = oEvent.getSource();
2267
+ var oPage = this._page();
2268
+ var oAction = support.action(oPage, oButton.data("action"), oPage.list.table.actions);
2269
+ var oItem = this.byId("table").getSelectedItem();
2270
+ var oContext = oItem ? oItem.getBindingContext() : undefined;
2271
+ var that = this;
2272
+ if (!oAction || (!oAction.static && !oContext)) {
2273
+ return;
2274
+ }
2275
+ support.invoke(this.getView(), oPage, oAction, oContext).then(function (oResult) {
2276
+ if (oResult === null) {
2277
+ return;
2278
+ }
2279
+ MessageToast.show(oAction.label + ": done");
2280
+ that.byId("table").getBinding("items").refresh();
2281
+ if ((oAction.factory || oAction.static) && oResult && oAction.returnsEntity) {
2282
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: support.keyPredicate(oPage, oResult.getObject()) });
2283
+ }
2284
+ }, function (oError) {
2285
+ MessageBox.error(support.errorText(oError));
2286
+ });
2287
+ }
2288
+ });
2289
+ });
2290
+ `;
2291
+ /**
2292
+ * The views (variants) of a user: saved by the service (`$variants`, a table of the database,
2293
+ * per user), in the local storage of the browser when the service does not keep them.
2294
+ */
2295
+ export const VARIANTS = `sap.ui.define([], function () {
2296
+ "use strict";
2297
+
2298
+ function local(sKey) {
2299
+ return {
2300
+ load: function () {
2301
+ try {
2302
+ return Promise.resolve(JSON.parse(window.localStorage.getItem(sKey) || "null") || { items: [] });
2303
+ } catch (e) {
2304
+ return Promise.resolve({ items: [] });
2305
+ }
2306
+ },
2307
+ save: function (oData) {
2308
+ try {
2309
+ window.localStorage.setItem(sKey, JSON.stringify(oData));
2310
+ } catch (e) {
2311
+ return Promise.reject(e);
2312
+ }
2313
+ return Promise.resolve();
2314
+ }
2315
+ };
2316
+ }
2317
+
2318
+ return {
2319
+ store: function (oComponent, sPage) {
2320
+ var sService = oComponent.getManifestEntry("/sap.app/dataSources/mainService/uri");
2321
+ var sUrl = sService + "$variants?page=" + encodeURIComponent(sPage);
2322
+ var oLocal = local("rapt.variants." + sService + sPage);
2323
+ var bServer = true;
2324
+ return {
2325
+ load: function () {
2326
+ return fetch(sUrl, { credentials: "same-origin", headers: { accept: "application/json" } }).then(function (oResponse) {
2327
+ if (!oResponse.ok) {
2328
+ throw new Error(String(oResponse.status));
2329
+ }
2330
+ return oResponse.json();
2331
+ }).then(function (oData) {
2332
+ return { items: oData.items || [], defaultKey: oData.defaultKey };
2333
+ }).catch(function () {
2334
+ bServer = false;
2335
+ return oLocal.load();
2336
+ });
2337
+ },
2338
+ save: function (oData) {
2339
+ if (!bServer) {
2340
+ return oLocal.save(oData);
2341
+ }
2342
+ return fetch(sService, { method: "HEAD", credentials: "same-origin", headers: { "X-CSRF-Token": "Fetch" } }).then(function (oHead) {
2343
+ return fetch(sUrl, {
2344
+ method: "PUT",
2345
+ credentials: "same-origin",
2346
+ headers: { "content-type": "application/json", "X-CSRF-Token": oHead.headers.get("X-CSRF-Token") || "" },
2347
+ body: JSON.stringify(oData)
2348
+ });
2349
+ }).then(function (oResponse) {
2350
+ if (!oResponse.ok) {
2351
+ throw new Error(String(oResponse.status));
2352
+ }
2353
+ });
2354
+ }
2355
+ };
2356
+ }
2357
+ };
2358
+ });
2359
+ `;
2360
+ export const OBJECT_CONTROLLER = `sap.ui.define([
2361
+ "sap/ui/core/mvc/Controller",
2362
+ "sap/ui/core/Messaging",
2363
+ "sap/m/MessageBox",
2364
+ "sap/m/MessageToast",
2365
+ "sap/m/MessagePopover",
2366
+ "sap/m/MessageItem",
2367
+ "sap/ui/core/routing/History",
2368
+ "rapt/app/controller/support",
2369
+ "sap/m/Dialog",
2370
+ "sap/m/Button",
2371
+ "sap/m/Input",
2372
+ "sap/m/Label",
2373
+ "sap/m/VBox"
2374
+ ], function (Controller, Messaging, MessageBox, MessageToast, MessagePopover, MessageItem, History, support, Dialog, Button, Input, Label, VBox) {
2375
+ "use strict";
2376
+
2377
+ return Controller.extend("rapt.app.controller.Object", {
2378
+ onInit: function () {
2379
+ this._sSet = this.byId("page").data("set");
2380
+ var oRouter = this.getOwnerComponent().getRouter();
2381
+ oRouter.getRoute(this._sSet + "Object").attachPatternMatched(this._onObjectMatched, this);
2382
+ var oNew = oRouter.getRoute(this._sSet + "New");
2383
+ if (oNew) {
2384
+ oNew.attachPatternMatched(this._onNewMatched, this);
2385
+ }
2386
+ // form entry (D53): the start page creates, again and again
2387
+ var oFormEntry = oRouter.getRoute(this._sSet + "FormEntry");
2388
+ if (oFormEntry) {
2389
+ oFormEntry.attachPatternMatched(this._onFormEntryMatched, this);
2390
+ }
2391
+ },
2392
+
2393
+ _page: function () {
2394
+ return support.page(this, this._sSet);
2395
+ },
2396
+
2397
+ _ui: function () {
2398
+ return this.getView().getModel("ui");
2399
+ },
2400
+
2401
+ _mode: function (bEdit, bCreate) {
2402
+ this._ui().setProperty("/editMode", bEdit);
2403
+ this._ui().setProperty("/createMode", bCreate);
2404
+ this._ui().setProperty("/childSelected", false);
2405
+ },
2406
+
2407
+ /** Collaborative draft (D59): the users of the draft, the changes of the others (polling). */
2408
+ _startCollab: function () {
2409
+ var that = this;
2410
+ this._loadCollabUsers();
2411
+ this._iCollab = setInterval(function () {
2412
+ var oModel = that.getView().getModel();
2413
+ if (!that._ui().getProperty("/draft") || oModel.hasPendingChanges()) {
2414
+ return;
2415
+ }
2416
+ var oBinding = that.getView().getElementBinding();
2417
+ if (oBinding) {
2418
+ oBinding.refresh();
2419
+ }
2420
+ that._loadCollabUsers();
2421
+ }, 5000);
2422
+ },
2423
+
2424
+ _stopCollab: function () {
2425
+ if (this._iCollab) {
2426
+ clearInterval(this._iCollab);
2427
+ this._iCollab = undefined;
2428
+ }
2429
+ },
2430
+
2431
+ _loadCollabUsers: function () {
2432
+ var oContext = this.getView().getBindingContext();
2433
+ var oUi = this._ui();
2434
+ var sService = this.getOwnerComponent().getManifestEntry("/sap.app/dataSources/mainService/uri");
2435
+ if (!oContext) {
2436
+ return;
2437
+ }
2438
+ oContext.requestProperty("DraftUUID").then(function (sUuid) {
2439
+ if (!sUuid) {
2440
+ return undefined;
2441
+ }
2442
+ return fetch(sService + "DraftAdministrativeUser?$orderby=UserID&$filter=" + encodeURIComponent("DraftUUID eq " + sUuid), { credentials: "same-origin", headers: { accept: "application/json" } })
2443
+ .then(function (r) { return r.ok ? r.json() : { value: [] }; })
2444
+ .then(function (oData) {
2445
+ oUi.setProperty("/collab/users", (oData.value || []).map(function (u) {
2446
+ return { UserID: u.UserID, initials: String(u.UserID).slice(0, 2).toUpperCase(), owner: u.UserAccessRole === "O" };
2447
+ }));
2448
+ });
2449
+ }).catch(function () { /* the list stays as it was */ });
2450
+ },
2451
+
2452
+ /** Invites a user to the collaborative draft: the action Share (D59). */
2453
+ onInvite: function () {
2454
+ var that = this;
2455
+ var oInput = new Input({ placeholder: "User ID", width: "100%" });
2456
+ var oDialog = new Dialog({
2457
+ title: "Invite Users",
2458
+ content: [new VBox({ items: [new Label({ text: "User", labelFor: oInput }), oInput] }).addStyleClass("sapUiSmallMargin")],
2459
+ beginButton: new Button({
2460
+ text: "Invite",
2461
+ type: "Emphasized",
2462
+ press: function () {
2463
+ var sUser = oInput.getValue().trim();
2464
+ oDialog.close();
2465
+ if (sUser === "") {
2466
+ return;
2467
+ }
2468
+ that._draftAction("Share", { Users: [{ UserID: sUser, UserAccessRole: "E" }], ShareAll: false }).then(function () {
2469
+ MessageToast.show(sUser.toUpperCase() + " can now edit the draft");
2470
+ that._loadCollabUsers();
2471
+ }, function (oError) {
2472
+ MessageBox.error(support.errorText(oError));
2473
+ });
2474
+ }
2475
+ }),
2476
+ endButton: new Button({ text: "Cancel", press: function () { oDialog.close(); } }),
2477
+ afterClose: function () { oDialog.destroy(); }
2478
+ });
2479
+ this.getView().addDependent(oDialog);
2480
+ oDialog.open();
2481
+ },
2482
+
2483
+ _onObjectMatched: function (oEvent) {
2484
+ // changes left behind (back button in edit mode) must not be sent with the next save
2485
+ this.getView().getModel().resetChanges("changes");
2486
+ this._mode(false, false);
2487
+ Messaging.removeAllMessages();
2488
+ var sKey = oEvent.getParameter("arguments").key;
2489
+ // a draft (D54): edited at once, each change saved in the draft
2490
+ var bDraft = /IsActiveEntity=false/.test(sKey);
2491
+ this._ui().setProperty("/draft", bDraft);
2492
+ this.getView().bindElement({
2493
+ path: "/" + this._sSet + "(" + sKey + ")",
2494
+ parameters: { $$updateGroupId: bDraft ? "$auto" : "changes" }
2495
+ });
2496
+ this._stopCollab();
2497
+ if (bDraft) {
2498
+ // each change is saved in the draft: "Saving draft..." then "Draft saved" (D58)
2499
+ var oIndicator = this.byId("draftStatus");
2500
+ var oBinding = this.getView().getElementBinding();
2501
+ if (oIndicator && oBinding && !oBinding._raptIndicator) {
2502
+ oBinding._raptIndicator = true;
2503
+ oBinding.attachPatchSent(function () { oIndicator.showDraftSaving(); });
2504
+ oBinding.attachPatchCompleted(function (oPatch) {
2505
+ if (oPatch.getParameter("success")) {
2506
+ oIndicator.showDraftSaved();
2507
+ } else {
2508
+ oIndicator.clearDraftState();
2509
+ }
2510
+ });
2511
+ }
2512
+ if (this.byId("collab")) {
2513
+ this._startCollab();
2514
+ }
2515
+ }
2516
+ if (bDraft) {
2517
+ // a new draft (no active instance) is a creation: its keys may still be entered
2518
+ var that = this;
2519
+ var sService = this.getOwnerComponent().getManifestEntry("/sap.app/dataSources/mainService/uri");
2520
+ this._bHasActive = true;
2521
+ fetch(sService + this._sSet + "(" + sKey + ")?$select=HasActiveEntity", { credentials: "same-origin", headers: { accept: "application/json" } })
2522
+ .then(function (r) { return r.ok ? r.json() : { HasActiveEntity: true }; })
2523
+ .then(function (oData) {
2524
+ that._bHasActive = oData.HasActiveEntity !== false;
2525
+ that._mode(true, !that._bHasActive);
2526
+ }, function () {
2527
+ that._mode(true, false);
2528
+ });
2529
+ }
2530
+ },
2531
+
2532
+ /** Runs an action of the draft on the bound instance; resolves the context it returns. */
2533
+ _draftAction: function (sName, oParameters) {
2534
+ var oView = this.getView();
2535
+ var oNamespace = oView.getModel("layout").getProperty("/namespace");
2536
+ // Share returns nothing: no inherited $expand / $select (D59)
2537
+ var oAction = oView.getModel().bindContext(oNamespace + "." + sName + "(...)", oView.getBindingContext(), sName === "Share" ? {} : { $$inheritExpandSelect: true });
2538
+ Object.keys(oParameters || {}).forEach(function (sParameter) {
2539
+ oAction.setParameter(sParameter, oParameters[sParameter]);
2540
+ });
2541
+ return oAction.invoke("$auto");
2542
+ },
2543
+
2544
+ _onFormEntryMatched: function () {
2545
+ this._bFormEntry = true;
2546
+ this._newTransient();
2547
+ this._onNewMatched();
2548
+ },
2549
+
2550
+ /** A new instance to create, when none was prepared (form entry, link to …/new). */
2551
+ _newTransient: function () {
2552
+ var oBinding = this.getView().getModel().bindList("/" + this._sSet, undefined, undefined, undefined, { $$updateGroupId: "changes" });
2553
+ this.getOwnerComponent().setTransient(oBinding.create({}));
2554
+ },
2555
+
2556
+ _onNewMatched: function (oEvent) {
2557
+ if (oEvent) {
2558
+ this._bFormEntry = false;
2559
+ }
2560
+ if (!this.getOwnerComponent().getTransient()) {
2561
+ this._newTransient();
2562
+ }
2563
+ var oContext = this.getOwnerComponent().getTransient();
2564
+ Messaging.removeAllMessages();
2565
+ this.getView().unbindElement();
2566
+ this.getView().setBindingContext(oContext);
2567
+ this._mode(true, true);
2568
+ },
2569
+
2570
+ _back: function () {
2571
+ if (History.getInstance().getPreviousHash() !== undefined) {
2572
+ window.history.go(-1);
2573
+ } else {
2574
+ this.getOwnerComponent().getRouter().navTo(this.getOwnerComponent().getModel("layout").getProperty("/start") + "List", {}, true);
2575
+ }
2576
+ },
2577
+
2578
+ /** Leaves the page, after a confirmation when changes are not saved. */
2579
+ _leave: function (fnLeave) {
2580
+ var oModel = this.getView().getModel();
2581
+ var that = this;
2582
+ if (this._ui().getProperty("/draft")) {
2583
+ // leaving a draft, as Fiori Elements: keep it for later, or discard it (D58)
2584
+ var oContext = this.getView().getBindingContext();
2585
+ MessageBox.show("Do you want to keep the draft of your changes?", {
2586
+ title: "Leave Page",
2587
+ actions: ["Keep Draft", "Discard Draft", MessageBox.Action.CANCEL],
2588
+ emphasizedAction: "Keep Draft",
2589
+ onClose: function (sAction) {
2590
+ if (sAction === "Keep Draft") {
2591
+ that._stopCollab();
2592
+ that.getOwnerComponent().markStale();
2593
+ fnLeave();
2594
+ } else if (sAction === "Discard Draft" && oContext) {
2595
+ oContext.delete("$auto").then(function () {
2596
+ that._stopCollab();
2597
+ that.getOwnerComponent().markStale();
2598
+ MessageToast.show("Draft discarded");
2599
+ fnLeave();
2600
+ }, function (oError) {
2601
+ MessageBox.error(support.errorText(oError));
2602
+ });
2603
+ }
2604
+ }
2605
+ });
2606
+ return;
2607
+ }
2608
+ if (!this._ui().getProperty("/editMode") || !oModel.hasPendingChanges("changes")) {
2609
+ fnLeave();
2610
+ return;
2611
+ }
2612
+ MessageBox.confirm("Discard the changes?", {
2613
+ onClose: function (sAction) {
2614
+ if (sAction === MessageBox.Action.OK) {
2615
+ oModel.resetChanges("changes");
2616
+ that.getOwnerComponent().setTransient(undefined);
2617
+ that._mode(false, false);
2618
+ fnLeave();
2619
+ }
2620
+ }
2621
+ });
2622
+ },
2623
+
2624
+ onNavList: function () {
2625
+ var oRouter = this.getOwnerComponent().getRouter();
2626
+ var sStart = this.getOwnerComponent().getModel("layout").getProperty("/start");
2627
+ this._leave(function () {
2628
+ oRouter.navTo(sStart + "List", {}, false);
2629
+ });
2630
+ },
2631
+
2632
+ onNavBack: function () {
2633
+ var that = this;
2634
+ this._leave(function () {
2635
+ that._back();
2636
+ });
2637
+ },
2638
+
2639
+ onValueHelp: function (oEvent) {
2640
+ var oInput = oEvent.getSource();
2641
+ var sProperty = oInput.data("property");
2642
+ var oField = this._page().fields[sProperty] || this._childField(oInput, sProperty);
2643
+ if (!oField || !oField.valueHelp) {
2644
+ return;
2645
+ }
2646
+ var oContext = oInput.getBindingContext();
2647
+ support.valueHelp(this.getView(), oField.valueHelp, false, oContext).then(function (aValues) {
2648
+ if (aValues.length > 0) {
2649
+ oContext.setProperty(sProperty, aValues[0]);
2650
+ oInput.setValueState("None");
2651
+ }
2652
+ });
2653
+ },
2654
+
2655
+ /** A value typed or chosen in a field with a value help: validation, additional bindings. */
2656
+ onValueHelpChange: function (oEvent) {
2657
+ var oControl = oEvent.getSource();
2658
+ var sProperty = oControl.data("property");
2659
+ var oField = this._page().fields[sProperty] || this._childField(oControl, sProperty);
2660
+ var oContext = oControl.getBindingContext();
2661
+ if (!oField || !oField.valueHelp || !oContext) {
2662
+ return;
2663
+ }
2664
+ var oHelp = oField.valueHelp;
2665
+ var oItem = oEvent.getParameter("selectedItem") || (oControl.getSelectedItem && oControl.getSelectedItem());
2666
+ if (oItem) {
2667
+ // drop-down list: the row of the chosen item
2668
+ support.applyValueHelpResults(oHelp, oContext, oItem.getBindingContext().getObject());
2669
+ oControl.setValueState("None");
2670
+ return;
2671
+ }
2672
+ var sValue = oControl.getValue ? oControl.getValue() : "";
2673
+ support.checkValueHelp(this.getView(), oHelp, oContext, sValue).then(function (bFound) {
2674
+ if (!bFound && oHelp.validation) {
2675
+ oControl.setValueState("Error");
2676
+ oControl.setValueStateText("Enter a valid value: " + sValue + " is not in " + oHelp.label);
2677
+ } else {
2678
+ oControl.setValueState("None");
2679
+ }
2680
+ });
2681
+ },
2682
+
2683
+ /** The field of a child table (the input is in a row of a table of the page). */
2684
+ _childField: function (oInput, sProperty) {
2685
+ var oParent = oInput.getParent();
2686
+ while (oParent && !(oParent.data && oParent.data("set"))) {
2687
+ oParent = oParent.getParent();
2688
+ }
2689
+ var oPage = oParent ? support.page(this, oParent.data("set")) : undefined;
2690
+ return oPage ? oPage.fields[sProperty] : undefined;
2691
+ },
2692
+
2693
+ onEdit: function () {
2694
+ Messaging.removeAllMessages();
2695
+ if (this._page().draft) {
2696
+ // draft (D54): Edit opens the draft of the instance
2697
+ var that = this;
2698
+ this._ui().setProperty("/busy", true);
2699
+ this._draftAction("Edit", { PreserveChanges: false }).then(function (oDraft) {
2700
+ that._ui().setProperty("/busy", false);
2701
+ that.getOwnerComponent().markStale();
2702
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: support.keyOf(oDraft.getPath()) }, true);
2703
+ }, function (oError) {
2704
+ that._ui().setProperty("/busy", false);
2705
+ MessageBox.error(support.errorText(oError));
2706
+ });
2707
+ return;
2708
+ }
2709
+ this._mode(true, false);
2710
+ },
2711
+
2712
+ onSave: function () {
2713
+ if (this._ui().getProperty("/draft")) {
2714
+ this._activate();
2715
+ return;
2716
+ }
2717
+ var oView = this.getView();
2718
+ var oModel = oView.getModel();
2719
+ var oUi = this._ui();
2720
+ var that = this;
2721
+ var bCreate = oUi.getProperty("/createMode");
2722
+ var oContext = oView.getBindingContext();
2723
+ Messaging.removeAllMessages();
2724
+ oUi.setProperty("/busy", true);
2725
+ oModel.submitBatch("changes").then(function () {
2726
+ oUi.setProperty("/busy", false);
2727
+ if (oModel.hasPendingChanges("changes")) {
2728
+ MessageBox.error(support.errorText());
2729
+ return;
2730
+ }
2731
+ that.getOwnerComponent().markStale();
2732
+ if (bCreate) {
2733
+ return oContext.created().then(function () {
2734
+ that.getOwnerComponent().setTransient(undefined);
2735
+ if (that._bFormEntry) {
2736
+ // form entry: a new, empty form
2737
+ MessageToast.show("Object created");
2738
+ that._newTransient();
2739
+ that._onNewMatched();
2740
+ return;
2741
+ }
2742
+ that._mode(false, false);
2743
+ MessageToast.show("Object created");
2744
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: support.keyOf(oContext.getPath()) }, true);
2745
+ });
2746
+ }
2747
+ that._mode(false, false);
2748
+ MessageToast.show("Changes saved");
2749
+ }, function (oError) {
2750
+ oUi.setProperty("/busy", false);
2751
+ MessageBox.error(support.errorText(oError));
2752
+ });
2753
+ },
2754
+
2755
+ /** Save of a draft: Activate, then the active instance (D54). */
2756
+ _activate: function () {
2757
+ var that = this;
2758
+ var oUi = this._ui();
2759
+ Messaging.removeAllMessages();
2760
+ oUi.setProperty("/busy", true);
2761
+ this.getView().getModel().submitBatch("$auto").then(function () {
2762
+ return that._draftAction("Activate");
2763
+ }).then(function (oActive) {
2764
+ oUi.setProperty("/busy", false);
2765
+ that.getOwnerComponent().markStale();
2766
+ MessageToast.show(oUi.getProperty("/createMode") ? "Object created" : "Changes saved");
2767
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: support.keyOf(oActive.getPath()) }, true);
2768
+ }, function (oError) {
2769
+ oUi.setProperty("/busy", false);
2770
+ MessageBox.error(support.errorText(oError));
2771
+ });
2772
+ },
2773
+
2774
+ onCancel: function () {
2775
+ if (this._ui().getProperty("/draft")) {
2776
+ // draft (D54): Discard, back to the active instance (or the list)
2777
+ var that = this;
2778
+ var oContext = this.getView().getBindingContext();
2779
+ MessageBox.confirm(that._bHasActive ? "Discard all changes?" : "Discard this draft?", {
2780
+ title: "Discard Draft",
2781
+ actions: ["Discard", MessageBox.Action.CANCEL],
2782
+ emphasizedAction: "Discard",
2783
+ onClose: function (sAction) {
2784
+ if (sAction !== "Discard") {
2785
+ return;
2786
+ }
2787
+ that._stopCollab();
2788
+ var bHasActive = that._bHasActive;
2789
+ var sActive = support.keyOf(oContext.getPath()).replace("IsActiveEntity=false", "IsActiveEntity=true");
2790
+ // DELETE of the draft root discards the draft (D54)
2791
+ oContext.delete("$auto").then(function () {
2792
+ that.getOwnerComponent().markStale();
2793
+ MessageToast.show("Draft discarded");
2794
+ if (bHasActive) {
2795
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: sActive }, true);
2796
+ } else {
2797
+ that._back();
2798
+ }
2799
+ }, function (oError) {
2800
+ MessageBox.error(support.errorText(oError));
2801
+ });
2802
+ }
2803
+ });
2804
+ return;
2805
+ }
2806
+ var bCreate = this._ui().getProperty("/createMode");
2807
+ this.getView().getModel().resetChanges("changes");
2808
+ Messaging.removeAllMessages();
2809
+ this._mode(false, false);
2810
+ if (bCreate && this._bFormEntry) {
2811
+ this._newTransient();
2812
+ this._onNewMatched();
2813
+ } else if (bCreate) {
2814
+ this.getOwnerComponent().setTransient(undefined);
2815
+ this._back();
2816
+ }
2817
+ },
2818
+
2819
+ onDelete: function () {
2820
+ var oContext = this.getView().getBindingContext();
2821
+ var that = this;
2822
+ MessageBox.confirm("Delete this object?", {
2823
+ onClose: function (sAction) {
2824
+ if (sAction !== MessageBox.Action.OK) {
2825
+ return;
2826
+ }
2827
+ oContext.delete("$auto").then(function () {
2828
+ that.getOwnerComponent().markStale();
2829
+ MessageToast.show("Object deleted");
2830
+ that._back();
2831
+ }, function (oError) {
2832
+ MessageBox.error(support.errorText(oError));
2833
+ });
2834
+ }
2835
+ });
2836
+ },
2837
+
2838
+ onAction: function (oEvent) {
2839
+ var oPage = this._page();
2840
+ var oAction = support.action(oPage, oEvent.getSource().data("action"));
2841
+ var oContext = this.getView().getBindingContext();
2842
+ var that = this;
2843
+ if (!oAction) {
2844
+ return;
2845
+ }
2846
+ support.invoke(this.getView(), oPage, oAction, oContext).then(function (oResult) {
2847
+ if (oResult === null) {
2848
+ return;
2849
+ }
2850
+ MessageToast.show(oAction.label + ": done");
2851
+ that.getOwnerComponent().markStale();
2852
+ if (oAction.factory && oResult && oAction.returnsEntity) {
2853
+ that.getOwnerComponent().getRouter().navTo(that._sSet + "Object", { key: support.keyPredicate(oPage, oResult.getObject()) });
2854
+ return;
2855
+ }
2856
+ that.getView().getElementBinding().refresh();
2857
+ }, function (oError) {
2858
+ MessageBox.error(support.errorText(oError));
2859
+ });
2860
+ },
2861
+
2862
+ onCreateChild: function (oEvent) {
2863
+ var oTable = this.byId(oEvent.getSource().data("table"));
2864
+ oTable.getBinding("items").create({}, true);
2865
+ },
2866
+
2867
+ onDeleteChild: function (oEvent) {
2868
+ var oTable = this.byId(oEvent.getSource().data("table"));
2869
+ var oItem = oTable.getSelectedItem();
2870
+ if (oItem) {
2871
+ oItem.getBindingContext().delete(this._ui().getProperty("/draft") ? "$auto" : "changes");
2872
+ this._ui().setProperty("/childSelected", false);
2873
+ }
2874
+ },
2875
+
2876
+ onChildSelectionChange: function (oEvent) {
2877
+ this._ui().setProperty("/childSelected", !!oEvent.getSource().getSelectedItem());
2878
+ },
2879
+
2880
+ onChildPress: function (oEvent) {
2881
+ if (this._ui().getProperty("/editMode")) {
2882
+ return;
2883
+ }
2884
+ var oItem = oEvent.getSource();
2885
+ var sSet = oItem.getParent().data("set");
2886
+ this.getOwnerComponent().getRouter().navTo(sSet + "Object", { key: support.keyOf(oItem.getBindingContext().getPath()) });
2887
+ },
2888
+
2889
+ onMessages: function (oEvent) {
2890
+ if (!this._oPopover) {
2891
+ this._oPopover = new MessagePopover({
2892
+ items: { path: "message>/", template: new MessageItem({ type: "{message>type}", title: "{message>message}", subtitle: "{message>additionalText}" }) }
2893
+ });
2894
+ this.getView().addDependent(this._oPopover);
2895
+ }
2896
+ this._oPopover.toggle(oEvent.getSource());
2897
+ }
2898
+ });
2899
+ });
2900
+ `;
2901
+ //# sourceMappingURL=static.js.map