@sapui5/sap.fe.core 1.136.7 → 1.136.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/package.json +1 -1
  2. package/src/sap/fe/core/.library +1 -1
  3. package/src/sap/fe/core/AppComponent.js +28 -16
  4. package/src/sap/fe/core/AppComponent.ts +36 -20
  5. package/src/sap/fe/core/AppStateHandler.js +6 -5
  6. package/src/sap/fe/core/AppStateHandler.ts +5 -5
  7. package/src/sap/fe/core/CommonUtils.js +22 -2
  8. package/src/sap/fe/core/CommonUtils.ts +22 -4
  9. package/src/sap/fe/core/ExtensionAPI.js +5 -2
  10. package/src/sap/fe/core/ExtensionAPI.ts +7 -1
  11. package/src/sap/fe/core/PageController.js +4 -4
  12. package/src/sap/fe/core/PageController.ts +3 -3
  13. package/src/sap/fe/core/TemplateComponent.js +3 -3
  14. package/src/sap/fe/core/TemplateComponent.ts +2 -2
  15. package/src/sap/fe/core/buildingBlocks/BuildingBlock.js +6 -2
  16. package/src/sap/fe/core/buildingBlocks/BuildingBlock.ts +8 -1
  17. package/src/sap/fe/core/controllerextensions/EditFlow.js +159 -34
  18. package/src/sap/fe/core/controllerextensions/EditFlow.ts +177 -39
  19. package/src/sap/fe/core/controllerextensions/SideEffects.js +7 -3
  20. package/src/sap/fe/core/controllerextensions/SideEffects.ts +2 -2
  21. package/src/sap/fe/core/controllerextensions/editFlow/draftDataLossPopup.js +28 -17
  22. package/src/sap/fe/core/controllerextensions/editFlow/draftDataLossPopup.ts +35 -18
  23. package/src/sap/fe/core/controllerextensions/editFlow/operations/ODataOperation.js +5 -2
  24. package/src/sap/fe/core/controllerextensions/editFlow/operations/ODataOperation.ts +3 -1
  25. package/src/sap/fe/core/controllerextensions/routing/RouterProxy.js +13 -9
  26. package/src/sap/fe/core/controllerextensions/routing/RouterProxy.ts +13 -9
  27. package/src/sap/fe/core/converters/annotations/DataField.js +6 -2
  28. package/src/sap/fe/core/converters/annotations/DataField.ts +5 -1
  29. package/src/sap/fe/core/converters/controls/Common/Action.js +5 -5
  30. package/src/sap/fe/core/converters/controls/Common/Action.ts +7 -4
  31. package/src/sap/fe/core/converters/controls/Common/table/Columns.js +6 -5
  32. package/src/sap/fe/core/converters/controls/Common/table/Columns.ts +5 -4
  33. package/src/sap/fe/core/converters/templates/ObjectPageConverter.js +2 -2
  34. package/src/sap/fe/core/converters/templates/ObjectPageConverter.ts +1 -1
  35. package/src/sap/fe/core/fpm/manifest.json +1 -1
  36. package/src/sap/fe/core/helpers/DeleteHelper.js +8 -9
  37. package/src/sap/fe/core/helpers/DeleteHelper.ts +10 -8
  38. package/src/sap/fe/core/library.js +3 -7
  39. package/src/sap/fe/core/library.ts +5 -21
  40. package/src/sap/fe/core/messagebundle_da.properties +3 -3
  41. package/src/sap/fe/core/messagebundle_es.properties +1 -1
  42. package/src/sap/fe/core/messagebundle_fr.properties +9 -9
  43. package/src/sap/fe/core/messagebundle_hu.properties +1 -1
  44. package/src/sap/fe/core/messagebundle_id.properties +1 -1
  45. package/src/sap/fe/core/messagebundle_ja.properties +1 -1
  46. package/src/sap/fe/core/messagebundle_lv.properties +1 -1
  47. package/src/sap/fe/core/messagebundle_uk.properties +1 -1
  48. package/src/sap/fe/core/messagebundle_zh_CN.properties +1 -1
  49. package/src/sap/fe/core/rootView/RootViewBaseController.js +17 -17
  50. package/src/sap/fe/core/rootView/RootViewBaseController.ts +24 -29
  51. package/src/sap/fe/core/services/SideEffectsServiceFactory.js +13 -6
  52. package/src/sap/fe/core/services/SideEffectsServiceFactory.ts +17 -5
  53. package/src/sap/fe/core/templating/DisplayModeFormatter.js +4 -5
  54. package/src/sap/fe/core/templating/DisplayModeFormatter.ts +4 -6
  55. package/src/sap/fe/core/templating/UIFormatters.js +2 -2
  56. package/src/sap/fe/core/templating/UIFormatters.ts +1 -1
