diff --git a/src/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index 45e7ba5925ca..474a59c68718 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -10,6 +10,10 @@

Dialog demo

Open dialog with template content + +

Dialog dimensions

@@ -67,6 +71,10 @@

Other options

Last close result: {{lastCloseResult}}

- + I'm a template dialog. I've been opened {{numTemplateOpens}} times! + + + {{ closeAttemptType ? 'Tried to close with: ' + closeAttemptType : 'Have not tried to close yet.' }} + diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 5ccd67a0ec68..7958ec00c2f2 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -12,6 +12,7 @@ import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/ma export class DialogDemo { dialogRef: MdDialogRef; lastCloseResult: string; + closeAttemptType: string; actionsAlignment: string; config: MdDialogConfig = { disableClose: false, @@ -29,7 +30,8 @@ export class DialogDemo { }; numTemplateOpens = 0; - @ViewChild(TemplateRef) template: TemplateRef; + @ViewChild('templateRef') template: TemplateRef; + @ViewChild('disabledCloseRef') templateForDisabledClose: TemplateRef; constructor(public dialog: MdDialog, @Inject(DOCUMENT) doc: any) { // Possible useful example for the open and closeAll events. @@ -63,8 +65,19 @@ export class DialogDemo { this.numTemplateOpens++; this.dialog.open(this.template, this.config); } -} + openWithDisabledClose() { + let disableCloseConfig = new MdDialogConfig(); + disableCloseConfig.disableClose = true; + + let dialogRef = this.dialog.open(this.templateForDisabledClose, disableCloseConfig); + + dialogRef.closeAttempt().subscribe((type) => { + this.closeAttemptType = type; + }); + + } +} @Component({ selector: 'demo-jazz-dialog', diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 1416d36a7bb3..d869942e31ce 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -21,12 +21,15 @@ import {BasePortalHost, ComponentPortal, PortalHostDirective, TemplatePortal} fr import {MdDialogConfig} from './dialog-config'; import {MdDialogContentAlreadyAttachedError} from './dialog-errors'; import {FocusTrapFactory, FocusTrap} from '../core/a11y/focus-trap'; +import {Subject} from 'rxjs/Subject'; import 'rxjs/add/operator/first'; /** Possible states for the dialog container animation. */ export type MdDialogContainerAnimationState = 'void' | 'enter' | 'exit' | 'exit-start'; +/** Possible types of close events */ +export type MdDialogCloseAttempt = 'escape' | 'backdrop'; /** * Internal component that wraps user-provided dialog content. @@ -67,6 +70,9 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy { /** The dialog configuration. */ dialogConfig: MdDialogConfig; + /** subject to expose any close attempt */ + _closeAttempt = new Subject(); + /** State of the dialog animation. */ _state: MdDialogContainerAnimationState = 'enter'; diff --git a/src/lib/dialog/dialog-ref.ts b/src/lib/dialog/dialog-ref.ts index 429dc914c384..0a0bcf713521 100644 --- a/src/lib/dialog/dialog-ref.ts +++ b/src/lib/dialog/dialog-ref.ts @@ -2,8 +2,11 @@ import {OverlayRef, GlobalPositionStrategy} from '../core'; import {DialogPosition} from './dialog-config'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; -import {MdDialogContainer, MdDialogContainerAnimationState} from './dialog-container'; - +import { + MdDialogContainer, + MdDialogContainerAnimationState, + MdDialogCloseAttempt, +} from './dialog-container'; // TODO(jelbourn): resizing // TODO(jelbourn): afterOpen and beforeClose @@ -53,6 +56,13 @@ export class MdDialogRef { return this._afterClosed.asObservable(); } + /** + * Gets an observable that is notified when a close attempt is made + */ + closeAttempt(): Observable { + return this._containerInstance._closeAttempt.asObservable(); + } + /** * Updates the dialog's position. * @param position New dialog position. diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index d9833fe3b53f..0f9e2b2ad8de 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -157,6 +157,52 @@ describe('MdDialog', () => { }); })); + it('should let the user know that there was a close attempt via the backdrop click', async(() => { + const dialogRef = dialog.open(PizzaMsg, { + viewContainerRef: testViewContainerRef, + disableClose: true + }); + + let attemptType: any; + + dialogRef.closeAttempt().subscribe((type) => { + attemptType = type; + }); + + viewContainerFixture.detectChanges(); + + let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; + + backdrop.click(); + viewContainerFixture.detectChanges(); + + viewContainerFixture.whenStable().then(() => { + expect(attemptType).toEqual('backdrop'); + expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy(); + }); + })); + + it('should let the user know that there was a close attempt via the escape key', async(() => { + const dialogRef = dialog.open(PizzaMsg, { + viewContainerRef: testViewContainerRef, + disableClose: true + }); + + let attemptType: any; + + dialogRef.closeAttempt().subscribe((type) => { + attemptType = type; + }); + + dispatchKeyboardEvent(document, 'keydown', ESCAPE); + viewContainerFixture.detectChanges(); + + viewContainerFixture.whenStable().then(() => { + expect(attemptType).toEqual('escape'); + expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy(); + }); + })); + it('should notify the observers if a dialog has been opened', () => { let ref: MdDialogRef; dialog.afterOpen.subscribe(r => { diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index bc828b62d317..1f21cbd063e8 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -9,6 +9,7 @@ import {MdDialogConfig} from './dialog-config'; import {MdDialogRef} from './dialog-ref'; import {MdDialogContainer} from './dialog-container'; import {TemplatePortal} from '../core/portal/portal'; +import {Subscription} from 'rxjs/Subscription'; import 'rxjs/add/operator/first'; @@ -21,6 +22,7 @@ export class MdDialog { private _afterAllClosedAtThisLevel = new Subject(); private _afterOpenAtThisLevel = new Subject>(); private _boundKeydown = this._handleKeydown.bind(this); + private _backdropClickSubscription: Subscription; /** Keeps track of the currently-open dialogs. */ get _openDialogs(): MdDialogRef[] { @@ -152,6 +154,10 @@ export class MdDialog { if (!config.disableClose) { // When the dialog backdrop is clicked, we want to close it. overlayRef.backdropClick().first().subscribe(() => dialogRef.close()); + } else { + this._backdropClickSubscription = overlayRef.backdropClick().subscribe(() => { + dialogContainer._closeAttempt.next('backdrop'); + }); } // We create an injector specifically for the component we're instantiating so that it can @@ -187,6 +193,10 @@ export class MdDialog { // no open dialogs are left, call next on afterAllClosed Subject if (!this._openDialogs.length) { + if (this._backdropClickSubscription) { + this._backdropClickSubscription.unsubscribe(); + } + this._afterAllClosed.next(); document.removeEventListener('keydown', this._boundKeydown); } @@ -201,8 +211,11 @@ export class MdDialog { let topDialog = this._openDialogs[this._openDialogs.length - 1]; let canClose = topDialog ? !topDialog._containerInstance.dialogConfig.disableClose : false; - if (event.keyCode === ESCAPE && canClose) { - topDialog.close(); + if (event.keyCode === ESCAPE && topDialog) { + topDialog._containerInstance._closeAttempt.next('escape'); + if (canClose) { + topDialog.close(); + } } } }