diff options
| author | fat <[email protected]> | 2015-01-03 13:58:44 -0800 |
|---|---|---|
| committer | fat <[email protected]> | 2015-02-11 11:29:43 -0800 |
| commit | 834220ea20ce5b7cd31edfb624a28b4bf8b29a6a (patch) | |
| tree | 19bfdadb0c140df437e7b7034e5e1f5ce58343cc /js/carousel.js | |
| parent | aed1cd31218113d67d2eca3296edf5d1700b19b8 (diff) | |
| download | bootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.tar.xz bootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.zip | |
bootstrap onto closure
Diffstat (limited to 'js/carousel.js')
| -rw-r--r-- | js/carousel.js | 669 |
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())) + }) +}) |
