mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(md-snack-bar): Create initial MdSnackBar. (#1296)
This commit is contained in:
@@ -29,6 +29,7 @@ import {MdCheckboxDemoNestedChecklist, CheckboxDemo} from './checkbox/checkbox-d
|
||||
import {SelectDemo} from './select/select-demo';
|
||||
import {SliderDemo} from './slider/slider-demo';
|
||||
import {SidenavDemo} from './sidenav/sidenav-demo';
|
||||
import {SnackBarDemo} from './snack-bar/snack-bar-demo';
|
||||
import {PortalDemo, ScienceJoke} from './portal/portal-demo';
|
||||
import {MenuDemo} from './menu/menu-demo';
|
||||
import {TabsDemo} from './tabs/tab-group-demo';
|
||||
@@ -61,6 +62,7 @@ import {TabsDemo} from './tabs/tab-group-demo';
|
||||
LiveAnnouncerDemo,
|
||||
MdCheckboxDemoNestedChecklist,
|
||||
MenuDemo,
|
||||
SnackBarDemo,
|
||||
OverlayDemo,
|
||||
PortalDemo,
|
||||
ProgressBarDemo,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<a md-list-item [routerLink]="['sidenav']">Sidenav</a>
|
||||
<a md-list-item [routerLink]="['slider']">Slider</a>
|
||||
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
|
||||
<a md-list-item [routerLink]="['snack-bar']">Snack Bar</a>
|
||||
<a md-list-item [routerLink]="['tabs']">Tabs</a>
|
||||
<a md-list-item [routerLink]="['toolbar']">Toolbar</a>
|
||||
<a md-list-item [routerLink]="['tooltip']">Tooltip</a>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {MenuDemo} from '../menu/menu-demo';
|
||||
import {RippleDemo} from '../ripple/ripple-demo';
|
||||
import {DialogDemo} from '../dialog/dialog-demo';
|
||||
import {TooltipDemo} from '../tooltip/tooltip-demo';
|
||||
import {SnackBarDemo} from '../snack-bar/snack-bar-demo';
|
||||
|
||||
|
||||
export const DEMO_APP_ROUTES: Routes = [
|
||||
@@ -56,4 +57,5 @@ export const DEMO_APP_ROUTES: Routes = [
|
||||
{path: 'ripple', component: RippleDemo},
|
||||
{path: 'dialog', component: DialogDemo},
|
||||
{path: 'tooltip', component: TooltipDemo},
|
||||
{path: 'snack-bar', component: SnackBarDemo},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<h1>SnackBar demo</h1>
|
||||
<div>
|
||||
<div>Message: <md-input type="text" [(ngModel)]="message"></md-input></div>
|
||||
<div>
|
||||
<md-checkbox [(ngModel)]="action">
|
||||
<p *ngIf="!action">Show button on snack bar</p>
|
||||
<md-input type="text" class="demo-button-label-input"
|
||||
*ngIf="action"
|
||||
placeholder="Snack bar action label"
|
||||
[(ngModel)]="actionButtonLabel"></md-input>
|
||||
</md-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button md-raised-button (click)="open()">OPEN</button>
|
||||
@@ -0,0 +1,3 @@
|
||||
.demo-button-label-input {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import {Component, ViewContainerRef} from '@angular/core';
|
||||
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'snack-bar-demo',
|
||||
templateUrl: 'snack-bar-demo.html',
|
||||
})
|
||||
export class SnackBarDemo {
|
||||
message: string = 'Snack Bar opened.';
|
||||
actionButtonLabel: string = 'Retry';
|
||||
action: boolean = false;
|
||||
|
||||
constructor(
|
||||
public snackBar: MdSnackBar,
|
||||
public viewContainerRef: ViewContainerRef) { }
|
||||
|
||||
open() {
|
||||
let config = new MdSnackBarConfig(this.viewContainerRef);
|
||||
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'demo-snack',
|
||||
templateUrl: 'snack-bar-demo.html',
|
||||
styleUrls: ['./snack-bar-demo.css'],
|
||||
})
|
||||
export class DemoSnack {}
|
||||
@@ -18,6 +18,7 @@ export * from './select/index';
|
||||
export * from './sidenav/index';
|
||||
export * from './slider/index';
|
||||
export * from './slide-toggle/index';
|
||||
export * from './snack-bar/index';
|
||||
export * from './tabs/index';
|
||||
export * from './toolbar/index';
|
||||
export * from './tooltip/index';
|
||||
|
||||
@@ -23,6 +23,7 @@ import {MdIconModule} from './icon/index';
|
||||
import {MdProgressCircleModule} from './progress-circle/index';
|
||||
import {MdProgressBarModule} from './progress-bar/index';
|
||||
import {MdInputModule} from './input/index';
|
||||
import {MdSnackBarModule} from './snack-bar/snack-bar';
|
||||
import {MdTabsModule} from './tabs/index';
|
||||
import {MdToolbarModule} from './toolbar/index';
|
||||
import {MdTooltipModule} from './tooltip/index';
|
||||
@@ -49,6 +50,7 @@ const MATERIAL_MODULES = [
|
||||
MdSidenavModule,
|
||||
MdSliderModule,
|
||||
MdSlideToggleModule,
|
||||
MdSnackBarModule,
|
||||
MdTabsModule,
|
||||
MdToolbarModule,
|
||||
MdTooltipModule,
|
||||
@@ -83,6 +85,7 @@ const MATERIAL_MODULES = [
|
||||
MdRadioModule.forRoot(),
|
||||
MdSliderModule.forRoot(),
|
||||
MdSlideToggleModule.forRoot(),
|
||||
MdSnackBarModule.forRoot(),
|
||||
MdTooltipModule.forRoot(),
|
||||
OverlayModule.forRoot(),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# MdSnackBar
|
||||
`MdSnackBar` is a service, which opens snack bar notifications in the view.
|
||||
|
||||
### Methods
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `open(message: string,<br> actionLabel: string, config: MdSnackBarConfig): MdSnackBarRef<SimpleSnackBar>` | Creates and opens a simple snack bar noticiation matching material spec. |
|
||||
| `openFromComponent(component: ComponentType<T>, config: MdSnackBarConfig): MdSnackBarRef<T>` | Creates and opens a snack bar noticiation with a custom component as content. |
|
||||
|
||||
### Config
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| `viewContainerRef: ViewContainerRef` | The view container ref to attach the snack bar to. |
|
||||
| `role: AriaLivePoliteness = 'assertive'` | The politeness level to announce the snack bar with. |
|
||||
| `announcementMessage: string` | The message to announce with the snack bar. |
|
||||
|
||||
|
||||
### Example
|
||||
The service can be injected in a component.
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'my-component'
|
||||
providers: [MdSnackBar]
|
||||
})
|
||||
export class MyComponent {
|
||||
|
||||
constructor(snackBar: MdSnackBar
|
||||
viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
failedAttempt() {
|
||||
config = new MdSnackBarConfig(this.viewContainerRef);
|
||||
this.snackBar.open('It didn\'t quite work!', 'Try Again', config);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
export * from './snack-bar';
|
||||
@@ -0,0 +1,3 @@
|
||||
<span class="md-simple-snackbar-message">{{message}}</span>
|
||||
<button md-button class="md-simple-snackbar-action"
|
||||
*ngIf="hasAction" (click)="dismiss()">{{action}}</button>
|
||||
@@ -0,0 +1,28 @@
|
||||
md-simple-snackbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.md-simple-snackbar-message {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
color: white;
|
||||
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.md-simple-snackbar-action {
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
float: right;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
margin: -5px 0 0 48px;
|
||||
min-width: initial;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {MdSnackBarRef} from './snack-bar-ref';
|
||||
|
||||
|
||||
/**
|
||||
* A component used to open as the default snack bar, matching material spec.
|
||||
* This should only be used internally by the snack bar service.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'simple-snack-bar',
|
||||
templateUrl: 'simple-snack-bar.html',
|
||||
styleUrls: ['simple-snack-bar.css'],
|
||||
})
|
||||
export class SimpleSnackBar {
|
||||
/** The message to be shown in the snack bar. */
|
||||
message: string;
|
||||
|
||||
/** The label for the button in the snack bar. */
|
||||
action: string;
|
||||
|
||||
/** The instance of the component making up the content of the snack bar. */
|
||||
snackBarRef: MdSnackBarRef<SimpleSnackBar>;
|
||||
|
||||
/** Dismisses the snack bar. */
|
||||
dismiss(): void {
|
||||
this.snackBarRef.dismiss();
|
||||
}
|
||||
|
||||
/** If the action button should be shown. */
|
||||
get hasAction(): boolean { return !!this.action; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {ViewContainerRef} from '@angular/core';
|
||||
import {AriaLivePoliteness} from '../core';
|
||||
|
||||
|
||||
export class MdSnackBarConfig {
|
||||
/** The politeness level for the MdAriaLiveAnnouncer announcement. */
|
||||
politeness: AriaLivePoliteness = 'assertive';
|
||||
|
||||
/** Message to be announced by the MdAriaLiveAnnouncer */
|
||||
announcementMessage: string;
|
||||
|
||||
/** The view container to place the overlay for the snack bar into. */
|
||||
viewContainerRef: ViewContainerRef;
|
||||
|
||||
constructor(viewContainerRef: ViewContainerRef) {
|
||||
this.viewContainerRef = viewContainerRef;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<template portalHost></template>
|
||||
@@ -0,0 +1,19 @@
|
||||
@import '../core/style/elevation';
|
||||
|
||||
$md-snack-bar-padding: 14px 24px !default;
|
||||
$md-snack-bar-height: 20px !default;
|
||||
$md-snack-bar-min-width: 288px !default;
|
||||
$md-snack-bar-max-width: 568px !default;
|
||||
|
||||
|
||||
:host {
|
||||
@include md-elevation(24);
|
||||
background: #323232;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
height: $md-snack-bar-height;
|
||||
max-width: $md-snack-bar-max-width;
|
||||
min-width: $md-snack-bar-min-width;
|
||||
overflow: hidden;
|
||||
padding: $md-snack-bar-padding;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
Component,
|
||||
ComponentRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {
|
||||
BasePortalHost,
|
||||
ComponentPortal,
|
||||
TemplatePortal,
|
||||
PortalHostDirective
|
||||
} from '../core';
|
||||
import {MdSnackBarConfig} from './snack-bar-config';
|
||||
import {MdSnackBarContentAlreadyAttached} from './snack-bar-errors';
|
||||
|
||||
|
||||
/**
|
||||
* Internal component that wraps user-provided snack bar content.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'snack-bar-container',
|
||||
templateUrl: 'snack-bar-container.html',
|
||||
styleUrls: ['snack-bar-container.css'],
|
||||
host: {
|
||||
'role': 'alert'
|
||||
}
|
||||
})
|
||||
export class MdSnackBarContainer extends BasePortalHost {
|
||||
/** The portal host inside of this container into which the snack bar content will be loaded. */
|
||||
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
|
||||
|
||||
/** The snack bar configuration. */
|
||||
snackBarConfig: MdSnackBarConfig;
|
||||
|
||||
/** Attach a portal as content to this snack bar container. */
|
||||
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
|
||||
if (this._portalHost.hasAttached()) {
|
||||
throw new MdSnackBarContentAlreadyAttached();
|
||||
}
|
||||
|
||||
return this._portalHost.attachComponentPortal(portal);
|
||||
}
|
||||
|
||||
attachTemplatePortal(portal: TemplatePortal): Map<string, any> {
|
||||
throw Error('Not yet implemented');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {MdError} from '../core';
|
||||
|
||||
|
||||
export class MdSnackBarContentAlreadyAttached extends MdError {
|
||||
constructor() {
|
||||
super('Attempting to attach snack bar content after content is already attached');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {OverlayRef} from '../core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
// TODO(josephperrott): Implement onAction observable.
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a snack bar dispatched from the snack bar service.
|
||||
*/
|
||||
export class MdSnackBarRef<T> {
|
||||
/** The instance of the component making up the content of the snack bar. */
|
||||
readonly instance: T;
|
||||
|
||||
/** Subject for notifying the user that the snack bar has closed. */
|
||||
private _afterClosed: Subject<any> = new Subject();
|
||||
|
||||
constructor(instance: T, private _overlayRef: OverlayRef) {
|
||||
// Sets the readonly instance of the snack bar content component.
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
/** Dismisses the snack bar. */
|
||||
dismiss(): void {
|
||||
if (!this._afterClosed.closed) {
|
||||
this._overlayRef.dispose();
|
||||
this._afterClosed.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets an observable that is notified when the snack bar is finished closing. */
|
||||
afterDismissed(): Observable<void> {
|
||||
return this._afterClosed.asObservable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
ComponentFixture,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
import {
|
||||
NgModule,
|
||||
Component,
|
||||
Directive,
|
||||
ViewChild,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import {MdSnackBar, MdSnackBarModule} from './snack-bar';
|
||||
import {OverlayContainer} from '../core';
|
||||
import {MdSnackBarConfig} from './snack-bar-config';
|
||||
import {SimpleSnackBar} from './simple-snack-bar';
|
||||
|
||||
|
||||
describe('MdSnackBar', () => {
|
||||
let snackBar: MdSnackBar;
|
||||
let overlayContainerElement: HTMLElement;
|
||||
|
||||
let testViewContainerRef: ViewContainerRef;
|
||||
let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>;
|
||||
|
||||
let simpleMessage = 'Burritos are here!';
|
||||
let simpleActionLabel = 'pickup';
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MdSnackBarModule.forRoot(), SnackBarTestModule],
|
||||
providers: [
|
||||
{provide: OverlayContainer, useFactory: () => {
|
||||
overlayContainerElement = document.createElement('div');
|
||||
return {getContainerElement: () => overlayContainerElement};
|
||||
}}
|
||||
],
|
||||
});
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(inject([MdSnackBar], (sb: MdSnackBar) => {
|
||||
snackBar = sb;
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer);
|
||||
|
||||
viewContainerFixture.detectChanges();
|
||||
testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer;
|
||||
});
|
||||
|
||||
it('should have the role of alert', () => {
|
||||
let config = new MdSnackBarConfig(testViewContainerRef);
|
||||
snackBar.open(simpleMessage, simpleActionLabel, config);
|
||||
|
||||
let containerElement = overlayContainerElement.querySelector('snack-bar-container');
|
||||
|
||||
expect(containerElement.getAttribute('role'))
|
||||
.toBe('alert', 'Expected snack bar container to have role="alert"');
|
||||
});
|
||||
|
||||
it('should open a simple message with a button', () => {
|
||||
let config = new MdSnackBarConfig(testViewContainerRef);
|
||||
let snackBarRef = snackBar.open(simpleMessage, simpleActionLabel, config);
|
||||
|
||||
viewContainerFixture.detectChanges();
|
||||
|
||||
expect(snackBarRef.instance)
|
||||
.toEqual(jasmine.any(SimpleSnackBar),
|
||||
'Expected the snack bar content component to be SimpleSnackBar');
|
||||
expect(snackBarRef.instance.snackBarRef)
|
||||
.toBe(snackBarRef, 'Expected the snack bar reference to be placed in the component instance');
|
||||
|
||||
let messageElement = overlayContainerElement.querySelector('span.md-simple-snackbar-message');
|
||||
expect(messageElement.tagName).toBe('SPAN', 'Expected snack bar message element to be <span>');
|
||||
expect(messageElement.textContent)
|
||||
.toBe(simpleMessage, `Expected the snack bar message to be '${simpleMessage}''`);
|
||||
|
||||
let buttonElement = overlayContainerElement.querySelector('button.md-simple-snackbar-action');
|
||||
expect(buttonElement.tagName)
|
||||
.toBe('BUTTON', 'Expected snack bar action label to be a <button>');
|
||||
expect(buttonElement.textContent)
|
||||
.toBe(simpleActionLabel,
|
||||
`Expected the snack bar action labe; to be '${simpleActionLabel}'`);
|
||||
});
|
||||
|
||||
it('should open a simple message with no button', () => {
|
||||
let config = new MdSnackBarConfig(testViewContainerRef);
|
||||
let snackBarRef = snackBar.open(simpleMessage, null, config);
|
||||
|
||||
viewContainerFixture.detectChanges();
|
||||
|
||||
expect(snackBarRef.instance)
|
||||
.toEqual(jasmine.any(SimpleSnackBar),
|
||||
'Expected the snack bar content component to be SimpleSnackBar');
|
||||
expect(snackBarRef.instance.snackBarRef)
|
||||
.toBe(snackBarRef, 'Expected the snack bar reference to be placed in the component instance');
|
||||
|
||||
let messageElement = overlayContainerElement.querySelector('span.md-simple-snackbar-message');
|
||||
expect(messageElement.tagName).toBe('SPAN', 'Expected snack bar message element to be <span>');
|
||||
expect(messageElement.textContent)
|
||||
.toBe(simpleMessage, `Expected the snack bar message to be '${simpleMessage}''`);
|
||||
|
||||
expect(overlayContainerElement.querySelector('button.md-simple-snackbar-action'))
|
||||
.toBeNull('Expected the query selection for action label to be null');
|
||||
});
|
||||
|
||||
it('should dismiss the snack bar and remove itself from the view', () => {
|
||||
let config = new MdSnackBarConfig(testViewContainerRef);
|
||||
let snackBarRef = snackBar.open(simpleMessage, null, config);
|
||||
let dismissed = true;
|
||||
snackBarRef.afterDismissed().subscribe(result => {
|
||||
dismissed = true;
|
||||
});
|
||||
|
||||
viewContainerFixture.detectChanges();
|
||||
expect(overlayContainerElement.childElementCount)
|
||||
.toBeGreaterThan(0, 'Expected overlay container element to have at least one child');
|
||||
|
||||
snackBarRef.dismiss();
|
||||
|
||||
expect(dismissed).toBeTruthy('Expected the snack bar to be dismissed');
|
||||
expect(overlayContainerElement.childElementCount)
|
||||
.toBe(0, 'Expected the overlay container element to have no child elements');
|
||||
});
|
||||
|
||||
it('should open a custom component', () => {
|
||||
let config = new MdSnackBarConfig(testViewContainerRef);
|
||||
let snackBarRef = snackBar.openFromComponent(BurritosNotification, config);
|
||||
|
||||
expect(snackBarRef.instance)
|
||||
.toEqual(jasmine.any(BurritosNotification),
|
||||
'Expected the snack bar content component to be BurritosNotification');
|
||||
expect(overlayContainerElement.textContent)
|
||||
.toBe('Burritos are on the way.',
|
||||
`Expected the overlay text content to be 'Burritos are on the way'`);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@Directive({selector: 'dir-with-view-container'})
|
||||
class DirectiveWithViewContainer {
|
||||
constructor(public viewContainerRef: ViewContainerRef) { }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'arbitrary-component',
|
||||
template: `<dir-with-view-container></dir-with-view-container>`,
|
||||
})
|
||||
class ComponentWithChildViewContainer {
|
||||
@ViewChild(DirectiveWithViewContainer) childWithViewContainer: DirectiveWithViewContainer;
|
||||
|
||||
get childViewContainer() {
|
||||
return this.childWithViewContainer.viewContainerRef;
|
||||
}
|
||||
}
|
||||
|
||||
/** Simple component for testing ComponentPortal. */
|
||||
@Component({template: '<p>Burritos are on the way.</p>'})
|
||||
class BurritosNotification {}
|
||||
|
||||
|
||||
/** Simple component to open snack bars from.
|
||||
* Create a real (non-test) NgModule as a workaround forRoot
|
||||
* https://github.com/angular/angular/issues/10760
|
||||
*/
|
||||
const TEST_DIRECTIVES = [ComponentWithChildViewContainer,
|
||||
BurritosNotification,
|
||||
DirectiveWithViewContainer];
|
||||
@NgModule({
|
||||
imports: [MdSnackBarModule],
|
||||
exports: TEST_DIRECTIVES,
|
||||
declarations: TEST_DIRECTIVES,
|
||||
entryComponents: [ComponentWithChildViewContainer, BurritosNotification],
|
||||
})
|
||||
class SnackBarTestModule { }
|
||||
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
NgModule,
|
||||
ModuleWithProviders,
|
||||
Injectable,
|
||||
ComponentRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ComponentType,
|
||||
ComponentPortal,
|
||||
Overlay,
|
||||
OverlayModule,
|
||||
OverlayRef,
|
||||
OverlayState,
|
||||
PortalModule,
|
||||
OVERLAY_PROVIDERS,
|
||||
MdLiveAnnouncer,
|
||||
} from '../core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MdSnackBarConfig} from './snack-bar-config';
|
||||
import {MdSnackBarRef} from './snack-bar-ref';
|
||||
import {MdSnackBarContainer} from './snack-bar-container';
|
||||
import {SimpleSnackBar} from './simple-snack-bar';
|
||||
|
||||
export {MdSnackBarRef} from './snack-bar-ref';
|
||||
export {MdSnackBarConfig} from './snack-bar-config';
|
||||
|
||||
// TODO(josephperrott): Animate entrance and exit of snack bars.
|
||||
// TODO(josephperrott): Automate dismiss after timeout.
|
||||
|
||||
|
||||
/**
|
||||
* Service to dispatch Material Design snack bar messages.
|
||||
*/
|
||||
@Injectable()
|
||||
export class MdSnackBar {
|
||||
/** A reference to the current snack bar in the view. */
|
||||
private _snackBarRef: MdSnackBarRef<any>;
|
||||
|
||||
constructor(private _overlay: Overlay,
|
||||
private _live: MdLiveAnnouncer) {}
|
||||
|
||||
/**
|
||||
* Creates and dispatches a snack bar with a custom component for the content, removing any
|
||||
* currently opened snack bars.
|
||||
*/
|
||||
openFromComponent<T>(component: ComponentType<T>,
|
||||
config: MdSnackBarConfig): MdSnackBarRef<T> {
|
||||
if (this._snackBarRef) {
|
||||
this._snackBarRef.dismiss();
|
||||
}
|
||||
let overlayRef = this._createOverlay();
|
||||
let snackBarContainer = this._attachSnackBarContainer(overlayRef, config);
|
||||
let mdSnackBarRef = this._attachSnackbarContent(component, snackBarContainer, overlayRef);
|
||||
this._live.announce(config.announcementMessage, config.politeness);
|
||||
return mdSnackBarRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and dispatches a snack bar.
|
||||
*/
|
||||
open(message: string, actionLabel: string,
|
||||
config: MdSnackBarConfig): MdSnackBarRef<SimpleSnackBar> {
|
||||
config.announcementMessage = message;
|
||||
let simpleSnackBarRef = this.openFromComponent(SimpleSnackBar, config);
|
||||
simpleSnackBarRef.instance.snackBarRef = simpleSnackBarRef;
|
||||
simpleSnackBarRef.instance.message = message;
|
||||
simpleSnackBarRef.instance.action = actionLabel;
|
||||
return simpleSnackBarRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the snack bar container component to the overlay.
|
||||
*/
|
||||
private _attachSnackBarContainer(overlayRef: OverlayRef,
|
||||
config: MdSnackBarConfig): MdSnackBarContainer {
|
||||
let containerPortal = new ComponentPortal(MdSnackBarContainer, config.viewContainerRef);
|
||||
let containerRef: ComponentRef<MdSnackBarContainer> = overlayRef.attach(containerPortal);
|
||||
containerRef.instance.snackBarConfig = config;
|
||||
|
||||
return containerRef.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a new component as the content of the snack bar container.
|
||||
*/
|
||||
private _attachSnackbarContent<T>(component: ComponentType<T>,
|
||||
container: MdSnackBarContainer,
|
||||
overlayRef: OverlayRef): MdSnackBarRef<T> {
|
||||
let portal = new ComponentPortal(component);
|
||||
let contentRef = container.attachComponentPortal(portal);
|
||||
let snackBarRef = <MdSnackBarRef<T>> new MdSnackBarRef(contentRef.instance, overlayRef);
|
||||
|
||||
this._snackBarRef = snackBarRef;
|
||||
return snackBarRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new overlay and places it in the correct location.
|
||||
*/
|
||||
private _createOverlay(): OverlayRef {
|
||||
let state = new OverlayState();
|
||||
state.positionStrategy = this._overlay.position().global()
|
||||
.fixed()
|
||||
.centerHorizontally()
|
||||
.bottom('0');
|
||||
return this._overlay.create(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [OverlayModule, PortalModule, CommonModule],
|
||||
exports: [MdSnackBarContainer],
|
||||
declarations: [MdSnackBarContainer, SimpleSnackBar],
|
||||
entryComponents: [MdSnackBarContainer, SimpleSnackBar],
|
||||
})
|
||||
export class MdSnackBarModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: MdSnackBarModule,
|
||||
providers: [MdSnackBar, OVERLAY_PROVIDERS, MdLiveAnnouncer]
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user