aboutsummaryrefslogtreecommitdiff
path: root/js/carousel.js
diff options
context:
space:
mode:
authorfat <[email protected]>2015-01-03 13:58:44 -0800
committerfat <[email protected]>2015-02-11 11:29:43 -0800
commit834220ea20ce5b7cd31edfb624a28b4bf8b29a6a (patch)
tree19bfdadb0c140df437e7b7034e5e1f5ce58343cc /js/carousel.js
parentaed1cd31218113d67d2eca3296edf5d1700b19b8 (diff)
downloadbootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.tar.xz
bootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.zip
bootstrap onto closure
Diffstat (limited to 'js/carousel.js')
-rw-r--r--js/carousel.js669
1 files changed, 504 insertions, 165 deletions
diff --git a/js/carousel.js b/js/carousel.js
index 8aa04e117..8f33f4860 100644
--- a/js/carousel.js
+++ b/js/carousel.js
@@ -1,237 +1,576 @@
-/* ========================================================================
- * Bootstrap: carousel.js v3.3.2
+/** =======================================================================
+ * Bootstrap: carousel.js v4.0.0
* http://getbootstrap.com/javascript/#carousel
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
+ * ========================================================================
+ * @fileoverview - Bootstrap's carousel. A slideshow component for cycling
+ * through elements, like a carousel. Nested carousels are not supported.
+ *
+ * Public Methods & Properties:
+ *
+ * + $.carousel
+ * + $.carousel.noConflict
+ * + $.carousel.Constructor
+ * + $.carousel.Constructor.VERSION
+ * + $.carousel.Constructor.Defaults
+ * + $.carousel.Constructor.Defaults.interval
+ * + $.carousel.Constructor.Defaults.pause
+ * + $.carousel.Constructor.Defaults.wrap
+ * + $.carousel.Constructor.Defaults.keyboard
+ * + $.carousel.Constructor.Defaults.slide
+ * + $.carousel.Constructor.prototype.next
+ * + $.carousel.Constructor.prototype.prev
+ * + $.carousel.Constructor.prototype.pause
+ * + $.carousel.Constructor.prototype.cycle
+ *
+ * ========================================================================
+ */
+
+'use strict';
+
+
+/**
+ * Our carousel class.
+ * @param {Element!} element
+ * @param {Object=} opt_config
+ * @constructor
+ */
+var Carousel = function (element, opt_config) {
+
+ /** @private {Element} */
+ this._element = $(element)[0]
+
+ /** @private {Element} */
+ this._indicatorsElement = $(this._element).find(Carousel._Selector.INDICATORS)[0]
+
+ /** @private {?Object} */
+ this._config = opt_config || null
+ /** @private {boolean} */
+ this._isPaused = false
-+function ($) {
- 'use strict';
+ /** @private {boolean} */
+ this._isSliding = false
+
+ /** @private {?number} */
+ this._interval = null
+
+ /** @private {?Element} */
+ this._activeElement = null
+
+ /** @private {?Array} */
+ this._items = null
+
+ this._addEventListeners()
+
+}
+
+
+/**
+ * @const
+ * @type {string}
+ */
+Carousel['VERSION'] = '4.0.0'
+
+
+/**
+ * @const
+ * @type {Object}
+ */
+Carousel['Defaults'] = {
+ 'interval' : 5000,
+ 'pause' : 'hover',
+ 'wrap' : true,
+ 'keyboard' : true,
+ 'slide' : false
+}
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+Carousel._NAME = 'carousel'
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+Carousel._DATA_KEY = 'bs.carousel'
+
+
+/**
+ * @const
+ * @type {number}
+ * @private
+ */
+Carousel._TRANSITION_DURATION = 600
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+Carousel._Direction = {
+ NEXT : 'next',
+ PREVIOUS : 'prev'
+}
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+Carousel._Event = {
+ SLIDE : 'slide.bs.carousel',
+ SLID : 'slid.bs.carousel'
+}
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+Carousel._ClassName = {
+ CAROUSEL : 'carousel',
+ ACTIVE : 'active',
+ SLIDE : 'slide',
+ RIGHT : 'right',
+ LEFT : 'left',
+ ITEM : 'carousel-item'
+}
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+Carousel._Selector = {
+ ACTIVE : '.active',
+ ACTIVE_ITEM : '.active.carousel-item',
+ ITEM : '.carousel-item',
+ NEXT_PREV : '.next, .prev',
+ INDICATORS : '.carousel-indicators'
+}
+
+
+/**
+ * @const
+ * @type {Function}
+ * @private
+ */
+Carousel._JQUERY_NO_CONFLICT = $.fn[Carousel._NAME]
+
+
+/**
+ * @param {Object=} opt_config
+ * @this {jQuery}
+ * @return {jQuery}
+ * @private
+ */
+Carousel._jQueryInterface = function (opt_config) {
+ return this.each(function () {
+ var data = $(this).data(Carousel._DATA_KEY)
+ var config = $.extend({}, Carousel['Defaults'], $(this).data(), typeof opt_config == 'object' && opt_config)
+ var action = typeof opt_config == 'string' ? opt_config : config.slide
+
+ if (!data) {
+ data = new Carousel(this, config)
+ $(this).data(Carousel._DATA_KEY, data)
+ }
+
+ if (typeof opt_config == 'number') {
+ data.to(opt_config)
- // CAROUSEL CLASS DEFINITION
- // =========================
+ } else if (action) {
+ data[action]()
+
+ } else if (config.interval) {
+ data['pause']()
+ data['cycle']()
+ }
+ })
+}
- var Carousel = function (element, options) {
- this.$element = $(element)
- this.$indicators = this.$element.find('.carousel-indicators')
- this.options = options
- this.paused =
- this.sliding =
- this.interval =
- this.$active =
- this.$items = null
- this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+/**
+ * Click handler for data api
+ * @param {Event} event
+ * @this {Element}
+ * @private
+ */
+Carousel._dataApiClickHandler = function (event) {
+ var selector = Bootstrap.getSelectorFromElement(this)
- this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
- .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
- .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+ if (!selector) {
+ return
}
- Carousel.VERSION = '3.3.2'
+ var target = $(selector)[0]
+
+ if (!target || !$(target).hasClass(Carousel._ClassName.CAROUSEL)) {
+ return
+ }
- Carousel.TRANSITION_DURATION = 600
+ var config = $.extend({}, $(target).data(), $(this).data())
- Carousel.DEFAULTS = {
- interval: 5000,
- pause: 'hover',
- wrap: true,
- keyboard: true
+ var slideIndex = this.getAttribute('data-slide-to')
+ if (slideIndex) {
+ config.interval = false
}
- Carousel.prototype.keydown = function (e) {
- if (/input|textarea/i.test(e.target.tagName)) return
- switch (e.which) {
- case 37: this.prev(); break
- case 39: this.next(); break
- default: return
- }
+ Carousel._jQueryInterface.call($(target), config)
- e.preventDefault()
+ if (slideIndex) {
+ $(target).data(Carousel._DATA_KEY).to(slideIndex)
}
- Carousel.prototype.cycle = function (e) {
- e || (this.paused = false)
+ event.preventDefault()
+}
- this.interval && clearInterval(this.interval)
- this.options.interval
- && !this.paused
- && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+/**
+ * Advance the carousel to the next slide
+ */
+Carousel.prototype['next'] = function () {
+ if (!this._isSliding) {
+ this._slide(Carousel._Direction.NEXT)
+ }
+}
+
- return this
+/**
+ * Return the carousel to the previous slide
+ */
+Carousel.prototype['prev'] = function () {
+ if (!this._isSliding) {
+ this._slide(Carousel._Direction.PREVIOUS)
}
+}
- Carousel.prototype.getItemIndex = function (item) {
- this.$items = item.parent().children('.carousel-item')
- return this.$items.index(item || this.$active)
+
+/**
+ * Pause the carousel cycle
+ * @param {Event=} opt_event
+ */
+Carousel.prototype['pause'] = function (opt_event) {
+ if (!opt_event) {
+ this._isPaused = true
}
- Carousel.prototype.getItemForDirection = function (direction, active) {
- var activeIndex = this.getItemIndex(active)
- var willWrap = (direction == 'prev' && activeIndex === 0)
- || (direction == 'next' && activeIndex == (this.$items.length - 1))
- if (willWrap && !this.options.wrap) return active
- var delta = direction == 'prev' ? -1 : 1
- var itemIndex = (activeIndex + delta) % this.$items.length
- return this.$items.eq(itemIndex)
+ if ($(this._element).find(Carousel._Selector.NEXT_PREV)[0] && Bootstrap.transition) {
+ $(this._element).trigger(Bootstrap.transition.end)
+ this['cycle'](true)
}
- Carousel.prototype.to = function (pos) {
- var that = this
- var activeIndex = this.getItemIndex(this.$active = this.$element.find('.carousel-item.active'))
+ clearInterval(this._interval)
+ this._interval = null
+}
+
- if (pos > (this.$items.length - 1) || pos < 0) return
+/**
+ * Cycle to the next carousel item
+ * @param {Event|boolean=} opt_event
+ */
+Carousel.prototype['cycle'] = function (opt_event) {
+ if (!opt_event) {
+ this._isPaused = false
+ }
- if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
- if (activeIndex == pos) return this.pause().cycle()
+ if (this._interval) {
+ clearInterval(this._interval)
+ this._interval = null
+ }
- return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+ if (this._config['interval'] && !this._isPaused) {
+ this._interval = setInterval(this['next'].bind(this), this._config['interval'])
}
+}
- Carousel.prototype.pause = function (e) {
- e || (this.paused = true)
- if (this.$element.find('.next, .prev').length && $.support.transition) {
- this.$element.trigger($.support.transition.end)
- this.cycle(true)
- }
+/**
+ * @return {Object}
+ */
+Carousel.prototype['getConfig'] = function () {
+ return this._config
+}
+
- this.interval = clearInterval(this.interval)
+/**
+ * Move active carousel item to specified index
+ * @param {number} index
+ */
+Carousel.prototype.to = function (index) {
+ this._activeElement = $(this._element).find(Carousel._Selector.ACTIVE_ITEM)[0]
- return this
+ var activeIndex = this._getItemIndex(this._activeElement)
+
+ if (index > (this._items.length - 1) || index < 0) {
+ return
}
- Carousel.prototype.next = function () {
- if (this.sliding) return
- return this.slide('next')
+ if (this._isSliding) {
+ $(this._element).one(Carousel._Event.SLID, function () { this.to(index) }.bind(this))
+ return
}
- Carousel.prototype.prev = function () {
- if (this.sliding) return
- return this.slide('prev')
+ if (activeIndex == index) {
+ this['pause']()
+ this['cycle']()
+ return
}
- Carousel.prototype.slide = function (type, next) {
- var $active = this.$element.find('.carousel-item.active')
- var $next = next || this.getItemForDirection(type, $active)
- var isCycling = this.interval
- var direction = type == 'next' ? 'left' : 'right'
- var that = this
+ var direction = index > activeIndex ?
+ Carousel._Direction.NEXT :
+ Carousel._Direction.PREVIOUS
- if ($next.hasClass('active')) return (this.sliding = false)
+ this._slide(direction, this._items[index])
+}
- var relatedTarget = $next[0]
- var slideEvent = $.Event('slide.bs.carousel', {
- relatedTarget: relatedTarget,
- direction: direction
- })
- this.$element.trigger(slideEvent)
- if (slideEvent.isDefaultPrevented()) return
- this.sliding = true
+/**
+ * Add event listeners to root element
+ * @private
+ */
+Carousel.prototype._addEventListeners = function () {
+ if (this._config['keyboard']) {
+ $(this._element).on('keydown.bs.carousel', this._keydown.bind(this))
+ }
- isCycling && this.pause()
+ if (this._config['pause'] == 'hover' && !('ontouchstart' in document.documentElement)) {
+ $(this._element)
+ .on('mouseenter.bs.carousel', this['pause'].bind(this))
+ .on('mouseleave.bs.carousel', this['cycle'].bind(this))
+ }
+}
- if (this.$indicators.length) {
- this.$indicators.find('.active').removeClass('active')
- var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
- $nextIndicator && $nextIndicator.addClass('active')
- }
- var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
- if ($.support.transition && this.$element.hasClass('slide')) {
- $next.addClass(type)
- $next[0].offsetWidth // force reflow
- $active.addClass(direction)
- $next.addClass(direction)
- $active
- .one('bsTransitionEnd', function () {
- $next.removeClass([type, direction].join(' ')).addClass('active')
- $active.removeClass(['active', direction].join(' '))
- that.sliding = false
- setTimeout(function () {
- that.$element.trigger(slidEvent)
- }, 0)
- })
- .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
- } else {
- $active.removeClass('active')
- $next.addClass('active')
- this.sliding = false
- this.$element.trigger(slidEvent)
- }
+/**
+ * Keydown handler
+ * @param {Event} event
+ * @private
+ */
+Carousel.prototype._keydown = function (event) {
+ event.preventDefault()
- isCycling && this.cycle()
+ if (/input|textarea/i.test(event.target.tagName)) return
- return this
+ switch (event.which) {
+ case 37: this['prev'](); break
+ case 39: this['next'](); break
+ default: return
+ }
+}
+
+
+/**
+ * Get item index
+ * @param {Element} element
+ * @return {number}
+ * @private
+ */
+Carousel.prototype._getItemIndex = function (element) {
+ this._items = $.makeArray($(element).parent().find(Carousel._Selector.ITEM))
+
+ return this._items.indexOf(element)
+}
+
+
+/**
+ * Get next displayed item based on direction
+ * @param {Carousel._Direction} direction
+ * @param {Element} activeElement
+ * @return {Element}
+ * @private
+ */
+Carousel.prototype._getItemByDirection = function (direction, activeElement) {
+ var activeIndex = this._getItemIndex(activeElement)
+ var isGoingToWrap = (direction === Carousel._Direction.PREVIOUS && activeIndex === 0) ||
+ (direction === Carousel._Direction.NEXT && activeIndex == (this._items.length - 1))
+
+ if (isGoingToWrap && !this._config['wrap']) {
+ return activeElement
}
+ var delta = direction == Carousel._Direction.PREVIOUS ? -1 : 1
+ var itemIndex = (activeIndex + delta) % this._items.length
- // CAROUSEL PLUGIN DEFINITION
- // ==========================
+ return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex]
+}
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.carousel')
- var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
- var action = typeof option == 'string' ? option : options.slide
- if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
- if (typeof option == 'number') data.to(option)
- else if (action) data[action]()
- else if (options.interval) data.pause().cycle()
- })
+/**
+ * Trigger slide event on element
+ * @param {Element} relatedTarget
+ * @param {Carousel._ClassName} directionalClassname
+ * @return {$.Event}
+ * @private
+ */
+Carousel.prototype._triggerSlideEvent = function (relatedTarget, directionalClassname) {
+ var slideEvent = $.Event(Carousel._Event.SLIDE, {
+ relatedTarget: relatedTarget,
+ direction: directionalClassname
+ })
+
+ $(this._element).trigger(slideEvent)
+
+ return slideEvent
+}
+
+
+/**
+ * Set the active indicator if available
+ * @param {Element} element
+ * @private
+ */
+Carousel.prototype._setActiveIndicatorElement = function (element) {
+ if (this._indicatorsElement) {
+ $(this._indicatorsElement)
+ .find(Carousel._Selector.ACTIVE)
+ .removeClass(Carousel._ClassName.ACTIVE)
+
+ var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)]
+ if (nextIndicator) {
+ $(nextIndicator).addClass(Carousel._ClassName.ACTIVE)
+ }
}
+}
+
+
+/**
+ * Slide the carousel element in a direction
+ * @param {Carousel._Direction} direction
+ * @param {Element=} opt_nextElement
+ */
+Carousel.prototype._slide = function (direction, opt_nextElement) {
+ var activeElement = $(this._element).find(Carousel._Selector.ACTIVE_ITEM)[0]
+ var nextElement = opt_nextElement || activeElement && this._getItemByDirection(direction, activeElement)
+
+ var isCycling = !!this._interval
- var old = $.fn.carousel
+ var directionalClassName = direction == Carousel._Direction.NEXT ?
+ Carousel._ClassName.LEFT :
+ Carousel._ClassName.RIGHT
- $.fn.carousel = Plugin
- $.fn.carousel.Constructor = Carousel
+ if (nextElement && $(nextElement).hasClass(Carousel._ClassName.ACTIVE)) {
+ this._isSliding = false
+ return
+ }
+
+ var slideEvent = this._triggerSlideEvent(nextElement, directionalClassName)
+ if (slideEvent.isDefaultPrevented()) {
+ return
+ }
+ if (!activeElement || !nextElement) {
+ // some weirdness is happening, so we bail (maybe throw exception here alerting user that they're dom is off
+ return
+ }
- // CAROUSEL NO CONFLICT
- // ====================
+ this._isSliding = true
- $.fn.carousel.noConflict = function () {
- $.fn.carousel = old
- return this
+ if (isCycling) {
+ this['pause']()
}
+ this._setActiveIndicatorElement(nextElement)
- // CAROUSEL DATA-API
- // =================
+ var slidEvent = $.Event(Carousel._Event.SLID, { relatedTarget: nextElement, direction: directionalClassName })
- var clickHandler = function (e) {
- var href
- var $this = $(this)
- var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
- if (!$target.hasClass('carousel')) return
- var options = $.extend({}, $target.data(), $this.data())
- var slideIndex = $this.attr('data-slide-to')
- if (slideIndex) options.interval = false
+ if (Bootstrap.transition && $(this._element).hasClass(Carousel._ClassName.SLIDE)) {
+ $(nextElement).addClass(direction)
- Plugin.call($target, options)
+ Bootstrap.reflow(nextElement)
- if (slideIndex) {
- $target.data('bs.carousel').to(slideIndex)
- }
+ $(activeElement).addClass(directionalClassName)
+ $(nextElement).addClass(directionalClassName)
+
+ $(activeElement)
+ .one(Bootstrap.TRANSITION_END, function () {
+ $(nextElement)
+ .removeClass(directionalClassName)
+ .removeClass(direction)
+
+ $(nextElement).addClass(Carousel._ClassName.ACTIVE)
+
+ $(activeElement)
+ .removeClass(Carousel._ClassName.ACTIVE)
+ .removeClass(direction)
+ .removeClass(directionalClassName)
- e.preventDefault()
+ this._isSliding = false
+
+ setTimeout(function () {
+ $(this._element).trigger(slidEvent)
+ }.bind(this), 0)
+ }.bind(this))
+ .emulateTransitionEnd(Carousel._TRANSITION_DURATION)
+
+ } else {
+ $(activeElement).removeClass(Carousel._ClassName.ACTIVE)
+ $(nextElement).addClass(Carousel._ClassName.ACTIVE)
+
+ this._isSliding = false
+ $(this._element).trigger(slidEvent)
+ }
+
+ if (isCycling) {
+ this['cycle']()
}
+}
- $(document)
- .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
- .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
- $(window).on('load', function () {
- $('[data-ride="carousel"]').each(function () {
- var $carousel = $(this)
- Plugin.call($carousel, $carousel.data())
- })
- })
+/**
+ * ------------------------------------------------------------------------
+ * jQuery Interface + noConflict implementaiton
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * @const
+ * @type {Function}
+ */
+$.fn[Carousel._NAME] = Carousel._jQueryInterface
+
-}(jQuery);
+/**
+ * @const
+ * @type {Function}
+ */
+$.fn[Carousel._NAME]['Constructor'] = Carousel
+
+
+/**
+ * @const
+ * @type {Function}
+ */
+$.fn[Carousel._NAME]['noConflict'] = function () {
+ $.fn[Carousel._NAME] = Carousel._JQUERY_NO_CONFLICT
+ return this
+}
+
+
+/**
+ * ------------------------------------------------------------------------
+ * Data Api implementation
+ * ------------------------------------------------------------------------
+ */
+
+$(document)
+ .on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', Carousel._dataApiClickHandler)
+
+$(window).on('load', function () {
+ $('[data-ride="carousel"]').each(function () {
+ var $carousel = $(this)
+ Carousel._jQueryInterface.call($carousel, /** @type {Object} */ ($carousel.data()))
+ })
+})