@sapui5/sap.suite.ui.generic.template 1.148.1 → 1.148.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/sap/suite/ui/generic/template/.library +1 -1
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/Canvas/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/controller/ControllerImplementation.js +6 -3
- package/src/sap/suite/ui/generic/template/ListReport/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/QuickCreate/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/QuickView/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/genericUtilities/controlStateWrapperFactory/SmartFilterBarWrapper.js +20 -1
- package/src/sap/suite/ui/generic/template/lib/AppComponent.js +1 -1
- package/src/sap/suite/ui/generic/template/lib/CRUDManager.js +0 -7
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillAIOrchestrator.js +43 -12
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillApplyHandler.js +67 -218
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillDialogController.js +55 -8
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFieldCollector.js +214 -35
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFormRenderer.js +93 -18
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillHandler.js +426 -48
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillTableRenderer.js +512 -63
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillDialog.fragment.xml +33 -22
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterBarHandler.js +90 -73
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n.properties +24 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ar.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_bg.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ca.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cnr.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cs.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cy.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_da.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_de.properties +19 -11
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_el.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_GB.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_US_saprigi.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es_MX.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_et.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fi.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr.properties +16 -8
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr_CA.properties +16 -8
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hi.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hr.properties +16 -8
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hu.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_id.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_it.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_iw.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ja.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_kk.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ko.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lt.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lv.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_mk.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ms.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_nl.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_no.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pl.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt_PT.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ro.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ru.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sh.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sk.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sl.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sr.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sv.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_th.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_tr.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_uk.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_vi.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_CN.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_TW.properties +15 -7
- package/src/sap/suite/ui/generic/template/lib/navigation/NavigationController.js +1 -1
- package/src/sap/suite/ui/generic/template/library.js +1 -1
- package/src/sap/suite/ui/generic/template/themes/base/ObjectPage.less +22 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
sap.ui.define([
|
|
2
2
|
"sap/ui/comp/smartfield/SmartField",
|
|
3
|
+
"sap/m/GenericTag",
|
|
4
|
+
"sap/m/MessageStrip",
|
|
3
5
|
"sap/m/Text",
|
|
4
6
|
"sap/m/Title",
|
|
5
7
|
"sap/m/Table",
|
|
@@ -7,69 +9,157 @@ sap.ui.define([
|
|
|
7
9
|
"sap/m/ColumnListItem",
|
|
8
10
|
"sap/m/VBox",
|
|
9
11
|
"sap/m/HBox",
|
|
12
|
+
"sap/m/ScrollContainer",
|
|
10
13
|
"sap/m/SegmentedButton",
|
|
11
14
|
"sap/m/SegmentedButtonItem",
|
|
15
|
+
"sap/uxap/ObjectPageSection",
|
|
16
|
+
"sap/uxap/ObjectPageSubSection",
|
|
17
|
+
"sap/ui/core/CustomData",
|
|
12
18
|
"sap/suite/ui/generic/template/genericUtilities/FeLogger",
|
|
13
19
|
"sap/suite/ui/generic/template/genericUtilities/FeError"
|
|
14
|
-
], function(SmartField, Text, Title, Table, Column, ColumnListItem, VBox, HBox, SegmentedButton, SegmentedButtonItem, FeLogger, FeError) {
|
|
20
|
+
], function(SmartField, GenericTag, MessageStrip, Text, Title, Table, Column, ColumnListItem, VBox, HBox, ScrollContainer, SegmentedButton, SegmentedButtonItem, ObjectPageSection, ObjectPageSubSection, CustomData, FeLogger, FeError) {
|
|
15
21
|
"use strict";
|
|
16
22
|
|
|
17
23
|
var sClassName = "lib.ai.EasyFill.EasyFillTableRenderer";
|
|
18
24
|
var oLogger = new FeLogger(sClassName).getLogger();
|
|
19
25
|
var iMaxPreviewRows = 20;
|
|
20
26
|
|
|
27
|
+
/** CustomData key used to identify dynamically-added table sections in the ObjectPageLayout. */
|
|
28
|
+
var sTableSectionMarker = "easyfillTableSection";
|
|
29
|
+
|
|
30
|
+
/** CustomData key used to identify the condensed-mode wrapper VBox in the Filled Fields SubSection. */
|
|
31
|
+
var sCondensedWrapperMarker = "easyfillCondensedWrapper";
|
|
32
|
+
|
|
21
33
|
/**
|
|
22
34
|
* EasyFillTableRenderer
|
|
23
35
|
*
|
|
24
36
|
* Responsible for rendering the SmartTable collection preview in the EasyFill dialog.
|
|
25
37
|
* For each collection (navigation property) with LLM-suggested row updates, this module:
|
|
26
38
|
*
|
|
27
|
-
|
|
39
|
+
* 1. Creates transient OData row contexts for preview rendering.
|
|
28
40
|
* 2. Creates a "Previous Values" read-only table and a "New Values" editable SmartField table.
|
|
29
41
|
* 3. Renders a SegmentedButton toggle between the two views.
|
|
30
|
-
|
|
42
|
+
* 4. Injects each collection as an ObjectPageSection into the shared EasyFillObjectPageLayout
|
|
43
|
+
* so the OPL header and anchor bar remain visible while scrolling through table content.
|
|
44
|
+
* 5. Provides reset logic to discard staged values if the user cancels.
|
|
31
45
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
46
|
+
* Create mode:
|
|
47
|
+
* - Rows without _rowIndex or _contextPath are treated as new rows.
|
|
48
|
+
* - New rows are rendered as editable SmartField rows highlighted with "Information" row highlight.
|
|
49
|
+
* - Transient contexts for new rows are stored in oTableStagingState.mPendingNewRowContexts.
|
|
50
|
+
* - On confirm, EasyFillApplyHandler activates these contexts via submitChanges.
|
|
51
|
+
* - Only one new row per table is created (extra new rows are suppressed upstream).
|
|
34
52
|
*/
|
|
35
53
|
return {
|
|
36
54
|
|
|
37
55
|
/**
|
|
38
|
-
* Renders collection (SmartTable) previews
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* - Creates a Previous/New table pair with a toggle SegmentedButton
|
|
42
|
-
* - Appends the section to the container
|
|
56
|
+
* Renders collection (SmartTable) previews as ObjectPageSections appended to the
|
|
57
|
+
* EasyFillObjectPageLayout. Each collection becomes its own anchored section so that
|
|
58
|
+
* the OPL header stays visible when the user edits table fields.
|
|
43
59
|
*
|
|
44
60
|
* @param {object} mCollectionUpdates - map of { navProperty: [ rowUpdate ] } from AI response
|
|
45
61
|
* @param {object} mFieldMapping - full LLM field mapping (used to get item properties and descriptions)
|
|
46
62
|
* @param {Array} aTableValidationErrors - validation errors from value-help checks
|
|
47
|
-
* @param {object} oController - MVC controller (used to byId the
|
|
63
|
+
* @param {object} oController - MVC controller (used to byId the ObjectPageLayout)
|
|
48
64
|
* @param {object} oRb - i18n resource bundle
|
|
49
|
-
* @param {object} mCollectionState - collection tracking state { mCollectionRowContexts, mCollectionRowContextsByPath, mCollectionProperties }
|
|
50
|
-
* @param {object} oTableStagingState - mutable table staging state { mStagedRows }
|
|
65
|
+
* @param {object} mCollectionState - collection tracking state { mCollectionRowContexts, mCollectionRowContextsByPath, mCollectionProperties, mCollectionAllowsCreation }
|
|
66
|
+
* @param {object} oTableStagingState - mutable table staging state { mStagedRows, mPendingNewRowContexts }
|
|
51
67
|
* @param {object} oObjectPageModel - the OData model used to create transient staging contexts
|
|
52
68
|
* @param {function} fnOnTableFieldChange - callback invoked when a preview table cell changes
|
|
69
|
+
* @param {boolean} [bReuseStaging=false] - when true, skip re-initializing transient contexts (preserves user edits on mode switch)
|
|
70
|
+
* @param {object} [mCollectionsWithRejectedNewRows] - map of { navProperty: "all" | "mixed" } where
|
|
71
|
+
* AI proposed new rows for a non-creatable table. "all" = only new rows proposed (no diff table shown);
|
|
72
|
+
* "mixed" = some existing-row updates plus rejected new rows.
|
|
53
73
|
*/
|
|
54
|
-
renderCollectionPreviews: function(mCollectionUpdates, mFieldMapping, aTableValidationErrors, oController, oRb, mCollectionState, oTableStagingState, oObjectPageModel, fnOnTableFieldChange) {
|
|
55
|
-
var
|
|
56
|
-
if (!
|
|
57
|
-
oLogger.warning("renderCollectionPreviews:
|
|
74
|
+
renderCollectionPreviews: function(mCollectionUpdates, mFieldMapping, aTableValidationErrors, oController, oRb, mCollectionState, oTableStagingState, oObjectPageModel, fnOnTableFieldChange, bReuseStaging, mCollectionsWithRejectedNewRows) {
|
|
75
|
+
var oObjectPageLayout = oController.byId("EasyFillObjectPageLayout");
|
|
76
|
+
if (!oObjectPageLayout) {
|
|
77
|
+
oLogger.warning("renderCollectionPreviews: EasyFillObjectPageLayout not found");
|
|
58
78
|
return;
|
|
59
79
|
}
|
|
60
80
|
|
|
61
|
-
// Remove
|
|
62
|
-
|
|
81
|
+
// Remove previously injected table content before re-rendering.
|
|
82
|
+
// On mode switch (bReuseStaging=true) cleanup is done BEFORE the model update
|
|
83
|
+
// in onSelectionChange, so we skip it here to avoid operating on the already-fresh OPL.
|
|
84
|
+
// On first render (bReuseStaging=false) we do the cleanup here.
|
|
85
|
+
if (!bReuseStaging) {
|
|
86
|
+
this._removeTableSections(oObjectPageLayout);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!bReuseStaging) {
|
|
90
|
+
this._initializeStagedCollectionRows(
|
|
91
|
+
mCollectionUpdates,
|
|
92
|
+
mFieldMapping,
|
|
93
|
+
mCollectionState,
|
|
94
|
+
oTableStagingState,
|
|
95
|
+
oObjectPageModel
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Determine mode from the dialog model.
|
|
100
|
+
var oDialogModel = oController.getView().getModel("easyFillDialogModel");
|
|
101
|
+
var sMode = oDialogModel ? oDialogModel.getProperty("/easyFillMode") : "";
|
|
102
|
+
var bIsCondensedMode = sMode === "CondensedMode";
|
|
103
|
+
|
|
104
|
+
// Find the target sections by inspecting the binding context of each live OPL section.
|
|
105
|
+
// This is the only reliable approach: model-index ↔ getSections()-index mapping is
|
|
106
|
+
// broken whenever manually-added sections (OP table sections) are still present in the
|
|
107
|
+
// aggregation alongside newly-created bound sections.
|
|
108
|
+
// Bound sections always have a "easyFillDialogModel" binding context; manually-added
|
|
109
|
+
// ones never do, so there is no ambiguity.
|
|
110
|
+
var oFilledFieldsSubSection = null; // condensed mode: add table as block inside here
|
|
111
|
+
var oUnfilledFieldsSection = null; // OP mode: insert standalone section before this
|
|
112
|
+
oObjectPageLayout.getSections().forEach(function(oSection) {
|
|
113
|
+
var oCtx = oSection.getBindingContext("easyFillDialogModel");
|
|
114
|
+
if (!oCtx) { return; }
|
|
115
|
+
var sTitle = oCtx.getProperty("title");
|
|
116
|
+
if (bIsCondensedMode && sTitle === "{@i18n>EASY_FILL_FILLED_FIELDS}") {
|
|
117
|
+
oFilledFieldsSubSection = oSection.getSubSections()[0] || null;
|
|
118
|
+
}
|
|
119
|
+
// Keep a handle to the Unfilled Fields section in both modes.
|
|
120
|
+
// In condensed mode with only non-updatable fields, there is no
|
|
121
|
+
// Filled Fields section; table previews are rendered as standalone
|
|
122
|
+
// sections and must still be inserted BEFORE Unfilled Fields.
|
|
123
|
+
if (sTitle === "{@i18n>EASY_FILL_UNFILLED_FIELDS}") {
|
|
124
|
+
oUnfilledFieldsSection = oSection;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
63
127
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
mCollectionState,
|
|
68
|
-
oTableStagingState,
|
|
69
|
-
oObjectPageModel
|
|
70
|
-
);
|
|
128
|
+
// iInsertOffset keeps multiple consecutive tables in the correct order when
|
|
129
|
+
// inserting standalone sections before the Unfilled Fields section (OP mode).
|
|
130
|
+
var iInsertOffset = 0;
|
|
71
131
|
|
|
72
132
|
var that = this;
|
|
133
|
+
var oRejected = mCollectionsWithRejectedNewRows || {};
|
|
134
|
+
|
|
135
|
+
// Render "all rejected" tables: AI only proposed new rows, creation not allowed.
|
|
136
|
+
// These collections have no entry in mCollectionUpdates so they need their own loop.
|
|
137
|
+
Object.keys(oRejected).forEach(function(sCollectionName) {
|
|
138
|
+
if (oRejected[sCollectionName] !== "all") {
|
|
139
|
+
return; // "mixed" is handled below alongside the diff table
|
|
140
|
+
}
|
|
141
|
+
var oCollectionMeta = mFieldMapping[sCollectionName];
|
|
142
|
+
var sTitle = (oCollectionMeta && oCollectionMeta.description) || sCollectionName;
|
|
143
|
+
var oStrip = new MessageStrip({
|
|
144
|
+
text: oRb.getText("EASY_FILL_TABLE_NEW_ROW_NOT_ALLOWED"),
|
|
145
|
+
type: "Warning",
|
|
146
|
+
showIcon: true
|
|
147
|
+
});
|
|
148
|
+
var oNotifVBox = new VBox({ items: [oStrip] });
|
|
149
|
+
oNotifVBox.addCustomData(new CustomData({ key: sTableSectionMarker, value: "true" }));
|
|
150
|
+
var oNotifSubSection = new ObjectPageSubSection({ title: sTitle, titleUppercase: false, blocks: [oNotifVBox] });
|
|
151
|
+
var oNotifSection = new ObjectPageSection({ title: sTitle, titleUppercase: false, subSections: [oNotifSubSection] });
|
|
152
|
+
oNotifSection.addCustomData(new CustomData({ key: sTableSectionMarker, value: "true" }));
|
|
153
|
+
if (oUnfilledFieldsSection) {
|
|
154
|
+
var iRejectedAt = oObjectPageLayout.indexOfSection(oUnfilledFieldsSection);
|
|
155
|
+
oObjectPageLayout.insertSection(oNotifSection, iRejectedAt + iInsertOffset);
|
|
156
|
+
iInsertOffset++;
|
|
157
|
+
} else {
|
|
158
|
+
oObjectPageLayout.addSection(oNotifSection);
|
|
159
|
+
}
|
|
160
|
+
oLogger.debug("renderCollectionPreviews: rejected-all new rows for '" + sCollectionName + "'");
|
|
161
|
+
});
|
|
162
|
+
|
|
73
163
|
Object.keys(mCollectionUpdates).forEach(function(sCollectionName) {
|
|
74
164
|
var oCollectionMeta = mFieldMapping[sCollectionName];
|
|
75
165
|
if (!oCollectionMeta || !oCollectionMeta.itemProperties) {
|
|
@@ -83,12 +173,22 @@ sap.ui.define([
|
|
|
83
173
|
var aPreviewRows = (oTableStagingState.mStagedRows[sCollectionName] || []).filter(function(oStagedRow) {
|
|
84
174
|
return oStagedRow.isPreviewVisible;
|
|
85
175
|
});
|
|
86
|
-
|
|
87
|
-
|
|
176
|
+
var aNewRows = oTableStagingState.mPendingNewRowContexts[sCollectionName] || [];
|
|
177
|
+
var bHasUneditableChanges = aPreviewRows.some(function(oPreviewRow) {
|
|
178
|
+
return oPreviewRow.update && that._hasUneditableCollectionChanges(oPreviewRow.update, sCollectionName, {
|
|
179
|
+
context: oPreviewRow.realContext,
|
|
180
|
+
rowIndex: oPreviewRow.rowIndex
|
|
181
|
+
}, mCollectionState);
|
|
182
|
+
});
|
|
183
|
+
if (!aPreviewRows.length && !aNewRows.length) {
|
|
88
184
|
return;
|
|
89
185
|
}
|
|
90
186
|
|
|
91
|
-
|
|
187
|
+
var iExistingRowCount = aPreviewRows.length;
|
|
188
|
+
var iNewRowCount = aNewRows.length;
|
|
189
|
+
var iTotalCount = iExistingRowCount + iNewRowCount;
|
|
190
|
+
|
|
191
|
+
// Previous values table (read-only, initially hidden) — only existing rows
|
|
92
192
|
var aPreviousRows = aPreviewRows.map(function(oPreviewRow) {
|
|
93
193
|
return that._buildCollectionPreviewRowFromContext(aPropertyNames, oPreviewRow.realContext);
|
|
94
194
|
});
|
|
@@ -96,20 +196,26 @@ sap.ui.define([
|
|
|
96
196
|
oPreviousTable.setVisible(false);
|
|
97
197
|
|
|
98
198
|
// New values table (editable SmartFields, initially visible)
|
|
199
|
+
// Includes both updated existing rows and new rows
|
|
99
200
|
var oNewTable = that._createEditableNewTable(
|
|
100
201
|
aPropertyNames,
|
|
101
202
|
oCollectionMeta.itemProperties,
|
|
102
203
|
aPreviewRows,
|
|
204
|
+
aNewRows,
|
|
103
205
|
sCollectionName,
|
|
104
206
|
mCollectionState,
|
|
105
207
|
fnOnTableFieldChange
|
|
106
208
|
);
|
|
107
209
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
210
|
+
var oReadonlyTag = bHasUneditableChanges
|
|
211
|
+
? new GenericTag({
|
|
212
|
+
text: oRb.getText("EASYFILL_TABLE_READONLY_TAG"),
|
|
213
|
+
status: "Warning"
|
|
214
|
+
}).addStyleClass("sapUiTinyMarginBegin")
|
|
215
|
+
: null;
|
|
216
|
+
|
|
217
|
+
var sCollectionName_label = oCollectionMeta.description || sCollectionName;
|
|
218
|
+
var oTitleControl = new Title({ text: sCollectionName_label + " (" + iTotalCount + ")", titleStyle: "H4" });
|
|
113
219
|
|
|
114
220
|
// Toggle button between "Previous" and "New" views
|
|
115
221
|
var oSegmentedButton = new SegmentedButton({
|
|
@@ -123,27 +229,115 @@ sap.ui.define([
|
|
|
123
229
|
var sKey = oSegmentedButton.getSelectedKey();
|
|
124
230
|
oPreviousTable.setVisible(sKey === "previous");
|
|
125
231
|
oNewTable.setVisible(sKey === "new");
|
|
232
|
+
if (oReadonlyTag) {
|
|
233
|
+
oReadonlyTag.setVisible(sKey === "new");
|
|
234
|
+
}
|
|
235
|
+
oTitleControl.setText(sCollectionName_label + " (" + (sKey === "previous" ? iExistingRowCount : iTotalCount) + ")");
|
|
126
236
|
}
|
|
127
237
|
});
|
|
128
|
-
|
|
238
|
+
var aRightItems = oReadonlyTag ? [oReadonlyTag, oSegmentedButton] : [oSegmentedButton];
|
|
129
239
|
var oHeader = new HBox({
|
|
130
240
|
justifyContent: "SpaceBetween",
|
|
131
|
-
|
|
132
|
-
|
|
241
|
+
alignItems: "Center",
|
|
242
|
+
wrap: "Wrap",
|
|
243
|
+
items: [oTitleControl].concat(aRightItems)
|
|
244
|
+
}).addStyleClass("sapUiSmallMarginTop sapUiSmallMarginBottom");
|
|
133
245
|
|
|
134
|
-
var
|
|
246
|
+
var oTableVBox = new VBox({
|
|
135
247
|
items: [oHeader, oPreviousTable, oNewTable]
|
|
136
248
|
}).addStyleClass("sapSmartTemplatesObjectPageEasyFillTableSection");
|
|
137
249
|
|
|
138
|
-
|
|
139
|
-
|
|
250
|
+
// "mixed" rejection: AI also proposed new rows but creation is not allowed.
|
|
251
|
+
// Show a warning strip after the toggle header, before the tables.
|
|
252
|
+
if (oRejected[sCollectionName] === "mixed") {
|
|
253
|
+
var oRejectionStrip = new MessageStrip({
|
|
254
|
+
text: oRb.getText("EASY_FILL_TABLE_NEW_ROW_NOT_ALLOWED"),
|
|
255
|
+
type: "Warning",
|
|
256
|
+
showIcon: true
|
|
257
|
+
});
|
|
258
|
+
oTableVBox.insertItem(oRejectionStrip, 1); // after toggle header, before tables
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (bIsCondensedMode && oFilledFieldsSubSection) {
|
|
262
|
+
// ── Condensed mode ────────────────────────────────────────────────────────
|
|
263
|
+
// Wrap ALL current SubSection blocks together with the table VBox in a
|
|
264
|
+
// single outer VBox, then make that the only block in the SubSection.
|
|
265
|
+
// BlockLayout always renders a single block at 100% width, so this prevents
|
|
266
|
+
// the table from ever appearing side-by-side with the SmartForm regardless
|
|
267
|
+
// of how wide the dialog is resized.
|
|
268
|
+
oTableVBox.addCustomData(new CustomData({ key: sTableSectionMarker, value: "true" }));
|
|
269
|
+
|
|
270
|
+
// Check if a wrapper VBox already exists (multiple collections: re-use it)
|
|
271
|
+
var aExistingBlocks = oFilledFieldsSubSection.getBlocks();
|
|
272
|
+
var oExistingWrapper = aExistingBlocks.find(function(oBlock) {
|
|
273
|
+
return oBlock.getCustomData && oBlock.getCustomData().some(function(oData) {
|
|
274
|
+
return oData.getKey() === sCondensedWrapperMarker;
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (oExistingWrapper) {
|
|
279
|
+
// Append the new table VBox to the already-created wrapper
|
|
280
|
+
oExistingWrapper.addItem(oTableVBox);
|
|
281
|
+
} else {
|
|
282
|
+
// First table: pull all current blocks out of the SubSection,
|
|
283
|
+
// place them inside a new wrapper VBox alongside the table VBox,
|
|
284
|
+
// and add the wrapper as the sole block.
|
|
285
|
+
var aOriginalBlocks = aExistingBlocks.slice();
|
|
286
|
+
aOriginalBlocks.forEach(function(oBlock) {
|
|
287
|
+
oFilledFieldsSubSection.removeBlock(oBlock);
|
|
288
|
+
});
|
|
289
|
+
var oWrapper = new VBox({
|
|
290
|
+
width: "100%",
|
|
291
|
+
items: aOriginalBlocks.concat([oTableVBox])
|
|
292
|
+
});
|
|
293
|
+
oWrapper.addCustomData(new CustomData({ key: sCondensedWrapperMarker, value: "true" }));
|
|
294
|
+
oFilledFieldsSubSection.addBlock(oWrapper);
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
// ── OP mode ───────────────────────────────────────────────────────────────
|
|
298
|
+
// Wrap in a standalone ObjectPageSection so the anchor bar shows the table
|
|
299
|
+
// name. Insert just before the Unfilled Fields section (if present) so the
|
|
300
|
+
// order is: annotation sections → table sections → Unfilled Fields.
|
|
301
|
+
var oSubSection = new ObjectPageSubSection({
|
|
302
|
+
title: sCollectionName_label,
|
|
303
|
+
titleUppercase: false,
|
|
304
|
+
showTitle: false,
|
|
305
|
+
blocks: [oTableVBox]
|
|
306
|
+
});
|
|
307
|
+
var oOpSection = new ObjectPageSection({
|
|
308
|
+
title: sCollectionName_label,
|
|
309
|
+
titleUppercase: false,
|
|
310
|
+
subSections: [oSubSection]
|
|
311
|
+
});
|
|
312
|
+
oOpSection.addCustomData(new CustomData({ key: sTableSectionMarker, value: "true" }));
|
|
313
|
+
|
|
314
|
+
if (oUnfilledFieldsSection) {
|
|
315
|
+
// Use indexOfSection on the live control — this is the actual aggregation
|
|
316
|
+
// index regardless of how many manually-added sections are mixed in.
|
|
317
|
+
var iInsertAt = oObjectPageLayout.indexOfSection(oUnfilledFieldsSection);
|
|
318
|
+
oObjectPageLayout.insertSection(oOpSection, iInsertAt + iInsertOffset);
|
|
319
|
+
iInsertOffset++;
|
|
320
|
+
} else {
|
|
321
|
+
oObjectPageLayout.addSection(oOpSection);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
oLogger.debug("renderCollectionPreviews: rendered collection '" + sCollectionName + "' with " +
|
|
325
|
+
iExistingRowCount + " existing row(s) and " + iNewRowCount + " new row(s)");
|
|
140
326
|
});
|
|
141
327
|
},
|
|
142
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Public alias for _removeTableSections, called from EasyFillHandler before model update
|
|
331
|
+
* on mode switch so the OPL aggregation is clean when the sections binding recreates controls.
|
|
332
|
+
*/
|
|
333
|
+
removeTableSections: function(oObjectPageLayout) {
|
|
334
|
+
this._removeTableSections(oObjectPageLayout);
|
|
335
|
+
},
|
|
336
|
+
|
|
143
337
|
/**
|
|
144
338
|
* Resets and clears all transient collection contexts created for table preview staging.
|
|
145
339
|
*
|
|
146
|
-
* @param {object} oTableStagingState - table staging state { mStagedRows }
|
|
340
|
+
* @param {object} oTableStagingState - table staging state { mStagedRows, mPendingNewRowContexts }
|
|
147
341
|
* @param {object} oObjectPageModel - the OData model
|
|
148
342
|
*/
|
|
149
343
|
resetStagedCollectionContexts: function(oTableStagingState, oObjectPageModel) {
|
|
@@ -159,6 +353,16 @@ sap.ui.define([
|
|
|
159
353
|
});
|
|
160
354
|
});
|
|
161
355
|
|
|
356
|
+
// Also reset pending new-row contexts (these use the same deferred group)
|
|
357
|
+
Object.keys((oTableStagingState && oTableStagingState.mPendingNewRowContexts) || {}).forEach(function(sCollectionName) {
|
|
358
|
+
(oTableStagingState.mPendingNewRowContexts[sCollectionName] || []).forEach(function(oNewRowContext) {
|
|
359
|
+
var sPath = oNewRowContext && oNewRowContext.getPath && oNewRowContext.getPath();
|
|
360
|
+
if (sPath) {
|
|
361
|
+
aTransientPaths.push(sPath);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
162
366
|
if (aTransientPaths.length && oObjectPageModel) {
|
|
163
367
|
oObjectPageModel.resetChanges(aTransientPaths, false, true);
|
|
164
368
|
}
|
|
@@ -169,6 +373,52 @@ sap.ui.define([
|
|
|
169
373
|
|
|
170
374
|
// ─── Private Helpers ─────────────────────────────────────────────────────
|
|
171
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Removes all ObjectPageSections that were dynamically added by renderCollectionPreviews.
|
|
378
|
+
* Identified by the custom data marker key set during creation.
|
|
379
|
+
*
|
|
380
|
+
* @param {sap.uxap.ObjectPageLayout} oObjectPageLayout
|
|
381
|
+
*/
|
|
382
|
+
_removeTableSections: function(oObjectPageLayout) {
|
|
383
|
+
// OP mode: remove marked standalone ObjectPageSections.
|
|
384
|
+
oObjectPageLayout.getSections().filter(function(oSection) {
|
|
385
|
+
return oSection.getCustomData().some(function(oData) {
|
|
386
|
+
return oData.getKey() === sTableSectionMarker;
|
|
387
|
+
});
|
|
388
|
+
}).forEach(function(oSection) {
|
|
389
|
+
oObjectPageLayout.removeSection(oSection);
|
|
390
|
+
oSection.destroy();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Condensed mode: find and unwrap the wrapper VBox, restoring the original
|
|
394
|
+
// SmartForm block(s) directly into their SubSection, then destroy the wrapper.
|
|
395
|
+
oObjectPageLayout.getSections().forEach(function(oSection) {
|
|
396
|
+
oSection.getSubSections().forEach(function(oSubSec) {
|
|
397
|
+
oSubSec.getBlocks().filter(function(oBlock) {
|
|
398
|
+
return oBlock.getCustomData && oBlock.getCustomData().some(function(oData) {
|
|
399
|
+
return oData.getKey() === sCondensedWrapperMarker;
|
|
400
|
+
});
|
|
401
|
+
}).forEach(function(oWrapper) {
|
|
402
|
+
// Separate original (non-table) items from table VBox items
|
|
403
|
+
var aItems = oWrapper.getItems().slice();
|
|
404
|
+
aItems.forEach(function(oItem) {
|
|
405
|
+
var bIsTableVBox = oItem.getCustomData && oItem.getCustomData().some(function(oData) {
|
|
406
|
+
return oData.getKey() === sTableSectionMarker;
|
|
407
|
+
});
|
|
408
|
+
oWrapper.removeItem(oItem);
|
|
409
|
+
if (bIsTableVBox) {
|
|
410
|
+
oItem.destroy();
|
|
411
|
+
} else {
|
|
412
|
+
oSubSec.addBlock(oItem); // restore SmartForm
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
oSubSec.removeBlock(oWrapper);
|
|
416
|
+
oWrapper.destroy();
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
|
|
172
422
|
_initializeStagedCollectionRows: function(mCollectionUpdates, mFieldMapping, mCollectionState, oTableStagingState, oObjectPageModel) {
|
|
173
423
|
var that = this;
|
|
174
424
|
this._clearStagedCollectionState(oTableStagingState);
|
|
@@ -178,14 +428,27 @@ sap.ui.define([
|
|
|
178
428
|
var aRowUpdates = mCollectionUpdates[sCollectionName] || [];
|
|
179
429
|
var aCollectionRowContexts = mCollectionState.mCollectionRowContexts[sCollectionName] || [];
|
|
180
430
|
var mCollectionPropertyMap = mCollectionState.mCollectionProperties[sCollectionName] || Object.create(null);
|
|
431
|
+
var oEntitySet = mCollectionState.mCollectionEntitySets && mCollectionState.mCollectionEntitySets[sCollectionName];
|
|
181
432
|
var aPropertyNames = Object.keys((oCollectionMeta && oCollectionMeta.itemProperties) || {});
|
|
433
|
+
var bAllowsCreation = !!(mCollectionState.mCollectionAllowsCreation &&
|
|
434
|
+
mCollectionState.mCollectionAllowsCreation[sCollectionName]);
|
|
182
435
|
|
|
183
436
|
if (!oCollectionMeta || !oCollectionMeta.entitySet || !aPropertyNames.length) {
|
|
184
437
|
return;
|
|
185
438
|
}
|
|
186
439
|
|
|
440
|
+
// Separate existing-row updates from new-row creates
|
|
441
|
+
var aExistingRowUpdates = aRowUpdates.filter(function(oRow) {
|
|
442
|
+
return typeof oRow._contextPath === "string" || typeof oRow._rowIndex === "number";
|
|
443
|
+
});
|
|
444
|
+
var aNewRowData = bAllowsCreation
|
|
445
|
+
? aRowUpdates.filter(function(oRow) {
|
|
446
|
+
return typeof oRow._contextPath !== "string" && typeof oRow._rowIndex !== "number";
|
|
447
|
+
})
|
|
448
|
+
: [];
|
|
449
|
+
|
|
187
450
|
var mUpdatesByIdentity = Object.create(null);
|
|
188
|
-
|
|
451
|
+
aExistingRowUpdates.forEach(function(oRowUpdate, iUpdateIndex) {
|
|
189
452
|
var oResolvedUpdate = that._resolveCollectionRowContext(sCollectionName, oRowUpdate, mCollectionState);
|
|
190
453
|
mUpdatesByIdentity[that._getCollectionRowIdentity(oResolvedUpdate, oRowUpdate, iUpdateIndex)] = oRowUpdate;
|
|
191
454
|
});
|
|
@@ -209,28 +472,53 @@ sap.ui.define([
|
|
|
209
472
|
|
|
210
473
|
var oRealRowObject = oRealContext.getObject && oRealContext.getObject();
|
|
211
474
|
that._seedTransientContextFromRowObject(oTransientContext, oRealRowObject);
|
|
212
|
-
that._applyRowUpdateToTransientContext(oTransientContext, oRowUpdate, mCollectionPropertyMap, oRealRowObject);
|
|
475
|
+
that._applyRowUpdateToTransientContext(oTransientContext, oRowUpdate, mCollectionPropertyMap, oRealRowObject, oEntitySet);
|
|
213
476
|
|
|
214
477
|
oTableStagingState.mStagedRows[sCollectionName].push({
|
|
215
478
|
realContext: oRealContext,
|
|
216
479
|
transientContext: oTransientContext,
|
|
480
|
+
update: oRowUpdate,
|
|
217
481
|
rowIndex: iRowIndex,
|
|
218
482
|
propertyNames: aPropertyNames,
|
|
219
483
|
isPreviewVisible: bIsPreviewVisible
|
|
220
484
|
});
|
|
221
485
|
});
|
|
486
|
+
|
|
487
|
+
// Create and seed transient contexts for new rows
|
|
488
|
+
oTableStagingState.mPendingNewRowContexts[sCollectionName] = [];
|
|
489
|
+
aNewRowData.forEach(function(oNewRow) {
|
|
490
|
+
var oNewContext = that._createTransientCollectionContext(oCollectionMeta.entitySet, oObjectPageModel);
|
|
491
|
+
if (!oNewContext) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
// Seed only editable (non-key, non-computed) properties from the AI data
|
|
495
|
+
Object.keys(oNewRow).forEach(function(sPropName) {
|
|
496
|
+
if (sPropName === "_rowIndex" || sPropName === "_contextPath") {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
var oPropertyMeta = mCollectionPropertyMap[sPropName];
|
|
500
|
+
if (oPropertyMeta && !that._isKeyProperty(oPropertyMeta) &&
|
|
501
|
+
typeof oNewRow[sPropName] !== "object") {
|
|
502
|
+
oNewContext.setProperty(sPropName, oNewRow[sPropName]);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
oTableStagingState.mPendingNewRowContexts[sCollectionName].push(oNewContext);
|
|
506
|
+
});
|
|
222
507
|
});
|
|
223
508
|
},
|
|
224
509
|
|
|
225
510
|
/**
|
|
226
511
|
* Creates a read-only sap.m.Table for displaying table row data (used for "Previous Values" view).
|
|
227
512
|
* All cells are rendered as plain Text controls.
|
|
513
|
+
* The table is wrapped in a sap.m.ScrollContainer to enable horizontal scrolling when the
|
|
514
|
+
* dialog is narrower than the table content (autoPopinMode is disabled to prevent columns
|
|
515
|
+
* from collapsing instead of scrolling).
|
|
228
516
|
*
|
|
229
517
|
* @param {Array<string>} aPropertyNames - ordered list of property names to show as columns
|
|
230
518
|
* @param {object} oItemProperties - map of { propName: { description, dataType } }
|
|
231
519
|
* @param {Array<object>} aRows - array of plain row value objects
|
|
232
520
|
* @param {function} [fnCellFormatter] - optional custom cell formatter (oRow, sPropName, sRawValue) → string
|
|
233
|
-
* @returns {sap.m.
|
|
521
|
+
* @returns {sap.m.ScrollContainer} ScrollContainer wrapping the table with horizontal scrolling enabled
|
|
234
522
|
*/
|
|
235
523
|
_createPreviewTable: function(aPropertyNames, oItemProperties, aRows, fnCellFormatter) {
|
|
236
524
|
var aColumns = aPropertyNames.map(function(sPropertyName) {
|
|
@@ -252,27 +540,40 @@ sap.ui.define([
|
|
|
252
540
|
});
|
|
253
541
|
}.bind(this));
|
|
254
542
|
|
|
255
|
-
|
|
543
|
+
var oTable = new Table({
|
|
256
544
|
columns: aColumns,
|
|
257
545
|
items: aItems,
|
|
258
546
|
fixedLayout: false,
|
|
259
|
-
autoPopinMode:
|
|
547
|
+
autoPopinMode: false
|
|
260
548
|
}).addStyleClass("sapSmartTemplatesObjectPageEasyFillTablePreview");
|
|
549
|
+
|
|
550
|
+
return new ScrollContainer({
|
|
551
|
+
horizontal: true,
|
|
552
|
+
vertical: false,
|
|
553
|
+
width: "100%",
|
|
554
|
+
content: [oTable]
|
|
555
|
+
});
|
|
261
556
|
},
|
|
262
557
|
|
|
263
558
|
/**
|
|
264
559
|
* Creates an editable sap.m.Table for displaying AI-suggested row updates.
|
|
265
560
|
* Editable cells are rendered as SmartField controls bound to the OData row context.
|
|
266
561
|
* Non-editable cells are rendered as plain Text controls.
|
|
562
|
+
* New rows (proposed by the AI for creation) are highlighted with "Information" row color.
|
|
563
|
+
* The table is wrapped in a sap.m.ScrollContainer to enable horizontal scrolling when the
|
|
564
|
+
* dialog is narrower than the table content (autoPopinMode is disabled to prevent columns
|
|
565
|
+
* from collapsing instead of scrolling).
|
|
267
566
|
*
|
|
268
567
|
* @param {Array<string>} aPropertyNames - ordered list of property names to show as columns
|
|
269
568
|
* @param {object} oItemProperties - map of { propName: { description, dataType } }
|
|
270
|
-
* @param {Array<object>} aPreviewRows - enriched row objects with context, newValues, etc.
|
|
569
|
+
* @param {Array<object>} aPreviewRows - enriched row objects with context, newValues, etc. (existing rows)
|
|
570
|
+
* @param {Array<object>} aNewRowContexts - transient OData contexts for AI-proposed new rows
|
|
271
571
|
* @param {string} sCollectionName - navigation property name (used to look up property metadata)
|
|
272
572
|
* @param {object} mCollectionState - collection tracking state (used to get property metadata)
|
|
273
|
-
* @
|
|
573
|
+
* @param {function} fnOnTableFieldChange - callback when a cell value changes
|
|
574
|
+
* @returns {sap.m.ScrollContainer} ScrollContainer wrapping the table with horizontal scrolling enabled
|
|
274
575
|
*/
|
|
275
|
-
_createEditableNewTable: function(aPropertyNames, oItemProperties, aPreviewRows, sCollectionName, mCollectionState, fnOnTableFieldChange) {
|
|
576
|
+
_createEditableNewTable: function(aPropertyNames, oItemProperties, aPreviewRows, aNewRowContexts, sCollectionName, mCollectionState, fnOnTableFieldChange) {
|
|
276
577
|
var aColumns = aPropertyNames.map(function(sPropName) {
|
|
277
578
|
return new Column({
|
|
278
579
|
header: new Text({
|
|
@@ -282,9 +583,11 @@ sap.ui.define([
|
|
|
282
583
|
});
|
|
283
584
|
|
|
284
585
|
var mCollectionPropertyMap = mCollectionState.mCollectionProperties[sCollectionName] || Object.create(null);
|
|
586
|
+
var oEntitySet = mCollectionState.mCollectionEntitySets && mCollectionState.mCollectionEntitySets[sCollectionName];
|
|
285
587
|
var that = this;
|
|
286
588
|
|
|
287
|
-
|
|
589
|
+
// Rows for existing rows with AI-proposed updates
|
|
590
|
+
var aExistingItems = aPreviewRows.map(function(oPreviewRow) {
|
|
288
591
|
var oContext = oPreviewRow.transientContext;
|
|
289
592
|
var oRowObject = oPreviewRow.realContext && oPreviewRow.realContext.getObject && oPreviewRow.realContext.getObject();
|
|
290
593
|
|
|
@@ -294,9 +597,12 @@ sap.ui.define([
|
|
|
294
597
|
}
|
|
295
598
|
|
|
296
599
|
var oPropertyMeta = mCollectionPropertyMap[sPropName];
|
|
297
|
-
var bEditable = that._isEditableProperty(oPropertyMeta, oRowObject);
|
|
298
|
-
|
|
299
|
-
|
|
600
|
+
var bEditable = that._isEditableProperty(oPropertyMeta, oRowObject, oEntitySet);
|
|
601
|
+
var bHasUneditableAiProp = !!(
|
|
602
|
+
oPreviewRow.update &&
|
|
603
|
+
Object.prototype.hasOwnProperty.call(oPreviewRow.update, sPropName) &&
|
|
604
|
+
!bEditable
|
|
605
|
+
);
|
|
300
606
|
var oSmartField = new SmartField({
|
|
301
607
|
value: "{" + sPropName + "}",
|
|
302
608
|
editable: bEditable,
|
|
@@ -308,13 +614,16 @@ sap.ui.define([
|
|
|
308
614
|
}
|
|
309
615
|
},
|
|
310
616
|
editableChanged: function(oEvent) {
|
|
311
|
-
// Keep table SmartField editability deterministic during preview
|
|
312
617
|
if (oEvent.getParameter("editable") !== bEditable) {
|
|
313
618
|
oEvent.getSource().setEditable(bEditable);
|
|
314
619
|
}
|
|
315
620
|
}
|
|
316
621
|
});
|
|
317
622
|
|
|
623
|
+
if (bHasUneditableAiProp) {
|
|
624
|
+
that._markSmartFieldAsWarning(oSmartField);
|
|
625
|
+
}
|
|
626
|
+
|
|
318
627
|
return oSmartField;
|
|
319
628
|
});
|
|
320
629
|
|
|
@@ -325,12 +634,61 @@ sap.ui.define([
|
|
|
325
634
|
return oItem;
|
|
326
635
|
});
|
|
327
636
|
|
|
328
|
-
|
|
637
|
+
// Rows for AI-proposed new entries — all non-key properties are editable
|
|
638
|
+
// sEntitySetName is set explicitly on SmartField so it can resolve OData metadata
|
|
639
|
+
// even though it lives inside a plain sap.m.Table (not a SmartTable).
|
|
640
|
+
var sEntitySetName = (oEntitySet && oEntitySet.name) || sCollectionName;
|
|
641
|
+
var aNewItems = (aNewRowContexts || []).map(function(oNewContext) {
|
|
642
|
+
var aCells = aPropertyNames.map(function(sPropName) {
|
|
643
|
+
if (!oNewContext) {
|
|
644
|
+
return new Text({ text: "" });
|
|
645
|
+
}
|
|
646
|
+
var oPropertyMeta = mCollectionPropertyMap[sPropName];
|
|
647
|
+
// On a new row: key fields and computed fields are not user-editable;
|
|
648
|
+
// all other fields are editable so the user can review and adjust.
|
|
649
|
+
var bEditable = oPropertyMeta
|
|
650
|
+
? !that._isKeyProperty(oPropertyMeta) && that._isEditableProperty(oPropertyMeta, {}, oEntitySet)
|
|
651
|
+
: false;
|
|
652
|
+
var oSmartField = new SmartField({
|
|
653
|
+
value: "{" + sPropName + "}",
|
|
654
|
+
entitySet: sEntitySetName,
|
|
655
|
+
editable: bEditable,
|
|
656
|
+
enabled: bEditable,
|
|
657
|
+
contextEditable: bEditable,
|
|
658
|
+
change: function() {
|
|
659
|
+
if (typeof fnOnTableFieldChange === "function") {
|
|
660
|
+
fnOnTableFieldChange();
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
editableChanged: function(oEvent) {
|
|
664
|
+
if (oEvent.getParameter("editable") !== bEditable) {
|
|
665
|
+
oEvent.getSource().setEditable(bEditable);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
return oSmartField;
|
|
670
|
+
});
|
|
671
|
+
var oItem = new ColumnListItem({
|
|
672
|
+
cells: aCells,
|
|
673
|
+
highlight: "Information"
|
|
674
|
+
});
|
|
675
|
+
oItem.setBindingContext(oNewContext);
|
|
676
|
+
return oItem;
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
var oTable = new Table({
|
|
329
680
|
columns: aColumns,
|
|
330
|
-
items:
|
|
681
|
+
items: aExistingItems.concat(aNewItems),
|
|
331
682
|
fixedLayout: false,
|
|
332
|
-
autoPopinMode:
|
|
683
|
+
autoPopinMode: false
|
|
333
684
|
}).addStyleClass("sapSmartTemplatesObjectPageEasyFillTablePreview");
|
|
685
|
+
|
|
686
|
+
return new ScrollContainer({
|
|
687
|
+
horizontal: true,
|
|
688
|
+
vertical: false,
|
|
689
|
+
width: "100%",
|
|
690
|
+
content: [oTable]
|
|
691
|
+
});
|
|
334
692
|
},
|
|
335
693
|
|
|
336
694
|
/**
|
|
@@ -370,7 +728,7 @@ sap.ui.define([
|
|
|
370
728
|
});
|
|
371
729
|
},
|
|
372
730
|
|
|
373
|
-
_applyRowUpdateToTransientContext: function(oTransientContext, oRowUpdate, mCollectionPropertyMap, oRealRowObject) {
|
|
731
|
+
_applyRowUpdateToTransientContext: function(oTransientContext, oRowUpdate, mCollectionPropertyMap, oRealRowObject, oEntitySet) {
|
|
374
732
|
var that = this;
|
|
375
733
|
Object.keys(oRowUpdate || {}).forEach(function(sPropertyName) {
|
|
376
734
|
if (sPropertyName === "_rowIndex" || sPropertyName === "_contextPath") {
|
|
@@ -378,7 +736,7 @@ sap.ui.define([
|
|
|
378
736
|
}
|
|
379
737
|
|
|
380
738
|
var oPropertyMeta = mCollectionPropertyMap[sPropertyName];
|
|
381
|
-
if (!that._isEditableProperty(oPropertyMeta, oRealRowObject)) {
|
|
739
|
+
if (!that._isEditableProperty(oPropertyMeta, oRealRowObject, oEntitySet)) {
|
|
382
740
|
return;
|
|
383
741
|
}
|
|
384
742
|
|
|
@@ -390,6 +748,9 @@ sap.ui.define([
|
|
|
390
748
|
Object.keys((oTableStagingState && oTableStagingState.mStagedRows) || {}).forEach(function(sCollectionName) {
|
|
391
749
|
delete oTableStagingState.mStagedRows[sCollectionName];
|
|
392
750
|
});
|
|
751
|
+
Object.keys((oTableStagingState && oTableStagingState.mPendingNewRowContexts) || {}).forEach(function(sCollectionName) {
|
|
752
|
+
delete oTableStagingState.mPendingNewRowContexts[sCollectionName];
|
|
753
|
+
});
|
|
393
754
|
},
|
|
394
755
|
|
|
395
756
|
/**
|
|
@@ -459,6 +820,45 @@ sap.ui.define([
|
|
|
459
820
|
return String(vValue);
|
|
460
821
|
},
|
|
461
822
|
|
|
823
|
+
_markSmartFieldAsWarning: function(oSmartField) {
|
|
824
|
+
if (!oSmartField) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
/*Adding the style class 'sapMInputBaseContentWrapper' to make the uneditable field,
|
|
828
|
+
in the table appear visually like an input field */
|
|
829
|
+
oSmartField.addStyleClass("sapUiEasyFillWarningCell sapMInputBaseContentWrapper");
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Checks whether the AI proposed at least one change for the row that cannot be applied
|
|
834
|
+
* because the target property is not editable in the current row context.
|
|
835
|
+
*
|
|
836
|
+
* @param {object} oRowUpdate - AI row update object
|
|
837
|
+
* @param {string} sCollectionName - navigation property name
|
|
838
|
+
* @param {object} oResolvedRow - resolved row context metadata
|
|
839
|
+
* @param {object} mCollectionState - collection tracking state
|
|
840
|
+
* @returns {boolean}
|
|
841
|
+
*/
|
|
842
|
+
_hasUneditableCollectionChanges: function(oRowUpdate, sCollectionName, oResolvedRow, mCollectionState) {
|
|
843
|
+
if (!oRowUpdate) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
var mCollectionPropertyMap = mCollectionState.mCollectionProperties[sCollectionName] || Object.create(null);
|
|
848
|
+
var oEntitySet = mCollectionState.mCollectionEntitySets && mCollectionState.mCollectionEntitySets[sCollectionName];
|
|
849
|
+
var oContext = oResolvedRow && oResolvedRow.context;
|
|
850
|
+
var oRowObject = oContext && oContext.getObject && oContext.getObject();
|
|
851
|
+
var that = this;
|
|
852
|
+
|
|
853
|
+
return Object.keys(oRowUpdate).some(function(sPropName) {
|
|
854
|
+
if (sPropName === "_rowIndex" || sPropName === "_contextPath") {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return !that._isEditableProperty(mCollectionPropertyMap[sPropName], oRowObject, oEntitySet);
|
|
859
|
+
});
|
|
860
|
+
},
|
|
861
|
+
|
|
462
862
|
/**
|
|
463
863
|
* Checks whether a collection property is editable given the current row data object.
|
|
464
864
|
* Delegates to the same annotation checks used in EasyFillFieldCollector.isEditableByContextObject.
|
|
@@ -468,10 +868,27 @@ sap.ui.define([
|
|
|
468
868
|
* @param {object} oRowObject - plain row data object
|
|
469
869
|
* @returns {boolean}
|
|
470
870
|
*/
|
|
471
|
-
_isEditableProperty: function(oPropertyMeta, oRowObject) {
|
|
871
|
+
_isEditableProperty: function(oPropertyMeta, oRowObject, oEntitySet) {
|
|
472
872
|
if (!oPropertyMeta) {
|
|
473
873
|
return false;
|
|
474
874
|
}
|
|
875
|
+
|
|
876
|
+
// Check entity-set-level sap:updatable (entire entity set is read-only)
|
|
877
|
+
if (oEntitySet) {
|
|
878
|
+
var sEntitySetUpdatable = oEntitySet["sap:updatable"];
|
|
879
|
+
if (sEntitySetUpdatable === "false") {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
// sap:updatable-path: resolved against the row context object (boolean property)
|
|
883
|
+
var sEntitySetUpdatablePath = oEntitySet["sap:updatable-path"];
|
|
884
|
+
if (sEntitySetUpdatablePath && oRowObject) {
|
|
885
|
+
var bUpdatableFromPath = oRowObject[sEntitySetUpdatablePath];
|
|
886
|
+
if (bUpdatableFromPath === false || bUpdatableFromPath === "false") {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
475
892
|
var sUpdatable = oPropertyMeta["sap:updatable"];
|
|
476
893
|
if (sUpdatable) {
|
|
477
894
|
return sUpdatable !== "false";
|
|
@@ -480,12 +897,44 @@ sap.ui.define([
|
|
|
480
897
|
if (oAnnotation && oAnnotation["EnumMember"]) {
|
|
481
898
|
return oAnnotation["EnumMember"] !== "com.sap.vocabularies.Common.v1.FieldControlType/ReadOnly";
|
|
482
899
|
}
|
|
900
|
+
// Dynamic FieldControl via Path (e.g. { "Path": "SomeField_fc" })
|
|
901
|
+
// FieldControl values: 0=Hidden, 1=ReadOnly, 3=Optional, 7=Mandatory
|
|
902
|
+
if (oAnnotation && oAnnotation["Path"]) {
|
|
903
|
+
var iDynamicFieldControl = oRowObject && oRowObject[oAnnotation["Path"]];
|
|
904
|
+
if (iDynamicFieldControl !== undefined && iDynamicFieldControl !== null) {
|
|
905
|
+
return iDynamicFieldControl !== 0 && iDynamicFieldControl !== 1;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// Check "sap:field-control" dynamic path (value 1 = read-only)
|
|
909
|
+
// If the _fc property is defined in metadata but its runtime value is absent from
|
|
910
|
+
// the row object (not fetched / not in $select), treat as non-editable (safe fallback).
|
|
483
911
|
var sFieldControl = oPropertyMeta["sap:field-control"];
|
|
484
912
|
if (sFieldControl) {
|
|
485
|
-
|
|
486
|
-
|
|
913
|
+
if (!oRowObject || !(sFieldControl in oRowObject)) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
var iFieldControlValue = oRowObject[sFieldControl];
|
|
917
|
+
return iFieldControlValue !== 0 && iFieldControlValue !== 1;
|
|
487
918
|
}
|
|
488
919
|
return true;
|
|
920
|
+
},
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Returns true if an OData property is a key field (sap:key="true" or isKey: true).
|
|
924
|
+
* Key fields are not user-editable when creating new rows.
|
|
925
|
+
*
|
|
926
|
+
* @param {object} oPropertyMeta - OData property metadata
|
|
927
|
+
* @returns {boolean}
|
|
928
|
+
*/
|
|
929
|
+
_isKeyProperty: function(oPropertyMeta) {
|
|
930
|
+
if (!oPropertyMeta) {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
return oPropertyMeta.isKey === true ||
|
|
934
|
+
oPropertyMeta["sap:key"] === "true" ||
|
|
935
|
+
!!(oPropertyMeta.extensions && oPropertyMeta.extensions.some(function(ext) {
|
|
936
|
+
return ext.name === "key" && ext.value === "true";
|
|
937
|
+
}));
|
|
489
938
|
}
|
|
490
939
|
};
|
|
491
|
-
});
|
|
940
|
+
});
|