@@ -71,6 +71,7 @@ import ActionRuntime from "../ActionRuntime";
71
71
  import { ConfirmRecommendationDialog, RecommendationDialogDecision } from "../controls/Recommendations/ConfirmRecommendationDialog";
72
72
  import { CreationMode, type BaseManifestSettings } from "../converters/ManifestSettings";
73
73
  import { isFulfilled } from "../helpers/TypeGuards";
74
+ import draftDataLossPopup from "./editFlow/draftDataLossPopup";
74
75
  import type { OperationResult } from "./editFlow/operations/ODataOperation";
75
76
  import NavigationReason from "./routing/NavigationReason";
76
77
  const ProgrammingModel = FELibrary.ProgrammingModel,
@@ -245,6 +246,8 @@ class EditFlow extends BaseControllerExtension {
245
246
  stickyContext = newDocumentContext;
246
247
  }
247
248
  await this.handleStickyOn(stickyContext);
249
+ } else if (this._hiddenDraft) {
250
+ await this.handleHiddenDraftEdit(newDocumentContext);
248
251
  }
249
252
  overrideMessageUIDecision = true;
250
253
 
@@ -755,6 +758,11 @@ class EditFlow extends BaseControllerExtension {
755
758
  }
756
759
  }
757
760
  if (newDocumentContext) {
761
+ // Handle hidden draft session for created documents, similar to editDocument
762
+ if (this._hiddenDraft && programmingModel === ProgrammingModel.Draft) {
763
+ await this.handleHiddenDraftEdit(newDocumentContext);
764
+ }
765
+
758
766
  this.setDocumentModifiedOnCreate(listBinding);
759
767
  if (!this._isFclEnabled()) {
760
768
  EditState.setEditStateDirty();
@@ -1775,7 +1783,7 @@ class EditFlow extends BaseControllerExtension {
1775
1783
  if (
1776
1784
  ((sProgrammingModel === ProgrammingModel.Sticky || oContext.getProperty("HasActiveEntity")) &&
1777
1785
  rootViewController.isFclEnabled()) ||
1778
- this._hiddenDraft
1786
+ (this._hiddenDraft && oContext.getProperty("HasActiveEntity"))
1779
1787
  ) {
1780
1788
  siblingInfo = await this._computeSiblingInformation(
1781
1789
  rootContext,
@@ -1808,6 +1816,9 @@ class EditFlow extends BaseControllerExtension {
1808
1816
  mParameters?.isStandardSave
1809
1817
  );
1810
1818
  this._removeStickySessionInternalProperties(sProgrammingModel);
1819
+ if (this._hiddenDraft) {
1820
+ this.handleSessionOff();
1821
+ }
1811
1822
 
1812
1823
  this._sendActivity(Activity.Activate, activeDocumentContext);
1813
1824
  this.base.collaborativeDraft.disconnect();
@@ -1958,8 +1969,17 @@ class EditFlow extends BaseControllerExtension {
1958
1969
 
1959
1970
  try {
1960
1971
  await this.syncTask();
1972
+ let rootContext: Context;
1961
1973
  const sProgrammingModel = this.getProgrammingModel(oContext);
1962
- const rootContext: Context = await this.getRootContext(oContext);
1974
+
1975
+ // Determine the root context based on hidden draft mode and programming model
1976
+ if (this._hiddenDraft && sProgrammingModel === "Draft") {
1977
+ const rootContextPath = ModelHelper.getDraftRootPath(oContext);
1978
+ rootContext = oContext.getPath() === rootContextPath ? oContext : await this.getRootContext(oContext);
1979
+ } else {
1980
+ rootContext = await this.getRootContext(oContext);
1981
+ }
1982
+
1963
1983
  if (
1964
1984
  ((sProgrammingModel === ProgrammingModel.Sticky || oContext.getProperty("HasActiveEntity")) && this._isFclEnabled()) ||
1965
1985
  this._hiddenDraft
@@ -1993,6 +2013,9 @@ class EditFlow extends BaseControllerExtension {
1993
2013
  }
1994
2014
  });
1995
2015
  this._removeStickySessionInternalProperties(sProgrammingModel);
2016
+ if (this._hiddenDraft) {
2017
+ this.handleSessionOff();
2018
+ }
1996
2019
 
1997
2020
  this.setEditMode(EditMode.Display, false);
1998
2021
  this.setDocumentModified(false);
@@ -3069,26 +3092,77 @@ class EditFlow extends BaseControllerExtension {
3069
3092
  }
3070
3093
 
3071
3094
  if (!appComponent.getRouterProxy().hasNavigationGuard()) {
3072
- const hashTracker = appComponent.getRouterProxy().getHash();
3073
- const internalModel = this.getInternalModel();
3074
-
3075
- // Set a guard in the RouterProxy
3076
- // A timeout is necessary, as with deferred creation the hashChanger is not updated yet with
3077
- // the new hash, and the guard cannot be found in the managed history of the router proxy
3078
- setTimeout(function () {
3079
- appComponent.getRouterProxy().setNavigationGuard(context.getPath().substring(1));
3080
- }, 0);
3081
-
3082
- // Setting back navigation on shell service, to get the dicard message box in case of sticky
3083
- await appComponent.getShellServices().setBackNavigation(this.onBackNavigationInSession.bind(this, context));
3084
-
3085
- // Attach sticky session callbacks
3086
- const i18nModel = this.base.getView().getModel("sap.fe.i18n");
3087
- appComponent.registerStickySessionCallbacks(
3088
- this.getDirtyStateProvider(appComponent, internalModel, hashTracker),
3089
- this.getSessionTimeoutFunction(context, i18nModel),
3090
- this.getRouteMatchedFunction(context, appComponent)
3091
- );
3095
+ await this.registerCallbacks(context, appComponent);
3096
+ }
3097
+ } catch (error: unknown) {
3098
+ Log.info(error as string);
3099
+ return false;
3100
+ }
3101
+
3102
+ return true;
3103
+ }
3104
+
3105
+ /**
3106
+ * Registers session callbacks for draft handling.
3107
+ * @param context The context being edited
3108
+ * @param appComponent The application component
3109
+ * @param semanticPath Optional semantic path for navigation guard
3110
+ */
3111
+ private async registerCallbacks(context: Context, appComponent: AppComponent, semanticPath?: string): Promise<void> {
3112
+ const hashTracker = appComponent.getRouterProxy().getHash();
3113
+ const internalModel = this.getInternalModel();
3114
+
3115
+ // Set a guard in the RouterProxy
3116
+ // A timeout is necessary, as with deferred creation the hashChanger is not updated yet with
3117
+ // the new hash, and the guard cannot be found in the managed history of the router proxy
3118
+ setTimeout(function () {
3119
+ appComponent.getRouterProxy().setNavigationGuard(context.getPath().substring(1), semanticPath);
3120
+ }, 0);
3121
+
3122
+ // Setting back navigation on shell service, to get the discard message box in case of sticky
3123
+ await appComponent.getShellServices().setBackNavigation(this.onBackNavigationInSession.bind(this, context));
3124
+
3125
+ // Attach sticky session callbacks
3126
+ const i18nModel = this.base.getView().getModel("sap.fe.i18n");
3127
+ appComponent.registerCallbacks(
3128
+ this.getDirtyStateProvider(appComponent, internalModel, hashTracker),
3129
+ this.getRouteMatchedFunction(context, appComponent),
3130
+ !this._hiddenDraft ? this.getSessionTimeoutFunction(context, i18nModel) : undefined
3131
+ );
3132
+ }
3133
+
3134
+ /**
3135
+ * Enables hidden draft edit session handling.
3136
+ * Sets up the necessary session callbacks and navigation guards for hidden draft scenarios.
3137
+ * @param context The context being edited in the hidden draft session
3138
+ * @returns Promise resolving to true if session setup succeeds, false otherwise
3139
+ */
3140
+ private async handleHiddenDraftEdit(context: Context): Promise<boolean> {
3141
+ const appComponent = this.getAppComponent();
3142
+
3143
+ try {
3144
+ if (appComponent === undefined) {
3145
+ throw new Error("undefined AppComponent for function handleHiddenDraftEdit");
3146
+ }
3147
+
3148
+ // Ensure we have the correct context for hidden draft handling
3149
+ let hiddenDraftContext: Context;
3150
+ if (!this._isFclEnabled()) {
3151
+ hiddenDraftContext = context;
3152
+ } else {
3153
+ hiddenDraftContext = context?.getModel().getKeepAliveContext(context.getPath());
3154
+ }
3155
+
3156
+ //Create can be handled from any context, but we need to create the guard only for the create in root Context.
3157
+ if (this.getCreationMode()) {
3158
+ const rootContextPath = ModelHelper.getDraftRootPath(hiddenDraftContext);
3159
+ hiddenDraftContext =
3160
+ hiddenDraftContext.getPath() === rootContextPath ? hiddenDraftContext : await this.getRootContext(hiddenDraftContext);
3161
+ }
3162
+
3163
+ if (!appComponent.getRouterProxy().hasNavigationGuard()) {
3164
+ const semanticPath = SemanticKeyHelper.getSemanticPath(hiddenDraftContext, true)?.substring(1);
3165
+ await this.registerCallbacks(hiddenDraftContext, appComponent, semanticPath);
3092
3166
  }
3093
3167
  } catch (error: unknown) {
3094
3168
  Log.info(error as string);
@@ -3102,7 +3176,7 @@ class EditFlow extends BaseControllerExtension {
3102
3176
  * Disables the sticky edit session.
3103
3177
  * @returns True in case of success, false otherwise
3104
3178
  */
3105
- private handleStickyOff(): boolean {
3179
+ private handleSessionOff(): boolean {
3106
3180
  const appComponent = this.getAppComponent();
3107
3181
  try {
3108
3182
  if (appComponent === undefined) {
@@ -3115,10 +3189,10 @@ class EditFlow extends BaseControllerExtension {
3115
3189
  appComponent.getRouterProxy().discardNavigationGuard();
3116
3190
  }
3117
3191
 
3118
- if (appComponent.unregisterStickySessionCallbacks) {
3192
+ if (appComponent.unregisterCallbacks) {
3119
3193
  // If we have exited from the app, CommonUtils.getAppComponent doesn't return a
3120
3194
  // sap.fe.core.AppComponent, hence the 'if' above
3121
- appComponent.unregisterStickySessionCallbacks();
3195
+ appComponent.unregisterCallbacks();
3122
3196
  }
3123
3197
 
3124
3198
  this.setEditMode(EditMode.Display, false);
@@ -3164,7 +3238,7 @@ class EditFlow extends BaseControllerExtension {
3164
3238
  let isDirty: boolean;
3165
3239
  const isSessionOn = internalModel.getProperty("/sessionOn");
3166
3240
 
3167
- if (!isSessionOn) {
3241
+ if (!isSessionOn && !this._hiddenDraft) {
3168
3242
  // If the sticky session was terminated before hand.
3169
3243
  // Example in case of navigating away from application using IBN.
3170
3244
  return undefined;
@@ -3183,6 +3257,10 @@ class EditFlow extends BaseControllerExtension {
3183
3257
  // or crossing the guard has already been allowed by the RouterProxy
3184
3258
  lclHashTracker = targetHash;
3185
3259
  isDirty = false;
3260
+ } else if (this._hiddenDraft && !this.isDocumentModified()) {
3261
+ // the user attempts to navigate within the hidden draft when the document is not modified
3262
+ isDirty = false;
3263
+ lclHashTracker = targetHash;
3186
3264
  } else {
3187
3265
  // the user attempts to navigate within the app, for example back to the list report
3188
3266
  isDirty = true;
@@ -3230,7 +3308,7 @@ class EditFlow extends BaseControllerExtension {
3230
3308
  type: "Emphasized",
3231
3309
  press: (): void => {
3232
3310
  // remove sticky handling after navigation since session has already been terminated
3233
- this.handleStickyOff();
3311
+ this.handleSessionOff();
3234
3312
  this.getInternalRouting().navigateBackFromContext(stickyContext);
3235
3313
  }
3236
3314
  }),
@@ -3256,32 +3334,92 @@ class EditFlow extends BaseControllerExtension {
3256
3334
  * @param appComponent The app component
3257
3335
  * @returns The callback function
3258
3336
  */
3259
- private getRouteMatchedFunction(context: Context, appComponent: AppComponent) {
3260
- return (): void => {
3337
+ private getRouteMatchedFunction(context: Context, appComponent: AppComponent): () => Promise<void> {
3338
+ const programmingModel = this.getProgrammingModel(context);
3339
+ const model = context.getModel();
3340
+ const contextPath = context.getPath();
3341
+ return async (): Promise<void> => {
3261
3342
  const currentHash = appComponent.getRouterProxy().getHash();
3262
- // either current hash is empty so the user left the app or he navigated away from the object
3343
+ // If the current hash is empty or the navigation is outside the guard, discard the session.
3263
3344
  if (!currentHash || !appComponent.getRouterProxy().checkHashWithGuard(currentHash)) {
3264
- this.discardStickySession(context);
3265
- context.getModel().clearSessionContext();
3345
+ if (this._hiddenDraft && programmingModel === ProgrammingModel.Draft) {
3346
+ return this.handleHiddenDraftDiscard(context, model, contextPath);
3347
+ } else {
3348
+ // For sticky session, discard changes and clear session context.
3349
+ await this.discardStickySession(context);
3350
+ context.getModel().clearSessionContext();
3351
+ }
3266
3352
  }
3267
3353
  };
3268
3354
  }
3269
3355
 
3356
+ /**
3357
+ * Handles the discard process for hidden draft sessions.
3358
+ * Prepares the context and ensures we have the required properties before discarding.
3359
+ * @param context The context being edited.
3360
+ * @param model The OData model instance used for binding the context.
3361
+ * @param contextPath The path of the context being edited.
3362
+ * @returns A promise that resolves when the hidden draft session is discarded.
3363
+ */
3364
+ private async handleHiddenDraftDiscard(context: ODataV4Context, model: ODataModel, contextPath: string): Promise<void> {
3365
+ // Prepare the context for discard operation
3366
+ let discardContext: Context;
3367
+ if (context?.getBinding()) {
3368
+ discardContext = context;
3369
+ } else {
3370
+ discardContext = model.bindContext(contextPath, undefined, {}).getBoundContext();
3371
+ }
3372
+
3373
+ // Request necessary properties before discarding the draft session.
3374
+ await discardContext.requestProperty(["HasActiveEntity", "IsActiveEntity"]);
3375
+ return this.discardHiddenDraftSession(discardContext);
3376
+ }
3377
+
3378
+ /**
3379
+ * Discards the current context changes and refreshes the context if it is not a creation operation.
3380
+ * @param context The context to be discarded.
3381
+ * @param isCreation A boolean indicating if the context is for a creation operation.
3382
+ */
3383
+ private discardContext(context: Context, isCreation: boolean): void {
3384
+ if (context?.hasPendingChanges()) {
3385
+ context.getBinding().resetChanges();
3386
+ }
3387
+ if (!isCreation) {
3388
+ context.refresh();
3389
+ }
3390
+ }
3391
+
3270
3392
  /**
3271
3393
  * Ends a sticky session by discarding changes.
3272
3394
  * @param context The context being edited (root of the sticky session)
3273
3395
  */
3274
3396
  private async discardStickySession(context: Context): Promise<void> {
3275
- const isCreation = this.getCreationMode(); // Store this value in a variable, as it will be reset in handleStickyOff
3276
- this.handleStickyOff(); // Remove all callbacks before calling the async method on the server, otherwise they will conflict with RouteProxy.restoreHistory that is called synchronously in the routing service
3397
+ const isCreation = this.getCreationMode();
3398
+ this.handleSessionOff();
3277
3399
 
3278
3400
  const discardedContext = await sticky.discardDocument(context);
3279
- if (discardedContext?.hasPendingChanges()) {
3280
- discardedContext.getBinding().resetChanges();
3401
+ if (discardedContext !== undefined && discardedContext !== null) {
3402
+ this.discardContext(discardedContext, isCreation);
3281
3403
  }
3282
- if (!isCreation) {
3283
- discardedContext?.refresh();
3404
+ }
3405
+
3406
+ /**
3407
+ * Discards the hidden draft session for the given context.
3408
+ * @param context The context from which the draft session will be discarded.
3409
+ * @returns A promise that resolves when the discard operation is complete.
3410
+ */
3411
+ private async discardHiddenDraftSession(context: Context): Promise<void> {
3412
+ const isCreation = this.getCreationMode();
3413
+ const discardedContext = (await draftDataLossPopup.discardDraft(
3414
+ this.getView().getController() as PageController,
3415
+ undefined,
3416
+ context,
3417
+ true
3418
+ )) as Context;
3419
+ if (discardedContext !== undefined && discardedContext !== null) {
3420
+ this.discardContext(discardedContext, isCreation);
3284
3421
  }
3422
+ this.handleSessionOff();
3285
3423
  }
3286
3424
 
3287
3425
  /**
@@ -3455,7 +3593,7 @@ class EditFlow extends BaseControllerExtension {
3455
3593
  const internalModel = this.getInternalModel();
3456
3594
  internalModel.setProperty("/sessionOn", false);
3457
3595
  internalModel.setProperty("/stickySessionToken", undefined);
3458
- this.handleStickyOff();
3596
+ this.handleSessionOff();
3459
3597
  }
3460
3598
  }
3461
3599