@sapui5/sap.suite.ui.generic.template 1.150.1 → 1.152.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/package.json +1 -1
- package/src/sap/suite/ui/generic/template/.library +1 -1
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/control/visualfilterbar/SmartVisualFilterBar.js +4 -3
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/i18n/i18n_en_US_sappsd.properties +17 -9
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/i18n/i18n_en_US_saptrc.properties +113 -105
- 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/AnnotationHelperSmartList.js +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/Component.js +4 -2
- package/src/sap/suite/ui/generic/template/ListReport/controller/ControllerImplementation.js +0 -6
- package/src/sap/suite/ui/generic/template/ListReport/controller/IappStateHandler.js +9 -0
- package/src/sap/suite/ui/generic/template/ListReport/i18n/i18n_en_US_sappsd.properties +8 -5
- package/src/sap/suite/ui/generic/template/ListReport/i18n/i18n_en_US_saptrc.properties +61 -58
- package/src/sap/suite/ui/generic/template/ListReport/i18n/i18n_fr.properties +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/i18n/i18n_sv.properties +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/view/fragments/FullscreenPage.fragment.xml +3 -0
- package/src/sap/suite/ui/generic/template/ObjectPage/controller/ControllerImplementation.js +3 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/controller/RelatedAppsHandler.js +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/controller/inlineCreationRows/InlineCreationRowsHelper.js +10 -7
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_de.properties +4 -4
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_en.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_en_US_sappsd.properties +53 -9
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_en_US_saprigi.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_en_US_saptrc.properties +143 -95
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_fr.properties +3 -3
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_no.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_ro.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_ru.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_sl.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_sv.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/i18n/i18n_uk.properties +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/QuickCreate/i18n/i18n_en_US_saptrc.properties +14 -14
- package/src/sap/suite/ui/generic/template/QuickCreate/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/QuickView/i18n/i18n_en_US_saptrc.properties +2 -2
- package/src/sap/suite/ui/generic/template/QuickView/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/detailTemplates/i18n/i18n_en_US_saptrc.properties +6 -6
- package/src/sap/suite/ui/generic/template/fragments/AnalyticalTableColumns.fragment.xml +0 -10
- package/src/sap/suite/ui/generic/template/fragments/EasyFilter.fragment.xml +0 -2
- package/src/sap/suite/ui/generic/template/fragments/GridTableColumns.fragment.xml +5 -2
- package/src/sap/suite/ui/generic/template/fragments/TreeTableColumns.fragment.xml +5 -2
- package/src/sap/suite/ui/generic/template/genericUtilities/controlHelper.js +10 -1
- package/src/sap/suite/ui/generic/template/genericUtilities/controlStateWrapperFactory/SmartFilterBarWrapper.js +45 -32
- package/src/sap/suite/ui/generic/template/genericUtilities/controlStateWrapperFactory/SmartTableChartCommon.js +10 -0
- package/src/sap/suite/ui/generic/template/js/AnnotationHelper.js +21 -17
- package/src/sap/suite/ui/generic/template/lib/AppComponent.js +3 -1
- package/src/sap/suite/ui/generic/template/lib/Application.js +15 -2
- package/src/sap/suite/ui/generic/template/lib/CRUDManager.js +5 -24
- package/src/sap/suite/ui/generic/template/lib/CommonEventHandlers.js +30 -8
- package/src/sap/suite/ui/generic/template/lib/CommonUtils.js +13 -5
- package/src/sap/suite/ui/generic/template/lib/PageLeaveHandler.js +19 -14
- package/src/sap/suite/ui/generic/template/lib/ShareUtils.js +1 -1
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillAIOrchestrator.js +1 -6
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillApplyHandler.js +25 -44
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillDialogController.js +80 -64
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFieldCollector.js +442 -259
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFormRenderer.js +125 -161
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillHandler.js +134 -160
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillTableRenderer.js +55 -189
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillDialog.fragment.xml +67 -86
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterBarHandler.js +175 -54
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterDataFetcherHelper.js +1 -1
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_US_sappsd.properties +275 -14
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_US_saptrc.properties +392 -163
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fi.properties +4 -4
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sl.properties +7 -7
- package/src/sap/suite/ui/generic/template/lib/insights/InsightsCardHelper.js +16 -4
- package/src/sap/suite/ui/generic/template/lib/navigation/NavigationController.js +3 -2
- package/src/sap/suite/ui/generic/template/lib/presentationControl/SmartTableHandler.js +13 -13
- package/src/sap/suite/ui/generic/template/library.js +1 -1
- package/src/sap/suite/ui/generic/template/listTemplates/controller/IappStateHandler.js +12 -3
- package/src/sap/suite/ui/generic/template/listTemplates/i18n/i18n_en_US_saptrc.properties +1 -1
- package/src/sap/suite/ui/generic/template/themes/base/ObjectPage.less +1 -1
|
@@ -25,6 +25,58 @@ sap.ui.define([
|
|
|
25
25
|
|
|
26
26
|
// ─── Field Mapping for LLM ───────────────────────────────────────────────
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Builds a live editability map by scanning all SmartFields on the ObjectPage
|
|
30
|
+
* and checking their actual runtime editable state.
|
|
31
|
+
*
|
|
32
|
+
* This is the authoritative source for whether a field is editable — it reflects
|
|
33
|
+
* hardcoded editable="false" in extension fragments, dynamic field-control values,
|
|
34
|
+
* key fields, and any other UI-level restriction, without needing to re-implement
|
|
35
|
+
* each rule from metadata. Mirrors the FE V4 approach (EasyFillButton._getEditableFields).
|
|
36
|
+
*
|
|
37
|
+
* If the same property appears in multiple SmartFields, it is treated as editable
|
|
38
|
+
* if at least one instance is editable (OR logic — consistent with V4).
|
|
39
|
+
*
|
|
40
|
+
* @param {object} oObjectPage - the ObjectPage control
|
|
41
|
+
* @returns {object} map of { propertyName: boolean } — true means editable in live UI
|
|
42
|
+
*/
|
|
43
|
+
buildLiveEditabilityMap: function(oObjectPage) {
|
|
44
|
+
// This function is kept for backward compatibility but the full
|
|
45
|
+
// rendered walk is now done inside buildObjectPageStructureMap which
|
|
46
|
+
// returns both sections/fieldMap AND liveEditable in one pass.
|
|
47
|
+
var mLiveEditable = Object.create(null);
|
|
48
|
+
if (!oObjectPage || !oObjectPage.findAggregatedObjects) {
|
|
49
|
+
return mLiveEditable;
|
|
50
|
+
}
|
|
51
|
+
oObjectPage.findAggregatedObjects(true, function(oControl) {
|
|
52
|
+
if (!oControl.isA("sap.ui.comp.smartfield.SmartField")) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
var oBindingInfo = oControl.getBindingInfo("value");
|
|
56
|
+
var sPropertyName = oBindingInfo && oBindingInfo.path;
|
|
57
|
+
if (!sPropertyName || sPropertyName.indexOf("/") !== -1) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
var bEditable = oControl.getEditable();
|
|
61
|
+
if (mLiveEditable[sPropertyName] === undefined) {
|
|
62
|
+
mLiveEditable[sPropertyName] = bEditable;
|
|
63
|
+
} else {
|
|
64
|
+
// Hardcoded editable="false" (no binding) is non-negotiable — overrides everything.
|
|
65
|
+
// OR logic only applies when the existing stored value is not already a settled hardcoded false,
|
|
66
|
+
// preventing a later dynamic-true instance from overwriting it.
|
|
67
|
+
var oEditableBinding = oControl.getBindingInfo("editable");
|
|
68
|
+
if (!bEditable && !oEditableBinding) {
|
|
69
|
+
mLiveEditable[sPropertyName] = false;
|
|
70
|
+
} else if (mLiveEditable[sPropertyName] !== false) {
|
|
71
|
+
mLiveEditable[sPropertyName] = mLiveEditable[sPropertyName] || bEditable;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
});
|
|
76
|
+
oLogger.debug("buildLiveEditabilityMap: scanned " + Object.keys(mLiveEditable).length + " SmartField(s)");
|
|
77
|
+
return mLiveEditable;
|
|
78
|
+
},
|
|
79
|
+
|
|
28
80
|
/**
|
|
29
81
|
* Builds the flat property map for the main entity type, used as the LLM input schema.
|
|
30
82
|
* Each entry contains the field label and OData data type.
|
|
@@ -175,16 +227,26 @@ sap.ui.define([
|
|
|
175
227
|
// ─── Updatable / Non-Updatable Classification ────────────────────────────
|
|
176
228
|
|
|
177
229
|
/**
|
|
178
|
-
* Splits LLM-returned simple fields into updatable and non-updatable buckets
|
|
179
|
-
* based on OData metadata annotations (sap:updatable, FieldControl, etc.).
|
|
230
|
+
* Splits LLM-returned simple fields into updatable and non-updatable buckets.
|
|
180
231
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
232
|
+
* Uses mLiveEditable (scanned from live SmartFields at dialog open) as the primary
|
|
233
|
+
* source when available — it captures all runtime restrictions: hardcoded editable="false"
|
|
234
|
+
* in extension fragments, key fields, dynamic field-control values, etc.
|
|
235
|
+
* Falls back to OData metadata annotation checks for fields not present in the live map.
|
|
236
|
+
*
|
|
237
|
+
* Decision per field:
|
|
238
|
+
* 1. mLiveEditable[field] === false → non-updatable (UI definitively non-editable)
|
|
239
|
+
* 2. mLiveEditable[field] === true → updatable (UI definitively editable)
|
|
240
|
+
* 3. mLiveEditable[field] === undefined → fall back to metadata annotation check
|
|
241
|
+
*
|
|
242
|
+
* @param {object} oAiCallResult - flat map of { fieldName: aiValue } from the LLM
|
|
243
|
+
* @param {object} oEntityType - OData entity type metadata
|
|
183
244
|
* @param {object} oObjectPageContext - current ObjectPage binding context
|
|
184
|
-
* @param {object} oEntitySet
|
|
245
|
+
* @param {object} oEntitySet - OData entity set metadata
|
|
246
|
+
* @param {object} [mLiveEditable] - live editability map from buildLiveEditabilityMap
|
|
185
247
|
* @returns {{ updatableFields: object, nonUpdatableFields: object }}
|
|
186
248
|
*/
|
|
187
|
-
getUpdatableAndNonUpdatableFields: function(oAiCallResult, oEntityType, oObjectPageContext, oEntitySet) {
|
|
249
|
+
getUpdatableAndNonUpdatableFields: function(oAiCallResult, oEntityType, oObjectPageContext, oEntitySet, mLiveEditable) {
|
|
188
250
|
var updatableFields = {};
|
|
189
251
|
var nonUpdatableFields = {};
|
|
190
252
|
|
|
@@ -194,9 +256,22 @@ sap.ui.define([
|
|
|
194
256
|
}
|
|
195
257
|
|
|
196
258
|
Object.keys(oAiCallResult).forEach(function(sKey) {
|
|
259
|
+
// Live map is the primary source when available
|
|
260
|
+
if (mLiveEditable) {
|
|
261
|
+
if (mLiveEditable[sKey] === false) {
|
|
262
|
+
nonUpdatableFields[sKey] = oAiCallResult[sKey];
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (mLiveEditable[sKey] === true) {
|
|
266
|
+
updatableFields[sKey] = oAiCallResult[sKey];
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// undefined → field not present in live UI (e.g. table-only property)
|
|
270
|
+
// fall through to metadata check
|
|
271
|
+
}
|
|
272
|
+
|
|
197
273
|
var oProperty = oEntityType.property.find(function(oP) { return oP.name === sKey; });
|
|
198
274
|
if (!oProperty) {
|
|
199
|
-
// Property not found in metadata → treat as non-updatable
|
|
200
275
|
nonUpdatableFields[sKey] = oAiCallResult[sKey];
|
|
201
276
|
return;
|
|
202
277
|
}
|
|
@@ -234,7 +309,8 @@ sap.ui.define([
|
|
|
234
309
|
return oHiddenAnnotation["Bool"] !== "true";
|
|
235
310
|
}
|
|
236
311
|
if (oHiddenAnnotation["Path"] && oObjectPageContext) {
|
|
237
|
-
|
|
312
|
+
var oContextObj = oObjectPageContext.getObject();
|
|
313
|
+
return oContextObj ? !oContextObj[oHiddenAnnotation["Path"]] : false;
|
|
238
314
|
}
|
|
239
315
|
// <Annotation Term="UI.Hidden"/> with no value → hidden
|
|
240
316
|
return false;
|
|
@@ -243,7 +319,8 @@ sap.ui.define([
|
|
|
243
319
|
// Check "sap:field-control" attribute (value 0 = hidden)
|
|
244
320
|
var sFieldControl = oProperty["sap:field-control"];
|
|
245
321
|
if (sFieldControl && oObjectPageContext) {
|
|
246
|
-
var
|
|
322
|
+
var oCtxObj = oObjectPageContext.getObject();
|
|
323
|
+
var iFieldControlValue = oCtxObj ? oCtxObj[sFieldControl] : undefined;
|
|
247
324
|
return iFieldControlValue !== 0;
|
|
248
325
|
}
|
|
249
326
|
|
|
@@ -306,122 +383,338 @@ sap.ui.define([
|
|
|
306
383
|
return sUpdatable !== "false";
|
|
307
384
|
}
|
|
308
385
|
|
|
309
|
-
// Check "com.sap.vocabularies.Common.v1.FieldControl" enum
|
|
386
|
+
// Check "com.sap.vocabularies.Common.v1.FieldControl" — static enum or dynamic path
|
|
387
|
+
// FieldControl values: 0=Hidden, 1=ReadOnly, 3=Optional, 7=Mandatory
|
|
310
388
|
var oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
|
|
311
389
|
if (oAnnotation && oAnnotation["EnumMember"]) {
|
|
312
390
|
return oAnnotation["EnumMember"] !== "com.sap.vocabularies.Common.v1.FieldControlType/ReadOnly";
|
|
313
391
|
}
|
|
392
|
+
if (oAnnotation && oAnnotation["Path"]) {
|
|
393
|
+
var iDynamicFieldControl = oContextObject && oContextObject[oAnnotation["Path"]];
|
|
394
|
+
if (iDynamicFieldControl !== undefined && iDynamicFieldControl !== null) {
|
|
395
|
+
return iDynamicFieldControl !== 0 && iDynamicFieldControl !== 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
314
398
|
|
|
315
|
-
// Check "sap:field-control" dynamic path (
|
|
399
|
+
// Check "sap:field-control" dynamic path (values: 0=Hidden, 1=ReadOnly)
|
|
400
|
+
// If the _fc property is defined in metadata but absent from the context object
|
|
401
|
+
// (not fetched / not in $select), treat as non-editable — safe fallback.
|
|
316
402
|
var sFieldControl = oProperty["sap:field-control"];
|
|
317
403
|
if (sFieldControl) {
|
|
318
|
-
|
|
319
|
-
|
|
404
|
+
if (!oContextObject || !(sFieldControl in oContextObject)) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
var iFieldControlValue = oContextObject[sFieldControl];
|
|
408
|
+
return iFieldControlValue !== 0 && iFieldControlValue !== 1;
|
|
320
409
|
}
|
|
321
410
|
|
|
322
411
|
// No restriction → editable by default
|
|
323
412
|
return true;
|
|
324
413
|
},
|
|
325
414
|
|
|
326
|
-
// ───
|
|
415
|
+
// ─── ObjectPage Structure Map & Section Building ─────────────────────────
|
|
327
416
|
|
|
328
417
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
418
|
+
* Walks the RENDERED ObjectPage using findAggregatedObjects — the same proven
|
|
419
|
+
* approach used by buildLiveEditabilityMap — and produces the section/group/field
|
|
420
|
+
* structure map AND the live editability map in a SINGLE pass.
|
|
421
|
+
*
|
|
422
|
+
* IMPORTANT: Must be called when the page is in EDIT MODE with the OData entity
|
|
423
|
+
* context fully resolved (i.e. inside _handleAISubmit, not at dialog open).
|
|
424
|
+
* At that point getBindingInfo("value").path is reliably populated for all SmartFields.
|
|
425
|
+
*
|
|
426
|
+
* This approach is correct because:
|
|
427
|
+
* ✓ Extension fragment SmartFields are included automatically
|
|
428
|
+
* ✓ Editability reflects actual UI state (editable="false" in extension = non-editable)
|
|
429
|
+
* ✓ Section/group titles come from the rendered page — same as what the user sees
|
|
430
|
+
* ✓ Hidden sections are naturally excluded by checking the parent Section's visible state
|
|
431
|
+
* ✓ One walk replaces two separate scans
|
|
331
432
|
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
433
|
+
* For each SmartField found, we walk UP the parent chain to get section/group context:
|
|
434
|
+
* SmartField → GroupElement → SmartForm.Group → SmartForm → block → SubSection → Section
|
|
435
|
+
*
|
|
436
|
+
* Falls back to annotation-based structure if the rendered walk finds no fields
|
|
437
|
+
* (edge case: entity context not yet resolved).
|
|
438
|
+
*
|
|
439
|
+
* @param {object} oObjectPage - the REAL rendered FLP ObjectPage (not EasyFill dialog OPL)
|
|
440
|
+
* @param {object} [oEntityTypeFallback] - OData entity type for annotation fallback
|
|
441
|
+
* @param {object} [oRbFallback] - i18n resource bundle for annotation fallback
|
|
442
|
+
* @returns {{ sections: Array, fieldMap: Map, liveEditable: object }}
|
|
336
443
|
*/
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
444
|
+
buildObjectPageStructureMap: function(oObjectPage, oEntityTypeFallback, oRbFallback) {
|
|
445
|
+
if (!oObjectPage || !oObjectPage.findAggregatedObjects) {
|
|
446
|
+
return { sections: [], fieldMap: new Map(), liveEditable: Object.create(null) };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
var aSections = [];
|
|
450
|
+
var mFieldMap = new Map();
|
|
451
|
+
var mLiveEditable = Object.create(null);
|
|
452
|
+
var mSectionByKey = {};
|
|
453
|
+
var mGroupByKey = {};
|
|
454
|
+
var that = this;
|
|
455
|
+
|
|
456
|
+
oObjectPage.findAggregatedObjects(true, function(oControl) {
|
|
457
|
+
if (!oControl.isA("sap.ui.comp.smartfield.SmartField")) { return false; }
|
|
458
|
+
|
|
459
|
+
// Get property name from value binding
|
|
460
|
+
var oBindingInfo = oControl.getBindingInfo("value");
|
|
461
|
+
var sPropertyName = oBindingInfo && oBindingInfo.path;
|
|
462
|
+
if (!sPropertyName || sPropertyName.indexOf("/") !== -1) { return false; }
|
|
463
|
+
|
|
464
|
+
// Walk UP parent chain to get section + group context
|
|
465
|
+
var sSectionTitle = "";
|
|
466
|
+
var sGroupTitle = "";
|
|
467
|
+
var oParent = oControl.getParent();
|
|
468
|
+
while (oParent) {
|
|
469
|
+
if (oParent.isA && oParent.isA("sap.ui.comp.smartform.Group")) {
|
|
470
|
+
var oTitleObj = oParent.getTitle && oParent.getTitle();
|
|
471
|
+
sGroupTitle = (oTitleObj && typeof oTitleObj.getText === "function")
|
|
472
|
+
? (oTitleObj.getText() || "") : "";
|
|
473
|
+
}
|
|
474
|
+
if (oParent.isA && oParent.isA("sap.uxap.ObjectPageSection")) {
|
|
475
|
+
// Skip hidden sections
|
|
476
|
+
if (oParent.getVisible && !oParent.getVisible()) { return false; }
|
|
477
|
+
sSectionTitle = oParent.getTitle() || "";
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
oParent = oParent.getParent && oParent.getParent();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Skip fields not in any visible section (e.g. header display fields)
|
|
484
|
+
if (!sSectionTitle) { return false; }
|
|
485
|
+
if (!sGroupTitle) { sGroupTitle = sSectionTitle; }
|
|
486
|
+
|
|
487
|
+
var sGroupKey = sSectionTitle + "|" + sGroupTitle;
|
|
488
|
+
|
|
489
|
+
// Ensure section + group exist in our structures
|
|
490
|
+
if (!Object.prototype.hasOwnProperty.call(mSectionByKey, sSectionTitle)) {
|
|
491
|
+
mSectionByKey[sSectionTitle] = aSections.length;
|
|
492
|
+
aSections.push({
|
|
493
|
+
id: that._generateId(sSectionTitle, []),
|
|
494
|
+
title: sSectionTitle,
|
|
495
|
+
groups: []
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
var iSectionIdx = mSectionByKey[sSectionTitle];
|
|
499
|
+
var oSectionEntry = aSections[iSectionIdx];
|
|
500
|
+
|
|
501
|
+
if (!Object.prototype.hasOwnProperty.call(mGroupByKey, sGroupKey)) {
|
|
502
|
+
var sGroupId = that._generateId(sGroupKey, []);
|
|
503
|
+
mGroupByKey[sGroupKey] = { sectionId: oSectionEntry.id, groupId: sGroupId };
|
|
504
|
+
oSectionEntry.groups.push({ id: sGroupId, title: sGroupTitle });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Register in fieldMap (first occurrence wins)
|
|
508
|
+
if (!mFieldMap.has(sPropertyName)) {
|
|
509
|
+
mFieldMap.set(sPropertyName, {
|
|
510
|
+
sectionId: mGroupByKey[sGroupKey].sectionId,
|
|
511
|
+
groupId: mGroupByKey[sGroupKey].groupId
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Live editability — hardcoded false overrides dynamic instances regardless of order
|
|
516
|
+
var bEditable = oControl.getEditable();
|
|
517
|
+
var oEditableBinding = oControl.getBindingInfo("editable");
|
|
518
|
+
var bIsHardcodedFalse = !bEditable && !oEditableBinding;
|
|
519
|
+
|
|
520
|
+
if (mLiveEditable[sPropertyName] === undefined) {
|
|
521
|
+
mLiveEditable[sPropertyName] = bEditable;
|
|
522
|
+
} else if (bIsHardcodedFalse) {
|
|
523
|
+
// Extension fragment has explicit editable="false" — non-negotiable
|
|
524
|
+
mLiveEditable[sPropertyName] = false;
|
|
525
|
+
} else if (mLiveEditable[sPropertyName] !== false) {
|
|
526
|
+
// Only OR-in the new value if a hardcoded false hasn't already settled this field
|
|
527
|
+
mLiveEditable[sPropertyName] = mLiveEditable[sPropertyName] || bEditable;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return false;
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
oLogger.debug("buildObjectPageStructureMap: rendered walk found "
|
|
534
|
+
+ mFieldMap.size + " fields in " + aSections.length + " sections");
|
|
535
|
+
|
|
536
|
+
// Fallback: if rendered walk found nothing (entity context not yet resolved),
|
|
537
|
+
// fall back to annotation walk so at least section structure is available
|
|
538
|
+
if (mFieldMap.size === 0 && oEntityTypeFallback) {
|
|
539
|
+
oLogger.debug("buildObjectPageStructureMap: falling back to annotation walk");
|
|
540
|
+
var oAnnotationResult = this._buildFromAnnotations(oEntityTypeFallback, oRbFallback);
|
|
541
|
+
return {
|
|
542
|
+
sections: oAnnotationResult.sections,
|
|
543
|
+
fieldMap: oAnnotationResult.fieldMap,
|
|
544
|
+
liveEditable: this.buildLiveEditabilityMap(oObjectPage)
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return { sections: aSections, fieldMap: mFieldMap, liveEditable: mLiveEditable };
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Annotation-based fallback for buildObjectPageStructureMap.
|
|
553
|
+
* Used when the rendered walk finds no fields (entity context not yet resolved).
|
|
554
|
+
* @private
|
|
555
|
+
*/
|
|
556
|
+
_buildFromAnnotations: function(oEntityType, oRb) {
|
|
557
|
+
var aRaw = this._buildRawAnnotationTree(oEntityType, oRb);
|
|
340
558
|
var aSections = [];
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
559
|
+
var mSectionByKey = {};
|
|
560
|
+
var mGroupByKey = {};
|
|
561
|
+
var mFieldMap = new Map();
|
|
562
|
+
|
|
563
|
+
aRaw.forEach(function(oEntry) {
|
|
564
|
+
var aTitle = oEntry.title;
|
|
565
|
+
var sField = oEntry.property;
|
|
566
|
+
var sSectionTitle = aTitle[0] || "";
|
|
567
|
+
var sGroupTitle = aTitle.length > 1 ? aTitle[aTitle.length - 1] : "";
|
|
568
|
+
var sGroupKey = sSectionTitle + "|" + sGroupTitle;
|
|
569
|
+
|
|
570
|
+
if (!Object.prototype.hasOwnProperty.call(mSectionByKey, sSectionTitle)) {
|
|
571
|
+
mSectionByKey[sSectionTitle] = aSections.length;
|
|
572
|
+
aSections.push({ id: this._generateId(sSectionTitle, []), title: sSectionTitle, groups: [] });
|
|
573
|
+
}
|
|
574
|
+
var oSection = aSections[mSectionByKey[sSectionTitle]];
|
|
575
|
+
|
|
576
|
+
if (!Object.prototype.hasOwnProperty.call(mGroupByKey, sGroupKey)) {
|
|
577
|
+
var sGroupId = this._generateId(sGroupKey, []);
|
|
578
|
+
mGroupByKey[sGroupKey] = { sectionId: oSection.id, groupId: sGroupId };
|
|
579
|
+
oSection.groups.push({ id: sGroupId, title: sGroupTitle });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (!mFieldMap.has(sField)) {
|
|
583
|
+
mFieldMap.set(sField, {
|
|
584
|
+
sectionId: mGroupByKey[sGroupKey].sectionId,
|
|
585
|
+
groupId: mGroupByKey[sGroupKey].groupId
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}.bind(this));
|
|
589
|
+
|
|
590
|
+
return { sections: aSections, fieldMap: mFieldMap };
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Applies LLM results to the pre-built structure map and returns a sections array
|
|
595
|
+
* ready to be written to easyFillDialogModel>/sections.
|
|
596
|
+
*
|
|
597
|
+
* Each section node shape: { type, title, id, visible, fields, children }
|
|
598
|
+
* Each child group shape: { id, title, visible, aActiveFields, fields }
|
|
599
|
+
*
|
|
600
|
+
* O(fields returned by LLM) — no annotation walk, no DOM traversal.
|
|
601
|
+
*
|
|
602
|
+
* @param {Map} mFieldMap - from buildObjectPageStructureMap
|
|
603
|
+
* @param {Array} aOpSections - sections from buildObjectPageStructureMap
|
|
604
|
+
* @param {object} oUpdatableFields - { fieldName: value }
|
|
605
|
+
* @param {object} oNonUpdatableFields - { fieldName: value }
|
|
606
|
+
* @returns {Array} sections array for easyFillDialogModel>/sections
|
|
607
|
+
*/
|
|
608
|
+
applyLLMResultToStructureMap: function(mFieldMap, aOpSections, oUpdatableFields, oNonUpdatableFields) {
|
|
609
|
+
var mSectionById = {};
|
|
610
|
+
var mGroupById = {};
|
|
611
|
+
(aOpSections || []).forEach(function(oSec, iSec) {
|
|
612
|
+
mSectionById[oSec.id] = iSec;
|
|
613
|
+
(oSec.groups || []).forEach(function(oGrp, iGrp) {
|
|
614
|
+
mGroupById[oGrp.id] = { sectionIdx: iSec, groupIdx: iGrp };
|
|
351
615
|
});
|
|
352
|
-
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Start with all sections hidden, no active fields
|
|
619
|
+
var aSections = (aOpSections || []).map(function(oSec) {
|
|
620
|
+
return {
|
|
621
|
+
title: oSec.title,
|
|
622
|
+
id: oSec.id,
|
|
623
|
+
visible: false,
|
|
624
|
+
fields: [],
|
|
625
|
+
children: (oSec.groups || []).map(function(oGrp) {
|
|
626
|
+
return {
|
|
627
|
+
id: oGrp.id,
|
|
628
|
+
title: oGrp.title,
|
|
629
|
+
visible: false,
|
|
630
|
+
aActiveFields: [],
|
|
631
|
+
fields: []
|
|
632
|
+
};
|
|
633
|
+
})
|
|
634
|
+
};
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Flip sections/groups that have LLM-matched updatable fields
|
|
638
|
+
Object.keys(oUpdatableFields || {}).forEach(function(sField) {
|
|
639
|
+
if (!mFieldMap.has(sField)) { return; }
|
|
640
|
+
var oLoc = mFieldMap.get(sField);
|
|
641
|
+
var iSec = mSectionById[oLoc.sectionId];
|
|
642
|
+
var oGrpLoc = mGroupById[oLoc.groupId];
|
|
643
|
+
if (iSec === undefined || !oGrpLoc) { return; }
|
|
644
|
+
aSections[iSec].visible = true;
|
|
645
|
+
var oGrp = aSections[oGrpLoc.sectionIdx].children[oGrpLoc.groupIdx];
|
|
646
|
+
oGrp.visible = true;
|
|
647
|
+
if (oGrp.aActiveFields.indexOf(sField) === -1) {
|
|
648
|
+
oGrp.aActiveFields.push(sField);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
353
651
|
|
|
354
|
-
//
|
|
652
|
+
// Non-updatable fields → "Unfilled Fields" section appended at the end
|
|
653
|
+
var aNonUpdatableKeys = Object.keys(oNonUpdatableFields || {});
|
|
355
654
|
if (aNonUpdatableKeys.length > 0) {
|
|
655
|
+
var sUnfilledGrpId = this._generateId("unfilledFields", []);
|
|
356
656
|
aSections.push({
|
|
357
|
-
title:
|
|
358
|
-
|
|
657
|
+
title: "{@i18n>EASY_FILL_UNFILLED_FIELDS}",
|
|
658
|
+
id: this._generateId("unfilledFieldsSection", []),
|
|
659
|
+
visible: true,
|
|
660
|
+
fields: [],
|
|
359
661
|
children: [{
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
662
|
+
id: sUnfilledGrpId,
|
|
663
|
+
title: "",
|
|
664
|
+
visible: true,
|
|
665
|
+
aActiveFields: aNonUpdatableKeys,
|
|
666
|
+
fields: aNonUpdatableKeys
|
|
363
667
|
}]
|
|
364
668
|
});
|
|
365
669
|
}
|
|
366
670
|
|
|
367
|
-
// Fallback: if no split provided, use old behavior
|
|
368
|
-
if (aSections.length === 0) {
|
|
369
|
-
return this._transformToTreeExpanded(oLLMResult);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
671
|
return aSections;
|
|
373
672
|
},
|
|
374
673
|
|
|
375
674
|
/**
|
|
376
|
-
* Builds a
|
|
377
|
-
*
|
|
378
|
-
* updatable fields returned by the LLM. Non-updatable (read-only) fields are
|
|
379
|
-
* excluded from the annotation sections and shown exclusively in the
|
|
380
|
-
* "Unfilled Fields" section appended at the end.
|
|
675
|
+
* Builds a sections array for Condensed mode ready for easyFillDialogModel>/sections.
|
|
676
|
+
* Two flat groups: "Filled Fields" (updatable) and "Unfilled Fields" (non-updatable).
|
|
381
677
|
*
|
|
382
|
-
* @param {object}
|
|
383
|
-
* @param {object}
|
|
384
|
-
* @
|
|
385
|
-
* @param {object} oNonUpdatableFields - flat map of non-updatable fields only (optional)
|
|
386
|
-
* @returns {Array} filtered section tree array
|
|
678
|
+
* @param {object} oUpdatableFields - { fieldName: value }
|
|
679
|
+
* @param {object} oNonUpdatableFields - { fieldName: value }
|
|
680
|
+
* @returns {Array} sections array
|
|
387
681
|
*/
|
|
388
|
-
|
|
389
|
-
var
|
|
390
|
-
aSections = aSections
|
|
391
|
-
.concat(this._getSectionsForHeader(oEntityType, oRb))
|
|
392
|
-
.concat(this._getSectionsForObjectPageFacets(oEntityType));
|
|
393
|
-
var aTree = this._transformToTree(aSections);
|
|
394
|
-
|
|
395
|
-
// Annotation sections must only show updatable fields.
|
|
396
|
-
// Derive the updatable-only subset by removing non-updatable keys from oLLMResult
|
|
397
|
-
// so non-updatable fields do not appear in both annotation sections AND Unfilled Fields.
|
|
398
|
-
var aNonUpdatableSet = Object.keys(oNonUpdatableFields || {});
|
|
399
|
-
var oUpdatableResult = Object.keys(oLLMResult).reduce(function(oAcc, sKey) {
|
|
400
|
-
if (aNonUpdatableSet.indexOf(sKey) === -1) {
|
|
401
|
-
oAcc[sKey] = oLLMResult[sKey];
|
|
402
|
-
}
|
|
403
|
-
return oAcc;
|
|
404
|
-
}, {});
|
|
405
|
-
var aFilteredTree = this._filterTreeByLLMResult(aTree, oUpdatableResult);
|
|
406
|
-
aFilteredTree = this._flattenTreeForObjectPageDialog(aFilteredTree);
|
|
407
|
-
|
|
408
|
-
// "Unfilled Fields" contains only non-updatable (read-only) fields.
|
|
409
|
-
// Editable fields that have no annotation section are silently dropped here —
|
|
410
|
-
// they already appear in their respective annotation-matched sections above.
|
|
682
|
+
getSectionInfoForCondensedMode: function(oUpdatableFields, oNonUpdatableFields) {
|
|
683
|
+
var aUpdatableKeys = Object.keys(oUpdatableFields || {});
|
|
411
684
|
var aNonUpdatableKeys = Object.keys(oNonUpdatableFields || {});
|
|
685
|
+
var aSections = [];
|
|
686
|
+
|
|
687
|
+
if (aUpdatableKeys.length > 0) {
|
|
688
|
+
aSections.push({
|
|
689
|
+
title: "{@i18n>EASY_FILL_FILLED_FIELDS}",
|
|
690
|
+
visible: true,
|
|
691
|
+
fields: [],
|
|
692
|
+
children: [{
|
|
693
|
+
title: "",
|
|
694
|
+
visible: true,
|
|
695
|
+
aActiveFields: aUpdatableKeys,
|
|
696
|
+
fields: aUpdatableKeys,
|
|
697
|
+
id: this._generateId("filledFields", aUpdatableKeys)
|
|
698
|
+
}]
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
412
702
|
if (aNonUpdatableKeys.length > 0) {
|
|
413
|
-
|
|
414
|
-
title:
|
|
415
|
-
|
|
703
|
+
aSections.push({
|
|
704
|
+
title: "{@i18n>EASY_FILL_UNFILLED_FIELDS}",
|
|
705
|
+
visible: true,
|
|
706
|
+
fields: [],
|
|
416
707
|
children: [{
|
|
417
|
-
title:
|
|
418
|
-
|
|
419
|
-
|
|
708
|
+
title: "",
|
|
709
|
+
visible: true,
|
|
710
|
+
aActiveFields: aNonUpdatableKeys,
|
|
711
|
+
fields: aNonUpdatableKeys,
|
|
712
|
+
id: this._generateId("unfilledFields", aNonUpdatableKeys)
|
|
420
713
|
}]
|
|
421
714
|
});
|
|
422
715
|
}
|
|
423
716
|
|
|
424
|
-
return
|
|
717
|
+
return aSections;
|
|
425
718
|
},
|
|
426
719
|
|
|
427
720
|
// ─── SmartTable Utilities ─────────────────────────────────────────────────
|
|
@@ -513,102 +806,15 @@ sap.ui.define([
|
|
|
513
806
|
// ─── Private Helpers ─────────────────────────────────────────────────────
|
|
514
807
|
|
|
515
808
|
/**
|
|
516
|
-
*
|
|
517
|
-
* E.g. "/to_Items" → "to_Items", "/SalesOrderSet/to_Items" → "to_Items"
|
|
518
|
-
*
|
|
519
|
-
* @param {string} sBindingPath
|
|
520
|
-
* @returns {string|null}
|
|
521
|
-
*/
|
|
522
|
-
_extractNavigationPropertyName: function(sBindingPath) {
|
|
523
|
-
if (!sBindingPath || typeof sBindingPath !== "string") {
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
var aParts = sBindingPath.split("/").filter(Boolean);
|
|
527
|
-
return aParts.length ? aParts[aParts.length - 1] : null;
|
|
528
|
-
},
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Builds the "condensed" tree: all LLM fields in a single flat group.
|
|
532
|
-
* @private
|
|
533
|
-
*/
|
|
534
|
-
_transformToTreeExpanded: function(oLLMRes) {
|
|
535
|
-
return [
|
|
536
|
-
{
|
|
537
|
-
title: "",
|
|
538
|
-
fields: [],
|
|
539
|
-
children: [{
|
|
540
|
-
title: "",
|
|
541
|
-
fields: Object.keys(oLLMRes),
|
|
542
|
-
id: this._generateId("", Object.keys(oLLMRes))
|
|
543
|
-
}]
|
|
544
|
-
}
|
|
545
|
-
];
|
|
546
|
-
},
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Builds the "ObjectPage" section tree from a flat array of { title[], property } entries.
|
|
550
|
-
* Groups entries by their title path hierarchy.
|
|
551
|
-
* @private
|
|
552
|
-
*/
|
|
553
|
-
_transformToTree: function(data) {
|
|
554
|
-
var rootMap = {};
|
|
555
|
-
var seen = new Set();
|
|
556
|
-
var that = this;
|
|
557
|
-
|
|
558
|
-
data.forEach(function(entry) {
|
|
559
|
-
var title = entry.title;
|
|
560
|
-
var property = entry.property;
|
|
561
|
-
if (seen.has(property)) { return; }
|
|
562
|
-
seen.add(property);
|
|
563
|
-
|
|
564
|
-
var currentMap = rootMap;
|
|
565
|
-
var sTitlePath = "";
|
|
566
|
-
|
|
567
|
-
title.forEach(function(level, index) {
|
|
568
|
-
var isLast = index === title.length - 1;
|
|
569
|
-
sTitlePath = sTitlePath ? (sTitlePath + "_" + level) : level;
|
|
570
|
-
|
|
571
|
-
if (!currentMap[level]) {
|
|
572
|
-
currentMap[level] = {
|
|
573
|
-
title: level,
|
|
574
|
-
titlePath: sTitlePath,
|
|
575
|
-
fields: [],
|
|
576
|
-
_childrenMap: {}
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if (isLast) {
|
|
581
|
-
currentMap[level].fields.push(property);
|
|
582
|
-
} else {
|
|
583
|
-
currentMap = currentMap[level]._childrenMap;
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
function convertToArray(oMap) {
|
|
589
|
-
return Object.values(oMap).map(function(oNode) {
|
|
590
|
-
return {
|
|
591
|
-
title: oNode.title,
|
|
592
|
-
id: that._generateId(oNode.titlePath, oNode.fields),
|
|
593
|
-
fields: oNode.fields,
|
|
594
|
-
children: oNode._childrenMap ? convertToArray(oNode._childrenMap) : []
|
|
595
|
-
};
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return convertToArray(rootMap);
|
|
600
|
-
},
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Generates a stable DOM-safe ID for a SmartForm section based on its title path and fields.
|
|
809
|
+
* Generates a stable DOM-safe ID string from a title path and optional field list.
|
|
604
810
|
* @private
|
|
605
811
|
*/
|
|
606
812
|
_generateId: function(sTitlePath, aFields) {
|
|
607
|
-
var sSanitizedTitle = sTitlePath
|
|
813
|
+
var sSanitizedTitle = (sTitlePath || "")
|
|
608
814
|
.replace(/\s+/g, "_")
|
|
609
815
|
.replace(/[^a-zA-Z0-9_]/g, "")
|
|
610
816
|
.toLowerCase();
|
|
611
|
-
var sFieldsPart = aFields
|
|
817
|
+
var sFieldsPart = (aFields || [])
|
|
612
818
|
.join("_")
|
|
613
819
|
.replace(/[^a-zA-Z0-9_]/g, "")
|
|
614
820
|
.toLowerCase();
|
|
@@ -616,90 +822,29 @@ sap.ui.define([
|
|
|
616
822
|
},
|
|
617
823
|
|
|
618
824
|
/**
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* for surfacing them elsewhere (e.g. the "unfilledFields" section for non-updatable fields).
|
|
622
|
-
*
|
|
623
|
-
* @param {Array} aTree - section tree
|
|
624
|
-
* @param {object} oLLMResult - flat map of all LLM fields
|
|
625
|
-
* @returns {Array} filtered tree
|
|
825
|
+
* Extracts the navigation property name from an OData binding path.
|
|
826
|
+
* E.g. "/to_Items" → "to_Items", "/SalesOrderSet/to_Items" → "to_Items"
|
|
626
827
|
* @private
|
|
627
828
|
*/
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
function filterNode(oNode, sParentPath) {
|
|
633
|
-
var sTitlePath = sParentPath ? (sParentPath + "_" + oNode.title) : oNode.title;
|
|
634
|
-
|
|
635
|
-
var aFilteredFields = oNode.fields.filter(function(sField) {
|
|
636
|
-
return aRequestedKeys.includes(sField);
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
var aFilteredChildren = oNode.children
|
|
640
|
-
.map(function(oChild) { return filterNode(oChild, sTitlePath); })
|
|
641
|
-
.filter(function(oChild) { return oChild.fields.length > 0 || oChild.children.length > 0; });
|
|
642
|
-
|
|
643
|
-
return {
|
|
644
|
-
title: oNode.title,
|
|
645
|
-
id: that._generateId(sTitlePath, aFilteredFields),
|
|
646
|
-
fields: aFilteredFields,
|
|
647
|
-
children: aFilteredChildren
|
|
648
|
-
};
|
|
829
|
+
_extractNavigationPropertyName: function(sBindingPath) {
|
|
830
|
+
if (!sBindingPath || typeof sBindingPath !== "string") {
|
|
831
|
+
return null;
|
|
649
832
|
}
|
|
650
|
-
|
|
651
|
-
return
|
|
652
|
-
.map(function(oNode) { return filterNode(oNode, ""); })
|
|
653
|
-
.filter(function(oNode) { return oNode.fields.length > 0 || oNode.children.length > 0; });
|
|
833
|
+
var aParts = sBindingPath.split("/").filter(Boolean);
|
|
834
|
+
return aParts.length ? aParts[aParts.length - 1] : null;
|
|
654
835
|
},
|
|
655
836
|
|
|
656
837
|
/**
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
* If annotation facets produce deeper nesting, section titles can render while fields remain
|
|
661
|
-
* invisible because grandchildren are never bound as groups.
|
|
662
|
-
*
|
|
663
|
-
* @param {Array} aTree - filtered section tree
|
|
664
|
-
* @returns {Array} dialog-compatible tree with one-level children per section
|
|
838
|
+
* Walks all ObjectPage annotations (HeaderInfo + HeaderFacets + UI.Facets) and returns
|
|
839
|
+
* a flat array of { title[], property } entries. This is the single source of truth for
|
|
840
|
+
* the raw annotation structure and is shared by both the map builder and the un-cached path.
|
|
665
841
|
* @private
|
|
666
842
|
*/
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
var aGroups = [];
|
|
673
|
-
|
|
674
|
-
if (Array.isArray(oNode.fields) && oNode.fields.length > 0) {
|
|
675
|
-
aGroups.push({
|
|
676
|
-
title: oNode.title,
|
|
677
|
-
id: that._generateId(sTitlePath, oNode.fields),
|
|
678
|
-
fields: oNode.fields,
|
|
679
|
-
children: []
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (Array.isArray(oNode.children) && oNode.children.length > 0) {
|
|
684
|
-
oNode.children.forEach(function(oChild) {
|
|
685
|
-
aGroups = aGroups.concat(collectRenderableGroups(oChild, sTitlePath));
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
return aGroups;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
return (aTree || []).map(function(oSection) {
|
|
693
|
-
var sSectionPath = oSection.title || "";
|
|
694
|
-
return {
|
|
695
|
-
title: oSection.title,
|
|
696
|
-
id: oSection.id,
|
|
697
|
-
fields: [],
|
|
698
|
-
children: collectRenderableGroups(oSection, sSectionPath)
|
|
699
|
-
};
|
|
700
|
-
}).filter(function(oSection) {
|
|
701
|
-
return Array.isArray(oSection.children) && oSection.children.length > 0;
|
|
702
|
-
});
|
|
843
|
+
_buildRawAnnotationTree: function(oEntityType, oRb) {
|
|
844
|
+
return [].concat(
|
|
845
|
+
this._getSectionsForHeader(oEntityType, oRb),
|
|
846
|
+
this._getSectionsForObjectPageFacets(oEntityType)
|
|
847
|
+
);
|
|
703
848
|
},
|
|
704
849
|
|
|
705
850
|
/**
|
|
@@ -725,8 +870,12 @@ sap.ui.define([
|
|
|
725
870
|
|
|
726
871
|
if (aHeaderFacets && aHeaderFacets.length > 0) {
|
|
727
872
|
var aSections = this._extractSectionInfoFromReferenceFacet(oEntityType, aHeaderFacets);
|
|
873
|
+
var sHeaderTitle = oRb.getText("EASY_FILL_SECTION_HEADER");
|
|
728
874
|
aSections.forEach(function(oSection) {
|
|
729
|
-
|
|
875
|
+
// Prepend the "Header" section title while keeping the facet's own label as the
|
|
876
|
+
// second hierarchy level. Previously both levels were set to the same headerText,
|
|
877
|
+
// which collapsed all header facets into one group and lost their individual labels.
|
|
878
|
+
oSection.title = [sHeaderTitle].concat(oSection.title);
|
|
730
879
|
});
|
|
731
880
|
aRes.push.apply(aRes, aSections);
|
|
732
881
|
}
|
|
@@ -772,14 +921,49 @@ sap.ui.define([
|
|
|
772
921
|
|
|
773
922
|
/**
|
|
774
923
|
* Extracts { title[], property } entries from a set of ReferenceFacets.
|
|
775
|
-
*
|
|
924
|
+
* Handles UI.DataFieldForAnnotation by following its Target to the referenced FieldGroup —
|
|
925
|
+
* this is the primary pattern used in ObjectPage apps to embed FieldGroups inside UI.Facets.
|
|
926
|
+
* Skips IntentBasedNavigation, ForAction, and other non-renderable record types.
|
|
776
927
|
* @private
|
|
777
928
|
*/
|
|
778
929
|
_extractSectionInfoFromReferenceFacet: function(oEntityType, aFacets, aText) {
|
|
779
930
|
aText = aText || [];
|
|
780
931
|
var aRes = [];
|
|
781
932
|
aFacets.forEach(function(oFacet) {
|
|
782
|
-
|
|
933
|
+
var sNormalized = oFacet.RecordType
|
|
934
|
+
? oFacet.RecordType.replace("com.sap.vocabularies.UI.v1.", "UI.")
|
|
935
|
+
: null;
|
|
936
|
+
|
|
937
|
+
// DataFieldForAnnotation: the record itself is not a field — it is a pointer to a
|
|
938
|
+
// FieldGroup (or other annotation). Follow the Target.AnnotationPath to get its Data.
|
|
939
|
+
if (sNormalized === "UI.DataFieldForAnnotation") {
|
|
940
|
+
if (!oFacet.Target || !oFacet.Target.AnnotationPath) { return; }
|
|
941
|
+
var sInnerPath = oFacet.Target.AnnotationPath.substring(1);
|
|
942
|
+
var oInnerAnnotation = oEntityType[sInnerPath];
|
|
943
|
+
if (!oInnerAnnotation) { return; }
|
|
944
|
+
|
|
945
|
+
// Resolve the label: DataFieldForAnnotation.Label → FieldGroup.Label → fallback
|
|
946
|
+
var sInnerLabel = null;
|
|
947
|
+
if (oFacet.Label && oFacet.Label.String) {
|
|
948
|
+
sInnerLabel = oFacet.Label.String;
|
|
949
|
+
} else if (oInnerAnnotation.Label && oInnerAnnotation.Label.String) {
|
|
950
|
+
sInnerLabel = oInnerAnnotation.Label.String;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
var aTempText = aText.slice();
|
|
954
|
+
if (sInnerLabel) { aTempText.push(sInnerLabel); }
|
|
955
|
+
|
|
956
|
+
if (Array.isArray(oInnerAnnotation.Data)) {
|
|
957
|
+
oInnerAnnotation.Data.forEach(function(oData) {
|
|
958
|
+
if (!this._isSupportedRecordType(oData.RecordType)) { return; }
|
|
959
|
+
if (!oData.Value || !oData.Value.Path) { return; }
|
|
960
|
+
aRes.push({ title: aTempText, property: oData.Value.Path });
|
|
961
|
+
}.bind(this));
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Skip remaining unsupported types
|
|
783
967
|
if (!this._isSupportedRecordType(oFacet.RecordType)) {
|
|
784
968
|
return;
|
|
785
969
|
}
|
|
@@ -829,17 +1013,16 @@ sap.ui.define([
|
|
|
829
1013
|
},
|
|
830
1014
|
|
|
831
1015
|
/**
|
|
832
|
-
* Returns false for record types that
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
1016
|
+
* Returns false for record types that represent navigation/action triggers rather than
|
|
1017
|
+
* editable data fields. DataFieldForAnnotation is NOT in this list — it is handled
|
|
1018
|
+
* explicitly in _extractSectionInfoFromReferenceFacet by following its AnnotationPath.
|
|
1019
|
+
* Handles both fully-qualified names (com.sap.vocabularies.UI.v1.*) and alias form (UI.*).
|
|
836
1020
|
* @private
|
|
837
1021
|
*/
|
|
838
1022
|
_isSupportedRecordType: function(sRecordType) {
|
|
839
1023
|
if (!sRecordType) { return false; }
|
|
840
1024
|
var sNormalized = sRecordType.replace("com.sap.vocabularies.UI.v1.", "UI.");
|
|
841
1025
|
return sNormalized !== "UI.DataFieldForIntentBasedNavigation" &&
|
|
842
|
-
sNormalized !== "UI.DataFieldForAnnotation" &&
|
|
843
1026
|
sNormalized !== "UI.DataFieldForAction" &&
|
|
844
1027
|
sNormalized !== "UI.DataFieldWithNavigationPath" &&
|
|
845
1028
|
sNormalized !== "UI.DataFieldWithIntentBasedNavigation";
|