feat(material): add prototype dialog component w/ demo.
This commit is contained in:
parent
75da6e4c4a
commit
f88c4b77ca
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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…
Reference in New Issue