Skip to content

Commit

Permalink
feat(carousel): add event on carousel slide with direction info
Browse files Browse the repository at this point in the history
Closes #1356
Closes #1406
  • Loading branch information
giampierobono authored and pkozlowski-opensource committed Jun 28, 2017
1 parent b8dd032 commit 5d79c00
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 9 deletions.
1 change: 1 addition & 0 deletions demo/src/app/components/carousel/carousel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {DEMO_SNIPPETS} from './demos';
<ngbd-content-wrapper component="Carousel">
<ngbd-api-docs directive="NgbCarousel"></ngbd-api-docs>
<ngbd-api-docs directive="NgbSlide"></ngbd-api-docs>
<ngbd-api-docs-class type="NgbSlideEvent"></ngbd-api-docs-class>
<ngbd-api-docs-config type="NgbCarouselConfig"></ngbd-api-docs-config>
<ngbd-example-box demoTitle="Carousel" [snippets]="snippets" component="carousel" demo="basic">
<ngbd-carousel-basic></ngbd-carousel-basic>
Expand Down
4 changes: 2 additions & 2 deletions src/carousel/carousel.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {CommonModule} from '@angular/common';

import {NGB_CAROUSEL_DIRECTIVES} from './carousel';
import {NGB_CAROUSEL_DIRECTIVES, NgbSlideEvent} from './carousel';
import {NgbCarouselConfig} from './carousel-config';

export {NgbCarousel, NgbSlide} from './carousel';
export {NgbCarousel, NgbSlide, NgbSlideEvent} from './carousel';
export {NgbCarouselConfig} from './carousel-config';

@NgModule({declarations: NGB_CAROUSEL_DIRECTIVES, exports: NGB_CAROUSEL_DIRECTIVES, imports: [CommonModule]})
Expand Down
73 changes: 72 additions & 1 deletion src/carousel/carousel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {By} from '@angular/platform-browser';
import {Component} from '@angular/core';

import {NgbCarouselModule} from './carousel.module';
import {NgbCarousel} from './carousel';
import {NgbCarousel, NgbSlideEvent, NgbSlideEventDirection} from './carousel';
import {NgbCarouselConfig} from './carousel-config';

const createTestComponent = (html: string) =>
Expand Down Expand Up @@ -130,6 +130,41 @@ describe('ngb-carousel', () => {
discardPeriodicTasks();
}));

it('should fire a slide event with correct direction on indicator click', fakeAsync(() => {
const html = `
<ngb-carousel (slide)="carouselSlideCallBack($event)">
<ng-template ngbSlide>foo</ng-template>
<ng-template ngbSlide>bar</ng-template>
<ng-template ngbSlide>pluto</ng-template>
</ngb-carousel>
`;

const fixture = createTestComponent(html);
const indicatorElms = fixture.nativeElement.querySelectorAll('ol.carousel-indicators > li');
const spyCallBack = spyOn(fixture.componentInstance, 'carouselSlideCallBack');

indicatorElms[1].click();
fixture.detectChanges();
expect(fixture.componentInstance.carouselSlideCallBack).toHaveBeenCalledWith(jasmine.objectContaining({
direction: NgbSlideEventDirection.LEFT
}));

spyCallBack.calls.reset();
indicatorElms[0].click();
fixture.detectChanges();
expect(fixture.componentInstance.carouselSlideCallBack).toHaveBeenCalledWith(jasmine.objectContaining({
direction: NgbSlideEventDirection.RIGHT
}));

spyCallBack.calls.reset();
indicatorElms[2].click();
fixture.detectChanges();
expect(fixture.componentInstance.carouselSlideCallBack).toHaveBeenCalledWith(jasmine.objectContaining({
direction: NgbSlideEventDirection.LEFT
}));

discardPeriodicTasks();
}));

