@linkup-ai/abap-ai 2.0.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.
- package/README.md +384 -0
- package/dist/adt-client.js +364 -0
- package/dist/cli/activate.js +113 -0
- package/dist/cli/init.js +333 -0
- package/dist/cli/remove.js +80 -0
- package/dist/cli/status.js +229 -0
- package/dist/cli/systems.js +68 -0
- package/dist/cli.js +81 -0
- package/dist/index.js +1318 -0
- package/dist/knowledge/abap/abap-dictionary.md +199 -0
- package/dist/knowledge/abap/abap-sql.md +296 -0
- package/dist/knowledge/abap/amdp.md +273 -0
- package/dist/knowledge/abap/clean-code.md +293 -0
- package/dist/knowledge/abap/cloud-background-processing.md +250 -0
- package/dist/knowledge/abap/cloud-communication.md +265 -0
- package/dist/knowledge/abap/cloud-development.md +176 -0
- package/dist/knowledge/abap/cloud-extensibility.md +252 -0
- package/dist/knowledge/abap/cloud-released-apis.md +261 -0
- package/dist/knowledge/abap/constructor-expressions.md +289 -0
- package/dist/knowledge/abap/enhancements.md +232 -0
- package/dist/knowledge/abap/exceptions.md +271 -0
- package/dist/knowledge/abap/internal-tables.md +205 -0
- package/dist/knowledge/abap/object-orientation.md +298 -0
- package/dist/knowledge/abap/performance.md +216 -0
- package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
- package/dist/knowledge/abap/rap-business-events.md +216 -0
- package/dist/knowledge/abap/rap-draft.md +191 -0
- package/dist/knowledge/abap/rap-eml.md +453 -0
- package/dist/knowledge/abap/rap-end-to-end.md +486 -0
- package/dist/knowledge/abap/rap-feature-control.md +185 -0
- package/dist/knowledge/abap/rap-numbering.md +280 -0
- package/dist/knowledge/abap/rap-service-exposure.md +163 -0
- package/dist/knowledge/abap/rap-unmanaged.md +468 -0
- package/dist/knowledge/abap/string-processing.md +180 -0
- package/dist/knowledge/abap/unit-testing.md +303 -0
- package/dist/knowledge/abap-cds/access-control.md +241 -0
- package/dist/knowledge/abap-cds/annotations.md +331 -0
- package/dist/knowledge/abap-cds/associations.md +254 -0
- package/dist/knowledge/abap-cds/expressions.md +230 -0
- package/dist/knowledge/abap-cds/functions.md +245 -0
- package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
- package/dist/knowledge/cap/authentication.md +278 -0
- package/dist/knowledge/cap/cdl-syntax.md +247 -0
- package/dist/knowledge/cap/cql-queries.md +266 -0
- package/dist/knowledge/cap/deployment.md +343 -0
- package/dist/knowledge/cap/event-handlers.md +287 -0
- package/dist/knowledge/cap/fiori-integration.md +303 -0
- package/dist/knowledge/cap/service-definitions.md +287 -0
- package/dist/knowledge/fiori/annotations.md +347 -0
- package/dist/knowledge/fiori/deployment.md +340 -0
- package/dist/knowledge/fiori/fiori-elements.md +332 -0
- package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
- package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
- package/dist/knowledge/fiori/ui5-controllers.md +358 -0
- package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
- package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
- package/dist/knowledge/fiori/ui5-manifest.md +411 -0
- package/dist/knowledge/fiori/ui5-routing.md +303 -0
- package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
- package/dist/logger.js +114 -0
- package/dist/system-profile.js +207 -0
- package/dist/tools/abap-doc.js +72 -0
- package/dist/tools/abapgit.js +161 -0
- package/dist/tools/activate.js +68 -0
- package/dist/tools/atc-check.js +117 -0
- package/dist/tools/auth-object.js +56 -0
- package/dist/tools/breakpoints.js +76 -0
- package/dist/tools/call-hierarchy.js +84 -0
- package/dist/tools/cds-annotations.js +98 -0
- package/dist/tools/cds-dependencies.js +65 -0
- package/dist/tools/check.js +47 -0
- package/dist/tools/code-completion.js +70 -0
- package/dist/tools/code-coverage.js +111 -0
- package/dist/tools/create-amdp.js +111 -0
- package/dist/tools/create-dcl.js +81 -0
- package/dist/tools/create-transport.js +38 -0
- package/dist/tools/create.js +285 -0
- package/dist/tools/data-preview.js +37 -0
- package/dist/tools/delete.js +45 -0
- package/dist/tools/deploy-bsp.js +298 -0
- package/dist/tools/discovery.js +59 -0
- package/dist/tools/element-info.js +93 -0
- package/dist/tools/enhancements.js +186 -0
- package/dist/tools/extract-method.js +44 -0
- package/dist/tools/function-group.js +59 -0
- package/dist/tools/knowledge.js +275 -0
- package/dist/tools/lock-object.js +75 -0
- package/dist/tools/message-class.js +67 -0
- package/dist/tools/navigate.js +80 -0
- package/dist/tools/number-range.js +57 -0
- package/dist/tools/object-documentation.js +43 -0
- package/dist/tools/object-structure.js +78 -0
- package/dist/tools/object-versions.js +57 -0
- package/dist/tools/package-contents.js +60 -0
- package/dist/tools/pretty-printer.js +35 -0
- package/dist/tools/publish-binding.js +49 -0
- package/dist/tools/quick-fix.js +69 -0
- package/dist/tools/read.js +167 -0
- package/dist/tools/refactor-rename.js +60 -0
- package/dist/tools/release-transport.js +24 -0
- package/dist/tools/released-apis.js +51 -0
- package/dist/tools/repository-tree.js +90 -0
- package/dist/tools/scaffold-rap.js +642 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/shared/data-format.js +101 -0
- package/dist/tools/sql-console.js +17 -0
- package/dist/tools/system-info.js +270 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +67 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +101 -0
- package/package.json +49 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# UI5 Controllers — lifecycle, events, model access, navigation
|
|
2
|
+
|
|
3
|
+
## Controller Definition
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
sap.ui.define([
|
|
7
|
+
"sap/ui/core/mvc/Controller",
|
|
8
|
+
"sap/ui/model/json/JSONModel",
|
|
9
|
+
"sap/m/MessageBox",
|
|
10
|
+
"sap/m/MessageToast"
|
|
11
|
+
], function(Controller, JSONModel, MessageBox, MessageToast) {
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
return Controller.extend("com.myapp.controller.Main", {
|
|
15
|
+
|
|
16
|
+
onInit: function() {
|
|
17
|
+
// Called once when view is instantiated
|
|
18
|
+
var oViewModel = new JSONModel({ busy: false, editMode: false });
|
|
19
|
+
this.getView().setModel(oViewModel, "view");
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
onBeforeRendering: function() {
|
|
23
|
+
// Called before every rendering (initial + re-render)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
onAfterRendering: function() {
|
|
27
|
+
// Called after every rendering — DOM is available
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
onExit: function() {
|
|
31
|
+
// Called when view is destroyed — cleanup
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## TypeScript Controller
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import Controller from "sap/ui/core/mvc/Controller";
|
|
41
|
+
import JSONModel from "sap/ui/model/json/JSONModel";
|
|
42
|
+
import MessageBox from "sap/m/MessageBox";
|
|
43
|
+
import MessageToast from "sap/m/MessageToast";
|
|
44
|
+
import Event from "sap/ui/base/Event";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @namespace com.myapp.controller
|
|
48
|
+
*/
|
|
49
|
+
export default class Main extends Controller {
|
|
50
|
+
public onInit(): void {
|
|
51
|
+
const oViewModel = new JSONModel({ busy: false, editMode: false });
|
|
52
|
+
this.getView()?.setModel(oViewModel, "view");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public onItemPress(oEvent: Event): void {
|
|
56
|
+
const oItem = oEvent.getSource();
|
|
57
|
+
const sPath = oItem.getBindingContext().getPath();
|
|
58
|
+
// navigate...
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Accessing Controls
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// By ID (from XML view)
|
|
67
|
+
var oTable = this.byId("orderTable");
|
|
68
|
+
var oInput = this.byId("nameInput");
|
|
69
|
+
|
|
70
|
+
// Get view
|
|
71
|
+
var oView = this.getView();
|
|
72
|
+
|
|
73
|
+
// Get control from event
|
|
74
|
+
onButtonPress: function(oEvent) {
|
|
75
|
+
var oButton = oEvent.getSource();
|
|
76
|
+
var oItem = oEvent.getParameter("listItem"); // for List/Table events
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Model Access
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
// Get named model
|
|
84
|
+
var oModel = this.getView().getModel(); // default model
|
|
85
|
+
var oModel = this.getView().getModel("view"); // named model
|
|
86
|
+
|
|
87
|
+
// Get OData model (from component)
|
|
88
|
+
var oModel = this.getOwnerComponent().getModel();
|
|
89
|
+
|
|
90
|
+
// Read property
|
|
91
|
+
var sName = oModel.getProperty("/Name");
|
|
92
|
+
var sValue = oModel.getProperty(sPath + "/Amount");
|
|
93
|
+
|
|
94
|
+
// Set property
|
|
95
|
+
oModel.setProperty("/editMode", true);
|
|
96
|
+
oModel.setProperty(sPath + "/Status", "A");
|
|
97
|
+
|
|
98
|
+
// Get data from binding context
|
|
99
|
+
onItemPress: function(oEvent) {
|
|
100
|
+
var oContext = oEvent.getSource().getBindingContext();
|
|
101
|
+
var sOrderID = oContext.getProperty("OrderID");
|
|
102
|
+
var oData = oContext.getObject(); // entire record
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## OData V2 — CRUD Operations
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
var oModel = this.getView().getModel(); // sap.ui.model.odata.v2.ODataModel
|
|
110
|
+
|
|
111
|
+
// READ
|
|
112
|
+
oModel.read("/Orders('1000')", {
|
|
113
|
+
success: function(oData) { /* oData = record */ },
|
|
114
|
+
error: function(oError) { /* handle */ }
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// READ with expand
|
|
118
|
+
oModel.read("/Orders('1000')", {
|
|
119
|
+
urlParameters: { "$expand": "to_Items" },
|
|
120
|
+
success: function(oData) { /* oData.to_Items = items array */ }
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// CREATE
|
|
124
|
+
oModel.create("/Orders", oPayload, {
|
|
125
|
+
success: function(oData) {
|
|
126
|
+
MessageToast.show("Created: " + oData.OrderID);
|
|
127
|
+
},
|
|
128
|
+
error: function(oError) {
|
|
129
|
+
MessageBox.error("Creation failed");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// UPDATE
|
|
134
|
+
oModel.update("/Orders('1000')", oPayload, {
|
|
135
|
+
success: function() { MessageToast.show("Updated"); },
|
|
136
|
+
error: function(oError) { /* handle */ }
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// DELETE
|
|
140
|
+
oModel.remove("/Orders('1000')", {
|
|
141
|
+
success: function() { MessageToast.show("Deleted"); },
|
|
142
|
+
error: function(oError) { /* handle */ }
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Batch — group multiple operations
|
|
146
|
+
oModel.setDeferredGroups(["batchGroup"]);
|
|
147
|
+
oModel.create("/Orders", oPayload1, { groupId: "batchGroup" });
|
|
148
|
+
oModel.create("/Orders", oPayload2, { groupId: "batchGroup" });
|
|
149
|
+
oModel.submitChanges({
|
|
150
|
+
groupId: "batchGroup",
|
|
151
|
+
success: function() { /* all succeeded */ },
|
|
152
|
+
error: function() { /* batch failed */ }
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Refresh
|
|
156
|
+
oModel.refresh(true); // force reload from server
|
|
157
|
+
oTable.getBinding("items").refresh(); // refresh specific binding
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## OData V4 — CRUD Operations
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
var oModel = this.getView().getModel(); // sap.ui.model.odata.v4.ODataModel
|
|
164
|
+
|
|
165
|
+
// CREATE via list binding
|
|
166
|
+
var oList = this.byId("orderTable").getBinding("items");
|
|
167
|
+
var oContext = oList.create({
|
|
168
|
+
CustomerID: "CUST100",
|
|
169
|
+
Amount: 500
|
|
170
|
+
});
|
|
171
|
+
// oContext is immediately available (optimistic)
|
|
172
|
+
|
|
173
|
+
// Save pending changes
|
|
174
|
+
oModel.submitBatch(oModel.getUpdateGroupId());
|
|
175
|
+
|
|
176
|
+
// DELETE via context
|
|
177
|
+
var oContext = oEvent.getSource().getBindingContext();
|
|
178
|
+
oContext.delete().then(function() {
|
|
179
|
+
MessageToast.show("Deleted");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// UPDATE — modify bound property, then submitBatch
|
|
183
|
+
oModel.setProperty(sPath + "/Status", "A");
|
|
184
|
+
oModel.submitBatch(oModel.getUpdateGroupId());
|
|
185
|
+
|
|
186
|
+
// Refresh
|
|
187
|
+
oList.refresh();
|
|
188
|
+
oContext.refresh();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Event Handling
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
// Button press
|
|
195
|
+
onSave: function() {
|
|
196
|
+
var oModel = this.getView().getModel();
|
|
197
|
+
if (oModel.hasPendingChanges()) {
|
|
198
|
+
oModel.submitChanges({
|
|
199
|
+
success: function() { MessageToast.show("Saved"); }
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// List item press
|
|
205
|
+
onItemPress: function(oEvent) {
|
|
206
|
+
var oItem = oEvent.getSource();
|
|
207
|
+
var oContext = oItem.getBindingContext();
|
|
208
|
+
var sID = oContext.getProperty("OrderID");
|
|
209
|
+
this.getRouter().navTo("detail", { orderID: sID });
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Search
|
|
213
|
+
onSearch: function(oEvent) {
|
|
214
|
+
var sQuery = oEvent.getParameter("query");
|
|
215
|
+
var oFilter = sQuery
|
|
216
|
+
? new sap.ui.model.Filter("Name", "Contains", sQuery)
|
|
217
|
+
: [];
|
|
218
|
+
this.byId("orderTable").getBinding("items").filter(oFilter);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// Input change
|
|
222
|
+
onNameChange: function(oEvent) {
|
|
223
|
+
var sValue = oEvent.getParameter("value");
|
|
224
|
+
// validate, update model, etc.
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// Select change
|
|
228
|
+
onStatusChange: function(oEvent) {
|
|
229
|
+
var sKey = oEvent.getParameter("selectedItem").getKey();
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Formatters
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
// In controller or separate formatter.js
|
|
237
|
+
formatStatusState: function(sStatus) {
|
|
238
|
+
switch (sStatus) {
|
|
239
|
+
case "A": return "Success";
|
|
240
|
+
case "X": return "Error";
|
|
241
|
+
case "O": return "Warning";
|
|
242
|
+
default: return "None";
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
formatAmount: function(fAmount, sCurrency) {
|
|
247
|
+
if (!fAmount) return "";
|
|
248
|
+
return parseFloat(fAmount).toFixed(2) + " " + sCurrency;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
formatDate: function(sDate) {
|
|
252
|
+
if (!sDate) return "";
|
|
253
|
+
var oFormat = sap.ui.core.format.DateFormat.getDateInstance({ style: "medium" });
|
|
254
|
+
return oFormat.format(new Date(sDate));
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```xml
|
|
259
|
+
<!-- Using in XML view -->
|
|
260
|
+
<ObjectStatus text="{StatusText}"
|
|
261
|
+
state="{path: 'Status', formatter: '.formatStatusState'}"/>
|
|
262
|
+
<Text text="{parts: [{path: 'Amount'}, {path: 'Currency'}],
|
|
263
|
+
formatter: '.formatAmount'}"/>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Router Access
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
// Get router from component
|
|
270
|
+
getRouter: function() {
|
|
271
|
+
return this.getOwnerComponent().getRouter();
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// Navigate
|
|
275
|
+
onNavToDetail: function(sID) {
|
|
276
|
+
this.getRouter().navTo("detail", { orderID: sID });
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Navigate back
|
|
280
|
+
onNavBack: function() {
|
|
281
|
+
var oHistory = sap.ui.core.routing.History.getInstance();
|
|
282
|
+
var sPreviousHash = oHistory.getPreviousHash();
|
|
283
|
+
if (sPreviousHash !== undefined) {
|
|
284
|
+
window.history.go(-1);
|
|
285
|
+
} else {
|
|
286
|
+
this.getRouter().navTo("list", {}, true);
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
// Attach route matched (in onInit)
|
|
291
|
+
onInit: function() {
|
|
292
|
+
this.getRouter().getRoute("detail").attachPatternMatched(this._onRouteMatched, this);
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
_onRouteMatched: function(oEvent) {
|
|
296
|
+
var sOrderID = oEvent.getParameter("arguments").orderID;
|
|
297
|
+
// V2: bind element
|
|
298
|
+
this.getView().bindElement({
|
|
299
|
+
path: "/Orders('" + sOrderID + "')",
|
|
300
|
+
parameters: { expand: "to_Items" },
|
|
301
|
+
events: {
|
|
302
|
+
dataRequested: function() { /* show busy */ },
|
|
303
|
+
dataReceived: function() { /* hide busy */ }
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
// V4: bind element
|
|
307
|
+
this.getView().bindElement("/Orders('" + sOrderID + "')");
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## i18n Access
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
// Get resource bundle
|
|
315
|
+
var oBundle = this.getView().getModel("i18n").getResourceBundle();
|
|
316
|
+
var sText = oBundle.getText("orderCreated", [sOrderID]); // with placeholder
|
|
317
|
+
|
|
318
|
+
// In XML view
|
|
319
|
+
// text="{i18n>orderCreated}"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Busy Handling
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
onInit: function() {
|
|
326
|
+
this.getView().setModel(new JSONModel({ busy: false }), "view");
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
setBusy: function(bBusy) {
|
|
330
|
+
this.getView().getModel("view").setProperty("/busy", bBusy);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```xml
|
|
335
|
+
<Page busyIndicatorDelay="0" busy="{view>/busy}">
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Rules
|
|
339
|
+
- `onInit` for one-time setup (models, route attachment); never for DOM manipulation
|
|
340
|
+
- `onAfterRendering` for DOM access — but prefer data binding over DOM manipulation
|
|
341
|
+
- Always access controls via `this.byId()`, never `sap.ui.getCore().byId()`
|
|
342
|
+
- Get OData model from `getOwnerComponent().getModel()` (not view) for shared model
|
|
343
|
+
- Use `getBindingContext().getProperty()` to read data from events (not hardcoded paths)
|
|
344
|
+
- Separate formatters into `model/formatter.js` when reused across views
|
|
345
|
+
- Always handle `error` callback in OData operations
|
|
346
|
+
- V4: use `oContext.delete()` / `oList.create()` — not `oModel.remove/create`
|
|
347
|
+
|
|
348
|
+
## Anti-Patterns
|
|
349
|
+
| Anti-Pattern | Correct |
|
|
350
|
+
|---|---|
|
|
351
|
+
| `sap.ui.getCore().byId("id")` | `this.byId("id")` |
|
|
352
|
+
| DOM manipulation in `onInit` | Use data binding or `onAfterRendering` |
|
|
353
|
+
| Hardcoded paths in controller | Use binding context: `oContext.getProperty("field")` |
|
|
354
|
+
| Missing error handler in OData calls | Always handle `error` callback |
|
|
355
|
+
| Global variables in controller | Use view model (`JSONModel`) for state |
|
|
356
|
+
| `window.location` for navigation | Use `this.getRouter().navTo()` |
|
|
357
|
+
| Sync operations (`oModel.read` without callbacks) | Always use async pattern with success/error |
|
|
358
|
+
| Formatter with side effects | Formatters must be pure functions (no model changes) |
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# UI5 Data Binding — OData, JSON, property, aggregation, expression
|
|
2
|
+
|
|
3
|
+
## Binding Modes
|
|
4
|
+
|
|
5
|
+
| Mode | Direction | Use |
|
|
6
|
+
|------|-----------|-----|
|
|
7
|
+
| `OneWay` | Model → View | Display data (default for OData) |
|
|
8
|
+
| `TwoWay` | Model ↔ View | Editable forms (default for JSON) |
|
|
9
|
+
| `OneTime` | Model → View (once) | Static data, config |
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// Set default binding mode
|
|
13
|
+
oModel.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Model Types
|
|
17
|
+
|
|
18
|
+
### JSON Model (client-side)
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
var oModel = new sap.ui.model.json.JSONModel({
|
|
22
|
+
orders: [
|
|
23
|
+
{ id: "001", customer: "ACME", amount: 500 },
|
|
24
|
+
{ id: "002", customer: "Beta", amount: 300 }
|
|
25
|
+
],
|
|
26
|
+
selectedTab: "all",
|
|
27
|
+
editMode: false
|
|
28
|
+
});
|
|
29
|
+
this.getView().setModel(oModel, "local");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```xml
|
|
33
|
+
<Input value="{local>/selectedTab}"/>
|
|
34
|
+
<List items="{local>/orders}">
|
|
35
|
+
<StandardListItem title="{local>customer}" description="{local>amount}"/>
|
|
36
|
+
</List>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### OData V2 Model
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
// manifest.json
|
|
43
|
+
{
|
|
44
|
+
"sap.ui5": {
|
|
45
|
+
"models": {
|
|
46
|
+
"": {
|
|
47
|
+
"dataSource": "mainService",
|
|
48
|
+
"preload": true,
|
|
49
|
+
"settings": {
|
|
50
|
+
"defaultBindingMode": "TwoWay",
|
|
51
|
+
"defaultCountMode": "Inline",
|
|
52
|
+
"refreshAfterChange": false,
|
|
53
|
+
"useBatch": true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### OData V4 Model
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"sap.ui5": {
|
|
66
|
+
"models": {
|
|
67
|
+
"": {
|
|
68
|
+
"dataSource": "mainService",
|
|
69
|
+
"preload": true,
|
|
70
|
+
"settings": {
|
|
71
|
+
"synchronizationMode": "None",
|
|
72
|
+
"operationMode": "Server",
|
|
73
|
+
"autoExpandSelect": true,
|
|
74
|
+
"groupId": "$auto"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Property Binding
|
|
83
|
+
|
|
84
|
+
```xml
|
|
85
|
+
<!-- Simple -->
|
|
86
|
+
<Text text="{Name}"/>
|
|
87
|
+
<Input value="{Amount}"/>
|
|
88
|
+
|
|
89
|
+
<!-- Named model -->
|
|
90
|
+
<Text text="{view>/editMode}"/>
|
|
91
|
+
<Text text="{i18n>orderTitle}"/>
|
|
92
|
+
|
|
93
|
+
<!-- Absolute path -->
|
|
94
|
+
<Text text="{/Orders('001')/Name}"/>
|
|
95
|
+
|
|
96
|
+
<!-- With type (formatting + validation) -->
|
|
97
|
+
<Input value="{
|
|
98
|
+
path: 'Amount',
|
|
99
|
+
type: 'sap.ui.model.type.Float',
|
|
100
|
+
formatOptions: { minFractionDigits: 2, maxFractionDigits: 2 },
|
|
101
|
+
constraints: { minimum: 0 }
|
|
102
|
+
}"/>
|
|
103
|
+
|
|
104
|
+
<DatePicker value="{
|
|
105
|
+
path: 'OrderDate',
|
|
106
|
+
type: 'sap.ui.model.type.Date',
|
|
107
|
+
formatOptions: { style: 'medium', source: { pattern: 'yyyyMMdd' } }
|
|
108
|
+
}"/>
|
|
109
|
+
|
|
110
|
+
<Input value="{
|
|
111
|
+
path: 'Currency',
|
|
112
|
+
type: 'sap.ui.model.type.Currency',
|
|
113
|
+
formatOptions: { showMeasure: false }
|
|
114
|
+
}" description="{Currency}"/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Aggregation Binding (Lists/Tables)
|
|
118
|
+
|
|
119
|
+
```xml
|
|
120
|
+
<!-- Template binding -->
|
|
121
|
+
<List items="{/Orders}">
|
|
122
|
+
<StandardListItem title="{OrderID}" description="{CustomerName}"/>
|
|
123
|
+
</List>
|
|
124
|
+
|
|
125
|
+
<!-- With sorter -->
|
|
126
|
+
<List items="{
|
|
127
|
+
path: '/Orders',
|
|
128
|
+
sorter: { path: 'OrderDate', descending: true }
|
|
129
|
+
}">
|
|
130
|
+
<StandardListItem title="{OrderID}"/>
|
|
131
|
+
</List>
|
|
132
|
+
|
|
133
|
+
<!-- With filter -->
|
|
134
|
+
<List items="{
|
|
135
|
+
path: '/Orders',
|
|
136
|
+
filters: [{ path: 'Status', operator: 'EQ', value1: 'O' }]
|
|
137
|
+
}">
|
|
138
|
+
<StandardListItem title="{OrderID}"/>
|
|
139
|
+
</List>
|
|
140
|
+
|
|
141
|
+
<!-- With parameters (OData) -->
|
|
142
|
+
<Table items="{
|
|
143
|
+
path: '/Orders',
|
|
144
|
+
parameters: {
|
|
145
|
+
expand: 'to_Items',
|
|
146
|
+
select: 'OrderID,CustomerName,Amount,Status'
|
|
147
|
+
}
|
|
148
|
+
}">
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Element Binding (Object/Detail Pages)
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
// V2: Bind view to single entity
|
|
155
|
+
this.getView().bindElement({
|
|
156
|
+
path: "/Orders('1000')",
|
|
157
|
+
parameters: { expand: "to_Items" },
|
|
158
|
+
events: {
|
|
159
|
+
change: this._onBindingChange.bind(this),
|
|
160
|
+
dataRequested: function() { oView.setBusy(true); },
|
|
161
|
+
dataReceived: function() { oView.setBusy(false); }
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// V4: simpler
|
|
166
|
+
this.getView().bindElement("/Orders('1000')");
|
|
167
|
+
|
|
168
|
+
// After binding, all controls in the view resolve relative paths
|
|
169
|
+
// <Text text="{OrderID}"/> → resolves to /Orders('1000')/OrderID
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Expression Binding
|
|
173
|
+
|
|
174
|
+
```xml
|
|
175
|
+
<!-- Ternary -->
|
|
176
|
+
<Text text="{= ${Amount} > 1000 ? 'High Value' : 'Standard'}"/>
|
|
177
|
+
|
|
178
|
+
<!-- Boolean -->
|
|
179
|
+
<Input editable="{= ${Status} === 'O'}"/>
|
|
180
|
+
<Button visible="{= ${Status} !== 'A'}"/>
|
|
181
|
+
<Panel visible="{= ${Amount} > 0}"/>
|
|
182
|
+
|
|
183
|
+
<!-- Arithmetic -->
|
|
184
|
+
<Text text="{= ${Quantity} * ${UnitPrice}}"/>
|
|
185
|
+
|
|
186
|
+
<!-- Negation -->
|
|
187
|
+
<Input editable="{= !${view>/readOnly}}"/>
|
|
188
|
+
|
|
189
|
+
<!-- String comparison -->
|
|
190
|
+
<ObjectStatus state="{= ${Status} === 'A' ? 'Success' : ${Status} === 'X' ? 'Error' : 'Warning'}"/>
|
|
191
|
+
|
|
192
|
+
<!-- With formatter -->
|
|
193
|
+
<Text text="{= ${Amount}.toFixed(2) + ' ' + ${Currency}}"/>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Composite Binding (multiple parts)
|
|
197
|
+
|
|
198
|
+
```xml
|
|
199
|
+
<!-- parts array -->
|
|
200
|
+
<Text text="{
|
|
201
|
+
parts: [
|
|
202
|
+
{ path: 'FirstName' },
|
|
203
|
+
{ path: 'LastName' }
|
|
204
|
+
],
|
|
205
|
+
formatter: '.formatFullName'
|
|
206
|
+
}"/>
|
|
207
|
+
|
|
208
|
+
<!-- Simple concatenation with expression -->
|
|
209
|
+
<Text text="{= ${FirstName} + ' ' + ${LastName}}"/>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Filters (in Controller)
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
var aFilters = [];
|
|
216
|
+
|
|
217
|
+
// Simple filter
|
|
218
|
+
aFilters.push(new sap.ui.model.Filter("Status", "EQ", "O"));
|
|
219
|
+
|
|
220
|
+
// Contains
|
|
221
|
+
aFilters.push(new sap.ui.model.Filter("Name", "Contains", sQuery));
|
|
222
|
+
|
|
223
|
+
// Between
|
|
224
|
+
aFilters.push(new sap.ui.model.Filter("Amount", "BT", 100, 1000));
|
|
225
|
+
|
|
226
|
+
// AND (multiple filters)
|
|
227
|
+
var oFilter = new sap.ui.model.Filter({
|
|
228
|
+
filters: [
|
|
229
|
+
new sap.ui.model.Filter("Status", "EQ", "O"),
|
|
230
|
+
new sap.ui.model.Filter("Amount", "GT", 100)
|
|
231
|
+
],
|
|
232
|
+
and: true
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// OR
|
|
236
|
+
var oFilter = new sap.ui.model.Filter({
|
|
237
|
+
filters: [
|
|
238
|
+
new sap.ui.model.Filter("Status", "EQ", "O"),
|
|
239
|
+
new sap.ui.model.Filter("Status", "EQ", "P")
|
|
240
|
+
],
|
|
241
|
+
and: false
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Apply to binding
|
|
245
|
+
this.byId("orderTable").getBinding("items").filter(aFilters);
|
|
246
|
+
|
|
247
|
+
// Clear filters
|
|
248
|
+
this.byId("orderTable").getBinding("items").filter([]);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Filter Operators
|
|
252
|
+
|
|
253
|
+
| Operator | Description | Example |
|
|
254
|
+
|----------|-------------|---------|
|
|
255
|
+
| `EQ` | Equal | `"EQ", "A"` |
|
|
256
|
+
| `NE` | Not equal | `"NE", "D"` |
|
|
257
|
+
| `GT` | Greater than | `"GT", 100` |
|
|
258
|
+
| `GE` | Greater or equal | `"GE", 100` |
|
|
259
|
+
| `LT` | Less than | `"LT", 1000` |
|
|
260
|
+
| `LE` | Less or equal | `"LE", 1000` |
|
|
261
|
+
| `BT` | Between | `"BT", 100, 1000` |
|
|
262
|
+
| `Contains` | Contains string | `"Contains", "SAP"` |
|
|
263
|
+
| `StartsWith` | Starts with | `"StartsWith", "Z"` |
|
|
264
|
+
| `EndsWith` | Ends with | `"EndsWith", "GmbH"` |
|
|
265
|
+
|
|
266
|
+
## Sorters (in Controller)
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
var aSorters = [
|
|
270
|
+
new sap.ui.model.Sorter("OrderDate", true), // descending
|
|
271
|
+
new sap.ui.model.Sorter("Amount", false) // ascending
|
|
272
|
+
];
|
|
273
|
+
this.byId("orderTable").getBinding("items").sort(aSorters);
|
|
274
|
+
|
|
275
|
+
// Grouped sorting
|
|
276
|
+
var oSorter = new sap.ui.model.Sorter("Category", false, function(oContext) {
|
|
277
|
+
var sCategory = oContext.getProperty("Category");
|
|
278
|
+
return { key: sCategory, text: "Category: " + sCategory };
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Common Data Types
|
|
283
|
+
|
|
284
|
+
| Type | Use | Import |
|
|
285
|
+
|------|-----|--------|
|
|
286
|
+
| `sap.ui.model.type.String` | Text with constraints | `constraints: { maxLength: 40 }` |
|
|
287
|
+
| `sap.ui.model.type.Integer` | Whole numbers | `constraints: { minimum: 0 }` |
|
|
288
|
+
| `sap.ui.model.type.Float` | Decimals | `formatOptions: { minFractionDigits: 2 }` |
|
|
289
|
+
| `sap.ui.model.type.Date` | Date formatting | `formatOptions: { style: 'medium' }` |
|
|
290
|
+
| `sap.ui.model.type.DateTime` | Date + time | `formatOptions: { style: 'medium' }` |
|
|
291
|
+
| `sap.ui.model.type.Currency` | Amount + currency | `formatOptions: { showMeasure: true }` |
|
|
292
|
+
|
|
293
|
+
## Rules
|
|
294
|
+
- Default model (no name) accessed via `{PropertyName}`; named model via `{modelName>PropertyName}`
|
|
295
|
+
- JSON model: TwoWay by default; OData: OneWay by default (set TwoWay explicitly for forms)
|
|
296
|
+
- Use `type` in binding for automatic formatting + validation (dates, numbers, currency)
|
|
297
|
+
- Expression binding `{= ...}` for simple logic; formatter for complex logic
|
|
298
|
+
- V2: `expand` via parameters in binding; V4: `autoExpandSelect: true` handles it
|
|
299
|
+
- Always handle `change` event on element binding to detect missing records (404)
|
|
300
|
+
- `bindElement` for detail pages; aggregation binding for lists/tables
|
|
301
|
+
|
|
302
|
+
## Anti-Patterns
|
|
303
|
+
| Anti-Pattern | Correct |
|
|
304
|
+
|---|---|
|
|
305
|
+
| Manual date formatting in JS | Use `type: 'sap.ui.model.type.Date'` in binding |
|
|
306
|
+
| `oModel.getData()` on OData model | Use `oModel.getProperty(path)` or binding context |
|
|
307
|
+
| Hardcoded paths: `"/Orders('1000')"` | Build paths from route parameters |
|
|
308
|
+
| Missing `defaultCountMode: "Inline"` in V2 | Results in extra $count requests |
|
|
309
|
+
| Expression binding with model writes | Expressions are read-only — use controller for writes |
|
|
310
|
+
| `oModel.refresh(true)` after every change | Use `refreshAfterChange: false` + selective refresh |
|
|
311
|
+
| Filter without clearing previous | Always pass full filter array (or empty to clear) |
|