Skip to content

Commit

Permalink
feat(toastr): add duplicates behavior (#1628)
Browse files Browse the repository at this point in the history
  • Loading branch information
elupanov authored and nnixaa committed Jun 24, 2019
1 parent dad01b2 commit 4ab5037
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 10 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'ToastrPreventDuplicatesComponent',
name: 'Toastr Prevent Duplicates',
},
{
path: 'toastr-prevent-duplicates-behaviour.component',
link: '/toastr/toastr-prevent-duplicates-behaviour.component',
component: 'ToastrPreventDuplicatesBehaviourComponent',
name: 'Toastr Prevent Duplicates Behaviour',
},
{
path: 'toastr-showcase.component',
link: '/toastr/toastr-showcase.component',
Expand Down
10 changes: 9 additions & 1 deletion src/framework/theme/components/toastr/toastr-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type IconToClassMap = {

export const NB_TOASTR_CONFIG = new InjectionToken<NbToastrConfig>('Default toastr options');

export type NbDuplicateToastBehaviour = 'previous' | 'all';

/**
* The `NbToastrConfig` class describes configuration of the `NbToastrService.show` and global toastr configuration.
* */
Expand All @@ -36,9 +38,15 @@ export class NbToastrConfig {
* */
destroyByClick: boolean = true;
/**
* If preventDuplicates is true then the next toast with the same title and message will not be rendered.
* If preventDuplicates is true then the toast with the same title, message and status will not be rendered.
* Find duplicates behaviour determined by `preventDuplicates`.
* The default `previous` duplicate behaviour is used.
* */
preventDuplicates: boolean = false;
/**
* Determines the how to threat duplicates.
* */
duplicatesBehaviour: NbDuplicateToastBehaviour = 'previous';
/**
* Determines render icon or not.
* */
Expand Down
63 changes: 61 additions & 2 deletions src/framework/theme/components/toastr/toastr.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NbToastrContainerRegistry, NbToastrService } from './toastr.service';
import { NbToastContainer, NbToastrContainerRegistry, NbToastrService } from './toastr.service';
import { NbGlobalLogicalPosition, NbGlobalPhysicalPosition } from '../cdk/overlay/position-helper';
import { TestBed } from '@angular/core/testing';
import { ComponentFactoryResolver } from '@angular/core';
import { NbToastrModule } from '@nebular/theme';
import { NbToast, NbToastrModule } from '@nebular/theme';


describe('toastr-service', () => {
Expand Down Expand Up @@ -222,3 +222,62 @@ describe('toastr-container-registry', () => {
expect(topEnd).toBe(topRight);
});
});

describe('toastr-container', () => {
let toastrContainer: NbToastContainer;
let containerRefStub: any;
let positionHelperStub: any;

beforeEach(() => {
positionHelperStub = {
isTopPosition() {
return true;
},
};

containerRefStub = {
instance: {
toasts: [],
},
changeDetectorRef: {
detectChanges() {},
},
};
});

beforeEach(() => {
toastrContainer = new NbToastContainer(<any> {}, containerRefStub, positionHelperStub);
});

it('should prevent duplicates if previous toast is the same', () => {
const toast1 = {
title: 'toast1',
message: 'message',
config: { status: 'dander', preventDuplicates: true, duplicatesBehaviour: 'previous' },
};

const toast2 = Object.assign({title: 'toast2'}, toast1);

toastrContainer.attach(<NbToast> toast1);
toastrContainer.attach(<NbToast> toast2);
const duplicateToast = toastrContainer.attach(<NbToast> toast2);

expect(duplicateToast).toBeUndefined();
});

it('should prevent duplicates if existing toast is the same', () => {
const toast1 = {
title: 'toast1',
message: 'message',
config: { status: 'dander', preventDuplicates: true, duplicatesBehaviour: 'all' },
};

const toast2 = Object.assign({title: 'toast2'}, toast1);

toastrContainer.attach(<NbToast> toast1);
toastrContainer.attach(<NbToast> toast2);
const duplicateToast = toastrContainer.attach(<NbToast> toast1);

expect(duplicateToast).toBeUndefined();
});
});
35 changes: 29 additions & 6 deletions src/framework/theme/components/toastr/toastr.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
import { ComponentFactoryResolver, ComponentRef, Inject, Injectable } from '@angular/core';

import { NbComponentPortal } from '../cdk/overlay/mapping';
import { NbOverlayService } from '../cdk/overlay/overlay-service';
import { NbOverlayService, patch } from '../cdk/overlay/overlay-service';
import { NbPositionBuilderService } from '../cdk/overlay/overlay-position';
import { NbGlobalLogicalPosition, NbGlobalPosition, NbPositionHelper } from '../cdk/overlay/position-helper';
import { patch } from '../cdk/overlay/overlay-service';
import { NbToastrContainerComponent } from './toastr-container.component';
import { NB_TOASTR_CONFIG, NbToastrConfig } from './toastr-config';
import { NbToast } from './model';
Expand Down Expand Up @@ -61,16 +60,34 @@ export class NbToastContainer {
}

destroy(toast: NbToast) {
if (this.prevToast === toast) {
this.prevToast = null;
}

this.toasts = this.toasts.filter(t => t !== toast);
this.updateContainer();
}

protected isDuplicate(toast: NbToast): boolean {
return this.prevToast
&& this.prevToast.message === toast.message
&& this.prevToast.title === toast.title;
return toast.config.duplicatesBehaviour === 'previous'
? this.isDuplicatePrevious(toast)
: this.isDuplicateAmongAll(toast);
}

protected isDuplicatePrevious(toast: NbToast): boolean {
return this.prevToast && this.toastDuplicateCompareFunc(this.prevToast, toast);
}

protected isDuplicateAmongAll(toast: NbToast): boolean {
return this.toasts.some(t => this.toastDuplicateCompareFunc(t, toast));
}

protected toastDuplicateCompareFunc = (t1: NbToast, t2: NbToast): boolean => {
return t1.message === t2.message
&& t1.title === t2.title
&& t1.config.status === t2.config.status;
};

protected attachToast(toast: NbToast): NbToastComponent {
if (this.positionHelper.isTopPosition(toast.config.position)) {
return this.attachToTop(toast);
Expand Down Expand Up @@ -197,11 +214,17 @@ export class NbToastrContainerRegistry {
*
* @stacked-example(Destroy by click, toastr/toastr-destroy-by-click.component)
*
* `preventDuplicates` - don't create new toast if it has the same title and the same message with previous one.
* `preventDuplicates` - don't create new toast if it has the same title, message and status.
* Default is false.
*
* @stacked-example(Prevent duplicates, toastr/toastr-prevent-duplicates.component)
*
* `duplicatesBehaviour` - determines how to threat the toasts duplication.
* Compare with the previous message (`NbDuplicateToastBehaviour.PREVIOUS`)
* or with all visible messages (`NbDuplicateToastBehaviour.ALL`).
*
* @stacked-example(Prevent duplicates behaviour , toastr/toastr-prevent-duplicates-behaviour.component)
*
* `hasIcon` - if true then render toast icon.
* `icon` - you can pass icon class that will be applied into the toast.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component, HostBinding } from '@angular/core';
import { NbDuplicateToastBehaviour, NbToastrService } from '@nebular/theme';

@Component({
selector: 'nb-toastr-prevent-duplicates-behaviour',
template: `
<button nbButton (click)="showToast('Toast 1', 'success')">Show toast 1</button>
<button nbButton (click)="showToast('Toast 2', 'danger')">Show toast 2</button>
<nb-radio-group [(ngModel)]="option">
<nb-radio
*ngFor="let option of options"
[value]="option.value">
{{ option.label }}
</nb-radio>
</nb-radio-group>
`,
styles: [
`
::ng-deep nb-layout-column {
height: 80vw;
}
`,
],
})

export class ToastrPreventDuplicatesBehaviourComponent {

@HostBinding('class')
classes = 'example-items-rows';

constructor(private toastrService: NbToastrService) {
}

options = [
{ value: 'previous' , label: 'Duplicate previous', checked: true },
{ value: 'all' , label: 'Duplicate all' },
];

option: NbDuplicateToastBehaviour = 'previous';

showToast(message, status) {
this.toastrService.show(
message,
`This is toast title`,
{ preventDuplicates: true, duplicatesBehaviour: this.option, status });
}
}
5 changes: 5 additions & 0 deletions src/playground/with-layout/toastr/toastr-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ToastrPositionsComponent } from './toastr-positions.component';
import { ToastrPreventDuplicatesComponent } from './toastr-prevent-duplicates.component';
import { ToastrShowcaseComponent } from './toastr-showcase.component';
import { ToastrStatusesComponent } from './toastr-statuses.component';
import { ToastrPreventDuplicatesBehaviourComponent } from './toastr-prevent-duplicates-behaviour.component';

const routes: Route[] = [
{
Expand All @@ -35,6 +36,10 @@ const routes: Route[] = [
path: 'toastr-prevent-duplicates.component',
component: ToastrPreventDuplicatesComponent,
},
{
path: 'toastr-prevent-duplicates-behaviour.component',
component: ToastrPreventDuplicatesBehaviourComponent,
},
{
path: 'toastr-showcase.component',
component: ToastrShowcaseComponent,
Expand Down
9 changes: 8 additions & 1 deletion src/playground/with-layout/toastr/toastr.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { NgModule } from '@angular/core';
import { NbButtonModule, NbToastrModule } from '@nebular/theme';
import { NbButtonModule, NbRadioModule, NbToastrModule } from '@nebular/theme';
import { ToastrRoutingModule } from './toastr-routing.module';
import { ToastrDestroyByClickComponent } from './toastr-destroy-by-click.component';
import { ToastrDurationComponent } from './toastr-duration.component';
Expand All @@ -14,6 +14,9 @@ import { ToastrPositionsComponent } from './toastr-positions.component';
import { ToastrPreventDuplicatesComponent } from './toastr-prevent-duplicates.component';
import { ToastrShowcaseComponent } from './toastr-showcase.component';
import { ToastrStatusesComponent } from './toastr-statuses.component';
import { ToastrPreventDuplicatesBehaviourComponent } from './toastr-prevent-duplicates-behaviour.component';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@NgModule({
declarations: [
Expand All @@ -22,12 +25,16 @@ import { ToastrStatusesComponent } from './toastr-statuses.component';
ToastrIconComponent,
ToastrPositionsComponent,
ToastrPreventDuplicatesComponent,
ToastrPreventDuplicatesBehaviourComponent,
ToastrShowcaseComponent,
ToastrStatusesComponent,
],
imports: [
CommonModule,
FormsModule,
NbToastrModule.forRoot(),
NbButtonModule,
NbRadioModule,
ToastrRoutingModule,
],
})
Expand Down

0 comments on commit 4ab5037

Please sign in to comment.