@quadrel-enterprise-ui/framework 20.10.0 → 20.10.1-beta.145.1
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.
|
@@ -11402,7 +11402,7 @@ class QdDatepickerComponent {
|
|
|
11402
11402
|
_onChange = () => { };
|
|
11403
11403
|
_onTouched = () => { };
|
|
11404
11404
|
ngOnInit() {
|
|
11405
|
-
this.language
|
|
11405
|
+
this.language = this.translateService?.currentLang ?? DEFAULT_LANGUAGE;
|
|
11406
11406
|
this.subscribeToLanguageChange();
|
|
11407
11407
|
this.subscribeToActionEmitterEvents();
|
|
11408
11408
|
this.initControl();
|
|
@@ -27062,6 +27062,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
27062
27062
|
type: Input
|
|
27063
27063
|
}] } });
|
|
27064
27064
|
|
|
27065
|
+
class QdPageCommitActionExecutor {
|
|
27066
|
+
static execute(action, values, context) {
|
|
27067
|
+
const { formGroupManager, navigationInterceptor, destroyed$, onAfterSnapshot } = context;
|
|
27068
|
+
const captured = formGroupManager.captureFormValues();
|
|
27069
|
+
let result$;
|
|
27070
|
+
navigationInterceptor.executeWithBypass(() => {
|
|
27071
|
+
const handlerResult = action.handler(values);
|
|
27072
|
+
if (isObservable(handlerResult))
|
|
27073
|
+
result$ = handlerResult;
|
|
27074
|
+
});
|
|
27075
|
+
const applySuccess = () => {
|
|
27076
|
+
formGroupManager.setFormGroupsSnapshot(captured);
|
|
27077
|
+
navigationInterceptor.executeWithBypass(() => {
|
|
27078
|
+
try {
|
|
27079
|
+
onAfterSnapshot?.();
|
|
27080
|
+
}
|
|
27081
|
+
catch (err) {
|
|
27082
|
+
console.error('QD-UI | QdPage - internal onAfterSnapshot hook threw after form was marked as saved.', err);
|
|
27083
|
+
}
|
|
27084
|
+
try {
|
|
27085
|
+
action.onSuccess?.();
|
|
27086
|
+
}
|
|
27087
|
+
catch (err) {
|
|
27088
|
+
console.error('QD-UI | QdPage - onSuccess callback threw after form was marked as saved.', err);
|
|
27089
|
+
}
|
|
27090
|
+
});
|
|
27091
|
+
};
|
|
27092
|
+
if (result$) {
|
|
27093
|
+
result$.pipe(take(1), takeUntil(destroyed$)).subscribe({
|
|
27094
|
+
next: ok => {
|
|
27095
|
+
if (ok)
|
|
27096
|
+
applySuccess();
|
|
27097
|
+
},
|
|
27098
|
+
error: err => this.handleError(action, err)
|
|
27099
|
+
});
|
|
27100
|
+
}
|
|
27101
|
+
else {
|
|
27102
|
+
applySuccess();
|
|
27103
|
+
}
|
|
27104
|
+
}
|
|
27105
|
+
static handleError(action, err) {
|
|
27106
|
+
if (!action.onError) {
|
|
27107
|
+
console.error('QD-UI | QdPage - Commit action observable errored — form was not marked as saved. Provide an `onError` hook on the action, or handle errors in the handler.', err);
|
|
27108
|
+
return;
|
|
27109
|
+
}
|
|
27110
|
+
try {
|
|
27111
|
+
action.onError(err);
|
|
27112
|
+
}
|
|
27113
|
+
catch (callbackErr) {
|
|
27114
|
+
console.error('QD-UI | QdPage - onError callback threw.', callbackErr);
|
|
27115
|
+
}
|
|
27116
|
+
}
|
|
27117
|
+
}
|
|
27118
|
+
|
|
27065
27119
|
/**
|
|
27066
27120
|
* The QdFormGroupManagerService provides centralized registration, snapshotting, and value tracking
|
|
27067
27121
|
* for multiple Angular `FormGroup` instances. It supports efficient state restoration, including dynamic
|
|
@@ -27176,6 +27230,39 @@ class QdFormGroupManagerService {
|
|
|
27176
27230
|
takeFormGroupsSnapshot() {
|
|
27177
27231
|
this._formGroups.forEach((fg, key) => this._formGroupsSnapshot.set(key, fg.getRawValue()));
|
|
27178
27232
|
}
|
|
27233
|
+
/**
|
|
27234
|
+
* Captures a deep-cloned snapshot of all current form values without mutating internal state.
|
|
27235
|
+
*
|
|
27236
|
+
* Used by framework orchestration code that needs to remember the "at click time" state of a form
|
|
27237
|
+
* so the snapshot can later be set to exactly those values — even if the user edits the form
|
|
27238
|
+
* while an async commit action is pending.
|
|
27239
|
+
*
|
|
27240
|
+
* Throws if any form value is not structured-cloneable (functions, symbols, DOM nodes).
|
|
27241
|
+
*/
|
|
27242
|
+
captureFormValues() {
|
|
27243
|
+
const captured = new Map();
|
|
27244
|
+
this._formGroups.forEach((fg, key) => {
|
|
27245
|
+
try {
|
|
27246
|
+
captured.set(key, structuredClone(fg.getRawValue()));
|
|
27247
|
+
}
|
|
27248
|
+
catch (err) {
|
|
27249
|
+
throw new Error(`QD-UI | QdFormGroupManager - captureFormValues() failed for group "${key}". ` +
|
|
27250
|
+
`Form values must be structured-cloneable. Non-cloneable values like functions, ` +
|
|
27251
|
+
`symbols, or DOM nodes are not supported. Original error: ${String(err)}`);
|
|
27252
|
+
}
|
|
27253
|
+
});
|
|
27254
|
+
return captured;
|
|
27255
|
+
}
|
|
27256
|
+
/**
|
|
27257
|
+
* Replaces the internal saved snapshot with the provided values.
|
|
27258
|
+
*
|
|
27259
|
+
* Intended to be paired with `captureFormValues()` so that the snapshot can be set to the
|
|
27260
|
+
* values present when a commit action was initiated — independent of whether the user edited
|
|
27261
|
+
* the form in the meantime.
|
|
27262
|
+
*/
|
|
27263
|
+
setFormGroupsSnapshot(values) {
|
|
27264
|
+
values.forEach((snapshot, key) => this._formGroupsSnapshot.set(key, snapshot));
|
|
27265
|
+
}
|
|
27179
27266
|
restoreFormGroupsFromSnapshot() {
|
|
27180
27267
|
this._formGroups.forEach((fg, key) => {
|
|
27181
27268
|
const snapshot = this._formGroupsSnapshot.get(key);
|
|
@@ -27185,20 +27272,6 @@ class QdFormGroupManagerService {
|
|
|
27185
27272
|
});
|
|
27186
27273
|
this.cancelPendingAsyncValidation();
|
|
27187
27274
|
}
|
|
27188
|
-
restoreFormGroup(fg, snapshot) {
|
|
27189
|
-
Object.entries(fg.controls).forEach(([ctrlKey, ctrl]) => {
|
|
27190
|
-
const newValue = snapshot[ctrlKey];
|
|
27191
|
-
if (ctrl instanceof FormArray && Array.isArray(newValue)) {
|
|
27192
|
-
this.resetFormArrayToValues(ctrl, newValue);
|
|
27193
|
-
}
|
|
27194
|
-
else if (ctrl instanceof FormGroup && newValue && typeof newValue === 'object') {
|
|
27195
|
-
this.restoreFormGroup(ctrl, newValue);
|
|
27196
|
-
}
|
|
27197
|
-
else {
|
|
27198
|
-
ctrl.reset(newValue);
|
|
27199
|
-
}
|
|
27200
|
-
});
|
|
27201
|
-
}
|
|
27202
27275
|
/**
|
|
27203
27276
|
* Cancels any in-flight async validators on all registered form groups.
|
|
27204
27277
|
*
|
|
@@ -27222,6 +27295,20 @@ class QdFormGroupManagerService {
|
|
|
27222
27295
|
}
|
|
27223
27296
|
});
|
|
27224
27297
|
}
|
|
27298
|
+
restoreFormGroup(fg, snapshot) {
|
|
27299
|
+
Object.entries(fg.controls).forEach(([ctrlKey, ctrl]) => {
|
|
27300
|
+
const newValue = snapshot[ctrlKey];
|
|
27301
|
+
if (ctrl instanceof FormArray && Array.isArray(newValue)) {
|
|
27302
|
+
this.resetFormArrayToValues(ctrl, newValue);
|
|
27303
|
+
}
|
|
27304
|
+
else if (ctrl instanceof FormGroup && newValue && typeof newValue === 'object') {
|
|
27305
|
+
this.restoreFormGroup(ctrl, newValue);
|
|
27306
|
+
}
|
|
27307
|
+
else {
|
|
27308
|
+
ctrl.reset(newValue);
|
|
27309
|
+
}
|
|
27310
|
+
});
|
|
27311
|
+
}
|
|
27225
27312
|
collectPendingControls(control) {
|
|
27226
27313
|
const result = [];
|
|
27227
27314
|
if (control instanceof FormGroup) {
|
|
@@ -27287,6 +27374,122 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
27287
27374
|
type: Injectable
|
|
27288
27375
|
}] });
|
|
27289
27376
|
|
|
27377
|
+
/**
|
|
27378
|
+
* Intercepts router navigation when unsaved form changes exist on a QdPage.
|
|
27379
|
+
*
|
|
27380
|
+
* Provided per `QdPageComponent`. Activated automatically when the page enters an editable state
|
|
27381
|
+
* (create pages or inspect pages in edit mode). Deactivated when the page returns to view mode.
|
|
27382
|
+
*
|
|
27383
|
+
* #### When navigation is intercepted
|
|
27384
|
+
*
|
|
27385
|
+
* - A `NavigationStart` event occurs (browser back, shell back button, or programmatic navigation).
|
|
27386
|
+
* - The registered form groups report unsaved changes via `QdFormGroupManagerService.$hasValuesChanged()`.
|
|
27387
|
+
*
|
|
27388
|
+
* The navigation is cancelled and a confirmation dialog is shown. The user can either discard
|
|
27389
|
+
* changes and proceed or stay on the page.
|
|
27390
|
+
*
|
|
27391
|
+
* #### When navigation is allowed
|
|
27392
|
+
*
|
|
27393
|
+
* - No unsaved changes exist — the form is either pristine or the framework has updated the
|
|
27394
|
+
* saved snapshot after a successful commit action (Submit, Save, SaveDraft).
|
|
27395
|
+
* - The page switches to view mode via `deactivate()`.
|
|
27396
|
+
*
|
|
27397
|
+
* The service has no concept of "pending actions" or bypass windows. Framework actions update the
|
|
27398
|
+
* saved snapshot directly via `QdFormGroupManagerService.setFormGroupsSnapshot(...)`; cancel-discard
|
|
27399
|
+
* flows reset the form to the saved snapshot via `restoreFormGroupsFromSnapshot()`. Either way, the
|
|
27400
|
+
* interceptor only observes the resulting form state — there is no time window where a bypass is active.
|
|
27401
|
+
*/
|
|
27402
|
+
class QdPageNavigationInterceptorService {
|
|
27403
|
+
router = inject(Router);
|
|
27404
|
+
formGroupManager = inject(QdFormGroupManagerService);
|
|
27405
|
+
confirmationDialog = inject(QdConfirmationDialogOpenerService);
|
|
27406
|
+
_destroy$ = new Subject();
|
|
27407
|
+
_deactivate$ = new Subject();
|
|
27408
|
+
/**
|
|
27409
|
+
* Guards the re-dispatched navigation issued after the user confirms discard. While the dialog
|
|
27410
|
+
* is open and while the confirmed target is being re-dispatched, the router events must not
|
|
27411
|
+
* re-enter the interception pipeline — otherwise the dialog would open recursively.
|
|
27412
|
+
*/
|
|
27413
|
+
_bypassInterception = false;
|
|
27414
|
+
/**
|
|
27415
|
+
* URL of the navigation target accepted by the user in the most recent discard-confirm dialog.
|
|
27416
|
+
* Set by `navigateToConfirmedTarget()` and consumed once by `shouldIntercept()` when the
|
|
27417
|
+
* corresponding `NavigationStart` arrives.
|
|
27418
|
+
*/
|
|
27419
|
+
_confirmedTargetUrl;
|
|
27420
|
+
_confirmationMessage;
|
|
27421
|
+
ngOnDestroy() {
|
|
27422
|
+
this._confirmedTargetUrl = undefined;
|
|
27423
|
+
this._destroy$.next();
|
|
27424
|
+
this._destroy$.complete();
|
|
27425
|
+
this._deactivate$.complete();
|
|
27426
|
+
}
|
|
27427
|
+
/**
|
|
27428
|
+
* Starts intercepting navigation events. Replaces any previously active listener.
|
|
27429
|
+
*/
|
|
27430
|
+
activate(confirmationMessage) {
|
|
27431
|
+
this._confirmationMessage = confirmationMessage;
|
|
27432
|
+
this._deactivate$.next();
|
|
27433
|
+
this.listenForUnsavedNavigationAttempts();
|
|
27434
|
+
}
|
|
27435
|
+
/**
|
|
27436
|
+
* Stops intercepting and clears the pending confirmed-target bypass.
|
|
27437
|
+
*/
|
|
27438
|
+
deactivate() {
|
|
27439
|
+
this._deactivate$.next();
|
|
27440
|
+
this._confirmedTargetUrl = undefined;
|
|
27441
|
+
}
|
|
27442
|
+
/**
|
|
27443
|
+
* Runs the given action while temporarily suppressing navigation interception.
|
|
27444
|
+
*
|
|
27445
|
+
* Used by framework commit-action orchestration to wrap synchronous handler invocations and
|
|
27446
|
+
* post-commit `onSuccess` callbacks: any router navigation that occurs during `action()` is
|
|
27447
|
+
* passed through without raising the unsaved-changes dialog. Restoration happens in a `finally`
|
|
27448
|
+
* block, so the flag is released even if `action()` throws.
|
|
27449
|
+
*/
|
|
27450
|
+
executeWithBypass(action) {
|
|
27451
|
+
this._bypassInterception = true;
|
|
27452
|
+
try {
|
|
27453
|
+
action();
|
|
27454
|
+
}
|
|
27455
|
+
finally {
|
|
27456
|
+
this._bypassInterception = false;
|
|
27457
|
+
}
|
|
27458
|
+
}
|
|
27459
|
+
listenForUnsavedNavigationAttempts() {
|
|
27460
|
+
this.router.events
|
|
27461
|
+
.pipe(filter$1((event) => event instanceof NavigationStart), filter$1(() => !this._bypassInterception), filter$1(event => this.shouldIntercept(event)), concatMap$1(event => this.checkForPendingChanges(event)), filter$1(({ hasChanges }) => hasChanges), exhaustMap(({ event }) => this.cancelNavigationAndConfirm(event)), filter$1(({ confirmed }) => confirmed), takeUntil$1(this._deactivate$), takeUntil$1(this._destroy$))
|
|
27462
|
+
.subscribe(({ targetUrl }) => this.navigateToConfirmedTarget(targetUrl));
|
|
27463
|
+
}
|
|
27464
|
+
shouldIntercept(event) {
|
|
27465
|
+
if (this._confirmedTargetUrl !== undefined && this._confirmedTargetUrl === event.url) {
|
|
27466
|
+
this._confirmedTargetUrl = undefined;
|
|
27467
|
+
return false;
|
|
27468
|
+
}
|
|
27469
|
+
return true;
|
|
27470
|
+
}
|
|
27471
|
+
checkForPendingChanges(event) {
|
|
27472
|
+
return this.formGroupManager.$hasValuesChanged().pipe(take$1(1), map$1(hasChanges => ({ event, hasChanges })));
|
|
27473
|
+
}
|
|
27474
|
+
cancelNavigationAndConfirm(event) {
|
|
27475
|
+
this._bypassInterception = true;
|
|
27476
|
+
void this.router.navigateByUrl(this.router.url, { skipLocationChange: true });
|
|
27477
|
+
return this.confirmationDialog
|
|
27478
|
+
.showDiscardConfirmDialog(this._confirmationMessage, 'page-navigation-confirmation')
|
|
27479
|
+
.pipe(map$1(confirmed => ({ confirmed, targetUrl: event.url })), finalize(() => (this._bypassInterception = false)));
|
|
27480
|
+
}
|
|
27481
|
+
navigateToConfirmedTarget(targetUrl) {
|
|
27482
|
+
this._bypassInterception = false;
|
|
27483
|
+
this._confirmedTargetUrl = targetUrl;
|
|
27484
|
+
void this.router.navigateByUrl(targetUrl);
|
|
27485
|
+
}
|
|
27486
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
27487
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService });
|
|
27488
|
+
}
|
|
27489
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, decorators: [{
|
|
27490
|
+
type: Injectable
|
|
27491
|
+
}] });
|
|
27492
|
+
|
|
27290
27493
|
class QdResolverTriggerService {
|
|
27291
27494
|
route = inject(ActivatedRoute, { optional: true });
|
|
27292
27495
|
shouldTriggerResolver(triggerOn) {
|
|
@@ -27336,6 +27539,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
27336
27539
|
class QdPageObjectHeaderComponent {
|
|
27337
27540
|
pageObjectResolver = inject(QD_PAGE_OBJECT_RESOLVER_TOKEN, { optional: true });
|
|
27338
27541
|
formGroupManagerService = inject(QdFormGroupManagerService, { optional: true });
|
|
27542
|
+
navigationInterceptor = inject(QdPageNavigationInterceptorService);
|
|
27339
27543
|
dialogComponent = inject(QdDialogComponent, { optional: true });
|
|
27340
27544
|
dialog = inject(QdDialogService);
|
|
27341
27545
|
confirmationDialogService = inject(QdConfirmationDialogOpenerService);
|
|
@@ -27360,6 +27564,8 @@ class QdPageObjectHeaderComponent {
|
|
|
27360
27564
|
_isLoadingSubject = new BehaviorSubject(false);
|
|
27361
27565
|
_customActionsSubject = new BehaviorSubject({ actions: [] });
|
|
27362
27566
|
_customActionsSub;
|
|
27567
|
+
_metadataSub;
|
|
27568
|
+
_streamPartial = {};
|
|
27363
27569
|
_destroyed$ = new Subject();
|
|
27364
27570
|
_availableContexts = 0;
|
|
27365
27571
|
pageObjectData$ = this._pageObjectDataSubject.asObservable();
|
|
@@ -27450,19 +27656,21 @@ class QdPageObjectHeaderComponent {
|
|
|
27450
27656
|
if (originalUpdateMetadata)
|
|
27451
27657
|
originalUpdateMetadata(props);
|
|
27452
27658
|
};
|
|
27453
|
-
this.setupResolverTrigger();
|
|
27454
27659
|
}
|
|
27455
27660
|
}
|
|
27456
27661
|
ngOnInit() {
|
|
27457
27662
|
if (this.pageObjectResolver)
|
|
27458
27663
|
this.setupResolverTrigger();
|
|
27459
27664
|
this.updateCustomActions();
|
|
27665
|
+
this.subscribeToMetadataStream();
|
|
27460
27666
|
this.formGroupManagerService.takeFormGroupsSnapshot();
|
|
27461
27667
|
this.initContexts();
|
|
27462
27668
|
}
|
|
27463
27669
|
ngOnChanges(changes) {
|
|
27464
|
-
if (changes['config'] && !changes['config'].firstChange)
|
|
27670
|
+
if (changes['config'] && !changes['config'].firstChange) {
|
|
27465
27671
|
this.updateCustomActions();
|
|
27672
|
+
this.subscribeToMetadataStream();
|
|
27673
|
+
}
|
|
27466
27674
|
}
|
|
27467
27675
|
ngOnDestroy() {
|
|
27468
27676
|
this.pageStoreService.toggleViewonly(false);
|
|
@@ -27470,8 +27678,8 @@ class QdPageObjectHeaderComponent {
|
|
|
27470
27678
|
this._destroyed$.complete();
|
|
27471
27679
|
}
|
|
27472
27680
|
updateMetadata(metadata) {
|
|
27473
|
-
|
|
27474
|
-
this._pageObjectDataSubject.next(
|
|
27681
|
+
this._streamPartial = { ...this._streamPartial, ...metadata };
|
|
27682
|
+
this._pageObjectDataSubject.next({ ...this._pageObjectDataSubject.value, ...metadata });
|
|
27475
27683
|
}
|
|
27476
27684
|
handleAction(facet) {
|
|
27477
27685
|
facet?.action?.handler();
|
|
@@ -27500,13 +27708,18 @@ class QdPageObjectHeaderComponent {
|
|
|
27500
27708
|
});
|
|
27501
27709
|
}
|
|
27502
27710
|
save() {
|
|
27503
|
-
const
|
|
27504
|
-
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27711
|
+
const saveAction = this.saveButton;
|
|
27712
|
+
if (!saveAction)
|
|
27713
|
+
return;
|
|
27714
|
+
QdPageCommitActionExecutor.execute(saveAction, this.formGroupManagerService.getAllValues(), {
|
|
27715
|
+
formGroupManager: this.formGroupManagerService,
|
|
27716
|
+
navigationInterceptor: this.navigationInterceptor,
|
|
27717
|
+
destroyed$: this._destroyed$,
|
|
27718
|
+
onAfterSnapshot: () => {
|
|
27719
|
+
this.formGroupManagerService.cancelPendingAsyncValidation();
|
|
27720
|
+
this.pageStoreService.toggleViewonly(true);
|
|
27721
|
+
}
|
|
27722
|
+
});
|
|
27510
27723
|
}
|
|
27511
27724
|
changeContext(context, selection, event) {
|
|
27512
27725
|
event.stopPropagation();
|
|
@@ -27530,11 +27743,11 @@ class QdPageObjectHeaderComponent {
|
|
|
27530
27743
|
setupResolverTrigger() {
|
|
27531
27744
|
this.resolverTriggerService
|
|
27532
27745
|
.shouldTriggerResolver(this.pageObjectResolver.config?.triggerOn ?? 'pathParamsChange')
|
|
27533
|
-
.pipe(takeUntil(this._destroyed$), filter(shouldTrigger => shouldTrigger), tap(() => this._isLoadingSubject.next(true)), switchMap(() => this.pageObjectResolver.resolve()), tap(objectData => this._pageObjectDataSubject.next(objectData)), tap(() => this._isLoadingSubject.next(false)), tap(() => this.formGroupManagerService.takeFormGroupsSnapshot()))
|
|
27746
|
+
.pipe(takeUntil(this._destroyed$), filter(shouldTrigger => shouldTrigger), tap(() => this._isLoadingSubject.next(true)), switchMap(() => this.pageObjectResolver.resolve()), tap(objectData => this._pageObjectDataSubject.next({ ...objectData, ...this._streamPartial })), tap(() => this._isLoadingSubject.next(false)), tap(() => this.formGroupManagerService.takeFormGroupsSnapshot()))
|
|
27534
27747
|
.subscribe();
|
|
27535
27748
|
}
|
|
27536
27749
|
initContexts() {
|
|
27537
|
-
this.contexts$ = this.contextService.contexts$.pipe(map(contexts => contexts.filter(context => context.selection || this.config.pageType === 'overview')), map(contexts => contexts
|
|
27750
|
+
this.contexts$ = this.contextService.contexts$.pipe(map(contexts => contexts.filter(context => context.selection || this.config.pageType === 'overview')), map(contexts => contexts.map(({ selection, context }) => ({
|
|
27538
27751
|
label: context.label.i18n,
|
|
27539
27752
|
value: Array.isArray(selection?.value)
|
|
27540
27753
|
? selection?.value.filter(item => item !== null && item !== undefined)
|
|
@@ -27545,7 +27758,7 @@ class QdPageObjectHeaderComponent {
|
|
|
27545
27758
|
selection: selection?.value ?? []
|
|
27546
27759
|
}))));
|
|
27547
27760
|
this.contexts$
|
|
27548
|
-
.pipe(takeUntil(this._destroyed$), tap(contexts => (this._availableContexts = contexts
|
|
27761
|
+
.pipe(takeUntil(this._destroyed$), tap(contexts => (this._availableContexts = contexts.length)))
|
|
27549
27762
|
.subscribe();
|
|
27550
27763
|
}
|
|
27551
27764
|
updateCustomActions() {
|
|
@@ -27562,6 +27775,13 @@ class QdPageObjectHeaderComponent {
|
|
|
27562
27775
|
}
|
|
27563
27776
|
this.subscribeToViewOnlyMode();
|
|
27564
27777
|
}
|
|
27778
|
+
subscribeToMetadataStream() {
|
|
27779
|
+
this._metadataSub?.unsubscribe();
|
|
27780
|
+
const metadata$ = this.config.metadata$;
|
|
27781
|
+
if (!metadata$)
|
|
27782
|
+
return;
|
|
27783
|
+
this._metadataSub = metadata$.pipe(takeUntil(this._destroyed$)).subscribe(partial => this.updateMetadata(partial));
|
|
27784
|
+
}
|
|
27565
27785
|
subscribeToViewOnlyMode() {
|
|
27566
27786
|
this._customActionsSub?.unsubscribe();
|
|
27567
27787
|
this._customActionsSub = this.pageStoreService.isViewonly$
|
|
@@ -28564,126 +28784,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
28564
28784
|
type: Output
|
|
28565
28785
|
}] } });
|
|
28566
28786
|
|
|
28567
|
-
/**
|
|
28568
|
-
* Intercepts router navigation when unsaved form changes exist on a QdPage.
|
|
28569
|
-
*
|
|
28570
|
-
* Provided per `QdPageComponent`. Activated automatically when the page enters an editable state
|
|
28571
|
-
* (create pages or inspect pages in edit mode). Deactivated when the page returns to view mode.
|
|
28572
|
-
*
|
|
28573
|
-
* #### When navigation is intercepted
|
|
28574
|
-
*
|
|
28575
|
-
* - The user has unsaved form changes (tracked via `QdFormGroupManagerService`).
|
|
28576
|
-
* - A `NavigationStart` event occurs (browser back, shell back button, or programmatic navigation).
|
|
28577
|
-
*
|
|
28578
|
-
* The current navigation is cancelled, and a confirmation dialog is shown. The user can either
|
|
28579
|
-
* discard changes and proceed or cancel and stay on the page.
|
|
28580
|
-
*
|
|
28581
|
-
* #### When navigation is allowed
|
|
28582
|
-
*
|
|
28583
|
-
* - No unsaved changes exist — navigation proceeds silently.
|
|
28584
|
-
* - A framework action (Submit, SaveDraft) wraps its handler via `executeWithBypass()`.
|
|
28585
|
-
* - A confirmed discard sets `allowNextNavigation()` before the cancel handler navigates.
|
|
28586
|
-
* - The page switches to view mode via `deactivate()`.
|
|
28587
|
-
*
|
|
28588
|
-
* Custom actions defined by the application are not bypassed. If a custom action navigates away
|
|
28589
|
-
* while unsaved changes exist, the confirmation dialog is shown.
|
|
28590
|
-
*/
|
|
28591
|
-
class QdPageNavigationInterceptorService {
|
|
28592
|
-
router = inject(Router);
|
|
28593
|
-
formGroupManager = inject(QdFormGroupManagerService);
|
|
28594
|
-
confirmationDialog = inject(QdConfirmationDialogOpenerService);
|
|
28595
|
-
_destroy$ = new Subject();
|
|
28596
|
-
_deactivate$ = new Subject();
|
|
28597
|
-
_bypassInterception = false;
|
|
28598
|
-
_allowedTargetUrls = new Set();
|
|
28599
|
-
_confirmationMessage;
|
|
28600
|
-
ngOnDestroy() {
|
|
28601
|
-
this._allowedTargetUrls.clear();
|
|
28602
|
-
this._destroy$.next();
|
|
28603
|
-
this._destroy$.complete();
|
|
28604
|
-
this._deactivate$.complete();
|
|
28605
|
-
}
|
|
28606
|
-
/**
|
|
28607
|
-
* Starts intercepting navigation events. Replaces any previously active listener.
|
|
28608
|
-
*/
|
|
28609
|
-
activate(confirmationMessage) {
|
|
28610
|
-
this._confirmationMessage = confirmationMessage;
|
|
28611
|
-
this._deactivate$.next();
|
|
28612
|
-
this.listenForUnsavedNavigationAttempts();
|
|
28613
|
-
}
|
|
28614
|
-
/**
|
|
28615
|
-
* Stops intercepting and clears all pending URL bypasses.
|
|
28616
|
-
*/
|
|
28617
|
-
deactivate() {
|
|
28618
|
-
this._deactivate$.next();
|
|
28619
|
-
this._allowedTargetUrls.clear();
|
|
28620
|
-
}
|
|
28621
|
-
/**
|
|
28622
|
-
* Whitelists the next navigation so it bypasses interception.
|
|
28623
|
-
* Without a URL, any next navigation is bypassed (wildcard). With a URL, only that
|
|
28624
|
-
* specific navigation is bypassed — non-matching navigations are still intercepted.
|
|
28625
|
-
*/
|
|
28626
|
-
allowNextNavigation(targetUrl) {
|
|
28627
|
-
this._allowedTargetUrls.add(targetUrl ?? '*');
|
|
28628
|
-
}
|
|
28629
|
-
/**
|
|
28630
|
-
* Executes the callback with interception temporarily disabled.
|
|
28631
|
-
* The callback must navigate synchronously — async navigation after the callback returns
|
|
28632
|
-
* will not be bypassed. This works because Angular's router emits NavigationStart
|
|
28633
|
-
* synchronously within the navigateByUrl() / navigate() call.
|
|
28634
|
-
*/
|
|
28635
|
-
executeWithBypass(fn) {
|
|
28636
|
-
this._bypassInterception = true;
|
|
28637
|
-
try {
|
|
28638
|
-
fn();
|
|
28639
|
-
}
|
|
28640
|
-
finally {
|
|
28641
|
-
this._bypassInterception = false;
|
|
28642
|
-
}
|
|
28643
|
-
}
|
|
28644
|
-
listenForUnsavedNavigationAttempts() {
|
|
28645
|
-
this.router.events
|
|
28646
|
-
.pipe(filter$1((event) => event instanceof NavigationStart), filter$1(() => !this._bypassInterception), filter$1(event => this.shouldIntercept(event)), concatMap$1(event => this.checkForPendingChanges(event)), filter$1(({ hasChanges }) => hasChanges), exhaustMap(({ event }) => this.cancelNavigationAndConfirm(event)), filter$1(({ confirmed }) => confirmed), takeUntil$1(this._deactivate$), takeUntil$1(this._destroy$))
|
|
28647
|
-
.subscribe(({ targetUrl }) => this.navigateToConfirmedTarget(targetUrl));
|
|
28648
|
-
}
|
|
28649
|
-
shouldIntercept(event) {
|
|
28650
|
-
if (this._allowedTargetUrls.has('*')) {
|
|
28651
|
-
this._allowedTargetUrls.clear();
|
|
28652
|
-
return false;
|
|
28653
|
-
}
|
|
28654
|
-
if (this._allowedTargetUrls.has(event.url)) {
|
|
28655
|
-
this._allowedTargetUrls.delete(event.url);
|
|
28656
|
-
return false;
|
|
28657
|
-
}
|
|
28658
|
-
return true;
|
|
28659
|
-
}
|
|
28660
|
-
checkForPendingChanges(event) {
|
|
28661
|
-
return this.formGroupManager.$hasValuesChanged().pipe(take$1(1), map$1(hasChanges => ({ event, hasChanges })));
|
|
28662
|
-
}
|
|
28663
|
-
cancelNavigationAndConfirm(event) {
|
|
28664
|
-
this._bypassInterception = true;
|
|
28665
|
-
void this.router.navigateByUrl(this.router.url, { skipLocationChange: true });
|
|
28666
|
-
return this.confirmationDialog
|
|
28667
|
-
.showDiscardConfirmDialog(this._confirmationMessage, 'page-navigation-confirmation')
|
|
28668
|
-
.pipe(map$1(confirmed => ({ confirmed, targetUrl: event.url })), finalize(() => (this._bypassInterception = false)));
|
|
28669
|
-
}
|
|
28670
|
-
navigateToConfirmedTarget(targetUrl) {
|
|
28671
|
-
this._bypassInterception = false;
|
|
28672
|
-
this._allowedTargetUrls.add(targetUrl);
|
|
28673
|
-
void this.router.navigateByUrl(targetUrl);
|
|
28674
|
-
}
|
|
28675
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
28676
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService });
|
|
28677
|
-
}
|
|
28678
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: QdPageNavigationInterceptorService, decorators: [{
|
|
28679
|
-
type: Injectable
|
|
28680
|
-
}] });
|
|
28681
|
-
|
|
28682
28787
|
class QdPageSubmitActionService {
|
|
28683
28788
|
footerService = inject(QdPageFooterService);
|
|
28684
28789
|
formGroupManagerService = inject(QdFormGroupManagerService);
|
|
28790
|
+
navigationInterceptor = inject(QdPageNavigationInterceptorService);
|
|
28685
28791
|
_labelI18n = 'i18n.qd.page.footer.submit';
|
|
28686
|
-
|
|
28792
|
+
_submitAction;
|
|
28687
28793
|
_isVisible = true;
|
|
28688
28794
|
_destroyed$ = new Subject();
|
|
28689
28795
|
_cancelTrackFormValidity$ = new Subject();
|
|
@@ -28697,9 +28803,9 @@ class QdPageSubmitActionService {
|
|
|
28697
28803
|
this._isVisible = isVisible;
|
|
28698
28804
|
this.configureSubmitAction(pageTypeConfig.submit);
|
|
28699
28805
|
}
|
|
28700
|
-
configureSubmitAction(
|
|
28701
|
-
this._labelI18n =
|
|
28702
|
-
this.
|
|
28806
|
+
configureSubmitAction(action) {
|
|
28807
|
+
this._labelI18n = action.label?.i18n || 'i18n.qd.page.footer.submit';
|
|
28808
|
+
this._submitAction = action;
|
|
28703
28809
|
this.registerSubmitAction();
|
|
28704
28810
|
this.trackFormValidity();
|
|
28705
28811
|
}
|
|
@@ -28709,7 +28815,7 @@ class QdPageSubmitActionService {
|
|
|
28709
28815
|
key: 'submit',
|
|
28710
28816
|
action: {
|
|
28711
28817
|
titleI18n: this._labelI18n,
|
|
28712
|
-
handler: this.
|
|
28818
|
+
handler: (...args) => this.executeSubmit(...args),
|
|
28713
28819
|
isDisabled: true,
|
|
28714
28820
|
isVisible: this._isVisible,
|
|
28715
28821
|
type: QdFooterActionType.Primary
|
|
@@ -28717,12 +28823,16 @@ class QdPageSubmitActionService {
|
|
|
28717
28823
|
}
|
|
28718
28824
|
]);
|
|
28719
28825
|
}
|
|
28720
|
-
|
|
28721
|
-
|
|
28722
|
-
|
|
28723
|
-
|
|
28724
|
-
|
|
28725
|
-
|
|
28826
|
+
executeSubmit(...args) {
|
|
28827
|
+
const action = this._submitAction;
|
|
28828
|
+
if (!action)
|
|
28829
|
+
return;
|
|
28830
|
+
const values = this.formGroupManagerService.hasFormGroups() ? this.formGroupManagerService.getAllValues() : args;
|
|
28831
|
+
QdPageCommitActionExecutor.execute(action, values, {
|
|
28832
|
+
formGroupManager: this.formGroupManagerService,
|
|
28833
|
+
navigationInterceptor: this.navigationInterceptor,
|
|
28834
|
+
destroyed$: this._destroyed$
|
|
28835
|
+
});
|
|
28726
28836
|
}
|
|
28727
28837
|
trackFormValidity() {
|
|
28728
28838
|
this._cancelTrackFormValidity$.next();
|
|
@@ -28737,7 +28847,7 @@ class QdPageSubmitActionService {
|
|
|
28737
28847
|
key: 'submit',
|
|
28738
28848
|
action: {
|
|
28739
28849
|
titleI18n: this._labelI18n,
|
|
28740
|
-
handler: this.
|
|
28850
|
+
handler: (...args) => this.executeSubmit(...args),
|
|
28741
28851
|
isDisabled: !isValid,
|
|
28742
28852
|
isVisible: this._isVisible,
|
|
28743
28853
|
type: QdFooterActionType.Primary
|
|
@@ -28792,6 +28902,22 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
|
|
|
28792
28902
|
*
|
|
28793
28903
|
* Please check the relevant interfaces for each page type: `QdPageConfigOverview`, `QdPageConfigCreate`, `QdPageConfigInspect`, and `QdPageConfigCustom`.
|
|
28794
28904
|
*
|
|
28905
|
+
* #### **Commit Actions**
|
|
28906
|
+
*
|
|
28907
|
+
* Commit actions (`submit`, `save`, `saveDraft`) can either run synchronously (handler returns `void`) or wait on an async result (handler returns `Observable<boolean>`). The framework waits for the first emission, advances the saved-state baseline on `true`, and exposes race-free hooks for side effects so navigation after a successful commit does not trigger the unsaved-changes dialog.
|
|
28908
|
+
*
|
|
28909
|
+
* `onSuccess` runs when the handler emits `true` (after the baseline has advanced), `onError` runs when the handler observable errors — e.g. a failed HTTP request that is not caught inside the pipeline.
|
|
28910
|
+
*
|
|
28911
|
+
* ```ts
|
|
28912
|
+
* submit: {
|
|
28913
|
+
* handler: (formValues) => this.api.create(formValues).pipe(map(() => true)),
|
|
28914
|
+
* onSuccess: () => this.router.navigateByUrl('/'),
|
|
28915
|
+
* onError: (err) => this.notifications.add('', { type: 'critical', i18n: 'i18n.myApp.create.failed' })
|
|
28916
|
+
* }
|
|
28917
|
+
* ```
|
|
28918
|
+
*
|
|
28919
|
+
* The same mechanism applies to `save` and `saveDraft`. For the full contract (success vs. planned `false` vs. error, anti-patterns, `discardUnsavedChanges()`), see `QdPageCommitAction` and its action-specific extensions `QdPageSaveAction`, `QdPageSaveDraftAction`, `QdPageCreateSubmitAction`, and `QdPageInspectSubmitAction`.
|
|
28920
|
+
*
|
|
28795
28921
|
* #### **Validation/Parameterization**
|
|
28796
28922
|
*
|
|
28797
28923
|
* Validation and parameterization are covered in a dedicated chapter in the Storybook. Please check the "Validation" section for more information.
|
|
@@ -28861,7 +28987,15 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
|
|
|
28861
28987
|
* handler: () => handleCancel()
|
|
28862
28988
|
* },
|
|
28863
28989
|
* save: {
|
|
28864
|
-
* handler: (
|
|
28990
|
+
* handler: () => saveApi.save(form.value).pipe(
|
|
28991
|
+
* tap(result => notifications.success('Saved')),
|
|
28992
|
+
* map(() => true),
|
|
28993
|
+
* catchError(err => {
|
|
28994
|
+
* notifications.showError(err);
|
|
28995
|
+
* return of(false);
|
|
28996
|
+
* })
|
|
28997
|
+
* ),
|
|
28998
|
+
* onSuccess: () => router.navigate(['/overview'])
|
|
28865
28999
|
* }
|
|
28866
29000
|
* }
|
|
28867
29001
|
* };
|
|
@@ -28870,19 +29004,42 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
|
|
|
28870
29004
|
*
|
|
28871
29005
|
* #### **Updating Facets**
|
|
28872
29006
|
*
|
|
28873
|
-
* Typically, the values of the facets on a create or inspect page are set to read-only.
|
|
29007
|
+
* Typically, the values of the facets on a create or inspect page are set to read-only. If a facet value needs to change at runtime — for instance after a status update from a dialog — push the partial update through an observable on the page config. The header subscribes to `QdPageConfig.metadata$` and shallow-merges every emission into the current object data.
|
|
28874
29008
|
*
|
|
28875
29009
|
* **Please note: These values should not be modified directly within a QdPage.**
|
|
28876
29010
|
*
|
|
28877
29011
|
* ```ts
|
|
28878
|
-
* @
|
|
28879
|
-
*
|
|
28880
|
-
*
|
|
28881
|
-
*
|
|
29012
|
+
* @Component({
|
|
29013
|
+
* // ...
|
|
29014
|
+
* providers: [
|
|
29015
|
+
* {
|
|
29016
|
+
* provide: QD_PAGE_OBJECT_RESOLVER_TOKEN,
|
|
29017
|
+
* useClass: MyObjectModelResolver
|
|
29018
|
+
* }
|
|
29019
|
+
* ]
|
|
29020
|
+
* })
|
|
29021
|
+
* class MyPageComponent {
|
|
29022
|
+
* private metadataUpdates$ = new Subject<Partial<MyObjectModel>>();
|
|
29023
|
+
*
|
|
29024
|
+
* pageConfig: QdPageConfig<MyObjectModel> = {
|
|
29025
|
+
* title: { i18n: 'i18n.page.title' },
|
|
29026
|
+
* pageType: 'inspect',
|
|
29027
|
+
* headerFacets: [ /* ... *\/ ],
|
|
29028
|
+
* metadata$: this.metadataUpdates$,
|
|
29029
|
+
* pageTypeConfig: { /* ... *\/ }
|
|
29030
|
+
* };
|
|
29031
|
+
*
|
|
29032
|
+
* updateStatus() {
|
|
29033
|
+
* this.metadataUpdates$.next({ state: 'Updated' });
|
|
28882
29034
|
* }
|
|
29035
|
+
* }
|
|
29036
|
+
* ```
|
|
28883
29037
|
*
|
|
28884
|
-
*
|
|
29038
|
+
* Legacy approach (`@deprecated`): the resolver-level `updateMetadata` method is still wired up for backward compatibility, but prefer `metadata$` on the config for new code.
|
|
28885
29039
|
*
|
|
29040
|
+
* ```ts
|
|
29041
|
+
* @Injectable()
|
|
29042
|
+
* class MyObjectModelResolver implements QdPageObjectResolver<MyObjectModel> {
|
|
28886
29043
|
* resolve(): Observable<MyObjectModel> {
|
|
28887
29044
|
* // your implementation here
|
|
28888
29045
|
* }
|
|
@@ -28892,15 +29049,6 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
|
|
|
28892
29049
|
* }
|
|
28893
29050
|
* }
|
|
28894
29051
|
*
|
|
28895
|
-
* @Component({
|
|
28896
|
-
* // ...
|
|
28897
|
-
* providers: [
|
|
28898
|
-
* {
|
|
28899
|
-
* provide: QD_PAGE_OBJECT_RESOLVER_TOKEN,
|
|
28900
|
-
* useClass: MyObjectModelResolver
|
|
28901
|
-
* }
|
|
28902
|
-
* ]
|
|
28903
|
-
* })
|
|
28904
29052
|
* class MyPageComponent {
|
|
28905
29053
|
* constructor(@Inject(QD_PAGE_OBJECT_RESOLVER_TOKEN) private objectResolver: MyObjectModelResolver) {}
|
|
28906
29054
|
*
|
|
@@ -28979,7 +29127,8 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
|
|
|
28979
29127
|
* pageType: 'create',
|
|
28980
29128
|
* pageTypeConfig: {
|
|
28981
29129
|
* submit: {
|
|
28982
|
-
* handler: (formValues) =>
|
|
29130
|
+
* handler: (formValues) => createApi.create(formValues).pipe(map(() => true)),
|
|
29131
|
+
* onSuccess: () => router.navigate(['/items'])
|
|
28983
29132
|
* }
|
|
28984
29133
|
* }
|
|
28985
29134
|
* };
|
|
@@ -29072,7 +29221,14 @@ const SAFE_BOTTOM_OFFSET_PX = 64;
|
|
|
29072
29221
|
* handler: () => handleCancel()
|
|
29073
29222
|
* },
|
|
29074
29223
|
* save: {
|
|
29075
|
-
* handler: (formValues) =>
|
|
29224
|
+
* handler: (formValues) => saveApi.save(formValues).pipe(
|
|
29225
|
+
* map(() => true),
|
|
29226
|
+
* catchError(err => {
|
|
29227
|
+
* notifications.showError(err);
|
|
29228
|
+
* return of(false);
|
|
29229
|
+
* })
|
|
29230
|
+
* ),
|
|
29231
|
+
* onSuccess: () => router.navigate(['/overview'])
|
|
29076
29232
|
* }
|
|
29077
29233
|
* }
|
|
29078
29234
|
* };
|
|
@@ -29216,7 +29372,7 @@ class QdPageComponent {
|
|
|
29216
29372
|
if (this.config.pageType === 'create' && this.config?.pageTypeConfig?.cancel !== undefined)
|
|
29217
29373
|
this.handleCancelActionWithFormChanges();
|
|
29218
29374
|
if (this.config.pageType === 'create' && this.config?.pageTypeConfig?.saveDraft !== undefined)
|
|
29219
|
-
this.initSaveDraftFooterAction();
|
|
29375
|
+
this.initSaveDraftFooterAction(this.config.pageTypeConfig.saveDraft);
|
|
29220
29376
|
if (this.config.pageType === 'inspect')
|
|
29221
29377
|
this.pageStoreService.isViewonly$
|
|
29222
29378
|
.pipe(takeUntil(this._destroyed$))
|
|
@@ -29230,6 +29386,34 @@ class QdPageComponent {
|
|
|
29230
29386
|
this._destroyed$.next();
|
|
29231
29387
|
this._destroyed$.complete();
|
|
29232
29388
|
}
|
|
29389
|
+
/**
|
|
29390
|
+
* @description Resets all registered form groups to their last saved snapshot, marking the page
|
|
29391
|
+
* as no longer dirty.
|
|
29392
|
+
*
|
|
29393
|
+
* Intended for explicit consumer-driven discard scenarios where you want subsequent navigation
|
|
29394
|
+
* to bypass the unsaved-changes dialog — for example inside a commit action's `onError` hook
|
|
29395
|
+
* after an auth-expiry response, where the user must be redirected to the login page regardless
|
|
29396
|
+
* of unsaved edits.
|
|
29397
|
+
*
|
|
29398
|
+
* Prefer this over wiring custom bypass logic; it keeps the discard explicit and auditable in
|
|
29399
|
+
* application code.
|
|
29400
|
+
*
|
|
29401
|
+
* @example
|
|
29402
|
+
* ```ts
|
|
29403
|
+
* save: {
|
|
29404
|
+
* handler: (values) => api.save(values).pipe(map(() => true)),
|
|
29405
|
+
* onError: (err) => {
|
|
29406
|
+
* if (err.status === 401) {
|
|
29407
|
+
* this.pageComponent.discardUnsavedChanges();
|
|
29408
|
+
* this.router.navigateByUrl('/login');
|
|
29409
|
+
* }
|
|
29410
|
+
* }
|
|
29411
|
+
* }
|
|
29412
|
+
* ```
|
|
29413
|
+
*/
|
|
29414
|
+
discardUnsavedChanges() {
|
|
29415
|
+
this.formGroupManagerService.restoreFormGroupsFromSnapshot();
|
|
29416
|
+
}
|
|
29233
29417
|
checkConfigValidity() {
|
|
29234
29418
|
if (!this.config)
|
|
29235
29419
|
console.warn('QdUi | QdPageComponent - To configure the page you should provide a valid config.');
|
|
@@ -29242,11 +29426,14 @@ class QdPageComponent {
|
|
|
29242
29426
|
const action = pageTypeConfig[actionKey];
|
|
29243
29427
|
if (!action)
|
|
29244
29428
|
continue;
|
|
29429
|
+
const handler = actionKey === 'cancel'
|
|
29430
|
+
? () => action.handler()
|
|
29431
|
+
: this.generateCommitActionHandler(action);
|
|
29245
29432
|
actions.push({
|
|
29246
29433
|
actionKey,
|
|
29247
29434
|
partialAction: {
|
|
29248
29435
|
...(action?.label?.i18n ? { titleI18n: action.label.i18n } : {}),
|
|
29249
|
-
handler
|
|
29436
|
+
handler
|
|
29250
29437
|
}
|
|
29251
29438
|
});
|
|
29252
29439
|
}
|
|
@@ -29263,25 +29450,20 @@ class QdPageComponent {
|
|
|
29263
29450
|
partialAction: {
|
|
29264
29451
|
handler: hasChanged
|
|
29265
29452
|
? () => this.setupCancelConfirmation()
|
|
29266
|
-
: () =>
|
|
29267
|
-
this.navigationInterceptor.executeWithBypass(() => {
|
|
29268
|
-
this.config?.pageTypeConfig?.cancel?.handler();
|
|
29269
|
-
});
|
|
29270
|
-
}
|
|
29453
|
+
: () => this.config?.pageTypeConfig?.cancel?.handler()
|
|
29271
29454
|
}
|
|
29272
29455
|
}
|
|
29273
29456
|
]);
|
|
29274
29457
|
});
|
|
29275
29458
|
}
|
|
29276
|
-
initSaveDraftFooterAction() {
|
|
29277
|
-
const pageTypeConfig = this.config.pageTypeConfig;
|
|
29459
|
+
initSaveDraftFooterAction(saveDraft) {
|
|
29278
29460
|
this.footerService.setActions([
|
|
29279
29461
|
{
|
|
29280
29462
|
key: 'saveDraft',
|
|
29281
29463
|
action: {
|
|
29282
|
-
titleI18n:
|
|
29464
|
+
titleI18n: saveDraft.label?.i18n ?? 'i18n.qd.page.footer.saveDraft',
|
|
29283
29465
|
type: QdFooterActionType.Secondary,
|
|
29284
|
-
handler: this.
|
|
29466
|
+
handler: this.generateCommitActionHandler(saveDraft),
|
|
29285
29467
|
isVisible: true,
|
|
29286
29468
|
isDisabled: false
|
|
29287
29469
|
}
|
|
@@ -29308,12 +29490,14 @@ class QdPageComponent {
|
|
|
29308
29490
|
if (this._isInitialized)
|
|
29309
29491
|
this.operationModeChanged.emit(mode);
|
|
29310
29492
|
}
|
|
29311
|
-
|
|
29493
|
+
generateCommitActionHandler(action) {
|
|
29312
29494
|
return (...args) => {
|
|
29313
|
-
if (!handler)
|
|
29314
|
-
return;
|
|
29315
29495
|
const values = this.formGroupManagerService.hasFormGroups() ? this.formGroupManagerService.getAllValues() : args;
|
|
29316
|
-
|
|
29496
|
+
QdPageCommitActionExecutor.execute(action, values, {
|
|
29497
|
+
formGroupManager: this.formGroupManagerService,
|
|
29498
|
+
navigationInterceptor: this.navigationInterceptor,
|
|
29499
|
+
destroyed$: this._destroyed$
|
|
29500
|
+
});
|
|
29317
29501
|
};
|
|
29318
29502
|
}
|
|
29319
29503
|
setupCancelConfirmation() {
|
|
@@ -29322,8 +29506,8 @@ class QdPageComponent {
|
|
|
29322
29506
|
.showDiscardConfirmDialog(cancelConfig?.confirmationMessage, this.testId + '-cancel-confirmation')
|
|
29323
29507
|
.pipe(filter$1(result => result), takeUntil(this._destroyed$))
|
|
29324
29508
|
.subscribe(() => {
|
|
29509
|
+
this.formGroupManagerService.restoreFormGroupsFromSnapshot();
|
|
29325
29510
|
cancelConfig?.handler?.();
|
|
29326
|
-
this.navigationInterceptor.allowNextNavigation();
|
|
29327
29511
|
});
|
|
29328
29512
|
}
|
|
29329
29513
|
initSubmitValidation() {
|
|
@@ -29331,7 +29515,7 @@ class QdPageComponent {
|
|
|
29331
29515
|
this.formGroupManagerService
|
|
29332
29516
|
.$areFormGroupsValid()
|
|
29333
29517
|
.pipe(takeUntil(this._cancelSubmitValidation$), takeUntil(this._destroyed$), tap(isValid => {
|
|
29334
|
-
const submitDisabledInfoText = this.config.
|
|
29518
|
+
const submitDisabledInfoText = this.config.pageTypeConfig?.submit?.disabledInfo;
|
|
29335
29519
|
this.footerService.updateActions([
|
|
29336
29520
|
{
|
|
29337
29521
|
actionKey: 'submit',
|