Skip to content

Commit

Permalink
feat(carousel): direct setting of an active slide. Applying ng-bootst…
Browse files Browse the repository at this point in the history
…rap tests
  • Loading branch information
macroorganizm committed Dec 21, 2016
1 parent 35102e6 commit c0f41cf
Show file tree
Hide file tree
Showing 7 changed files with 582 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="row">
<div class="col-md-4">
<carousel [interval]="myInterval" [noWrap]="noWrapSlides" (activeSlideChanged)="activeSlideChanged($event)">
<carousel [interval]="myInterval" [noWrap]="noWrapSlides" [(activeSlide)]="activeSlideIndex">
<slide *ngFor="let slidez of slides; let index=index">
<img [src]="slidez.image" style="margin:auto;">

Expand All @@ -19,10 +19,14 @@ <h4>Slide {{index}}</h4>
</button>

<button type="button" class="btn btn-info"
(click)="removeSlide()">Remove Current Slide
(click)="selectSlide(2)">Select #3
</button>

<button type="button" class="btn btn-info"
(click)="removeSlide()">Remove Current
</button>git
<button type="button" class="btn btn-info"
(click)="removeSlide(2)">Remove Slide#3
(click)="removeSlide(2)">Remove #3
</button>
<br>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class CarouselDemoComponent {
public myInterval:number = 5000;
public noWrapSlides:boolean = false;
public slides:any[] = [];
public currentSlideIndex: number;
public activeSlideIndex: number;

public constructor() {
for (let i = 0; i < 4; i++) {
Expand All @@ -25,12 +25,12 @@ export class CarouselDemoComponent {
});
}

public activeSlideChanged(index: number): void {
this.currentSlideIndex = index;
public selectSlide(index: number): void {
this.activeSlideIndex = index;
}

public removeSlide(index?: number):void {
const toRemove = index ? index : this.currentSlideIndex;
const toRemove = index ? index : this.activeSlideIndex;
this.slides.splice(toRemove, 1);
}
}
220 changes: 146 additions & 74 deletions src/carousel/carousel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ export enum Direction {UNKNOWN, NEXT, PREV}
<li *ngFor="let slidez of slides; let i = index;" [class.active]="slidez.active === true" (click)="selectSlide(i)"></li>
</ol>
<div class="carousel-inner"><ng-content></ng-content></div>
<a class="left carousel-control" [class.disabled]="getCurrentSlideIndex() === 0 && noWrap" (click)="previousSlide()" *ngIf="slides.length > 1">
<a class="left carousel-control" [class.disabled]="activeSlide === 0 && noWrap" (click)="previousSlide()" *ngIf="slides.length > 1">
<span class="icon-prev" aria-hidden="true"></span>
<span *ngIf="isBs4" class="sr-only">Previous</span>
</a>
<a class="right carousel-control" (click)="nextSlide()" [class.disabled]="isLast(getCurrentSlideIndex()) && noWrap" *ngIf="slides.length > 1">
<a class="right carousel-control" (click)="nextSlide()" [class.disabled]="isLast(activeSlide) && noWrap" *ngIf="slides.length > 1">
<span class="icon-next" aria-hidden="true"></span>
<span *ngIf="isBs4" class="sr-only">Next</span>
</a>
Expand All @@ -47,34 +47,50 @@ export enum Direction {UNKNOWN, NEXT, PREV}
})
export class CarouselComponent implements OnDestroy {
/** if `true` carousel will not cycle continuously and will have hard stops (prevent looping) */
@Input() public noWrap:boolean;
@Input() public noWrap: boolean;

/** if `true` will disable pausing on carousel mouse hover */
@Input() public noPause:boolean;
@Input() public noPause: boolean;

protected _currentActiveSlide: number;

/** Will be emitted when active slide has been changed. Part of two-way-bindable [(activeSlide)] property */
@Output() public activeSlideChange: EventEmitter <any> = new EventEmitter<any>(false);

/** Index of currently displayed slide(started for 0) */
@Input()
public set activeSlide(index: number) {
if (this._slides.length && index !== this._currentActiveSlide) {
this._select(index);
}
}
public get activeSlide(): number {
return this._currentActiveSlide;
}

protected _interval: number;

/**
* Amount of time in milliseconds to delay between automatically
* cycling an item. If false, carousel will not automatically cycle
*/
@Input()
public get interval():number {
public get interval(): number {
return this._interval;
}
public set interval(value:number) {
public set interval(value: number) {
this._interval = value;
this.restartTimer();
}

@Output() public activeSlideChanged: EventEmitter <any> = new EventEmitter<any>(false);

protected _slides: LinkedList<SlideComponent> = new LinkedList<SlideComponent>();
public get slides(): SlideComponent[] {
return this._slides.toArray();
}

protected currentInterval:any;
protected isPlaying:boolean;
protected destroyed:boolean = false;
protected _interval:number;
protected currentInterval: any;
protected isPlaying: boolean;
protected destroyed: boolean = false;

public get isBs4():boolean {
return !isBs3();
Expand All @@ -84,131 +100,184 @@ export class CarouselComponent implements OnDestroy {
Object.assign(this, config);
}

public ngOnDestroy():void {
public ngOnDestroy(): void {
this.destroyed = true;
}

/**
* Adds new slide. If this slide is first in collection - set it as active and starts auto changing
* @param slide
*/
public addSlide(slide: SlideComponent): void {
this._slides.add(slide);
if (this._slides.length === 1) {
slide.active = true;
this._currentActiveSlide = void 0;
this.activeSlide = 0;
this.play();
}
}

/**
* Removes specified slide. If this slide is active - will roll to another slide
* @param slide
*/
public removeSlide(slide: SlideComponent): void {
const remIndex = this._slides.indexOf(slide);

if (this.getCurrentSlideIndex() === remIndex) {
if (this._currentActiveSlide === remIndex) {

// behavior in case removing of a current active slide
// removing of active slide
let nextSlideIndex: number = void 0;
if (this._slides.length > 1) {
if (this.isLast(remIndex) && this.noWrap) {

// last slide and looping is disabled - step backward
this._select(Direction.PREV, undefined, true);
} else {
this._select(Direction.NEXT, undefined, true);
}
// if this slide last - will roll to first slide, if noWrap flag is FALSE or to previous, if noWrap is TRUE
// in case, if this slide in middle of collection, index of next slide is same to removed
nextSlideIndex = !this.isLast(remIndex) ? remIndex :
this.noWrap ? remIndex - 1 : 0;
}
}
this._slides.remove(remIndex);

// prevents exception with changing some value after checking
setTimeout(() => {
this._select(nextSlideIndex);
}, 0);
} else {
this._slides.remove(remIndex);
const currentSlideIndex = this.getCurrentSlideIndex();
setTimeout(() => {
// after removing, need to actualize index of current active slide
this._currentActiveSlide = currentSlideIndex;
this.activeSlideChange.emit(this._currentActiveSlide);
}, 0);

this._slides.remove(remIndex);
this.activeSlideChanged.emit(this.getCurrentSlideIndex());
}
}

public nextSlide(): void {
this._select(Direction.NEXT);
/**
* Rolling to next slide
* @param force: {boolean} if true - will ignore noWrap flag
*/
public nextSlide(force: boolean = false): void {
this.activeSlide = this.findNextSlideIndex(Direction.NEXT, force);
}

public previousSlide(): void {
this._select(Direction.PREV);
/**
* Rolling to previous slide
* @param force: {boolean} if true - will ignore noWrap flag
*/
public previousSlide(force: boolean = false): void {
this.activeSlide = this.findNextSlideIndex(Direction.PREV, force);
}

/**
* Rolling to specified slide
* @param index: {number} index of slide, which must be shown
*/
public selectSlide(index: number): void {
this._select(undefined, index, true);
this.activeSlide = index;
}

public play():void {
/**
* Starts a auto changing of slides
*/
public play(): void {
if (!this.isPlaying) {
this.isPlaying = true;
this.restartTimer();
}
}

public pause():void {
/**
* Stops a auto changing of slides
*/
public pause(): void {
if (!this.noPause) {
this.isPlaying = false;
this.resetTimer();
}
}

/**
* Finds and returns index of currently displayed slide
* @returns {number}
*/
public getCurrentSlideIndex(): number {
return this._slides.findIndex((slide: SlideComponent) => slide.active);
}

/**
* Defines, whether the specified index is last in collection
* @param index
* @returns {boolean}
*/
public isLast(index: number): boolean {
return index + 1 >= this._slides.length;
}

/**
* Select slide
* @param direction: {Direction}
* @param nextIndex: {number}(optional) - index of next active slide
* @param force: boolean {optional} - if true, selection will ignore this.noWrap flag(for jumping after removing a current slide)
* @private
* Defines next slide index, depending of direction
* @param direction: Direction(UNKNOWN|PREV|NEXT)
* @param force: {boolean} if TRUE - will ignore noWrap flag, else will return undefined if next slide require wrapping
* @returns {any}
*/
private _select(direction: Direction = 0, nextIndex?: number, force: boolean = false): void {
const currentSlideIndex = this.getCurrentSlideIndex();
private findNextSlideIndex(direction: Direction, force: boolean): number {
let nextSlideIndex: number = 0;

// if this is last slide, need to going forward but looping is disabled
if (!force && (this.isLast(currentSlideIndex) && direction && direction !== Direction.PREV && this.noWrap)) {
this.pause();
return;
if (!force && (this.isLast(this.activeSlide) && direction !== Direction.PREV && this.noWrap)) {
return void 0;
}

let currentSlide = this._slides.get(currentSlideIndex);
let nextSlideIndex: number = !isNaN(nextIndex) ? nextIndex : undefined;

if (direction !== undefined && direction !== Direction.UNKNOWN) {
switch (direction) {
case Direction.NEXT:

// if this is last slide, not force, looping is disabled and need to going forward - select current slide, as a next
nextSlideIndex = (!this.isLast(currentSlideIndex)) ? currentSlideIndex + 1 :
(!force && this.noWrap ) ? currentSlideIndex : 0;
break;
case Direction.PREV:

// if this is first slide, not force, looping is disabled and need to going backward - select current slide, as a next
nextSlideIndex = (currentSlideIndex > 0) ? currentSlideIndex - 1 :
(!force && this.noWrap ) ? currentSlideIndex : this._slides.length - 1;
break;
default:
throw new Error('Wrong direction');
}
switch (direction) {
case Direction.NEXT:
// if this is last slide, not force, looping is disabled and need to going forward - select current slide, as a next
nextSlideIndex = (!this.isLast(this._currentActiveSlide)) ? this._currentActiveSlide + 1 :
(!force && this.noWrap ) ? this._currentActiveSlide : 0;
break;
case Direction.PREV:
// if this is first slide, not force, looping is disabled and need to going backward - select current slide, as a next
nextSlideIndex = (this._currentActiveSlide > 0) ? this._currentActiveSlide - 1 :
(!force && this.noWrap ) ? this._currentActiveSlide : this._slides.length - 1;
break;
default:
throw new Error('Unknown direction');
}
return nextSlideIndex;
}

if (nextSlideIndex === currentSlideIndex) {
/**
* Sets a slide, which specified through index, as active
* @param index
* @private
*/
private _select(index: number): void {
if (isNaN(index)) {
this.pause();
return;
}

let nextSlide = this._slides.get(nextSlideIndex);
currentSlide.active = false;
nextSlide.active = true;
this.activeSlideChanged.emit(nextSlideIndex);

let currentSlide = this._slides.get(this._currentActiveSlide);
if (currentSlide) {
currentSlide.active = false;
}
let nextSlide = this._slides.get(index);
if (nextSlide) {
this._currentActiveSlide = index;
nextSlide.active = true;
this.activeSlide = index;
this.activeSlideChange.emit(index);
}
}

private restartTimer():any {
/**
* Starts loop of auto changing of slides
*/
private restartTimer(): any {
this.resetTimer();
let interval = +this.interval;
if (!isNaN(interval) && interval > 0) {
this.currentInterval = setInterval(
() => {
let nInterval = +this.interval;
if (this.isPlaying && !isNaN(this.interval) && nInterval > 0 && this.slides.length) {
this._select(Direction.NEXT);
this.nextSlide();
} else {
this.pause();
}
Expand All @@ -217,7 +286,10 @@ export class CarouselComponent implements OnDestroy {
}
}

private resetTimer():void {
/**
* Stops loop of auto changing of slides
*/
private resetTimer(): void {
if (this.currentInterval) {
clearInterval(this.currentInterval);
this.currentInterval = void 0;
Expand Down
5 changes: 5 additions & 0 deletions src/carousel/carousel.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { Injectable } from '@angular/core';

@Injectable()
export class CarouselConfig {
/** Default interval of auto changing of slides */
public interval: number = 5000;

/** Is loop of auto changing of slides can be paused */
public noPause: boolean = false;

/** Is slides can wrap from the last to the first slide */
public noWrap: boolean = false;
}
Loading

0 comments on commit c0f41cf

Please sign in to comment.