johnnyfreeman/revolver

View on GitHub
src/revolver.coffee

Summary

Maintainability
Test Coverage
# =========================================================================================================
#
# 'Y8888888b.                                     `Y88                                        ::
#   888   Y88b                                     888
#   888   dX8P   .d888b. `Y8b      d8P  .d8888b.   888 `Y8b      d8P  .d888b.  `Y88.d88b.    `Y88  .d8888b
#   888888YK    d8P   Y8b  Y8b    d8P  d88P''Y88b  888   Y8b    d8P  d8P   Y8b  888P' 'Y8b    888  88K
#   888  'Y8b.  888888888   Y8b  d8P   88K    X88  888    Y8b  d8P   888888888  888           888  'Y8888b.
#   888    88b  Y8b.         Y8bd8P    Y88b..d88P  888     Y8bd8P    Y8b.       888           888       X88
# .d888    888   'Y888P'      Y88P      'Y8888P'   888.     Y88P      'Y888P'   888      ::   888   Y88888'
#          Y88b.                                                                             .88P
#                                                                                           d88'
# =========================================================================================================

'use strict'

# constructor
Revolver = (options) ->

  # setup initial values
  @currentSlide = 0
  @nextSlide = 0
  @numSlides = 0
  @lastSlide = 0
  @iteration = 0
  @intervalId = null
  @disabled = false

  # merge options
  @options = {}
  @setOptions Revolver.defaults, options

  # set container
  if @options.container
    @container = @options.container
  else if @options.containerSelector
    @container = Revolver.$(@options.containerSelector, document)[0]
  else
    @container = document

  # add all slides
  @slides = []
  slidesToAdd = @options.slides or Revolver.$(@options.slidesSelector, @container)
  _.each slidesToAdd, @addSlide, this

  # finish setting up init values
  @previousSlide = @lastSlide
  @status =
    paused: false
    playing: false
    stopped: true
  @isAnimating = false

  # Completely disable Revolver
  # if there is only one slide
  if @numSlides <= 1
    @disabled = true
    return

  # always disable isAnimating flag
  # after transition is complete
  @on 'transitionComplete', -> @isAnimating = false

  # register all event handlers
  @on 'play', @options.onPlay
  @on 'stop', @options.onStop
  @on 'pause', @options.onPause
  @on 'restart', @options.onRestart
  @on 'transitionStart', @options.transition.onStart
  @on 'transitionComplete', @options.transition.onComplete

  # fire onReady event handler
  _.bind(@options.onReady, this)()

  # begin auto play, if enabled
  @play {}, true  if @options.autoPlay

  # return instance
  this


# namespace for default options
# (gets merged with user defined
# options in the constructor)
Revolver.defaults =
  autoPlay: true          # whether or not to automatically begin playing the slides
  container: null         # dom element the contains the slides (optional)
  containerSelector: null # selector used to find the container element
  slides: null            # array of slide dom elements
  slidesSelector: null    # selector used to find all slides for this slider
  onReady: ->             # gets called when revolver is setup and ready to go
  onPlay: ->              # gets called when the play() method is called
  onStop: ->              # gets called when the stop() method is called
  onPause: ->             # gets called when the pause() method is called
  onRestart: ->           # gets called when the restart() method is called
  rotationSpeed: 4000     # how long (in milliseconds) to stay on each slide before going to the next
  transition:
    onStart: ->           # gets called when the transition animation begins
    onComplete: ->        # gets called when the animation is done
    name: 'default'       # default transition


# current version
Revolver.VERSION = '2.1.1'


# add a new slide
Revolver::addSlide = (slide) ->
  # add new slide to the slides array
  @slides.push slide
  # recalculate total number of slides
  @numSlides     = @slides.length
  # recalculate which is the last slide
  @lastSlide     = (if @numSlides is 0 then 0 else @numSlides - 1)
  # recalculate which is the next slide
  currentPlusOne = @currentSlide + 1
  @nextSlide     = (if currentPlusOne > @lastSlide then 0 else currentPlusOne)
  # return instance
  this


# set options
Revolver::setOptions = () ->
  # add existing options to beginning of arguments array
  args = Array.prototype.slice.call arguments, 0
  args.unshift @options
  # merge new options with the existing options
  _.merge.apply null, args
  # return instance
  this


# changes the status of the slider
Revolver::changeStatus = (newStatus) ->
  # loop over all statuses and set all as true or false
  _.each @status, ((val, key) ->
    @status[key] = key is newStatus
    true
  ), this
  # return instance
  this


# do transition
Revolver::transition = (options) ->
  # if slider isn't disabled and it isn't current in transition already
  if @disabled is false and @isAnimating is false
    options = _.merge({}, @options.transition, options)
    @isAnimating = true
    # start transition
    transition = _.bind Revolver.transitions[options.name], this
    done = _.bind @trigger, this, 'transitionComplete'
    transition options, done
    # update current, previous, next, and iteration
    @currentSlide = @nextSlide
    @previousSlide = (if @currentSlide is 0 then @lastSlide else @currentSlide - 1)
    @nextSlide = (if @currentSlide is @lastSlide then 0 else @currentSlide + 1)
    @iteration++
    # execute transitionStart event handlers
    @trigger 'transitionStart'
  # return instance
  this


