feat(material): add prototype dialog component w/ demo.
This commit is contained in:
		
							parent
							
								
									75da6e4c4a
								
							
						
					
					
						commit
						f88c4b77ca
					
				| @ -54,7 +54,7 @@ export class EventManagerPlugin { | ||||
| 
 | ||||
|   // We are assuming here that all plugins support bubbled and non-bubbled events.
 | ||||
|   // That is equivalent to having supporting $event.target
 | ||||
|   // The bubbling flag (currently ^) is stripped before calling the supports and 
 | ||||
|   // The bubbling flag (currently ^) is stripped before calling the supports and
 | ||||
|   // addEventListener methods.
 | ||||
|   supports(eventName: string): boolean { | ||||
|     return false; | ||||
|  | ||||
| @ -39,7 +39,7 @@ export class MdCheckbox { | ||||
|   /** Setter for tabindex */ | ||||
|   tabindex: number; | ||||
| 
 | ||||
|   constructor(@Attribute('tabindex') tabindex: string) { | ||||
|   constructor(@Attribute('tabindex') tabindex: String) { | ||||
|     this.role = 'checkbox'; | ||||
|     this.checked = false; | ||||
|     this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0; | ||||
|  | ||||
							
								
								
									
										33
									
								
								modules/angular2_material/src/components/dialog/dialog.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								modules/angular2_material/src/components/dialog/dialog.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| <style> | ||||
|   .md-dialog { | ||||
|     position: absolute; | ||||
|     z-index: 80; | ||||
| 
 | ||||
|     /** Center the dialog. */ | ||||
|     top: 50%; | ||||
|     left: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
| 
 | ||||
|     width: 300px; | ||||
|     height: 300px; | ||||
| 
 | ||||
|     background-color: white; | ||||
|     border: 1px solid black; | ||||
|     box-shadow: 0 4px 4px;; | ||||
| 
 | ||||
|     padding: 20px; | ||||
|   } | ||||
| 
 | ||||
|   .md-backdrop { | ||||
|     position: absolute; | ||||
|     top:0 ; | ||||
|     left: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     background-color: rgba(0, 0, 0, 0.12); | ||||
|   } | ||||
| </style> | ||||
| 
 | ||||
| <md-dialog-content></md-dialog-content> | ||||
| <div tabindex="0" (focus)="wrapFocus()"></div> | ||||
| 
 | ||||
							
								
								
									
										265
									
								
								modules/angular2_material/src/components/dialog/dialog.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								modules/angular2_material/src/components/dialog/dialog.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,265 @@ | ||||
| import {DynamicComponentLoader, ElementRef, ComponentRef, onDestroy} from 'angular2/angular2'; | ||||
| import {bind, Injector} from 'angular2/di'; | ||||
| import {ObservableWrapper, Promise, PromiseWrapper} from 'angular2/src/facade/async'; | ||||
| import {isPresent, Type} from 'angular2/src/facade/lang'; | ||||
| import {DOM} from 'angular2/src/dom/dom_adapter'; | ||||
| import {MouseEvent, KeyboardEvent} from 'angular2/src/facade/browser'; | ||||
| import {KEY_ESC} from 'angular2_material/src/core/constants' | ||||
| 
 | ||||
| // TODO(radokirov): Once the application is transpiled by TS instead of Traceur,
 | ||||
| // add those imports back into 'angular2/angular2';
 | ||||
| import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations'; | ||||
| import {Parent} from 'angular2/src/core/annotations_impl/visibility'; | ||||
| import {View} from 'angular2/src/core/annotations_impl/view'; | ||||
| 
 | ||||
| 
 | ||||
| // TODO(jelbourn): Opener of dialog can control where it is rendered.
 | ||||
| // TODO(jelbourn): body scrolling is disabled while dialog is open.
 | ||||
| // TODO(jelbourn): Don't manually construct and configure a DOM element. See #1402
 | ||||
| // TODO(jelbourn): Wrap focus from end of dialog back to the start. Blocked on #1251
 | ||||
| // TODO(jelbourn): Focus the dialog element when it is opened.
 | ||||
| // TODO(jelbourn): Real dialog styles.
 | ||||
| // TODO(jelbourn): Pre-built `alert` and `confirm` dialogs.
 | ||||
| // TODO(jelbourn): Animate dialog out of / into opening element.
 | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Service for opening modal dialogs. | ||||
|  */ | ||||
| export class MdDialog { | ||||
|   componentLoader: DynamicComponentLoader; | ||||
| 
 | ||||
|   constructor(loader: DynamicComponentLoader) { | ||||
|     this.componentLoader = loader; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Opens a modal dialog. | ||||
|    * @param type The component to open. | ||||
|    * @param elementRef The logical location into which the component will be opened. | ||||
|    * @returns Promise for a reference to the dialog. | ||||
|    */ | ||||
|   open( | ||||
|       type: Type, | ||||
|       elementRef: ElementRef, | ||||
|       parentInjector: Injector, | ||||
|       options: MdDialogConfig = null): Promise<MdDialogRef> { | ||||
|     var config = isPresent(options) ? options : new MdDialogConfig(); | ||||
| 
 | ||||
|     // TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
 | ||||
|     // directly on the document body (also needed for web workers stuff).
 | ||||
|     // Create a DOM node to serve as a physical host element for the dialog.
 | ||||
|     var dialogElement = DOM.createElement('div'); | ||||
|     DOM.appendChild(DOM.query('body'), dialogElement); | ||||
| 
 | ||||
|     // TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
 | ||||
|     // Configure properties on the host element.
 | ||||
|     DOM.addClass(dialogElement, 'md-dialog'); | ||||
|     DOM.setAttribute(dialogElement, 'tabindex', '0'); | ||||
| 
 | ||||
|     // TODO(jelbourn): Do this with hostProperties (or another rendering abstraction) once ready.
 | ||||
|     if (isPresent(config.width)) { | ||||
|       DOM.setStyle(dialogElement, 'width', config.width); | ||||
|     } | ||||
|     if (isPresent(config.height)) { | ||||
|       DOM.setStyle(dialogElement, 'height', config.height); | ||||
|     } | ||||
| 
 | ||||
|     // Create the dialogRef here so that it can be injected into the content component.
 | ||||
|     var dialogRef = new MdDialogRef(); | ||||
| 
 | ||||
|     var dialogRefBinding = bind(MdDialogRef).toValue(dialogRef); | ||||
|     var contentInjector = parentInjector.resolveAndCreateChild([dialogRefBinding]); | ||||
| 
 | ||||
|     var backdropRefPromise = this._openBackdrop(elementRef, contentInjector); | ||||
| 
 | ||||
|     // First, load the MdDialogContainer, into which the given component will be loaded.
 | ||||
|     return this.componentLoader.loadIntoNewLocation( | ||||
|         MdDialogContainer, elementRef, dialogElement).then(containerRef => { | ||||
|       dialogRef.containerRef = containerRef; | ||||
| 
 | ||||
|       // Now load the given component into the MdDialogContainer.
 | ||||
|       return this.componentLoader.loadNextToExistingLocation( | ||||
|           type, containerRef.instance.contentRef, contentInjector).then(contentRef => { | ||||
| 
 | ||||
|         // Wrap both component refs for the container and the content so that we can return
 | ||||
|         // the `instance` of the content but the dispose method of the container back to the
 | ||||
|         // opener.
 | ||||
|         dialogRef.contentRef = contentRef; | ||||
|         containerRef.instance.dialogRef = dialogRef; | ||||
| 
 | ||||
|         backdropRefPromise.then(backdropRef => { | ||||
|           dialogRef.whenClosed.then((_) => { | ||||
|             backdropRef.dispose(); | ||||
|           }); | ||||
|         }); | ||||
| 
 | ||||
|         return dialogRef; | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /** Loads the dialog backdrop (transparent overlay over the rest of the page). */ | ||||
|   _openBackdrop(elementRef:ElementRef, injector: Injector): Promise<ComponentRef> { | ||||
|     var backdropElement = DOM.createElement('div'); | ||||
|     DOM.addClass(backdropElement, 'md-backdrop'); | ||||
|     DOM.appendChild(DOM.query('body'), backdropElement); | ||||
| 
 | ||||
|     return this.componentLoader.loadIntoNewLocation( | ||||
|         MdBackdrop, elementRef, backdropElement, injector); | ||||
|   } | ||||
| 
 | ||||
|   alert(message: string, okMessage: string): Promise { | ||||
|     throw "Not implemented"; | ||||
|   } | ||||
| 
 | ||||
|   confirm(message: string, okMessage: string, cancelMessage: string): Promise { | ||||
|     throw "Not implemented"; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Reference to an opened dialog. | ||||
|  */ | ||||
| export class MdDialogRef { | ||||
|   // Reference to the MdDialogContainer component.
 | ||||
|   containerRef: ComponentRef; | ||||
| 
 | ||||
|   // Reference to the Component loaded as the dialog content.
 | ||||
|   _contentRef: ComponentRef; | ||||
| 
 | ||||
|   // Whether the dialog is closed.
 | ||||
|   isClosed: boolean; | ||||
| 
 | ||||
|   // Deferred resolved when the dialog is closed. The promise for this deferred is publicly exposed.
 | ||||
|   whenClosedDeferred: any; | ||||
| 
 | ||||
|   // Deferred resolved when the content ComponentRef is set. Only used internally.
 | ||||
|   contentRefDeferred: any; | ||||
| 
 | ||||
|   constructor() { | ||||
|     this._contentRef = null; | ||||
|     this.containerRef = null; | ||||
|     this.isClosed = false; | ||||
| 
 | ||||
|     this.contentRefDeferred = PromiseWrapper.completer(); | ||||
|     this.whenClosedDeferred = PromiseWrapper.completer(); | ||||
|   } | ||||
| 
 | ||||
|   set contentRef(value: ComponentRef) { | ||||
|     this._contentRef = value; | ||||
|     this.contentRefDeferred.resolve(value); | ||||
|   } | ||||
| 
 | ||||
|   /** Gets the component instance for the content of the dialog. */ | ||||
|   get instance() { | ||||
|     if (isPresent(this._contentRef)) { | ||||
|       return this._contentRef.instance; | ||||
|     } | ||||
| 
 | ||||
|     // The only time one could attempt to access this property before the value is set is if an access occurs during
 | ||||
|     // the constructor of the very instance they are trying to get (which is much more easily accessed as `this`).
 | ||||
|     throw "Cannot access dialog component instance *from* that component's constructor."; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   /** Gets a promise that is resolved when the dialog is closed. */ | ||||
|   get whenClosed(): Promise { | ||||
|     return this.whenClosedDeferred.promise; | ||||
|   } | ||||
| 
 | ||||
|   /** Closes the dialog. This operation is asynchronous. */ | ||||
|   close(result: any = null) { | ||||
|     this.contentRefDeferred.promise.then((_) => { | ||||
|       if (!this.isClosed) { | ||||
|         this.isClosed = true; | ||||
|         this.containerRef.dispose(); | ||||
|         this.whenClosedDeferred.resolve(result); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Confiuration for a dialog to be opened. */ | ||||
| export class MdDialogConfig { | ||||
|   width: string; | ||||
|   height: string; | ||||
| 
 | ||||
|   constructor() { | ||||
|     // Default configuration.
 | ||||
|     this.width = null; | ||||
|     this.height = null; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Container for user-provided dialog content. | ||||
|  */ | ||||
| @Component({ | ||||
|   selector: 'md-dialog-container', | ||||
|   hostListeners: { | ||||
|     'body:^keydown': 'documentKeypress($event)' | ||||
|   } | ||||
| }) | ||||
| @View({ | ||||
|   templateUrl: 'angular2_material/src/components/dialog/dialog.html', | ||||
|   directives: [MdDialogContent] | ||||
| }) | ||||
| class MdDialogContainer { | ||||
|   // Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content.
 | ||||
|   contentRef: ElementRef; | ||||
| 
 | ||||
|   // Ref to the open dialog. Used to close the dialog based on certain events.
 | ||||
|   dialogRef: MdDialogRef; | ||||
| 
 | ||||
|   constructor() { | ||||
|     this.contentRef = null; | ||||
|     this.dialogRef = null; | ||||
|   } | ||||
| 
 | ||||
|   wrapFocus() { | ||||
|     // Return the focus to the host element. Blocked on #1251.
 | ||||
|   } | ||||
| 
 | ||||
|   documentKeypress(event: KeyboardEvent) { | ||||
|     if (event.keyCode == KEY_ESC) { | ||||
|       this.dialogRef.close(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Component for the dialog "backdrop", a transparent overlay over the rest of the page. */ | ||||
| @Component({ | ||||
|   selector: 'md-backdrop', | ||||
|   hostListeners: { | ||||
|     'click': 'onClick()' | ||||
|   } | ||||
| }) | ||||
| @View({template: ''}) | ||||
| class MdBackdrop { | ||||
|   dialogRef: MdDialogRef; | ||||
| 
 | ||||
|   constructor(dialogRef: MdDialogRef) { | ||||
|     this.dialogRef = dialogRef; | ||||
|   } | ||||
| 
 | ||||
|   onClick() { | ||||
|     // TODO(jelbourn): Use MdDialogConfig to capture option for whether dialog should close on
 | ||||
|     // clicking outside.
 | ||||
|     this.dialogRef.close(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Simple decorator used only to communicate an ElementRef to the parent MdDialogContainer as the location | ||||
|  * for where the dialog content will be loaded. | ||||
|  */ | ||||
| @Directive({selector: 'md-dialog-content'}) | ||||
| class MdDialogContent { | ||||
|   constructor(@Parent() dialogContainer: MdDialogContainer, elementRef: ElementRef) { | ||||
|     dialogContainer.contentRef = elementRef; | ||||
|   } | ||||
| } | ||||
| @ -42,7 +42,7 @@ export class MdProgressLinear { | ||||
|   ariaValuemin: string; | ||||
|   ariaValuemax: string; | ||||
| 
 | ||||
|   constructor(@Attribute('md-mode') mode: string) { | ||||
|   constructor(@Attribute('md-mode') mode: String) { | ||||
|     this.primaryBarTransform = ''; | ||||
|     this.secondaryBarTransform = ''; | ||||
| 
 | ||||
|  | ||||
| @ -37,7 +37,7 @@ export class MdSwitch { | ||||
|   tabindex: number; | ||||
|   role: string; | ||||
| 
 | ||||
|   constructor(@Attribute('tabindex') tabindex: string) { | ||||
|   constructor(@Attribute('tabindex') tabindex: String) { | ||||
|     this.role = 'checkbox'; | ||||
|     this.checked = false; | ||||
|     this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| // TODO: switch to proper enums when we support them.
 | ||||
| 
 | ||||
| // Key codes
 | ||||
| export const KEY_ESC = 27; | ||||
| export const KEY_SPACE = 32; | ||||
| export const KEY_UP = 38; | ||||
| export const KEY_DOWN = 40; | ||||
|  | ||||
							
								
								
									
										32
									
								
								modules/examples/src/material/dialog/demo_app.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								modules/examples/src/material/dialog/demo_app.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <div> | ||||
|   <h2>Dialog demo</h2> | ||||
| 
 | ||||
|   <button type="button" (click)="open()" [disabled]="!!dialogRef"> | ||||
|     Open a dialog | ||||
|   </button> | ||||
| 
 | ||||
|   <button type="button" (click)="close()" [disabled]="!dialogRef"> | ||||
|     Close the dialog | ||||
|   </button> | ||||
| 
 | ||||
|   <p> | ||||
|     Last result: {{lastResult}} | ||||
|   </p> | ||||
| 
 | ||||
|   <hr> | ||||
| 
 | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
|   <p> Here are some paragaphs to make the page scrollable</p> | ||||
| 
 | ||||
| </div> | ||||
							
								
								
									
										18
									
								
								modules/examples/src/material/dialog/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								modules/examples/src/material/dialog/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head lang="en"> | ||||
|   <meta charset="UTF-8"> | ||||
|   <title>ng-material dialog demo</title> | ||||
|   <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=RobotoDraft:400,500,700,400italic"> | ||||
|   <style> | ||||
|     * { | ||||
|       font-family: RobotoDraft, Roboto; | ||||
|     } | ||||
|   </style> | ||||
| </head> | ||||
| <body> | ||||
| 
 | ||||
| $SCRIPTS$ | ||||
|   <demo-app>Loading...</demo-app> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										103
									
								
								modules/examples/src/material/dialog/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								modules/examples/src/material/dialog/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| import {bootstrap, ElementRef, ComponentRef} from 'angular2/angular2'; | ||||
| import {MdDialog, MdDialogRef, MdDialogConfig} from 'angular2_material/src/components/dialog/dialog' | ||||
| import {UrlResolver} from 'angular2/src/services/url_resolver'; | ||||
| import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; | ||||
| import {bind, Injector} from 'angular2/di'; | ||||
| import {isPresent} from 'angular2/src/facade/lang'; | ||||
| 
 | ||||
| // TODO(radokirov): Once the application is transpiled by TS instead of Traceur,
 | ||||
| // add those imports back into 'angular2/angular2';
 | ||||
| import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations'; | ||||
| import {View} from 'angular2/src/core/annotations_impl/view'; | ||||
| 
 | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'demo-app', | ||||
|   injectables: [MdDialog] | ||||
| }) | ||||
| @View({ | ||||
|   templateUrl: './demo_app.html', | ||||
|   directives: [] | ||||
| }) | ||||
| class DemoApp { | ||||
|   dialog: MdDialog; | ||||
|   elementRef: ElementRef; | ||||
|   dialogRef: MdDialogRef; | ||||
|   dialogConfig: MdDialogConfig; | ||||
|   injector: Injector; | ||||
|   lastResult: string; | ||||
| 
 | ||||
|   constructor(mdDialog: MdDialog, elementRef: ElementRef, injector: Injector) { | ||||
|     this.dialog = mdDialog; | ||||
|     this.elementRef = elementRef; | ||||
|     this.dialogConfig = new MdDialogConfig(); | ||||
|     this.injector = injector; | ||||
| 
 | ||||
|     this.dialogConfig.width = '60%'; | ||||
|     this.dialogConfig.height = '60%'; | ||||
|     this.lastResult = ''; | ||||
|   } | ||||
| 
 | ||||
|   open() { | ||||
|     if (isPresent(this.dialogRef)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.dialog.open(SimpleDialogComponent, | ||||
|         this.elementRef, this.injector, this.dialogConfig).then(ref => { | ||||
|       this.dialogRef = ref; | ||||
|       ref.instance.numCoconuts = 777; | ||||
| 
 | ||||
|       ref.whenClosed.then(result => { | ||||
|         this.dialogRef = null; | ||||
|         this.lastResult = result; | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   close() { | ||||
|     this.dialogRef.close(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'simple-dialog', | ||||
|   properties: {'numCoconuts': 'numCoconuts'} | ||||
| }) | ||||
| @View({ | ||||
|   template: ` | ||||
|     <h2>This is the dialog content</h2> | ||||
|     <p>There are {{numCoconuts}} coconuts.</p> | ||||
|     <p>Return: <input (input)="updateValue($event)"></p> | ||||
|     <button type="button" (click)="done()">Done</button> | ||||
|   ` | ||||
| }) | ||||
| class SimpleDialogComponent { | ||||
|   numCoconuts: number; | ||||
|   dialogRef: MdDialogRef; | ||||
|   toReturn: string; | ||||
| 
 | ||||
|   constructor(dialogRef: MdDialogRef) { | ||||
|     this.numCoconuts = 0; | ||||
|     this.dialogRef = dialogRef; | ||||
|     this.toReturn = ''; | ||||
|   } | ||||
| 
 | ||||
|   updateValue(event) { | ||||
|     this.toReturn = event.target.value; | ||||
|   } | ||||
| 
 | ||||
|   done() { | ||||
|     this.dialogRef.close(this.toReturn); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export function main() { | ||||
|   commonDemoSetup(); | ||||
|   bootstrap(DemoApp, [ | ||||
|     bind(UrlResolver).toValue(new DemoUrlResolver()) | ||||
|   ]); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user