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 */
|
/** Setter for tabindex */
|
||||||
tabindex: number;
|
tabindex: number;
|
||||||
|
|
||||||
constructor(@Attribute('tabindex') tabindex: string) {
|
constructor(@Attribute('tabindex') tabindex: String) {
|
||||||
this.role = 'checkbox';
|
this.role = 'checkbox';
|
||||||
this.checked = false;
|
this.checked = false;
|
||||||
this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0;
|
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;
|
ariaValuemin: string;
|
||||||
ariaValuemax: string;
|
ariaValuemax: string;
|
||||||
|
|
||||||
constructor(@Attribute('md-mode') mode: string) {
|
constructor(@Attribute('md-mode') mode: String) {
|
||||||
this.primaryBarTransform = '';
|
this.primaryBarTransform = '';
|
||||||
this.secondaryBarTransform = '';
|
this.secondaryBarTransform = '';
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class MdSwitch {
|
||||||
tabindex: number;
|
tabindex: number;
|
||||||
role: string;
|
role: string;
|
||||||
|
|
||||||
constructor(@Attribute('tabindex') tabindex: string) {
|
constructor(@Attribute('tabindex') tabindex: String) {
|
||||||
this.role = 'checkbox';
|
this.role = 'checkbox';
|
||||||
this.checked = false;
|
this.checked = false;
|
||||||
this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0;
|
this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// TODO: switch to proper enums when we support them.
|
// TODO: switch to proper enums when we support them.
|
||||||
|
|
||||||
// Key codes
|
// Key codes
|
||||||
|
export const KEY_ESC = 27;
|
||||||
export const KEY_SPACE = 32;
|
export const KEY_SPACE = 32;
|
||||||
export const KEY_UP = 38;
|
export const KEY_UP = 38;
|
||||||
export const KEY_DOWN = 40;
|
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