Home Reference Source Test Repository

src/components/slideshow/slideshow.js

import { Component } from "../../core/bane";
import waitForTransition from "../../core/utils/waitForTransition";
import SlideComponent from "./slide_component";

/**
 * slideshow Component
*/
export default class Slideshow extends Component {
  static get loopSpeed() {
    return 6000;
  }

  get padding() {
    return this.type === "slide" ? 2 : 1;
  }

  get slides(){
    return this._slides || (this._slides = []);
  }

  get stack(){
    return this._stack || (this._stack = []);
  }

  get $images(){
    if(!this._$images) {
      this._$images = this.$el.find(".slideshow__images");

      if (!this._$images.length) {
        this._$images = $("<div />", {
          "class": "slideshow__images"
        }).appendTo(this.$el);
      }
    }

    return this._$images;
  }

  

  get type(){
    return this.options.type || "fade";
  }

  initialize(options){
    this.options = options;
    this.$el.addClass(`slideshow--${this.type}`);
    this.currentSlideIndex = 0;

    this.events = {
      "click [class*=\"--num_-1\"]": "goLeft",
      "click [class*=\"--num_1\"]": "goRight"
    };

    let state = this.getInitialState();

    if (!state.images && !this.options.images) {
      return;
    }

    this.createBaseSlides(state);
    this.initSlideShow();

    if(this.slides.length > 1) {
      this.setCssClasses();
      this.startLoop();
    }
  }

  createBaseSlides(state) {
    let images = state.images || this.options.images || [];

    let hasStraplines = false;

    images.forEach((imageData) => {
      let slide = new SlideComponent({
        model: imageData
      });

      if(imageData.strapline) {
        hasStraplines = true;
      }

      this.slides.push(slide);
    });

    if(!hasStraplines) {
      this.$el.find(".slideshow__straplines").remove();
    }

    if (this.options.showProgress && images.length > 1) {
      this.makeProgress();
    }
  }

  initSlideShow() {
    let state = this.getNewState();

    this.showStraplineByIndex(this.currentSlideIndex);

    this.$firstImage = this.$images.find(".slideshow__slide");

    state.forEach((index) => {
      let $el = this.slides[index].getElement();

      this.stack.push($el);

      $el.appendTo(this.$images);
      
      // This removes the original first image to stop a flash when the new ones are added
      if (index === 0) {
        this.$firstImage.remove();
      }
    });

    if (this.options.height) {
      this.$el.height(this.options.height);
    }
  }

  startLoop() {
    return new Promise((resolve) => {
        this.loopTimer = setTimeout(resolve, Slideshow.loopSpeed);
      })
      .then(() => {
        return this.showNext();
      })
      .then(this.startLoop.bind(this));
  }

  goRight(){
    if(this.isAnimating){
      return Promise.all([]);
    }

    clearTimeout(this.loopTimer);

    return this.showNext()
      .then(this.startLoop.bind(this));
  }

  goLeft(){
    if(this.isAnimating){
      return Promise.all([]);
    }

    clearTimeout(this.loopTimer);

    return this.showNext({ reverse: true})
      .then(this.startLoop.bind(this));
  }

  getNewState() {
    if (this.slides.length === 1) {
      return [0];
    }

    let arr = [];

    for(let i = -this.padding; i <= this.padding; i++) {
      arr.push(this.getSlideFromIndex(i));
    }

    // E.G., make sure if the array has [0,0,0], it only returns [0]
    return arr;
  }

  // TODO: Fix double overflow (more then one circle)
  getSlideFromIndex(index){
    let currentIndex = this.currentSlideIndex;
    let nextIndex = currentIndex + index;
    let output = nextIndex;

    if(nextIndex < 0) {
      output = (this.slides.length) + currentIndex + index;
    }

    if(nextIndex > this.slides.length - 1){
      output = nextIndex - (this.slides.length);
    }

    if(output < 0 || output >= this.slides.length) {
      return 0;
    }

    return output;
  }

  setCssClasses() {
    let i = -this.padding;

    this.stack.forEach(($el) => {
      $el.attr("class", "slideshow__slide slideshow__slide--num_" + i++);
    });

    if (this.options.showProgress) {
      this.updateProgress();
    }
  }

  showNext({ reverse = false } = {}){
    let currentIndex = this.currentSlideIndex = reverse ?
      this.getPrevIndex(this.currentSlideIndex) :
      this.getNextIndex(this.currentSlideIndex);

    let state = this.getNewState();

    this.isAnimating = true;

    // remove first/last element of stack, [0,1,2,3] -> [1,2,3]
    let toBeRemoved = reverse ? this.stack.pop() : this.stack.shift();

    // add new item to stack, [1,2,3] -> [1,2,3,4]
    let nextIndexIn = reverse ? state[0] : state[state.length - 1];
    let nextSlide = this.slides[nextIndexIn];
    let nextEl = nextSlide.getElement();

    if(reverse){
      this.stack.unshift(nextEl);
    } else {
      this.stack.push(nextEl);
    }

    // preLoad new image before animating
    return nextSlide.preload()
      .then(() => {
        // remove old element
        $(toBeRemoved[0]).remove();

        // insert new element
        nextEl.addClass("slideshow__slide--next").appendTo(this.$images);

        // reset all css classes on stack for positioning / animation
        this.setCssClasses();
      })
      .then(() => {
        this.showStraplineByIndex(currentIndex);
      })
      .then(() => {
        // 5. wait for slide animation on primary slide
        return waitForTransition(this.stack[this.padding]);
      })
      .then(() => {
        this.isAnimating = false;
      });
  }

  getPrevIndex(index){
    let prevIndex = index - 1;

    if(prevIndex < 0) {
      prevIndex = this.slides.length - 1;
    }

    return prevIndex;
  }

  getNextIndex(index){
    let nextIndex = index + 1;

    if(nextIndex > this.slides.length - 1) {
      nextIndex = 0;
    }

    return nextIndex;
  }

  showStraplineByIndex(index){
    this.trigger("image.changed", {
      index
    });
  }

  makeProgress() {
    this.$progress = $("<div />", {
      "class": "slideshow__progress"
    });

    this.slides.forEach(() => {
      this.$progress.append($("<span />", {
        "class": "slideshow__progress__dot"
      }));
    });

    this.$images.append(this.$progress);
  }

  updateProgress() {
    let $dots = this.$progress.find(".slideshow__progress__dot");
      
    $dots.removeClass("slideshow__progress__dot--active");

    $dots.eq(this.currentSlideIndex).addClass("slideshow__progress__dot--active");
  }

}