it('should change slide on carousel control click', fakeAsync(() => {
const html = `
Expand Down Expand Up @@ -157,6 +192,41 @@ describe('ngb-carousel', () => {
discardPeriodicTasks();
}));

it('should fire a slide event with correct direction on carousel control click', fakeAsync(() => {
const html = `
<ngb-carousel (slide)="carouselSlideCallBack($event)">
<ng-template ngbSlide>foo</ng-template>
<ng-template ngbSlide>bar</ng-template>
</ngb-carousel>
`;

const fixture = createTestComponent(html);
const prevControlElm = fixture.nativeElement.querySelector('.carousel-control-prev');
const nextControlElm = fixture.nativeElement.querySelector('.carousel-control-next');
const spyCallBack = spyOn(fixture.componentInstance, 'carouselSlideCallBack');

prevControlElm.click();
fixture.detectChanges();
expect(fixture.componentInstance.carouselSlideCallBack).toHaveBeenCalledWith(jasmine.objectContaining({
direction: NgbSlideEventDirection.RIGHT
}));
spyCallBack.calls.reset();
nextControlElm.click();
fixture.detectChanges();
expect(fixture.componentInstance.carouselSlideCallBack).toHaveBeenCalledWith(jasmine.objectContaining({
direction: NgbSlideEventDirection.LEFT
}));

spyCallBack.calls.reset();
prevControlElm.click();
fixture.detectChanges();
expect(fixture.componentInstance.carouselSlideCallBack).toHaveBeenCalledWith(jasmine.objectContaining({
direction: NgbSlideEventDirection.RIGHT
}));

discardPeriodicTasks();
}));

it('should change slide on time passage (default interval value)', fakeAsync(() => {
const html = `
<ngb-carousel>
Expand Down Expand Up @@ -423,4 +493,5 @@ describe('ngb-carousel', () => {
class TestComponent {
activeSlideId;
keyboard = true;
carouselSlideCallBack = (event: NgbSlideEvent) => {};
}
59 changes: 53 additions & 6 deletions src/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
Input,
OnDestroy,
AfterContentChecked,
OnInit
OnInit,
Output,
EventEmitter
} from '@angular/core';
import {NgbCarouselConfig} from './carousel-config';

Expand Down Expand Up @@ -43,7 +45,8 @@ export class NgbSlide {
},
template: `
<ol class="carousel-indicators">
<li *ngFor="let slide of slides" [id]="slide.id" [class.active]="slide.id === activeId" (click)="cycleToSelected(slide.id)"></li>
<li *ngFor="let slide of slides" [id]="slide.id" [class.active]="slide.id === activeId"
(click)="cycleToSelected(slide.id, _getSlideEventDirection(activeId, slide.id))"></li>
</ol>
<div class="carousel-inner">
<div *ngFor="let slide of slides" class="carousel-item" [class.active]="slide.id === activeId">
Expand Down Expand Up @@ -85,6 +88,12 @@ export class NgbCarousel implements AfterContentChecked,
*/
@Input() activeId: string;

/**
* A carousel slide event fired when the slide transition is completed.
* See NgbSlideEvent for payload details
*/
@Output() slide = new EventEmitter<NgbSlideEvent>();

constructor(config: NgbCarouselConfig) {
this.interval = config.interval;
this.wrap = config.wrap;
Expand All @@ -104,7 +113,7 @@ export class NgbCarousel implements AfterContentChecked,
* Navigate to a slide with the specified identifier.
*/
select(slideId: string) {
this.cycleToSelected(slideId);
this.cycleToSelected(slideId, this._getSlideEventDirection(this.activeId, slideId));
this._restartTimer();
}

Expand Down Expand Up @@ -134,13 +143,16 @@ export class NgbCarousel implements AfterContentChecked,
*/
cycle() { this._startTimer(); }

cycleToNext() { this.cycleToSelected(this._getNextSlide(this.activeId)); }
cycleToNext() { this.cycleToSelected(this._getNextSlide(this.activeId), NgbSlideEventDirection.LEFT); }

cycleToPrev() { this.cycleToSelected(this._getPrevSlide(this.activeId)); }
cycleToPrev() { this.cycleToSelected(this._getPrevSlide(this.activeId), NgbSlideEventDirection.RIGHT); }

cycleToSelected(slideIdx: string) {
cycleToSelected(slideIdx: string, direction: NgbSlideEventDirection) {
let selectedSlide = this._getSlideById(slideIdx);
if (selectedSlide) {
if (selectedSlide.id !== this.activeId) {
this.slide.emit({prev: this.activeId, current: selectedSlide.id, direction: direction});
}
this.activeId = selectedSlide.id;
}
}
Expand Down Expand Up @@ -196,6 +208,41 @@ export class NgbCarousel implements AfterContentChecked,
return isFirstSlide ? (this.wrap ? slideArr[slideArr.length - 1].id : slideArr[0].id) :
slideArr[currentSlideIdx - 1].id;
}

private _getSlideEventDirection(currentActiveSlideId: string, nextActiveSlideId: string): NgbSlideEventDirection {
const currentActiveSlideIdx = this._getSlideIdxById(currentActiveSlideId);
const nextActiveSlideIdx = this._getSlideIdxById(nextActiveSlideId);

return currentActiveSlideIdx > nextActiveSlideIdx ? NgbSlideEventDirection.RIGHT : NgbSlideEventDirection.LEFT;
}
}

/**
* The payload of the slide event fired when the slide transition is completed
*/
export interface NgbSlideEvent {
/**
* Previous slide id
*/
prev: string;

/**
* New slide ids
*/
current: string;

/**
* Slide event direction
*/
direction: NgbSlideEventDirection;
}

/**
* Enum to define the carousel slide event direction
*/
export enum NgbSlideEventDirection {
LEFT = <any>'left',
RIGHT = <any>'right'
}

export const NGB_CAROUSEL_DIRECTIVES = [NgbCarousel, NgbSlide];

0 comments on commit 5d79c00

Please sign in to comment.