Skip to content

Commit 6bc8794

Browse files
authored
Merge pull request #8779 from IgniteUI/dTsvetkov/fix-dialog-8320-10.2.x
fix(dialog): two way data binding #8320 - 10.2.x
2 parents 5fe7629 + 632fa10 commit 6bc8794

File tree

6 files changed

+113
-48
lines changed

6 files changed

+113
-48
lines changed

projects/igniteui-angular/src/lib/dialog/dialog.component.spec.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { async, TestBed, fakeAsync, tick } from '@angular/core/testing';
33
import { By } from '@angular/platform-browser';
44
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
55
import { UIInteractions } from '../test-utils/ui-interactions.spec';
6-
import { IDialogEventArgs, IgxDialogComponent, IgxDialogModule } from './dialog.component';
6+
import { IDialogCancellableEventArgs, IDialogEventArgs, IgxDialogComponent, IgxDialogModule } from './dialog.component';
77
import { configureTestSuite } from '../test-utils/configure-suite';
88
import { useAnimation } from '@angular/animations';
99
import { PositionSettings, HorizontalAlignment, VerticalAlignment } from '../services/overlay/utilities';
@@ -87,7 +87,7 @@ describe('Dialog', () => {
8787
const expectedMessage = 'message';
8888

8989
dialog.isOpen = true;
90-
tick();
90+
tick(100);
9191
fixture.detectChanges();
9292
expect(dialog.isOpen).toEqual(true);
9393

@@ -105,7 +105,7 @@ describe('Dialog', () => {
105105
fixture.componentInstance.myDialog = true;
106106

107107
fixture.detectChanges();
108-
tick();
108+
tick(100);
109109
expect(dialog.isOpen).toEqual(true);
110110

111111

@@ -198,19 +198,36 @@ describe('Dialog', () => {
198198
const dialog = fixture.componentInstance.dialog;
199199
const args: IDialogEventArgs = {
200200
dialog,
201-
event: null
201+
event: undefined,
202+
};
203+
let cancellableArgs: IDialogCancellableEventArgs = {
204+
dialog,
205+
event: null,
206+
cancel: false
202207
};
208+
203209
spyOn(dialog.onOpen, 'emit');
210+
spyOn(dialog.onOpened, 'emit');
211+
spyOn(dialog.isOpenChange, 'emit');
212+
spyOn(dialog.onClose, 'emit');
213+
spyOn(dialog.onClosed, 'emit');
214+
204215
dialog.open();
205216
tick();
206217
fixture.detectChanges();
207-
expect(dialog.onOpen.emit).toHaveBeenCalledWith(args);
208218

209-
spyOn(dialog.onClose, 'emit');
219+
expect(dialog.onOpen.emit).toHaveBeenCalledWith(cancellableArgs);
220+
expect(dialog.isOpenChange.emit).toHaveBeenCalledWith(true);
221+
// expect(dialog.onOpened.emit).toHaveBeenCalled();
222+
210223
dialog.close();
211224
tick();
212225
fixture.detectChanges();
213-
expect(dialog.onClose.emit).toHaveBeenCalledWith(args);
226+
227+
cancellableArgs = { dialog, event: undefined, cancel: false };
228+
expect(dialog.onClose.emit).toHaveBeenCalledWith(cancellableArgs);
229+
expect(dialog.onClosed.emit).toHaveBeenCalledWith(args);
230+
expect(dialog.isOpenChange.emit).toHaveBeenCalledWith(false);
214231

215232
dialog.open();
216233
tick();
@@ -283,10 +300,10 @@ describe('Dialog', () => {
283300

284301
it('Should initialize igx-dialog custom title and actions', () => {
285302
const data = [{
286-
component: CustomTemplates1DialogComponent
287-
}, {
288-
component: CustomTemplates2DialogComponent
289-
}];
303+
component: CustomTemplates1DialogComponent
304+
}, {
305+
component: CustomTemplates2DialogComponent
306+
}];
290307

291308
data.forEach((item) => {
292309
const fixture = TestBed.createComponent(item.component);
@@ -338,7 +355,7 @@ describe('Dialog', () => {
338355
expect(overlayWrapper.classList.contains(OVERLAY_WRAPPER_CLASS)).toBe(false);
339356
}));
340357

341-
it('Default button of the dialog is focused after opening the dialog and can be closed with keyboard.', fakeAsync( () => {
358+
it('Default button of the dialog is focused after opening the dialog and can be closed with keyboard.', fakeAsync(() => {
342359
const fix = TestBed.createComponent(DialogComponent);
343360
fix.detectChanges();
344361

@@ -368,7 +385,7 @@ describe('Dialog', () => {
368385
let fix;
369386
let dialog;
370387

371-
beforeEach( fakeAsync(() => {
388+
beforeEach(fakeAsync(() => {
372389
fix = TestBed.createComponent(PositionSettingsDialogComponent);
373390
fix.detectChanges();
374391
dialog = fix.componentInstance.dialog;
@@ -414,7 +431,7 @@ describe('Dialog', () => {
414431
expect(dialog.isOpen).toEqual(false);
415432
}));
416433

417-
it('Set animation settings', () => {
434+
it('Set animation settings', () => {
418435
const currentElement = fix.componentInstance;
419436

420437
// Check initial animation settings
@@ -584,7 +601,7 @@ class PositionSettingsDialogComponent {
584601
horizontalStartPoint: HorizontalAlignment.Left,
585602
verticalStartPoint: VerticalAlignment.Middle,
586603
openAnimation: useAnimation(slideInTop, { params: { duration: '200ms' } }),
587-
closeAnimation: useAnimation(slideOutBottom, { params: { duration: '200ms'} })
604+
closeAnimation: useAnimation(slideOutBottom, { params: { duration: '200ms' } })
588605
};
589606

590607
public newPositionSettings: PositionSettings = {
@@ -594,7 +611,7 @@ class PositionSettingsDialogComponent {
594611

595612
public animationSettings: PositionSettings = {
596613
openAnimation: useAnimation(slideInTop, { params: { duration: '800ms' } }),
597-
closeAnimation: useAnimation(slideOutBottom, { params: { duration: '700ms'} })
614+
closeAnimation: useAnimation(slideOutBottom, { params: { duration: '700ms' } })
598615
};
599616

600617
}

projects/igniteui-angular/src/lib/dialog/dialog.component.ts

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { IgxToggleModule, IgxToggleDirective } from '../directives/toggle/toggle
2424
import { OverlaySettings, GlobalPositionStrategy, NoOpScrollStrategy, PositionSettings } from '../services/public_api';
2525
import { slideInBottom, slideOutTop } from '../animations/slide/index';
2626
import { IgxFocusModule } from '../directives/focus/focus.directive';
27-
import { IBaseEventArgs } from '../core/utils';
27+
import { CancelableEventArgs, IBaseEventArgs } from '../core/utils';
2828

2929
let DIALOG_ID = 0;
3030
/**
@@ -277,24 +277,44 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After
277277
}
278278

279279
/**
280-
* An event that is emitted when the dialog is opened.
280+
* An event that is emitted before the dialog is opened.
281281
* ```html
282282
* <igx-dialog (onOpen)="onDialogOpenHandler($event)" (onLeftButtonSelect)="dialog.close()" rightButtonLabel="OK">
283283
* </igx-dialog>
284284
* ```
285285
*/
286286
@Output()
287-
public onOpen = new EventEmitter<IDialogEventArgs>();
287+
public onOpen = new EventEmitter<IDialogCancellableEventArgs>();
288288

289289
/**
290-
* An event that is emitted when the dialog is closed.
290+
* An event that is emitted after the dialog is opened.
291+
* ```html
292+
* <igx-dialog (onOpened)="onDialogOpenedHandler($event)" (onLeftButtonSelect)="dialog.close()" rightButtonLabel="OK">
293+
* </igx-dialog>
294+
* ```
295+
*/
296+
@Output()
297+
public onOpened = new EventEmitter<IDialogEventArgs>();
298+
299+
/**
300+
* An event that is emitted before the dialog is closed.
291301
* ```html
292302
* <igx-dialog (onClose)="onDialogCloseHandler($event)" title="Confirmation" leftButtonLabel="Cancel" rightButtonLabel="OK">
293303
* </igx-dialog>
294304
* ```
295305
*/
296306
@Output()
297-
public onClose = new EventEmitter<IDialogEventArgs>();
307+
public onClose = new EventEmitter<IDialogCancellableEventArgs>();
308+
309+
/**
310+
* An event that is emitted after the dialog is closed.
311+
* ```html
312+
* <igx-dialog (onClosed)="onDialogClosedHandler($event)" title="Confirmation" leftButtonLabel="Cancel" rightButtonLabel="OK">
313+
* </igx-dialog>
314+
* ```
315+
*/
316+
@Output()
317+
public onClosed = new EventEmitter<IDialogEventArgs>();
298318

299319
/**
300320
* An event that is emitted when the left button is clicked.
@@ -389,12 +409,15 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After
389409
return !this.toggleRef.collapsed;
390410
}
391411
public set isOpen(value: boolean) {
392-
393-
this.isOpenChange.emit(value);
394-
if (value) {
395-
this.open();
396-
} else {
397-
this.close();
412+
if (value !== this.isOpen) {
413+
this.isOpenChange.emit(value);
414+
if (value) {
415+
requestAnimationFrame(() => {
416+
this.open();
417+
});
418+
} else {
419+
this.close();
420+
}
398421
}
399422
}
400423

@@ -458,11 +481,26 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After
458481
}
459482

460483
ngAfterContentInit() {
461-
this.toggleRef.onClosing.pipe(takeUntil(this.destroy$)).subscribe(() => this.emitCloseFromDialog());
484+
this.toggleRef.onClosing.pipe(takeUntil(this.destroy$)).subscribe((eventArgs) => this.emitCloseFromDialog(eventArgs));
485+
this.toggleRef.onClosed.pipe(takeUntil(this.destroy$)).subscribe((eventArgs) => this.emitClosedFromDialog(eventArgs));
486+
this.toggleRef.onOpened.pipe(takeUntil(this.destroy$)).subscribe((eventArgs) => this.emitOpenedFromDialog(eventArgs));
462487
}
463488

464-
private emitCloseFromDialog() {
465-
this.onClose.emit({ dialog: this, event: null });
489+
private emitCloseFromDialog(eventArgs) {
490+
const dialogEventsArgs = { dialog: this, event: eventArgs.event, cancel: eventArgs.cancel };
491+
this.onClose.emit(dialogEventsArgs);
492+
eventArgs.cancel = dialogEventsArgs.cancel;
493+
if (!eventArgs.cancel) {
494+
this.isOpenChange.emit(false);
495+
}
496+
}
497+
498+
private emitClosedFromDialog(eventArgs) {
499+
this.onClosed.emit({ dialog: this, event: eventArgs.event });
500+
}
501+
502+
private emitOpenedFromDialog(eventArgs) {
503+
this.onOpened.emit({ dialog: this, event: eventArgs.event });
466504
}
467505

468506
/**
@@ -474,10 +512,14 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After
474512
* ```
475513
*/
476514
public open(overlaySettings: OverlaySettings = this._overlayDefaultSettings) {
477-
this.toggleRef.open(overlaySettings);
478-
this.onOpen.emit({ dialog: this, event: null });
479-
if (!this.leftButtonLabel && !this.rightButtonLabel) {
480-
this.toggleRef.element.focus();
515+
const eventArgs: IDialogCancellableEventArgs = { dialog: this, event: null, cancel: false };
516+
this.onOpen.emit(eventArgs);
517+
if (!eventArgs.cancel) {
518+
this.toggleRef.open(overlaySettings);
519+
this.isOpenChange.emit(true);
520+
if (!this.leftButtonLabel && !this.rightButtonLabel) {
521+
this.toggleRef.element.focus();
522+
}
481523
}
482524
}
483525

@@ -559,6 +601,8 @@ export interface IDialogEventArgs extends IBaseEventArgs {
559601
event: Event;
560602
}
561603

604+
export interface IDialogCancellableEventArgs extends IDialogEventArgs, CancelableEventArgs { }
605+
562606
/**
563607
* @hidden
564608
*/

projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { CancelableBrowserEventArgs, IBaseEventArgs } from '../../core/utils';
3030
export interface ToggleViewEventArgs extends IBaseEventArgs {
3131
/** Id of the toggle view */
3232
id: string;
33+
event?: Event;
3334
}
3435

3536
export interface ToggleViewCancelableEventArgs extends ToggleViewEventArgs, CancelableBrowserEventArgs { }
@@ -357,12 +358,12 @@ export class IgxToggleDirective implements IToggleView, OnInit, OnDestroy {
357358
this.destroy$.complete();
358359
}
359360

360-
private overlayClosed = () => {
361+
private overlayClosed = (ev) => {
361362
this._collapsed = true;
362363
this.cdr.detectChanges();
363364
delete this._overlayId;
364365
this.unsubscribe();
365-
const closedEventArgs: ToggleViewEventArgs = { owner: this, id: this._overlayId };
366+
const closedEventArgs: ToggleViewEventArgs = { owner: this, id: this._overlayId, event: ev.event};
366367
this.onClosed.emit(closedEventArgs);
367368
}
368369

projects/igniteui-angular/src/lib/services/overlay/overlay.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,8 @@ describe('igxOverlay', () => {
459459

460460
tick();
461461
expect(overlayInstance.onClosed.emit).toHaveBeenCalledTimes(1);
462-
expect(overlayInstance.onClosed.emit).toHaveBeenCalledWith({ id: firstCallId, componentRef: jasmine.any(ComponentRef) as any });
462+
expect(overlayInstance.onClosed.emit).
463+
toHaveBeenCalledWith({ id: firstCallId, componentRef: jasmine.any(ComponentRef) as any, event: undefined });
463464

464465
const secondCallId = overlayInstance.show(overlayInstance.attach(fix.componentInstance.item));
465466
tick();
@@ -481,7 +482,7 @@ describe('igxOverlay', () => {
481482

482483
tick();
483484
expect(overlayInstance.onClosed.emit).toHaveBeenCalledTimes(2);
484-
expect(overlayInstance.onClosed.emit).toHaveBeenCalledWith({ componentRef: undefined, id: secondCallId });
485+
expect(overlayInstance.onClosed.emit).toHaveBeenCalledWith({ componentRef: undefined, id: secondCallId, event: undefined });
485486
}));
486487

487488
it('Should properly set style on position method call - GlobalPosition.', () => {

projects/igniteui-angular/src/lib/services/overlay/overlay.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@ export class IgxOverlayService implements OnDestroy {
372372
* this.overlay.hide(id);
373373
* ```
374374
*/
375-
hide(id: string) {
376-
this._hide(id);
375+
hide(id: string, event?: Event) {
376+
this._hide(id, event);
377377
}
378378

379379
/**
@@ -529,9 +529,9 @@ export class IgxOverlayService implements OnDestroy {
529529
}
530530

531531
if (info.settings.positionStrategy.settings.closeAnimation) {
532-
this.playCloseAnimation(info);
532+
this.playCloseAnimation(info, event);
533533
} else {
534-
this.onCloseDone(info);
534+
this.onCloseDone(info, event);
535535
}
536536
}
537537

@@ -633,9 +633,9 @@ export class IgxOverlayService implements OnDestroy {
633633
}
634634
}
635635

636-
private onCloseDone(info: OverlayInfo) {
636+
private onCloseDone(info: OverlayInfo, event?: Event) {
637637
this.cleanUp(info);
638-
this.onClosed.emit({ id: info.id, componentRef: info.componentRef });
638+
this.onClosed.emit({ id: info.id, componentRef: info.componentRef, event: event});
639639
}
640640

641641
private cleanUp(info: OverlayInfo) {
@@ -718,7 +718,7 @@ export class IgxOverlayService implements OnDestroy {
718718
info.openAnimationPlayer.play();
719719
}
720720

721-
private playCloseAnimation(info: OverlayInfo) {
721+
private playCloseAnimation(info: OverlayInfo, ev?: Event) {
722722
if (!info.closeAnimationPlayer) {
723723
const animationBuilder = this.builder.build(info.settings.positionStrategy.settings.closeAnimation);
724724
info.closeAnimationPlayer = animationBuilder.create(info.elementRef.nativeElement);
@@ -739,7 +739,7 @@ export class IgxOverlayService implements OnDestroy {
739739
if (info.openAnimationPlayer && info.openAnimationPlayer.hasStarted()) {
740740
info.openAnimationPlayer.reset();
741741
}
742-
this.onCloseDone(info);
742+
this.onCloseDone(info, ev);
743743
});
744744
}
745745

@@ -896,10 +896,10 @@ export class IgxOverlayService implements OnDestroy {
896896
if (info.settings.closeOnEscape && !this._keyPressEventListener) {
897897
this._keyPressEventListener = fromEvent(this._document, 'keydown').pipe(
898898
filter((ev: KeyboardEvent) => ev.key === 'Escape' || ev.key === 'Esc')
899-
).subscribe(() => {
899+
).subscribe((ev) => {
900900
const targetOverlay = this._overlayInfos[this._overlayInfos.length - 1];
901901
if (targetOverlay.settings.closeOnEscape) {
902-
this.hide(targetOverlay.id);
902+
this.hide(targetOverlay.id, ev);
903903
}
904904
});
905905
}

projects/igniteui-angular/src/lib/services/overlay/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ export interface OverlayEventArgs extends IBaseEventArgs {
109109
id: string;
110110
/** Available when `Type<T>` is provided to the `attach()` method and allows access to the created Component instance */
111111
componentRef?: ComponentRef<{}>;
112+
/** Will provide the original keyboard event if closed from ESC or click */
113+
event?: Event;
112114
}
113115

114116
export interface OverlayCancelableEventArgs extends OverlayEventArgs, CancelableEventArgs {

0 commit comments

Comments
 (0)