Skip to content

Commit f88c4b7

Browse files
committed
feat(material): add prototype dialog component w/ demo.
1 parent 75da6e4 commit f88c4b7

10 files changed

Lines changed: 456 additions & 4 deletions

File tree

modules/angular2/src/render/dom/events/event_manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class EventManagerPlugin {
5454

5555
// We are assuming here that all plugins support bubbled and non-bubbled events.
5656
// That is equivalent to having supporting $event.target
57-
// The bubbling flag (currently ^) is stripped before calling the supports and
57+
// The bubbling flag (currently ^) is stripped before calling the supports and
5858
// addEventListener methods.
5959
supports(eventName: string): boolean {
6060
return false;

modules/angular2_material/src/components/checkbox/checkbox.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class MdCheckbox {
3939
/** Setter for tabindex */
4040
tabindex: number;
4141

42-
constructor(@Attribute('tabindex') tabindex: string) {
42+
constructor(@Attribute('tabindex') tabindex: String) {
4343
this.role = 'checkbox';
4444
this.checked = false;
4545
this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<style>
2+
.md-dialog {
3+
position: absolute;
4+
z-index: 80;
5+
6+
/** Center the dialog. */
7+
top: 50%;
8+
left: 50%;
9+
transform: translate(-50%, -50%);
10+
11+
width: 300px;
12+
height: 300px;
13+
14+
background-color: white;
15+
border: 1px solid black;
16+
box-shadow: 0 4px 4px;;
17+
18+
padding: 20px;
19+
}
20+
21+
.md-backdrop {
22+
position: absolute;
23+
top:0 ;
24+
left: 0;
25+
width: 100%;
26+
height: 100%;
27+
background-color: rgba(0, 0, 0, 0.12);
28+
}
29+
</style>
30+
31+
<md-dialog-content></md-dialog-content>
32+
<div tabindex="0" (focus)="wrapFocus()"></div>
33+
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import {DynamicComponentLoader, ElementRef, ComponentRef, onDestroy} from 'angular2/angular2';
2+
import {bind, Injector} from 'angular2/di';
3+
import {ObservableWrapper, Promise, PromiseWrapper} from 'angular2/src/facade/async';
4+
import {isPresent, Type} from 'angular2/src/facade/lang';
5+
import {DOM} from 'angular2/src/dom/dom_adapter';
6+
import {MouseEvent, KeyboardEvent} from 'angular2/src/facade/browser';
7+
import {KEY_ESC} from 'angular2_material/src/core/constants'
8+
9+
// TODO(radokirov): Once the application is transpiled by TS instead of Traceur,
10+
// add those imports back into 'angular2/angular2';
11+
import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
12+
import {Parent} from 'angular2/src/core/annotations_impl/visibility';
13+
import {View} from 'angular2/src/core/annotations_impl/view';
14+
15+
16+
// TODO(jelbourn): Opener of dialog can control where it is rendered.
17+
// TODO(jelbourn): body scrolling is disabled while dialog is open.
18+
// TODO(jelbourn): Don't manually construct and configure a DOM element. See #1402
19+
// TODO(jelbourn): Wrap focus from end of dialog back to the start. Blocked on #1251
20+
// TODO(jelbourn): Focus the dialog element when it is opened.
21+
// TODO(jelbourn): Real dialog styles.
22+
// TODO(jelbourn): Pre-built `alert` and `confirm` dialogs.
23+
// TODO(jelbourn): Animate dialog out of / into opening element.
24+
25+
26+
/**
27+
* Service for opening modal dialogs.
28+
*/
29+
export class MdDialog {
30+
componentLoader: DynamicComponentLoader;
31+
32+
constructor(loader: DynamicComponentLoader) {
33+
this.componentLoader = loader;
34+
}
35+
36+
/**
37+
* Opens a modal dialog.
38+
* @param type The component to open.
39+
* @param elementRef The logical location into which the component will be opened.
40+
* @returns Promise for a reference to the dialog.
41+
*/
42+
open(
43+
type: Type,
44+
elementRef: ElementRef,
45+
parentInjector: Injector,
46+
options: MdDialogConfig = null): Promise<MdDialogRef> {
47+
var config = isPresent(options) ? options : new MdDialogConfig();
48+
49+
// TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
50+
// directly on the document body (also needed for web workers stuff).
51+
// Create a DOM node to serve as a physical host element for the dialog.
52+
var dialogElement = DOM.createElement('div');
53+
DOM.appendChild(DOM.query('body'), dialogElement);
54+
55+
// TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
56+
// Configure properties on the host element.
57+
DOM.addClass(dialogElement, 'md-dialog');
58+
DOM.setAttribute(dialogElement, 'tabindex', '0');
59+
60+
// TODO(jelbourn): Do this with hostProperties (or another rendering abstraction) once ready.
61+
if (isPresent(config.width)) {
62+
DOM.setStyle(dialogElement, 'width', config.width);
63+
}
64+
if (isPresent(config.height)) {
65+
DOM.setStyle(dialogElement, 'height', config.height);
66+
}
67+
68+
// Create the dialogRef here so that it can be injected into the content component.
69+
var dialogRef = new MdDialogRef();
70+
71+
var dialogRefBinding = bind(MdDialogRef).toValue(dialogRef);
72+
var contentInjector = parentInjector.resolveAndCreateChild([dialogRefBinding]);
73+
74+
var backdropRefPromise = this._openBackdrop(elementRef, contentInjector);
75+
76+
// First, load the MdDialogContainer, into which the given component will be loaded.
77+
return this.componentLoader.loadIntoNewLocation(
78+
MdDialogContainer, elementRef, dialogElement).then(containerRef => {
79+
dialogRef.containerRef = containerRef;
80+
81+
// Now load the given component into the MdDialogContainer.
82+
return this.componentLoader.loadNextToExistingLocation(
83+
type, containerRef.instance.contentRef, contentInjector).then(contentRef => {
84+
85+
// Wrap both component refs for the container and the content so that we can return
86+
// the `instance` of the content but the dispose method of the container back to the
87+
// opener.
88+
dialogRef.contentRef = contentRef;
89+
containerRef.instance.dialogRef = dialogRef;
90+
91+
backdropRefPromise.then(backdropRef => {
92+
dialogRef.whenClosed.then((_) => {
93+
backdropRef.dispose();
94+
});
95+
});
96+
97+
return dialogRef;
98+
});
99+
});
100+
}
101+
102+
/** Loads the dialog backdrop (transparent overlay over the rest of the page). */
103+
_openBackdrop(elementRef:ElementRef, injector: Injector): Promise<ComponentRef> {
104+
var backdropElement = DOM.createElement('div');
105+
DOM.addClass(backdropElement, 'md-backdrop');
106+
DOM.appendChild(DOM.query('body'), backdropElement);
107+
108+
return this.componentLoader.loadIntoNewLocation(
109+
MdBackdrop, elementRef, backdropElement, injector);
110+
}
111+
112+
alert(message: string, okMessage: string): Promise {
113+
throw "Not implemented";
114+
}
115+
116+
confirm(message: string, okMessage: string, cancelMessage: string): Promise {
117+
throw "Not implemented";
118+
}
119+
}
120+
121+
122+
/**
123+
* Reference to an opened dialog.
124+
*/
125+
export class MdDialogRef {
126+
// Reference to the MdDialogContainer component.
127+
containerRef: ComponentRef;
128+
129+
// Reference to the Component loaded as the dialog content.
130+
_contentRef: ComponentRef;
131+
132+
// Whether the dialog is closed.
133+
isClosed: boolean;
134+
135+
// Deferred resolved when the dialog is closed. The promise for this deferred is publicly exposed.
136+
whenClosedDeferred: any;
137+
138+
// Deferred resolved when the content ComponentRef is set. Only used internally.
139+
contentRefDeferred: any;
140+
141+
constructor() {
142+
this._contentRef = null;
143+
this.containerRef = null;
144+
this.isClosed = false;
145+
146+
this.contentRefDeferred = PromiseWrapper.completer();
147+
this.whenClosedDeferred = PromiseWrapper.completer();
148+
}
149+
150+
set contentRef(value: ComponentRef) {
151+
this._contentRef = value;
152+
this.contentRefDeferred.resolve(value);
153+
}
154+
155+
/** Gets the component instance for the content of the dialog. */
156+
get instance() {
157+
if (isPresent(this._contentRef)) {
158+
return this._contentRef.instance;
159+
}
160+
161+
// The only time one could attempt to access this property before the value is set is if an access occurs during
162+
// the constructor of the very instance they are trying to get (which is much more easily accessed as `this`).
163+
throw "Cannot access dialog component instance *from* that component's constructor.";
164+
}
165+
166+
167+
/** Gets a promise that is resolved when the dialog is closed. */
168+
get whenClosed(): Promise {
169+
return this.whenClosedDeferred.promise;
170+
}
171+
172+
/** Closes the dialog. This operation is asynchronous. */
173+
close(result: any = null) {
174+
this.contentRefDeferred.promise.then((_) => {
175+
if (!this.isClosed) {
176+
this.isClosed = true;
177+
this.containerRef.dispose();
178+
this.whenClosedDeferred.resolve(result);
179+
}
180+
});
181+
}
182+
}
183+
184+
/** Confiuration for a dialog to be opened. */
185+
export class MdDialogConfig {
186+
width: string;
187+
height: string;
188+
189+
constructor() {
190+
// Default configuration.
191+
this.width = null;
192+
this.height = null;
193+
}
194+
}
195+
196+
/**
197+
* Container for user-provided dialog content.
198+
*/
199+
@Component({
200+
selector: 'md-dialog-container',
201+
hostListeners: {
202+
'body:^keydown': 'documentKeypress($event)'
203+
}
204+
})
205+
@View({
206+
templateUrl: 'angular2_material/src/components/dialog/dialog.html',
207+
directives: [MdDialogContent]
208+
})
209+
class MdDialogContainer {
210+
// Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content.
211+
contentRef: ElementRef;
212+
213+
// Ref to the open dialog. Used to close the dialog based on certain events.
214+
dialogRef: MdDialogRef;
215+
216+
constructor() {
217+
this.contentRef = null;
218+
this.dialogRef = null;
219+
}
220+
221+
wrapFocus() {
222+
// Return the focus to the host element. Blocked on #1251.
223+
}
224+
225+
documentKeypress(event: KeyboardEvent) {
226+
if (event.keyCode == KEY_ESC) {
227+
this.dialogRef.close();
228+
}
229+
}
230+
}
231+
232+
233+
/** Component for the dialog "backdrop", a transparent overlay over the rest of the page. */
234+
@Component({
235+
selector: 'md-backdrop',
236+
hostListeners: {
237+
'click': 'onClick()'
238+
}
239+
})
240+
@View({template: ''})
241+
class MdBackdrop {
242+
dialogRef: MdDialogRef;
243+
244+
constructor(dialogRef: MdDialogRef) {
245+
this.dialogRef = dialogRef;
246+
}
247+
248+
onClick() {
249+
// TODO(jelbourn): Use MdDialogConfig to capture option for whether dialog should close on
250+
// clicking outside.
251+
this.dialogRef.close();
252+
}
253+
}
254+
255+
256+
/**
257+
* Simple decorator used only to communicate an ElementRef to the parent MdDialogContainer as the location
258+
* for where the dialog content will be loaded.
259+
*/
260+
@Directive({selector: 'md-dialog-content'})
261+
class MdDialogContent {
262+
constructor(@Parent() dialogContainer: MdDialogContainer, elementRef: ElementRef) {
263+
dialogContainer.contentRef = elementRef;
264+
}
265+
}

modules/angular2_material/src/components/progress-linear/progress_linear.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class MdProgressLinear {
4242
ariaValuemin: string;
4343
ariaValuemax: string;
4444

45-
constructor(@Attribute('md-mode') mode: string) {
45+
constructor(@Attribute('md-mode') mode: String) {
4646
this.primaryBarTransform = '';
4747
this.secondaryBarTransform = '';
4848

modules/angular2_material/src/components/switcher/switch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class MdSwitch {
3737
tabindex: number;
3838
role: string;
3939

40-
constructor(@Attribute('tabindex') tabindex: string) {
40+
constructor(@Attribute('tabindex') tabindex: String) {
4141
this.role = 'checkbox';
4242
this.checked = false;
4343
this.tabindex = isPresent(tabindex) ? NumberWrapper.parseInt(tabindex, 10) : 0;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// TODO: switch to proper enums when we support them.
22

33
// Key codes
4+
export const KEY_ESC = 27;
45
export const KEY_SPACE = 32;
56
export const KEY_UP = 38;
67
export const KEY_DOWN = 40;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<div>
2+
<h2>Dialog demo</h2>
3+
4+
<button type="button" (click)="open()" [disabled]="!!dialogRef">
5+
Open a dialog
6+
</button>
7+
8+
<button type="button" (click)="close()" [disabled]="!dialogRef">
9+
Close the dialog
10+
</button>
11+
12+
<p>
13+
Last result: {{lastResult}}
14+
</p>
15+
16+
<hr>
17+
18+
<p> Here are some paragaphs to make the page scrollable</p>
19+
<p> Here are some paragaphs to make the page scrollable</p>
20+
<p> Here are some paragaphs to make the page scrollable</p>
21+
<p> Here are some paragaphs to make the page scrollable</p>
22+
<p> Here are some paragaphs to make the page scrollable</p>
23+
<p> Here are some paragaphs to make the page scrollable</p>
24+
<p> Here are some paragaphs to make the page scrollable</p>
25+
<p> Here are some paragaphs to make the page scrollable</p>
26+
<p> Here are some paragaphs to make the page scrollable</p>
27+
<p> Here are some paragaphs to make the page scrollable</p>
28+
<p> Here are some paragaphs to make the page scrollable</p>
29+
<p> Here are some paragaphs to make the page scrollable</p>
30+
<p> Here are some paragaphs to make the page scrollable</p>
31+
32+
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head lang="en">
4+
<meta charset="UTF-8">
5+
<title>ng-material dialog demo</title>
6+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=RobotoDraft:400,500,700,400italic">
7+
<style>
8+
* {
9+
font-family: RobotoDraft, Roboto;
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
15+
$SCRIPTS$
16+
<demo-app>Loading...</demo-app>
17+
</body>
18+
</html>

0 commit comments

Comments
 (0)