From 640a655a9c627c825951cf80ad596959c145595f Mon Sep 17 00:00:00 2001 From: andreasonny83 Date: Sun, 2 Apr 2017 18:44:03 +0100 Subject: [PATCH] feat(CookieLawComponent): multiple components support multiple components support with attribute name; improved modularization; Angular2 version to 2.4.10; documentation updated. closes #7 --- README.md | 27 +++- demo/app.component.ts | 12 +- package.json | 12 +- src/cookie-law-element.component.ts | 122 ++++++++++++++ src/cookie-law.component.spec.ts | 240 ++++++++++++++++++++++++---- src/cookie-law.component.ts | 134 ++++------------ src/cookie-law.html | 19 ++- src/cookie-law.module.ts | 5 +- src/cookie-law.service.spec.ts | 10 ++ src/cookie-law.service.ts | 8 +- yarn.lock | 130 ++++++++------- 11 files changed, 486 insertions(+), 233 deletions(-) create mode 100644 src/cookie-law-element.component.ts diff --git a/README.md b/README.md index 53c7175..1910a66 100644 --- a/README.md +++ b/README.md @@ -163,13 +163,12 @@ to see the application running. If set to a valid absolute or relative URL, it will render an extra 'learn more' link pointing to the link. -eg. +###### Example + ```html ``` -###### Output - ![output with link](http://i.imgur.com/0nvb6sP.png) ### target @@ -201,6 +200,22 @@ Possible values are: `"bottom"` and `"top"`. ``` +### name + +| Type | Default value | +| --- | --- | +| string | "cookieLawSeen" | + +Allows you to decide which name will be used for storing the cookie in the client's browser. + +###### Example + +```html + +``` + +The previous example will generate a `myShinyCookieLaw=true` as soon as the user dismiss the banner. + ## Properties | Name | Type | Description | @@ -306,9 +321,9 @@ library starting from the version >=4. Make sure to include the `BrowserAnimationsModule` in your App module like in the following example: ```ts -import { NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { AppComponent } from './app.component'; +import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -319,7 +334,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; ], imports: [ BrowserModule, - BrowserAnimationsModule, // BrowserAnimationsModule + BrowserAnimationsModule, // Angular 4 Only ], }) export class AppModule { } diff --git a/demo/app.component.ts b/demo/app.component.ts index c14a8ff..0f5b44f 100644 --- a/demo/app.component.ts +++ b/demo/app.component.ts @@ -25,7 +25,7 @@ import { - + Allo! This is my awesome cookie-law message. Click here for more info @@ -52,15 +52,15 @@ import { `] }) export class AppComponent implements OnInit { - @ViewChild('cookieLaw') - private cookieLawEl: any; - private cookieLawSeen: boolean; + cookieLawSeen: boolean; + + @ViewChild('cookieLaw') private cookieLawEl: any; ngOnInit() { - this.cookieLawSeen = this.cookieLawEl.cookieLawSeen; + this.cookieLawSeen = this.cookieLawEl.cookieLawSeen(); } - public dismiss(): void { + dismiss(): void { this.cookieLawEl.dismiss(); } } diff --git a/package.json b/package.json index 61e91ab..9c9c666 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,12 @@ "@angular/platform-browser": ">=2.0.0" }, "devDependencies": { - "@angular/common": "2.4.7", - "@angular/compiler": "2.4.7", - "@angular/core": "2.4.7", - "@angular/platform-browser": "2.4.7", - "@angular/platform-browser-dynamic": "2.4.7", - "@angular/compiler-cli": "2.4.7", + "@angular/common": "2.4.10", + "@angular/compiler": "2.4.10", + "@angular/core": "2.4.10", + "@angular/platform-browser": "2.4.10", + "@angular/platform-browser-dynamic": "2.4.10", + "@angular/compiler-cli": "2.4.10", "@types/jasmine": "2.5.46", "@types/node": "7.0.5", "@types/selenium-webdriver": "2.53.39", diff --git a/src/cookie-law-element.component.ts b/src/cookie-law-element.component.ts new file mode 100644 index 0000000..fae47ef --- /dev/null +++ b/src/cookie-law-element.component.ts @@ -0,0 +1,122 @@ +import { + Component, + OnInit, + ViewEncapsulation, + Input, + Output, + EventEmitter, + AnimationTransitionEvent, + trigger, + state, + style, + animate, + transition, +} from '@angular/core'; + +import { + DomSanitizer, + SafeHtml, +} from '@angular/platform-browser'; + +import { + closeIcon, +} from './icons'; + +export type CookieLawPosition = 'top' | 'bottom'; +export type CookieLawAnimation = 'topIn' | 'bottomIn' | 'topOut' | 'bottomOut'; +export type CookieLawTarget = '_blank' | '_self'; + +@Component({ + selector: 'cookie-law-el', + animations: [ + trigger('state', [ + state('bottomOut', style({ transform: 'translateY(100%)' })), + state('topOut', style({ transform: 'translateY(-100%)' })), + state('*', style({ transform: 'translateY(0)' })), + + transition('void => topIn', [ + style({ transform: 'translateY(-100%)' }), + animate('1000ms ease-in-out'), + ]), + + transition('void => bottomIn', [ + style({ transform: 'translateY(100%)' }), + animate('1000ms ease-in-out'), + ]), + + transition('* => *', animate('1000ms ease-out')), + ]) + ], + styleUrls: [ './cookie-law.css' ], + templateUrl: './cookie-law.html', + encapsulation: ViewEncapsulation.None, + host: { + '[class.cookie-law]': 'true' + } +}) +export class CookieLawElementComponent implements OnInit { + animation: CookieLawAnimation; + closeSvg: SafeHtml; + currentStyles: any; + + @Input() + get learnMore() { return this._learnMore; } + set learnMore(value: string) { + this._learnMore = (value !== null && `${value}` !== 'false') ? value : null; + } + + @Input() + get target() { return this._target; } + set target(value: CookieLawTarget) { + this._target = (value !== null && `${value}` !== 'false' && + (`${value}` === '_blank' || `${value}` === '_self') + ) ? value : '_blank'; + } + + @Input() + get position() { return this._position; } + set position(value: CookieLawPosition) { + this._position = (value !== null && `${value}` !== 'false' && + (`${value}` === 'top' || `${value}` === 'bottom') + ) ? value : 'bottom'; + } + + @Output() isSeen = new EventEmitter(); + + private _learnMore: string; + private _target: CookieLawTarget; + private _position: CookieLawPosition; + + constructor( + private domSanitizer: DomSanitizer, + ) { + this.animation = 'bottomIn'; + this._position = 'bottom'; + } + + ngOnInit(): void { + this.animation = this.position === 'bottom' ? 'bottomIn' : 'topIn'; + + this.closeSvg = this.domSanitizer.bypassSecurityTrustHtml(closeIcon); + + this.currentStyles = { + 'top': this.position === 'top' ? '0' : null, + 'bottom': this.position === 'top' ? 'initial' : null, + }; + } + + afterDismissAnimation(evt: AnimationTransitionEvent): void { + if (evt.toState === 'topOut' || + evt.toState === 'bottomOut') { + this.isSeen.emit(true); + } + } + + dismiss(evt?: MouseEvent): void { + if (evt) { + evt.preventDefault(); + } + + this.animation = this.position === 'top' ? 'topOut' : 'bottomOut'; + } +} diff --git a/src/cookie-law.component.spec.ts b/src/cookie-law.component.spec.ts index 8f3829a..3e5de5c 100644 --- a/src/cookie-law.component.spec.ts +++ b/src/cookie-law.component.spec.ts @@ -12,20 +12,19 @@ import { async, } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { DebugElement } from '@angular/core'; +import { + DebugElement, + Component, +} from '@angular/core'; import { CookieLawModule, CookieLawComponent, + CookieLawElementComponent, + CookieLawService, } from './cookie-law.module'; -import { CookieLawService } from './cookie-law.service'; describe('CookieLawComponent', () => { - let comp: CookieLawComponent; - let fixture: ComponentFixture; - let de: DebugElement; - let el: HTMLElement; - let cookiesPolicyService: any; // async beforeEach @@ -48,6 +47,11 @@ describe('CookieLawComponent', () => { provide: CookieLawService, useValue: CookieLawServiceStub }], + declarations: [ + SimpleCookieLawComponent, + AttributesCookieLawComponent, + TwoCookieLawComponent, + ], imports: [ CookieLawModule, ] @@ -59,67 +63,233 @@ describe('CookieLawComponent', () => { beforeEach(() => { // CookieLawService from the root injector cookiesPolicyService = TestBed.get(CookieLawService); - - fixture = TestBed.createComponent(CookieLawComponent); - comp = fixture.componentInstance; // CookieLawComponent test instance - - // query for the element by CSS element selector - de = fixture.debugElement; - el = de.nativeElement; }); it('should render the cookie policy notification', () => { + let fixture: ComponentFixture = TestBed.createComponent(CookieLawComponent); + let comp: CookieLawComponent = fixture.debugElement.componentInstance; + fixture.detectChanges(); expect(cookiesPolicyService.seen()).toBe(false); - expect(el.textContent).toContain('By continuing to browse the site, you\'re agreeing to our use of cookies.'); + + expect(comp).toBeTruthy(); + expect(comp).not.toBeNull(); + + expect(fixture.debugElement.nativeElement.textContent) + .toContain('By continuing to browse the site, you\'re agreeing to our use of cookies.'); }); - it('dismiss the notification with mouse interaction', () => { + it('CookieLawComponent should have a `seen` attribute', () => { + let fixture: ComponentFixture = TestBed.createComponent(SimpleCookieLawComponent); + let element: DebugElement = fixture.debugElement.query(By.css('cookie-law')); + fixture.detectChanges(); + expect(element.nativeElement.getAttribute('seen')).toBe('false'); + }); - expect(cookiesPolicyService.seen()).toBe(false); - de.query(By.css('.dismiss')).nativeElement.click(); + it('CookieLawComponent should be initially visible', () => { + let fixture: ComponentFixture = TestBed.createComponent(CookieLawComponent); + let comp: CookieLawComponent = fixture.debugElement.componentInstance; fixture.detectChanges(); + expect(comp.seen).toBe(false); + expect(comp.cookieLawSeen()).toBe(false); + }); + + it('CookieLawComponent should be dismissible', () => { + let fixture: ComponentFixture = TestBed.createComponent(CookieLawComponent); + let comp: CookieLawComponent = fixture.debugElement.componentInstance; - expect(cookiesPolicyService.seen()).toBe(true); - expect(el.textContent).not.toContain('COOKIE POLICY'); + fixture.detectChanges(); + expect(comp.seen).toBe(false); + + comp.dismiss(); + comp.hasBeenDismissed(); + fixture.detectChanges(); + + expect(comp.seen).toBe(true); + expect(comp.cookieLawSeen()).toBe(true); }); - it('dismiss the notification invoking the `dismiss` method', () => { + it('CookieLawElementComponent should have a bunch of attributes', () => { + let fixture: ComponentFixture = TestBed.createComponent(CookieLawElementComponent); + let comp: CookieLawElementComponent = fixture.debugElement.componentInstance; + fixture.detectChanges(); - expect(cookiesPolicyService.seen()).toBe(false); + expect(comp.animation).toBe('bottomIn') + expect(comp.position).toBe('bottom') + expect(comp.learnMore).not.toBeDefined(); + expect(comp.target).not.toBeDefined(); + }); + + it('CookieLawElementComponent should accept attributes', () => { + let fixture: ComponentFixture = TestBed.createComponent(AttributesCookieLawComponent); + let comp: DebugElement = fixture.debugElement.query(By.css('cookie-law')); - comp.dismiss(); fixture.detectChanges(); - expect(cookiesPolicyService.seen()).toBe(true); + let el: DebugElement = fixture.debugElement.query(By.css('cookie-law-el')); + + expect(el.nativeElement.textContent) + .not.toContain(`Learn more in our privacy policy.`); + + expect(comp.nativeElement.getAttribute('seen')).toBe('false'); + expect(el.componentInstance.name).not.toBeDefined(); + expect(el.componentInstance.learnMore).not.toBeDefined(); + expect(el.componentInstance.target).toBe('_blank'); + expect(el.componentInstance.position).toBe('bottom'); + expect(el.componentInstance.animation).toBe('bottomIn'); }); - it('should hide the cookie policy notification', () => { - cookiesPolicyService._seen = true; + it('CookieLawElementComponent should renders on the top', () => { + let fixture: ComponentFixture = TestBed.createComponent(AttributesCookieLawComponent); + let app: AttributesCookieLawComponent = fixture.debugElement.componentInstance; + let comp: DebugElement = fixture.debugElement.query(By.css('cookie-law')); + + app.name = 'myCookie'; + app.position = 'top'; + fixture.detectChanges(); + let el: DebugElement = fixture.debugElement.query(By.css('cookie-law-el')); + + expect(comp.componentInstance.position).toBe('top'); + expect(el.componentInstance.position).toBe('top'); + expect(el.componentInstance.animation).toBe('topIn'); + }); + + it('CookieLawElementComponent learnMore', () => { + let fixture: ComponentFixture = TestBed.createComponent(AttributesCookieLawComponent); + let app: AttributesCookieLawComponent = fixture.debugElement.componentInstance; - expect(cookiesPolicyService.seen()).toBe(true); + app.learnMore = '/#cookies'; + app.target = '_self'; - expect(el.textContent).not.toContain('COOKIE POLICY'); + fixture.detectChanges(); + + let el: DebugElement = fixture.debugElement.query(By.css('cookie-law-el')); + + expect(el.componentInstance.target).toBe('_self'); + expect(el.nativeElement.textContent) + .toContain(`Learn more in our privacy policy.`); + + app.learnMore = 'false'; + + fixture.detectChanges(); + + expect(el.componentInstance.learnMore).toBeNull(); }); - it('cookieLawSeen should reflects cookiesPolicyService.seen', () => { - expect(cookiesPolicyService.seen()).toBe(false); + it('CookieLawElementComponent should dismiss the banner on the bottom', () => { + let fixture: ComponentFixture = TestBed.createComponent(AttributesCookieLawComponent); - comp.dismiss(); fixture.detectChanges(); - expect(cookiesPolicyService.seen()).toBe(true); + let el: DebugElement = fixture.debugElement.query(By.css('cookie-law-el')); + let comp: CookieLawElementComponent = el.componentInstance; + + let spy = spyOn(comp, 'dismiss').and.callThrough(); + + el.query(By.css('.dismiss')).nativeElement.click(); + + fixture.detectChanges(); + + expect(spy).toHaveBeenCalled(); + }); + + it('CookieLawElementComponent should dismiss the banner on the top', () => { + let fixture: ComponentFixture = TestBed.createComponent(AttributesCookieLawComponent); + let app: AttributesCookieLawComponent = fixture.debugElement.componentInstance; + + app.position = 'top'; + + fixture.detectChanges(); + + let el: DebugElement = fixture.debugElement.query(By.css('cookie-law-el')); + let comp: CookieLawElementComponent = el.componentInstance; + + let spy = spyOn(comp, 'dismiss').and.callThrough(); + + el.query(By.css('.dismiss')).nativeElement.click(); + + fixture.detectChanges(); + + expect(spy).toHaveBeenCalled(); + }); + + it('CookieLawComponent should dismiss the banner using a method', () => { + let fixture: ComponentFixture = TestBed.createComponent(AttributesCookieLawComponent); + let app: AttributesCookieLawComponent = fixture.debugElement.componentInstance; + + app.position = 'top'; + + fixture.detectChanges(); + + let parent: CookieLawComponent = fixture.debugElement.query(By.css('cookie-law')).componentInstance; + let comp: CookieLawElementComponent = fixture.debugElement.query(By.css('cookie-law-el')).componentInstance; + + let spy = spyOn(comp, 'dismiss').and.callThrough(); + + parent.dismiss(); + + fixture.detectChanges(); + + expect(spy).toHaveBeenCalled(); }); - it('should render a learn more link', () => { - comp.learnMore = 'http://www.google.com'; + it('CookieLawComponent with custom names', () => { + let fixture: ComponentFixture = TestBed.createComponent(TwoCookieLawComponent); fixture.detectChanges(); - expect(el.textContent).toContain('Learn more'); + + let cookieB: CookieLawComponent = fixture.debugElement.query(By.css('cookie-law#second')).componentInstance; + + fixture.detectChanges(); + expect(fixture.nativeElement.querySelectorAll('cookie-law#first .cookie-law').length).toBe(1); + expect(fixture.nativeElement.querySelectorAll('cookie-law#second .cookie-law').length).toBe(1); + + cookieB.hasBeenDismissed(); + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelectorAll('cookie-law#first .cookie-law').length).toBe(1); + expect(fixture.nativeElement.querySelectorAll('cookie-law#second .cookie-law').length).toBe(0); }); }); + +@Component({ + template: ` + + ` +}) +class SimpleCookieLawComponent { } + +@Component({ + template: ` + + + ` +}) +class TwoCookieLawComponent { } + +@Component({ + template: ` + + ` +}) +class AttributesCookieLawComponent { + name: string; + learnMore: string; + target: string; + position: string; + + isSeen: boolean = false; + + seen(evt: any) { + this.isSeen = evt; + } +} diff --git a/src/cookie-law.component.ts b/src/cookie-law.component.ts index c1786e1..d49f91d 100644 --- a/src/cookie-law.component.ts +++ b/src/cookie-law.component.ts @@ -9,136 +9,64 @@ import { Component, OnInit, - ViewEncapsulation, + ViewChild, HostBinding, Input, Output, EventEmitter, - AnimationTransitionEvent, - trigger, - state, - style, - animate, - transition, } from '@angular/core'; -import { - DomSanitizer, - SafeHtml, -} from '@angular/platform-browser'; - import { CookieLawService, } from './cookie-law.service'; import { - closeIcon, -} from './icons'; - -export type CookieLawPosition = 'top' | 'bottom'; -export type CookieLawAnimation = 'topIn' | 'bottomIn' | 'topOut' | 'bottomOut'; -export type CookieLawTarget = '_blank' | '_self'; + CookieLawElementComponent, + CookieLawTarget, + CookieLawPosition, +} from './cookie-law-element.component'; @Component({ selector: 'cookie-law', - encapsulation: ViewEncapsulation.None, - animations: [ - trigger('state', [ - state('bottomOut', style({ transform: 'translateY(100%)' })), - state('topOut', style({ transform: 'translateY(-100%)' })), - state('*', style({ transform: 'translateY(0)' })), - - transition('void => topIn', [ - style({ transform: 'translateY(-100%)' }), - animate('1000ms ease-in-out'), - ]), - - transition('void => bottomIn', [ - style({ transform: 'translateY(100%)' }), - animate('1000ms ease-in-out'), - ]), - - transition('* => *', animate('1000ms ease-out')), - ]) - ], - styleUrls: [ './cookie-law.css' ], - templateUrl: './cookie-law.html', - host: { - '[class.cookie-law]': 'true' - } + template: ` + + `, }) export class CookieLawComponent implements OnInit { - animation: CookieLawAnimation; - closeSvg: SafeHtml; - cookieLawSeen: boolean; - currentStyles: any; - - @Input('learnMore') - get learnMore() { return this._learnMore; } - set learnMore(value: string) { - this._learnMore = (value !== null && `${value}` !== 'false') ? value : null; - } + @HostBinding('attr.seen') + seen: boolean = true; - @Input('target') - get target() { return this._target; } - set target(value: CookieLawTarget) { - this._target = (value !== null && `${value}` !== 'false' && - (`${value}` === '_blank' || `${value}` === '_self') - ) ? value : '_blank'; - } + @ViewChild(CookieLawElementComponent) + cookieLawComponent: CookieLawElementComponent; - @Input('position') - get position() { return this._position; } - set position(value: CookieLawPosition) { - this._position = (value !== null && `${value}` !== 'false' && - (`${value}` === 'top' || `${value}` === 'bottom') - ) ? value : 'bottom'; - } + @Input() name: string; + @Input() learnMore: string; + @Input() target: CookieLawTarget; + @Input() position: CookieLawPosition; @Output() isSeen = new EventEmitter(); - @HostBinding('attr.seen') - private _learnMore: string; - private _target: CookieLawTarget; - private _position: CookieLawPosition; + constructor (private _service: CookieLawService) { } - constructor( - private _service: CookieLawService, - private domSanitizer: DomSanitizer, - ) { - this.animation = 'topIn'; - this._position = 'bottom'; - this.cookieLawSeen = this._service.seen(); + ngOnInit() { + this.seen = this._service.seen(this.name); } - ngOnInit(): void { - this.animation = this.position === 'bottom' ? 'bottomIn' : 'topIn'; - - this.closeSvg = this.domSanitizer.bypassSecurityTrustHtml(closeIcon); - - if (this.cookieLawSeen) { - this.isSeen.emit(true); - } - - this.currentStyles = { - 'top': this.position === 'top' ? '0' : null, - 'bottom': this.position === 'top' ? 'initial' : null, - }; + hasBeenDismissed(): void { + this.seen = true; + this.isSeen.emit(true); } - afterDismissAnimation(evt: AnimationTransitionEvent) { - if (evt.toState === 'topOut' || - evt.toState === 'bottomOut') { - this.isSeen.emit(true); - } + cookieLawSeen(): boolean { + return this._service.seen(this.name); } - dismiss(evt?: MouseEvent): void { - if (evt) { - evt.preventDefault(); - } - - this._service.storeCookie(); - this.animation = this.position === 'top' ? 'topOut' : 'bottomOut'; + dismiss(): void { + this._service.storeCookie(this.name); + this.cookieLawComponent.dismiss(); } } diff --git a/src/cookie-law.html b/src/cookie-law.html index 4033ba1..94bd065 100644 --- a/src/cookie-law.html +++ b/src/cookie-law.html @@ -1,21 +1,20 @@