# play the slider
Revolver::play = (options, firstTime) ->
  # if slider isn't disabled and it's not already playing
  if @disabled is false and not @status.playing
    # change status
    @changeStatus 'playing'
    # trigger all registered 'play' event listeners
    @trigger 'play'
    # do transition immediately unless it's first run
    @transition options unless firstTime
    # start the loop
    @intervalId = setInterval _.bind(@transition, this), parseFloat(@options.rotationSpeed)
  # return instance
  this

# internal method used for stopping the
# loop that is created by the play method
Revolver::_clearInterval = ->
  # if the intervalId has been set
  if @intervalId isnt null
    # stop the loop
    clearInterval @intervalId
    # delete the reference to the loop
    @intervalId = null
  # return instance
  this


# pause the slider
Revolver::pause = ->
  # if slider isn't disabled and not already paused
  if @disabled is false and not @status.paused
    # change status to paused
    @changeStatus 'paused'
    # fire all 'pause' event listeners
    @trigger 'pause'
    # stop the loop
    @_clearInterval()
  # return instance
  this


# stop the slider
Revolver::stop = ->
  # if slider isn't disabled and not currently stopped
  if @disabled is false and not @status.stopped
    # change status to stopped
    @changeStatus 'stopped'
    # fire all 'stop' event listeners
    @trigger 'stop'
    # stop the loop
    @_clearInterval()
    # queue up first slide as next
    @reset()
  # return instance
  this


# queues up the first slide
Revolver::reset = ->
  # reset only if not already on the first slide
  @nextSlide = 0  if @currentSlide isnt 0
  # return instance
  this


# restart the slider
Revolver::restart = (options) ->
  # bail out if slider is disabled
  return this if @disabled is true
  # fire all 'restart' event listeners
  @trigger 'restart'
  # stop and then play the
  # slider from the beginning
  @stop().play options
  # return instance
  this


# go to a specific slide
Revolver::goTo = (i, options) ->
  # keep transition arithmetic from breaking
  i = parseInt(i)
  # bail out if already on the intended slide
  return this  if @disabled is true or i is @currentSlide
  # queue up i as the next slide
  @nextSlide = i
  # if slider is playing, pause() and play()
  # (which will transition immediately and restart the loop)
  # if not play, simply transition() straight to it
  (if not @status.playing then @transition(options) else @pause().play(options))


# CONVENIENCE METHODS (for building slider controls)

# go to the first slide
Revolver::first = (options) -> @goTo 0, options

# go to the previous slide
Revolver::previous = (options) -> @goTo @previousSlide, options

# go to the next slide
Revolver::next = (options) -> @goTo @nextSlide, options

# go to the last slide
Revolver::last = (options) -> @goTo @lastSlide, options


# EVENTS

addNamespaces = (eventString) ->
  namespace = 'revolver'
  eventStringDelimiter = ' '
  events = eventString.split eventStringDelimiter
  _.each events, (eventName, i) ->
    events[i] = namespace.concat '.', events[i]
  events.join eventStringDelimiter

# attach an event listener
Revolver::on = (eventString, callback) ->
  # bind revolver instance to callback
  callback = _.bind callback, this
  # add revolver namespace to event(s)
  eventString = addNamespaces eventString
  # call bean.on
  bean.on this, eventString, callback
  # return instance
  this

# alias for on() except that the handler will removed after the first execution
Revolver::one = (eventString, callback) ->
  # bind revolver instance to callback
  callback = _.bind callback, this
  # add revolver namespace to event(s)
  eventString = addNamespaces eventString
  # call bean.on
  bean.one this, eventString, callback
  # return instance
  this

# remove an event listener using
Revolver::off = (eventString, callback) ->
  # bind revolver instance to callback
  callback = _.bind callback, this
  # add revolver namespace to event(s)
  eventString = addNamespaces eventString
  # call bean.on
  bean.off this, eventString, callback
  # return instance
  this

# execute all listeners for the given event
Revolver::trigger = (eventString) ->
  # add revolver namespace to event(s)
  eventString = addNamespaces eventString
  # call bean.on
  bean.fire this, eventString
  # return instance
  this


# SELECTOR ENGINE

# use querySelectorAll out of the box
Revolver.$ = (selector, root) ->
  root = document if root is undefined
  root.querySelectorAll selector

# override the default selector engine
# (ex jQuery.find, Qwery, Sel, Sizzle, NWMatcher)
Revolver.setSelectorEngine = (e) ->
  bean.setSelectorEngine e
  Revolver.$ = e
  this


# TRANSITIONS

# namespace for all transitions
Revolver.transitions = {}

# a default transition
Revolver.transitions['default'] = (options) ->
  # hide current slide
  @slides[@currentSlide].setAttribute 'style', 'display: none'
  # show next slide
  @slides[@nextSlide].setAttribute 'style', 'display: block'
  # fire all 'transitionComplete' event listeners
  @trigger 'transitionComplete'
  # return instance
  this

Revolver.registerTransition = (name, fn) ->
  Revolver.transitions[name] = fn
  this


# return the Revolver object globally available
window.Revolver = Revolver

# support AMD
if typeof window.define is "function" && window.define.amd
  window.define "revolver", [], -> window.Revolver