From 2b5a878aaa66e4d353727d91ff211749bb080674 Mon Sep 17 00:00:00 2001 From: Stabzs Date: Tue, 1 Dec 2020 12:33:36 -0700 Subject: [PATCH] feat(trustedHtml-pipe) Added pipe for trustedHtml BodyOutputType to enable re-rendering on content change. Added new types for typeClasses and iconClasses mappings --- CHANGELOG.md | 3 + README.md | 57 +++++++++++++++++-- .../src/lib/toast.component.spec.ts | 6 +- .../src/lib/toast.component.ts | 15 +---- .../src/lib/toaster-config.ts | 32 ++++++----- .../src/lib/toaster-container.component.ts | 6 +- .../src/lib/toaster.module.ts | 4 +- .../src/lib/trust-html.pipe.ts | 15 +++++ src/demo/src/app/app.component.html | 8 +-- src/demo/src/app/app.component.ts | 27 ++++----- 10 files changed, 111 insertions(+), 62 deletions(-) create mode 100644 src/angular2-toaster/src/lib/trust-html.pipe.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f931fa27..0fbbc296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ to `toaster-config.defaultToastType`. correctly changed to `info` instead of `toast-info` for the fallback case. * **toaster.css:** The close button positioning has been corrected. Closes [#192](https://github.com/Stabzs/Angular2-Toaster/issues/192). +* **toast.component:** The `BodyOutputType.TrustedHtml` body content did not properly update on change. A pipe has been +added at the suggestion of @rmeshksar to force re-rendering of the content if it changes. Replaces +[#185]](https://github.com/Stabzs/Angular2-Toaster/pull/185). # 10.0.0 diff --git a/README.md b/README.md index 4c56d9aa..66979ebb 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ largely based off of [AngularJS-Toaster](https://github.com/jirikavi/AngularJS-T [![Build Status](https://travis-ci.org/Stabzs/Angular2-Toaster.svg?branch=master)](https://travis-ci.org/Stabzs/Angular2-Toaster) [![Coverage Status](https://coveralls.io/repos/github/Stabzs/Angular2-Toaster/badge.svg?branch=master&b=8.0.0)](https://coveralls.io/github/Stabzs/Angular2-Toaster?branch=master) +Version ^11.0.0 has a number of new features, type definitions, and break changesing. Please review the +[CHANGELOG](CHANGELOG.md/#11.0.0) for a list of features and breaking changes before upgrading. Version ^5.0.0 requires either `.forRoot()` or `.forChild()` `ToasterModule` inclusion. Please -read the 5.x.x release notes and the `Getting Started` section before upgraded. +read the 5.x.x release notes and the `Getting Started` section before upgrading. Version ^4.0.0 now supports @angular/animations, which is a breaking change. Please read both the Getting Started and Animations sections before upgrading. @@ -257,16 +259,47 @@ If you need to target a non-supported browser, a [polyfill](https://github.com/w # Configurable Options ### Toast Types -By default, five toast types are defined: 'error, 'info', 'wait', 'success', and 'warning'. +By default, five toast types are defined via the `ToastType` type: 'error, 'info', 'wait', 'success', and 'warning'. -The default options can be overridden by passing a mapping object to the config, where the key corresponds to the toast type and the value corresponds to a custom style: +The existing toast type configurations can be overridden by passing a mapping object that uses the +same type names but overrides the style class: ```typescript template: `` public config: ToasterConfig = - new ToasterConfig({typeClasses: {'partial-success': '.toaster-partial-success' }}); + new ToasterConfig({typeClasses: { + error: 'custom-toast-error', + info: 'custom-toast-info', + wait: 'custom-toast-wait', + success: 'custom-toast-success', + warning: 'custom-toast-warning' + }}); +``` + +In addition, the default options can be overridden, replaced, or expanded, by extending the toast type with a +custom type and passing a mapping object to the config, where the key corresponds to the toast type and the +value corresponds to a custom class: + +**NOTE: When providing a custom type, both the typeClasses and iconClasses objects must be updated. +In the case where either are not provided, the toast type will fall back to the `defaultToastType` which +defaults to `info`.** +```typescript +import {DefaultTypeClasses, DefaultIconClasses} from 'angular2-toaster'; +type ExtendedToastType = ('partial-success') & ToastType; + +template: + `` + +extendedTypeClasses = { ...DefaultTypeClasses, ...{ 'partial-success': 'toast-partial-success' }}; +extendedIconClasses = { ...DefaultIconClasses, ...{ 'partial-success': 'icon-partial-success' }}; + +public config: ToasterConfig = + new ToasterConfig({ + typeClasses: this.extendedTypeClasses, + iconClasses: this.extendedIconClasses + }); ``` ### Animations @@ -548,6 +581,20 @@ var toast: Toast = { this.toasterService.pop(toast); ``` +### On Click Callback +An onClick callback function can be attached to each toast instance. The callback will be invoked upon the toast being +clicked, even if the click is the close button. The callback will be invoked before the toast is removed. + +```typescript +var toast: Toast = { + type: 'success', + title: 'parent', + onClickCallback: (toast) => this.toasterService.pop('success', 'invoked from ' + toast.title + ' onClick callback') +}; + +this.toasterService.pop(toast); +``` + # Building the Source In order to build Angular2-Toaster for development, you will need to have Git and Node.js installed. @@ -570,7 +617,7 @@ npm run build Run Karma test instance with coverage report: ```bash -npm run test +ng test angular2-toaster --code-coverage ``` # Frequently Asked Questions and Issues diff --git a/src/angular2-toaster/src/lib/toast.component.spec.ts b/src/angular2-toaster/src/lib/toast.component.spec.ts index a54af85d..09515ce5 100644 --- a/src/angular2-toaster/src/lib/toast.component.spec.ts +++ b/src/angular2-toaster/src/lib/toast.component.spec.ts @@ -286,7 +286,8 @@ describe('ToastComponent', () => { component['removeToastTick'] = new Date().getTime() + 75; component['updateProgressBar'](); - expect(component.progressBarWidth).toBe(75); + expect(component.progressBarWidth).toBeGreaterThan(70); + expect(component.progressBarWidth).toBeLessThan(80); }); it('should invert progressBarWidth if progressBarDirection is increasing', () => { @@ -299,7 +300,8 @@ describe('ToastComponent', () => { component['removeToastTick'] = new Date().getTime() + 75; component['updateProgressBar'](); - expect(component.progressBarWidth).toBe(25); + expect(component.progressBarWidth).toBeGreaterThan(20); + expect(component.progressBarWidth).toBeLessThan(30); }); it('should set progressBarWidth to 0 if offset is less than 0', () => { diff --git a/src/angular2-toaster/src/lib/toast.component.ts b/src/angular2-toaster/src/lib/toast.component.ts index f1ef8c1d..36aff714 100644 --- a/src/angular2-toaster/src/lib/toast.component.ts +++ b/src/angular2-toaster/src/lib/toast.component.ts @@ -15,7 +15,6 @@ import { ElementRef, Renderer2 } from '@angular/core'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { Toast } from './toast'; import { BodyOutputType } from './bodyOutputType'; import { ToasterConfig } from './toaster-config'; @@ -27,12 +26,12 @@ import { ToasterConfig } from './toaster-config';
{{toast.title}}
-
+
{{toast.body}}
@@ -45,8 +44,6 @@ export class ToastComponent implements OnInit, AfterViewInit, OnDestroy { @Input() messageClass: string; @ViewChild('componentBody', { read: ViewContainerRef, static: false }) componentBody: ViewContainerRef; - public safeCloseHtml: SafeHtml; - public safeBodyHtml: SafeHtml; public progressBarWidth: number = -1; public bodyOutputType = BodyOutputType; @@ -63,7 +60,6 @@ export class ToastComponent implements OnInit, AfterViewInit, OnDestroy { private removeMouseOverListener: () => void; constructor( - private sanitizer: DomSanitizer, private componentFactoryResolver: ComponentFactoryResolver, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, @@ -72,13 +68,6 @@ export class ToastComponent implements OnInit, AfterViewInit, OnDestroy { ) {} ngOnInit() { - if (this.toast.closeHtml) { - this.safeCloseHtml = this.sanitizer.bypassSecurityTrustHtml(this.toast.closeHtml); - } - if (this.toast.bodyOutputType === BodyOutputType.TrustedHtml) { - this.safeBodyHtml = this.sanitizer.bypassSecurityTrustHtml(this.toast.body); - } - if (this.toast.progressBar) { this.toast.progressBarDirection = this.toast.progressBarDirection || 'decreasing'; } diff --git a/src/angular2-toaster/src/lib/toaster-config.ts b/src/angular2-toaster/src/lib/toaster-config.ts index d010211b..9cdedbc1 100644 --- a/src/angular2-toaster/src/lib/toaster-config.ts +++ b/src/angular2-toaster/src/lib/toaster-config.ts @@ -2,6 +2,22 @@ import { Injectable } from '@angular/core'; import { BodyOutputType } from './bodyOutputType'; import { ToastType } from './toast'; +export const DefaultTypeClasses : { [key in ToastType] } = { + error: 'toast-error', + info: 'toast-info', + wait: 'toast-wait', + success: 'toast-success', + warning: 'toast-warning' +}; + +export const DefaultIconClasses : { [key in ToastType] } = { + error: 'icon-error', + info: 'icon-info', + wait: 'icon-wait', + success: 'icon-success', + warning: 'icon-warning' +}; + export interface IToasterConfig { limit?: number|null; tapToDismiss?: boolean; @@ -60,20 +76,8 @@ export class ToasterConfig implements IToasterConfig { this.closeHtml = configOverrides.closeHtml || '×'; this.newestOnTop = configOverrides.newestOnTop != null ? configOverrides.newestOnTop : true; this.timeout = configOverrides.timeout != null ? configOverrides.timeout : 5000; - this.typeClasses = configOverrides.typeClasses || { - error: 'toast-error', - info: 'toast-info', - wait: 'toast-wait', - success: 'toast-success', - warning: 'toast-warning' - }; - this.iconClasses = configOverrides.iconClasses || { - error: 'icon-error', - info: 'icon-info', - wait: 'icon-wait', - success: 'icon-success', - warning: 'icon-warning' - }; + 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'; diff --git a/src/angular2-toaster/src/lib/toaster-container.component.ts b/src/angular2-toaster/src/lib/toaster-container.component.ts index 7944c669..c6769785 100644 --- a/src/angular2-toaster/src/lib/toaster-container.component.ts +++ b/src/angular2-toaster/src/lib/toaster-container.component.ts @@ -153,12 +153,10 @@ export class ToasterContainerComponent implements OnInit, OnDestroy { } private clearToasts(clearWrapper: IClearWrapper) { - const toastId = clearWrapper.toastId; + const toastId = clearWrapper.toastId ; const toastContainerId = clearWrapper.toastContainerId; - if (this.isNullOrUndefined(toastContainerId)) { - this.clearToastsAction(toastId); - } else if (toastContainerId === this.toasterconfig.toastContainerId) { + if (this.isNullOrUndefined(toastContainerId) || (toastContainerId === this.toasterconfig.toastContainerId)) { this.clearToastsAction(toastId); } } diff --git a/src/angular2-toaster/src/lib/toaster.module.ts b/src/angular2-toaster/src/lib/toaster.module.ts index 0486d366..aceec583 100644 --- a/src/angular2-toaster/src/lib/toaster.module.ts +++ b/src/angular2-toaster/src/lib/toaster.module.ts @@ -3,12 +3,14 @@ import { CommonModule } from '@angular/common'; import { ToastComponent } from './toast.component'; import { ToasterContainerComponent } from './toaster-container.component'; import { ToasterService } from './toaster.service'; +import { TrustHtmlPipe } from './trust-html.pipe'; @NgModule({ imports: [CommonModule], declarations: [ ToastComponent, - ToasterContainerComponent + ToasterContainerComponent, + TrustHtmlPipe ], exports: [ ToasterContainerComponent, diff --git a/src/angular2-toaster/src/lib/trust-html.pipe.ts b/src/angular2-toaster/src/lib/trust-html.pipe.ts new file mode 100644 index 00000000..47eb04b7 --- /dev/null +++ b/src/angular2-toaster/src/lib/trust-html.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +@Pipe({ + name: 'trustHtml', + pure: true +}) +export class TrustHtmlPipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) { + } + + transform(content: any): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(content); + } +} diff --git a/src/demo/src/app/app.component.html b/src/demo/src/app/app.component.html index 4534d694..923d770c 100644 --- a/src/demo/src/app/app.component.html +++ b/src/demo/src/app/app.component.html @@ -365,12 +365,8 @@

Resources

- - - - Angular Blog - - + +
diff --git a/src/demo/src/app/app.component.ts b/src/demo/src/app/app.component.ts index b4c5b08d..c1352b21 100644 --- a/src/demo/src/app/app.component.ts +++ b/src/demo/src/app/app.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { - ToasterConfig, IToasterConfig, ToasterService, Toast, ToastType + ToasterConfig, IToasterConfig, ToasterService, Toast, ToastType, DefaultTypeClasses, DefaultIconClasses } from '../../../angular2-toaster/src/public-api'; @Component({ @@ -11,25 +11,14 @@ import { export class AppComponent { title = 'demo'; + extendedTypeClasses = { ...DefaultTypeClasses, ...{ customtype: 'toast-success' }}; + extendedIconClasses = { ...DefaultIconClasses, ...{ customtype: 'icon-error' }}; + appConfig: IToasterConfig = new ToasterConfig({ animation: 'fade', newestOnTop: true, positionClass: 'toast-bottom-right', toastContainerId: 1, timeout: 0, showCloseButton: true, // mouseoverTimerStop: true - typeClasses: { - customtype: 'toast-success', - error: 'toast-error', - info: 'toast-info', - wait: 'toast-wait', - success: 'toast-success', - warning: 'toast-warning' - }, - iconClasses: { - customtype: 'icon-error', - error: 'icon-error', - info: 'icon-info', - wait: 'icon-wait', - success: 'icon-success', - warning: 'icon-warning' - } + typeClasses: this.extendedTypeClasses, + iconClasses: this.extendedIconClasses }); testConfig: IToasterConfig = new ToasterConfig({ @@ -97,6 +86,10 @@ export class AppComponent { }; this.toasterService.pop(toast); } + + clear() { + this.toasterService.clear(); + } } type ExtendedToastType = ('customtype' | 'bad value') & ToastType;