angular2-toaster
Version:
An Angular Toaster Notification library based on AngularJS-Toaster
644 lines (632 loc) • 23.1 kB
JavaScript
import { Injectable, ɵɵdefineInjectable, Component, Input, EventEmitter, ComponentFactoryResolver, ChangeDetectorRef, NgZone, ElementRef, Renderer2, ViewChild, ViewContainerRef, Output, HostListener, Pipe, NgModule } from '@angular/core';
import { trigger, state, style, transition, animate, group } from '@angular/animations';
import { Observable, Subject } from 'rxjs';
import { share } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
import { DomSanitizer } from '@angular/platform-browser';
const Transitions = [
trigger('toastState', [
state('flyRight, flyLeft, slideDown, slideDown, slideUp, slideUp, fade', style({ opacity: 1, transform: 'translate(0,0)' })),
transition('void => flyRight', [
style({
opacity: 0,
transform: 'translateX(100%)',
height: 0
}),
animate('0.15s ease-in', style({
opacity: 1,
height: '*'
})),
animate('0.25s 15ms ease-in')
]),
transition('flyRight => void', [
animate('0.25s ease-out', style({
opacity: 0,
transform: 'translateX(100%)'
})),
animate('0.15s ease-out', style({
height: 0
}))
]),
transition('void => flyLeft', [
style({
opacity: 0,
transform: 'translateX(-100%)',
height: 0
}),
animate('0.15s ease-in', style({
opacity: 1,
height: '*'
})),
animate('0.25s 15ms ease-in')
]),
transition('flyLeft => void', [
animate('0.25s 10ms ease-out', style({
opacity: 0,
transform: 'translateX(-100%)'
})),
animate('0.15s ease-out', style({
height: 0
}))
]),
transition('void => slideDown', [
style({
opacity: 0,
transform: 'translateY(-500%)',
height: 0
}),
group([
animate('0.2s ease-in', style({
height: '*'
})),
animate('0.4s ease-in', style({
transform: 'translate(0,0)'
})),
animate('0.3s 0.1s ease-in', style({
opacity: 1
}))
])
]),
transition('slideDown => void', group([
animate('0.3s ease-out', style({
opacity: 0
})),
animate('0.4s ease-out', style({
transform: 'translateY(-500%)'
})),
animate('0.2s 0.2s ease-out', style({
height: 0
}))
])),
transition('void => slideUp', [
style({
opacity: 0,
transform: 'translateY(1000%)',
height: 0
}),
group([
animate('0.2s ease-in', style({
height: '*'
})),
animate('0.5s ease-in', style({
transform: 'translate(0,0)'
})),
animate('0.3s 0.1s ease-in', style({
opacity: 1
}))
])
]),
transition('slideUp => void', group([
animate('0.3s ease-out', style({
opacity: 0
})),
animate('0.5s ease-out', style({
transform: 'translateY(1000%)'
})),
animate('0.3s 0.15s ease-out', style({
height: 0
}))
])),
transition('void => fade', [
style({
opacity: 0,
height: 0
}),
animate('0.20s ease-in', style({
height: '*'
})),
animate('0.15s ease-in', style({
opacity: 1
}))
]),
transition('fade => void', [
group([
animate('0.0s ease-out', style({
// reposition the background to prevent
// a ghost image during transition
'background-position': '-99999px'
})),
animate('0.15s ease-out', style({
opacity: 0,
'background-image': ''
})),
animate('0.3s 20ms ease-out', style({
height: 0
}))
])
])
]),
];
var BodyOutputType;
(function (BodyOutputType) {
BodyOutputType[BodyOutputType["Default"] = 0] = "Default";
BodyOutputType[BodyOutputType["TrustedHtml"] = 1] = "TrustedHtml";
BodyOutputType[BodyOutputType["Component"] = 2] = "Component";
})(BodyOutputType || (BodyOutputType = {}));
const DefaultTypeClasses = {
error: 'toast-error',
info: 'toast-info',
wait: 'toast-wait',
success: 'toast-success',
warning: 'toast-warning'
};
const DefaultIconClasses = {
error: 'icon-error',
info: 'icon-info',
wait: 'icon-wait',
success: 'icon-success',
warning: 'icon-warning'
};
class ToasterConfig {
constructor(configOverrides) {
configOverrides = configOverrides || {};
this.limit = configOverrides.limit || null;
this.tapToDismiss = configOverrides.tapToDismiss != null ? configOverrides.tapToDismiss : true;
this.showCloseButton = configOverrides.showCloseButton != null ? configOverrides.showCloseButton : false;
this.closeHtml = configOverrides.closeHtml || '<span>×</span>';
this.newestOnTop = configOverrides.newestOnTop != null ? configOverrides.newestOnTop : true;
this.timeout = configOverrides.timeout != null ? configOverrides.timeout : 5000;
this.typeClasses = configOverrides.typeClasses || DefaultTypeClasses;
this.iconClasses = configOverrides.iconClasses || DefaultIconClasses;
this.bodyOutputType = configOverrides.bodyOutputType || BodyOutputType.Default;
this.bodyTemplate = configOverrides.bodyTemplate || 'toasterBodyTmpl.html';
this.defaultToastType = configOverrides.defaultToastType || 'info';
this.positionClass = configOverrides.positionClass || 'toast-top-right';
this.titleClass = configOverrides.titleClass || 'toast-title';
this.messageClass = configOverrides.messageClass || 'toast-message';
this.animation = configOverrides.animation || '';
this.preventDuplicates = configOverrides.preventDuplicates != null ? configOverrides.preventDuplicates : false;
this.mouseoverTimerStop = configOverrides.mouseoverTimerStop != null ? configOverrides.mouseoverTimerStop : false;
this.toastContainerId = configOverrides.toastContainerId != null ? configOverrides.toastContainerId : null;
}
}
ToasterConfig.decorators = [
{ type: Injectable }
];
ToasterConfig.ctorParameters = () => [
{ type: undefined }
];
// http://stackoverflow.com/questions/26501688/a-typescript-guid-class
class Guid {
static newGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
class ToasterService {
/**
* Creates an instance of ToasterService.
*/
constructor() {
this.addToast = new Observable((observer) => this._addToast = observer).pipe(share());
this.clearToasts = new Observable((observer) => this._clearToasts = observer).pipe(share());
this._removeToastSubject = new Subject();
this.removeToast = this._removeToastSubject.pipe(share());
}
/**
* Synchronously create and show a new toast instance.
*
* @param {(string | Toast)} type The type of the toast, or a Toast object.
* @param {string=} title The toast title.
* @param {string=} body The toast body.
* @returns {Toast}
* The newly created Toast instance with a randomly generated GUID Id.
*/
pop(type, title, body) {
const toast = typeof type === 'string' ? { type: type, title: title, body: body } : type;
if (!toast.toastId) {
toast.toastId = Guid.newGuid();
}
if (!this._addToast) {
throw new Error('No Toaster Containers have been initialized to receive toasts.');
}
this._addToast.next(toast);
return toast;
}
/**
* Asynchronously create and show a new toast instance.
*
* @param {(string | Toast)} type The type of the toast, or a Toast object.
* @param {string=} title The toast title.
* @param {string=} body The toast body.
* @returns {Observable<Toast>}
* A hot Observable that can be subscribed to in order to receive the Toast instance
* with a randomly generated GUID Id.
*/
popAsync(type, title, body) {
setTimeout(() => {
this.pop(type, title, body);
}, 0);
return this.addToast;
}
/**
* Clears a toast by toastId and/or toastContainerId.
*
* @param {string} toastId The toastId to clear.
* @param {number=} toastContainerId
* The toastContainerId of the container to remove toasts from.
*/
clear(toastId, toastContainerId) {
const clearWrapper = {
toastId: toastId, toastContainerId: toastContainerId
};
this._clearToasts.next(clearWrapper);
}
}
ToasterService.ɵprov = ɵɵdefineInjectable({ factory: function ToasterService_Factory() { return new ToasterService(); }, token: ToasterService, providedIn: "root" });
ToasterService.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
ToasterService.ctorParameters = () => [];
class ToasterContainerComponent {
constructor(toasterService) {
this.toasts = [];
this.toasterService = toasterService;
}
ngOnInit() {
this.registerSubscribers();
if (this.isNullOrUndefined(this.toasterconfig)) {
this.toasterconfig = new ToasterConfig();
}
}
// event handlers
click(toast, isCloseButton) {
if (toast.onClickCallback) {
toast.onClickCallback(toast);
}
const tapToDismiss = !this.isNullOrUndefined(toast.tapToDismiss)
? toast.tapToDismiss
: this.toasterconfig.tapToDismiss;
if (tapToDismiss || (toast.showCloseButton && isCloseButton)) {
this.removeToast(toast);
}
}
childClick($event) {
this.click($event.value.toast, $event.value.isCloseButton);
}
removeToast(toast) {
const index = this.toasts.indexOf(toast);
if (index < 0) {
return;
}
;
const toastId = this.toastIdOrDefault(toast);
this.toasts.splice(index, 1);
if (toast.onHideCallback) {
toast.onHideCallback(toast);
}
this.toasterService._removeToastSubject.next({ toastId: toastId, toastContainerId: toast.toastContainerId });
}
// private functions
registerSubscribers() {
this.addToastSubscriber = this.toasterService.addToast.subscribe((toast) => {
this.addToast(toast);
});
this.clearToastsSubscriber = this.toasterService.clearToasts.subscribe((clearWrapper) => {
this.clearToasts(clearWrapper);
});
}
addToast(toast) {
if (toast.toastContainerId && this.toasterconfig.toastContainerId
&& toast.toastContainerId !== this.toasterconfig.toastContainerId) {
return;
}
;
if (!toast.type
|| !this.toasterconfig.typeClasses[toast.type]
|| !this.toasterconfig.iconClasses[toast.type]) {
toast.type = this.toasterconfig.defaultToastType;
}
if (this.toasterconfig.preventDuplicates && this.toasts.length > 0) {
if (toast.toastId && this.toasts.some(t => t.toastId === toast.toastId)) {
return;
}
else if (this.toasts.some(t => t.body === toast.body)) {
return;
}
}
if (this.isNullOrUndefined(toast.showCloseButton)) {
if (typeof this.toasterconfig.showCloseButton === 'object') {
toast.showCloseButton = this.toasterconfig.showCloseButton[toast.type];
}
else if (typeof this.toasterconfig.showCloseButton === 'boolean') {
toast.showCloseButton = this.toasterconfig.showCloseButton;
}
}
if (toast.showCloseButton) {
toast.closeHtml = toast.closeHtml || this.toasterconfig.closeHtml;
}
toast.bodyOutputType = toast.bodyOutputType || this.toasterconfig.bodyOutputType;
if (this.toasterconfig.newestOnTop) {
this.toasts.unshift(toast);
if (this.isLimitExceeded()) {
this.toasts.pop();
}
}
else {
this.toasts.push(toast);
if (this.isLimitExceeded()) {
this.toasts.shift();
}
}
if (toast.onShowCallback) {
toast.onShowCallback(toast);
}
}
isLimitExceeded() {
return this.toasterconfig.limit && this.toasts.length > this.toasterconfig.limit;
}
removeAllToasts() {
for (let i = this.toasts.length - 1; i >= 0; i--) {
this.removeToast(this.toasts[i]);
}
}
clearToasts(clearWrapper) {
const toastId = clearWrapper.toastId;
const toastContainerId = clearWrapper.toastContainerId;
if (this.isNullOrUndefined(toastContainerId) || (toastContainerId === this.toasterconfig.toastContainerId)) {
this.clearToastsAction(toastId);
}
}
clearToastsAction(toastId) {
if (toastId) {
this.removeToast(this.toasts.filter(t => t.toastId === toastId)[0]);
}
else {
this.removeAllToasts();
}
}
toastIdOrDefault(toast) {
return toast.toastId || '';
}
isNullOrUndefined(value) {
return value === null || typeof value === 'undefined';
}
ngOnDestroy() {
if (this.addToastSubscriber) {
this.addToastSubscriber.unsubscribe();
}
if (this.clearToastsSubscriber) {
this.clearToastsSubscriber.unsubscribe();
}
}
}
ToasterContainerComponent.decorators = [
{ type: Component, args: [{
selector: 'toaster-container',
template: `
<div class="toast-container" [ngClass]="[toasterconfig.positionClass]">
<div toastComp *ngFor="let toast of toasts" class="toast" [toast]="toast"
[toasterconfig]="toasterconfig"
[@toastState]="toasterconfig.animation"
[titleClass]="toasterconfig.titleClass"
[messageClass]="toasterconfig.messageClass"
[ngClass]="[
toasterconfig.iconClasses[toast.type],
toasterconfig.typeClasses[toast.type]
]"
(click)="click(toast)" (clickEvent)="childClick($event)"
(removeToastEvent)="removeToast($event)"
>
</div>
</div>
`,
animations: Transitions
},] }
];
ToasterContainerComponent.ctorParameters = () => [
{ type: ToasterService }
];
ToasterContainerComponent.propDecorators = {
toasterconfig: [{ type: Input }]
};
class ToastComponent {
constructor(componentFactoryResolver, changeDetectorRef, ngZone, element, renderer2) {
this.componentFactoryResolver = componentFactoryResolver;
this.changeDetectorRef = changeDetectorRef;
this.ngZone = ngZone;
this.element = element;
this.renderer2 = renderer2;
this.progressBarWidth = -1;
this.bodyOutputType = BodyOutputType;
this.clickEvent = new EventEmitter();
this.removeToastEvent = new EventEmitter();
this.timeoutId = null;
this.timeout = 0;
this.progressBarIntervalId = null;
}
ngOnInit() {
if (this.toast.progressBar) {
this.toast.progressBarDirection = this.toast.progressBarDirection || 'decreasing';
}
let timeout = (typeof this.toast.timeout === 'number')
? this.toast.timeout : this.toasterconfig.timeout;
if (typeof timeout === 'object') {
timeout = timeout[this.toast.type];
}
;
this.timeout = timeout;
}
ngAfterViewInit() {
if (this.toast.bodyOutputType === this.bodyOutputType.Component) {
const component = this.componentFactoryResolver.resolveComponentFactory(this.toast.body);
const componentInstance = this.componentBody.createComponent(component, undefined, this.componentBody.injector);
componentInstance.instance.toast = this.toast;
this.changeDetectorRef.detectChanges();
}
if (this.toasterconfig.mouseoverTimerStop) {
// only apply a mouseenter event when necessary to avoid
// unnecessary event and change detection cycles.
this.removeMouseOverListener = this.renderer2.listen(this.element.nativeElement, 'mouseenter', () => this.stopTimer());
}
this.configureTimer();
}
click(event, toast) {
event.stopPropagation();
this.clickEvent.emit({ value: { toast: toast, isCloseButton: true } });
}
stopTimer() {
this.progressBarWidth = 0;
this.clearTimers();
}
restartTimer() {
if (this.toasterconfig.mouseoverTimerStop) {
if (!this.timeoutId) {
this.configureTimer();
}
}
else if (this.timeout && !this.timeoutId) {
this.removeToast();
}
}
ngOnDestroy() {
if (this.removeMouseOverListener) {
this.removeMouseOverListener();
}
this.clearTimers();
}
configureTimer() {
if (!this.timeout || this.timeout < 1) {
return;
}
if (this.toast.progressBar) {
this.removeToastTick = new Date().getTime() + this.timeout;
this.progressBarWidth = -1;
}
this.ngZone.runOutsideAngular(() => {
this.timeoutId = window.setTimeout(() => {
this.ngZone.run(() => {
this.changeDetectorRef.markForCheck();
this.removeToast();
});
}, this.timeout);
if (this.toast.progressBar) {
this.progressBarIntervalId = window.setInterval(() => {
this.ngZone.run(() => {
this.updateProgressBar();
});
}, 10);
}
});
}
updateProgressBar() {
if (this.progressBarWidth === 0 || this.progressBarWidth === 100) {
return;
}
this.progressBarWidth = ((this.removeToastTick - new Date().getTime()) / this.timeout) * 100;
if (this.toast.progressBarDirection === 'increasing') {
this.progressBarWidth = 100 - this.progressBarWidth;
}
if (this.progressBarWidth < 0) {
this.progressBarWidth = 0;
}
if (this.progressBarWidth > 100) {
this.progressBarWidth = 100;
}
}
clearTimers() {
if (this.timeoutId) {
window.clearTimeout(this.timeoutId);
}
if (this.progressBarIntervalId) {
window.clearInterval(this.progressBarIntervalId);
}
this.timeoutId = null;
this.progressBarIntervalId = null;
}
removeToast() {
this.removeToastEvent.emit(this.toast);
}
}
ToastComponent.decorators = [
{ type: Component, args: [{
selector: '[toastComp]',
template: `
<div class="toast-content">
<div [ngClass]="titleClass">{{toast.title}}</div>
<div [ngClass]="messageClass" [ngSwitch]="toast.bodyOutputType">
<div *ngSwitchCase="bodyOutputType.Component" #componentBody></div>
<div *ngSwitchCase="bodyOutputType.TrustedHtml" [innerHTML]="toast.body | trustHtml"></div>
<div *ngSwitchCase="bodyOutputType.Default">{{toast.body}}</div>
</div>
</div>
<button class="toast-close-button" *ngIf="toast.showCloseButton" (click)="click($event, toast)"
[innerHTML]="toast.closeHtml | trustHtml">
</button>
<div *ngIf="toast.progressBar">
<div class="toast-progress-bar" [style.width]="progressBarWidth + '%'"></div>
</div>`
},] }
];
ToastComponent.ctorParameters = () => [
{ type: ComponentFactoryResolver },
{ type: ChangeDetectorRef },
{ type: NgZone },
{ type: ElementRef },
{ type: Renderer2 }
];
ToastComponent.propDecorators = {
toasterconfig: [{ type: Input }],
toast: [{ type: Input }],
titleClass: [{ type: Input }],
messageClass: [{ type: Input }],
componentBody: [{ type: ViewChild, args: ['componentBody', { read: ViewContainerRef, static: false },] }],
clickEvent: [{ type: Output }],
removeToastEvent: [{ type: Output }],
restartTimer: [{ type: HostListener, args: ['mouseleave',] }]
};
class TrustHtmlPipe {
constructor(sanitizer) {
this.sanitizer = sanitizer;
}
transform(content) {
return this.sanitizer.bypassSecurityTrustHtml(content);
}
}
TrustHtmlPipe.decorators = [
{ type: Pipe, args: [{
name: 'trustHtml',
pure: true
},] }
];
TrustHtmlPipe.ctorParameters = () => [
{ type: DomSanitizer }
];
class ToasterModule {
static forRoot() {
return {
ngModule: ToasterModule,
providers: [ToasterService, ToasterContainerComponent]
};
}
static forChild() {
return {
ngModule: ToasterModule,
providers: [ToasterContainerComponent]
};
}
}
ToasterModule.decorators = [
{ type: NgModule, args: [{
imports: [CommonModule],
declarations: [
ToastComponent,
ToasterContainerComponent,
TrustHtmlPipe
],
exports: [
ToasterContainerComponent,
ToastComponent
]
},] }
];
/*
* Public API Surface of angular2-toaster
*/
/**
* Generated bundle index. Do not edit.
*/
export { BodyOutputType, DefaultIconClasses, DefaultTypeClasses, ToasterConfig, ToasterContainerComponent, ToasterModule, ToasterService, Transitions as ɵa, ToastComponent as ɵb, TrustHtmlPipe as ɵc };
//# sourceMappingURL=angular2-toaster.js.map