diff --git a/Gruntfile.js b/Gruntfile.js index c32f79f0e..b058bf3a0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -66,6 +66,7 @@ module.exports = function (grunt) { 'js/dist/util.js': 'js/src/util.js', 'js/dist/alert.js': 'js/src/alert.js', 'js/dist/button.js': 'js/src/button.js', + 'js/dist/carousel.js': 'js/src/carousel.js', } } }, diff --git a/js/button.js b/js/button.js deleted file mode 100644 index 3e2e34512..000000000 --- a/js/button.js +++ /dev/null @@ -1,120 +0,0 @@ -/* ======================================================================== - * Bootstrap: button.js v3.3.4 - * http://getbootstrap.com/javascript/#buttons - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // BUTTON PUBLIC CLASS DEFINITION - // ============================== - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Button.DEFAULTS, options) - this.isLoading = false - } - - Button.VERSION = '3.3.4' - - Button.DEFAULTS = { - loadingText: 'loading...' - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - var $el = this.$element - var val = $el.is('input') ? 'val' : 'html' - var data = $el.data() - - state += 'Text' - - if (data.resetText == null) $el.data('resetText', $el[val]()) - - // push to event loop to allow forms to submit - setTimeout($.proxy(function () { - $el[val](data[state] == null ? this.options[state] : data[state]) - - if (state == 'loadingText') { - this.isLoading = true - $el.addClass(d).attr(d, d) - } else if (this.isLoading) { - this.isLoading = false - $el.removeClass(d).removeAttr(d) - } - }, this), 0) - } - - Button.prototype.toggle = function () { - var changed = true - var $parent = this.$element.closest('[data-toggle="buttons"]') - - if ($parent.length) { - var $input = this.$element.find('input') - if ($input.prop('type') == 'radio') { - if ($input.prop('checked')) changed = false - $parent.find('.active').removeClass('active') - this.$element.addClass('active') - } else if ($input.prop('type') == 'checkbox') { - if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false - this.$element.toggleClass('active') - } - $input.prop('checked', this.$element.hasClass('active')) - if (changed) $input.trigger('change') - } else { - this.$element.attr('aria-pressed', !this.$element.hasClass('active')) - this.$element.toggleClass('active') - } - } - - - // BUTTON PLUGIN DEFINITION - // ======================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.button') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.button', (data = new Button(this, options))) - - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - var old = $.fn.button - - $.fn.button = Plugin - $.fn.button.Constructor = Button - - - // BUTTON NO CONFLICT - // ================== - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - // BUTTON DATA-API - // =============== - - $(document) - .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - Plugin.call($btn, 'toggle') - if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() - }) - .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { - $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) - }) - -}(jQuery); diff --git a/js/dist/alert.js b/js/dist/alert.js index 4226dd61a..35e19874c 100644 --- a/js/dist/alert.js +++ b/js/dist/alert.js @@ -51,9 +51,7 @@ var Alert = (function ($) { function Alert(element) { _classCallCheck(this, Alert); - if (element) { - this.element = element; - } + this._element = element; } _createClass(Alert, [{ @@ -62,7 +60,7 @@ var Alert = (function ($) { // public value: function close(element) { - element = element || this.element; + element = element || this._element; var rootElement = this._getRootElement(element); var customEvent = this._triggerCloseEvent(rootElement); diff --git a/js/dist/alert.js.map b/js/dist/alert.js.map index aa106df1e..4d8c6173e 100644 Binary files a/js/dist/alert.js.map and b/js/dist/alert.js.map differ diff --git a/js/dist/button.js b/js/dist/button.js index 76aa09a7f..20be59dc7 100644 --- a/js/dist/button.js +++ b/js/dist/button.js @@ -54,7 +54,7 @@ var Button = (function ($) { function Button(element) { _classCallCheck(this, Button); - this.element = element; + this._element = element; } _createClass(Button, [{ @@ -64,14 +64,14 @@ var Button = (function ($) { value: function toggle() { var triggerChangeEvent = true; - var rootElement = $(this.element).closest(Selector.DATA_TOGGLE)[0]; + var rootElement = $(this._element).closest(Selector.DATA_TOGGLE)[0]; if (rootElement) { - var input = $(this.element).find(Selector.INPUT)[0]; + var input = $(this._element).find(Selector.INPUT)[0]; if (input) { if (input.type === 'radio') { - if (input.checked && $(this.element).hasClass(ClassName.ACTIVE)) { + if (input.checked && $(this._element).hasClass(ClassName.ACTIVE)) { triggerChangeEvent = false; } else { var activeElement = $(rootElement).find(Selector.ACTIVE)[0]; @@ -83,16 +83,16 @@ var Button = (function ($) { } if (triggerChangeEvent) { - input.checked = !$(this.element).hasClass(ClassName.ACTIVE); - $(this.element).trigger('change'); + input.checked = !$(this._element).hasClass(ClassName.ACTIVE); + $(this._element).trigger('change'); } } } else { - this.element.setAttribute('aria-pressed', !$(this.element).hasClass(ClassName.ACTIVE)); + this._element.setAttribute('aria-pressed', !$(this._element).hasClass(ClassName.ACTIVE)); } if (triggerChangeEvent) { - $(this.element).toggleClass(ClassName.ACTIVE); + $(this._element).toggleClass(ClassName.ACTIVE); } } }], [{ diff --git a/js/dist/button.js.map b/js/dist/button.js.map index 690b5e0dc..ac111ccf4 100644 Binary files a/js/dist/button.js.map and b/js/dist/button.js.map differ diff --git a/js/dist/carousel.js b/js/dist/carousel.js new file mode 100644 index 000000000..55fd16024 --- /dev/null +++ b/js/dist/carousel.js @@ -0,0 +1,418 @@ +'use strict'; + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +var Carousel = (function ($) { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME = 'carousel'; + var VERSION = '4.0.0'; + var DATA_KEY = 'bs.carousel'; + var JQUERY_NO_CONFLICT = $.fn[NAME]; + var TRANSITION_DURATION = 600; + + var Defaults = { + 'interval': 5000, + 'keyboard': true, + 'slide': false, + 'pause': 'hover', + 'wrap': true + }; + + var Direction = { + NEXT: 'next', + PREVIOUS: 'prev' + }; + + var Event = { + SLIDE: 'slide.bs.carousel', + SLID: 'slid.bs.carousel', + CLICK: 'click.bs.carousel.data-api', + LOAD: 'load' + }; + + var ClassName = { + CAROUSEL: 'carousel', + ACTIVE: 'active', + SLIDE: 'slide', + RIGHT: 'right', + LEFT: 'left', + ITEM: 'carousel-item' + }; + + var Selector = { + ACTIVE: '.active', + ACTIVE_ITEM: '.active.carousel-item', + ITEM: '.carousel-item', + NEXT_PREV: '.next, .prev', + INDICATORS: '.carousel-indicators', + DATA_SLIDE: '[data-slide], [data-slide-to]', + DATA_RIDE: '[data-ride="carousel"]' + }; + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Carousel = (function () { + function Carousel(element, config) { + _classCallCheck(this, Carousel); + + this._items = null; + this._interval = null; + this._activeElement = null; + + this._isPaused = false; + this._isSliding = false; + + this._config = config; + this._element = $(element)[0]; + this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0]; + + this._addEventListeners(); + } + + _createClass(Carousel, [{ + key: 'next', + + // public + + value: function next() { + if (!this._isSliding) { + this._slide(Direction.NEXT); + } + } + }, { + key: 'prev', + value: function prev() { + if (!this._isSliding) { + this._slide(Direction.PREVIOUS); + } + } + }, { + key: 'pause', + value: function pause(event) { + if (!event) { + this._isPaused = true; + } + + if ($(this._element).find(Selector.NEXT_PREV)[0] && Util.supportsTransitionEnd()) { + Util.triggerTransitionEnd(this._element); + this.cycle(true); + } + + clearInterval(this._interval); + this._interval = null; + } + }, { + key: 'cycle', + value: function cycle(event) { + if (!event) { + this._isPaused = false; + } + + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + + if (this._config.interval && !this._isPaused) { + this._interval = setInterval(this.next.bind(this), this._config.interval); + } + } + }, { + key: 'to', + value: function to(index) { + var _this = this; + + this._activeElement = $(this._element).find(Selector.ACTIVE_ITEM)[0]; + + var activeIndex = this._getItemIndex(this._activeElement); + + if (index > this._items.length - 1 || index < 0) { + return; + } + + if (this._isSliding) { + $(this._element).one(Event.SLID, function () { + return _this.to(index); + }); + return; + } + + if (activeIndex == index) { + this.pause(); + this.cycle(); + return; + } + + var direction = index > activeIndex ? Direction.NEXT : Direction.PREVIOUS; + + this._slide(direction, this._items[index]); + } + }, { + key: '_addEventListeners', + + // private + + value: function _addEventListeners() { + if (this._config.keyboard) { + $(this._element).on('keydown.bs.carousel', this._keydown.bind(this)); + } + + 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)); + } + } + }, { + key: '_keydown', + value: function _keydown(event) { + event.preventDefault(); + + if (/input|textarea/i.test(event.target.tagName)) return; + + switch (event.which) { + case 37: + this.prev();break; + case 39: + this.next();break; + default: + return; + } + } + }, { + key: '_getItemIndex', + value: function _getItemIndex(element) { + this._items = $.makeArray($(element).parent().find(Selector.ITEM)); + return this._items.indexOf(element); + } + }, { + key: '_getItemByDirection', + value: function _getItemByDirection(direction, activeElement) { + var isNextDirection = direction === Direction.NEXT; + var isPrevDirection = direction === Direction.PREVIOUS; + var activeIndex = this._getItemIndex(activeElement); + var lastItemIndex = this._items.length - 1; + var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex == lastItemIndex; + + if (isGoingToWrap && !this._config.wrap) { + return activeElement; + } + + var delta = direction == Direction.PREVIOUS ? -1 : 1; + var itemIndex = (activeIndex + delta) % this._items.length; + + return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex]; + } + }, { + key: '_triggerSlideEvent', + value: function _triggerSlideEvent(relatedTarget, directionalClassname) { + var slideEvent = $.Event(Event.SLIDE, { + relatedTarget: relatedTarget, + direction: directionalClassname + }); + + $(this._element).trigger(slideEvent); + + return slideEvent; + } + }, { + key: '_setActiveIndicatorElement', + value: function _setActiveIndicatorElement(element) { + if (this._indicatorsElement) { + $(this._indicatorsElement).find(Selector.ACTIVE).removeClass(ClassName.ACTIVE); + + var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)]; + + if (nextIndicator) { + $(nextIndicator).addClass(ClassName.ACTIVE); + } + } + } + }, { + key: '_slide', + value: function _slide(direction, element) { + var _this2 = this; + + var activeElement = $(this._element).find(Selector.ACTIVE_ITEM)[0]; + var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement); + + var isCycling = !!this._interval; + + var directionalClassName = direction == Direction.NEXT ? ClassName.LEFT : ClassName.RIGHT; + + if (nextElement && $(nextElement).hasClass(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 + return; + } + + this._isSliding = true; + + if (isCycling) { + this.pause(); + } + + this._setActiveIndicatorElement(nextElement); + + var slidEvent = $.Event(Event.SLID, { + relatedTarget: nextElement, + direction: directionalClassName + }); + + if (Util.supportsTransitionEnd() && $(this._element).hasClass(ClassName.SLIDE)) { + + $(nextElement).addClass(direction); + + Util.reflow(nextElement); + + $(activeElement).addClass(directionalClassName); + $(nextElement).addClass(directionalClassName); + + $(activeElement).one(Util.TRANSITION_END, function () { + $(nextElement).removeClass(directionalClassName).removeClass(direction); + + $(nextElement).addClass(ClassName.ACTIVE); + + $(activeElement).removeClass(ClassName.ACTIVE).removeClass(direction).removeClass(directionalClassName); + + _this2._isSliding = false; + + setTimeout(function () { + return $(_this2._element).trigger(slidEvent); + }, 0); + }).emulateTransitionEnd(TRANSITION_DURATION); + } else { + $(activeElement).removeClass(ClassName.ACTIVE); + $(nextElement).addClass(ClassName.ACTIVE); + + this._isSliding = false; + $(this._element).trigger(slidEvent); + } + + if (isCycling) { + this.cycle(); + } + } + }], [{ + key: '_jQueryInterface', + + // static + + value: function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY); + var _config = $.extend({}, Defaults, $(this).data()); + + if (typeof config === 'object') { + $.extend(_config, config); + } + + var action = typeof config === 'string' ? config : _config.slide; + + if (!data) { + data = new Carousel(this, _config); + $(this).data(DATA_KEY, data); + } + + if (typeof config == 'number') { + data.to(config); + } else if (action) { + data[action](); + } else if (_config.interval) { + data.pause(); + data.cycle(); + } + }); + } + }, { + key: '_dataApiClickHandler', + value: function _dataApiClickHandler(event) { + var selector = Util.getSelectorFromElement(this); + + if (!selector) { + return; + } + + var target = $(selector)[0]; + + if (!target || !$(target).hasClass(ClassName.CAROUSEL)) { + return; + } + + var config = $.extend({}, $(target).data(), $(this).data()); + + var slideIndex = this.getAttribute('data-slide-to'); + if (slideIndex) { + config.interval = false; + } + + Carousel._jQueryInterface.call($(target), config); + + if (slideIndex) { + $(target).data(DATA_KEY).to(slideIndex); + } + + event.preventDefault(); + } + }]); + + return Carousel; + })(); + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document).on(Event.CLICK, Selector.DATA_SLIDE, Carousel._dataApiClickHandler); + + $(window).on(Event.LOAD, function () { + $(Selector.DATA_RIDE).each(function () { + var $carousel = $(this); + Carousel._jQueryInterface.call($carousel, $carousel.data()); + }); + }); + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Carousel._jQueryInterface; + $.fn[NAME].Constructor = Carousel; + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return Carousel._jQueryInterface; + }; + + return Carousel; +})(jQuery); +//# sourceMappingURL=carousel.js.map \ No newline at end of file diff --git a/js/dist/carousel.js.map b/js/dist/carousel.js.map new file mode 100644 index 000000000..12b48721a Binary files /dev/null and b/js/dist/carousel.js.map differ diff --git a/js/dist/util.js b/js/dist/util.js index 4f25e200d..f3654eacf 100644 --- a/js/dist/util.js +++ b/js/dist/util.js @@ -63,7 +63,7 @@ var Util = (function ($) { setTimeout(function () { if (!called) { - $(_this).trigger(transition.end); + Util.triggerTransitionEnd(_this); } }, duration); @@ -110,6 +110,10 @@ var Util = (function ($) { new Function('bs', 'return bs')(element.offsetHeight); }, + triggerTransitionEnd: function triggerTransitionEnd(element) { + $(element).trigger(transition.end); + }, + supportsTransitionEnd: function supportsTransitionEnd() { return !!transition; } diff --git a/js/dist/util.js.map b/js/dist/util.js.map index 5f640b50e..34275a5b7 100644 Binary files a/js/dist/util.js.map and b/js/dist/util.js.map differ diff --git a/js/src/alert.js b/js/src/alert.js index 67a1ceda4..e5e8eeacb 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -49,16 +49,14 @@ const Alert = (($) => { class Alert { constructor(element) { - if (element) { - this.element = element - } + this._element = element } // public close(element) { - element = element || this.element + element = element || this._element let rootElement = this._getRootElement(element) let customEvent = this._triggerCloseEvent(rootElement) diff --git a/js/src/button.js b/js/src/button.js index 7e9344923..0f1dab2af 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -49,24 +49,24 @@ const Button = (($) => { class Button { constructor(element) { - this.element = element + this._element = element } // public toggle() { let triggerChangeEvent = true - let rootElement = $(this.element).closest( + let rootElement = $(this._element).closest( Selector.DATA_TOGGLE )[0] if (rootElement) { - let input = $(this.element).find(Selector.INPUT)[0] + let input = $(this._element).find(Selector.INPUT)[0] if (input) { if (input.type === 'radio') { if (input.checked && - $(this.element).hasClass(ClassName.ACTIVE)) { + $(this._element).hasClass(ClassName.ACTIVE)) { triggerChangeEvent = false } else { @@ -79,17 +79,17 @@ const Button = (($) => { } if (triggerChangeEvent) { - input.checked = !$(this.element).hasClass(ClassName.ACTIVE) - $(this.element).trigger('change') + input.checked = !$(this._element).hasClass(ClassName.ACTIVE) + $(this._element).trigger('change') } } } else { - this.element.setAttribute('aria-pressed', - !$(this.element).hasClass(ClassName.ACTIVE)) + this._element.setAttribute('aria-pressed', + !$(this._element).hasClass(ClassName.ACTIVE)) } if (triggerChangeEvent) { - $(this.element).toggleClass(ClassName.ACTIVE) + $(this._element).toggleClass(ClassName.ACTIVE) } } diff --git a/js/src/carousel.js b/js/src/carousel.js new file mode 100644 index 000000000..08476d666 --- /dev/null +++ b/js/src/carousel.js @@ -0,0 +1,426 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Carousel = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'carousel' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.carousel' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 600 + + const Defaults = { + interval : 5000, + keyboard : true, + slide : false, + pause : 'hover', + wrap : true + } + + const Direction = { + NEXT : 'next', + PREVIOUS : 'prev' + } + + const Event = { + SLIDE : 'slide.bs.carousel', + SLID : 'slid.bs.carousel', + CLICK : 'click.bs.carousel.data-api', + LOAD : 'load' + } + + const ClassName = { + CAROUSEL : 'carousel', + ACTIVE : 'active', + SLIDE : 'slide', + RIGHT : 'right', + LEFT : 'left', + ITEM : 'carousel-item' + } + + const Selector = { + ACTIVE : '.active', + ACTIVE_ITEM : '.active.carousel-item', + ITEM : '.carousel-item', + NEXT_PREV : '.next, .prev', + INDICATORS : '.carousel-indicators', + DATA_SLIDE : '[data-slide], [data-slide-to]', + DATA_RIDE : '[data-ride="carousel"]' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Carousel { + + constructor(element, config) { + + this._items = null + this._interval = null + this._activeElement = null + + this._isPaused = false + this._isSliding = false + + this._config = config + this._element = $(element)[0] + this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0] + + this._addEventListeners() + + } + + + // public + + next() { + if (!this._isSliding) { + this._slide(Direction.NEXT) + } + } + + prev() { + if (!this._isSliding) { + this._slide(Direction.PREVIOUS) + } + } + + pause(event) { + if (!event) { + this._isPaused = true + } + + if ($(this._element).find(Selector.NEXT_PREV)[0] && + Util.supportsTransitionEnd()) { + Util.triggerTransitionEnd(this._element) + this.cycle(true) + } + + clearInterval(this._interval) + this._interval = null + } + + cycle(event) { + if (!event) { + this._isPaused = false + } + + if (this._interval) { + clearInterval(this._interval) + this._interval = null + } + + if (this._config.interval && !this._isPaused) { + this._interval = setInterval( + this.next.bind(this), this._config.interval + ) + } + } + + to(index) { + this._activeElement = $(this._element).find(Selector.ACTIVE_ITEM)[0] + + let activeIndex = this._getItemIndex(this._activeElement) + + if (index > (this._items.length - 1) || index < 0) { + return + } + + if (this._isSliding) { + $(this._element).one(Event.SLID, () => this.to(index)) + return + } + + if (activeIndex == index) { + this.pause() + this.cycle() + return + } + + var direction = index > activeIndex ? + Direction.NEXT : + Direction.PREVIOUS + + this._slide(direction, this._items[index]) + } + + + // private + + _addEventListeners() { + if (this._config.keyboard) { + $(this._element) + .on('keydown.bs.carousel', this._keydown.bind(this)) + } + + 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)) + } + } + + _keydown(event) { + event.preventDefault() + + if (/input|textarea/i.test(event.target.tagName)) return + + switch (event.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + } + + _getItemIndex(element) { + this._items = $.makeArray($(element).parent().find(Selector.ITEM)) + return this._items.indexOf(element) + } + + _getItemByDirection(direction, activeElement) { + let isNextDirection = direction === Direction.NEXT + let isPrevDirection = direction === Direction.PREVIOUS + let activeIndex = this._getItemIndex(activeElement) + let lastItemIndex = (this._items.length - 1) + let isGoingToWrap = (isPrevDirection && activeIndex === 0) || + (isNextDirection && activeIndex == lastItemIndex) + + if (isGoingToWrap && !this._config.wrap) { + return activeElement + } + + let delta = direction == Direction.PREVIOUS ? -1 : 1 + let itemIndex = (activeIndex + delta) % this._items.length + + return itemIndex === -1 ? + this._items[this._items.length - 1] : this._items[itemIndex] + } + + + _triggerSlideEvent(relatedTarget, directionalClassname) { + let slideEvent = $.Event(Event.SLIDE, { + relatedTarget: relatedTarget, + direction: directionalClassname + }) + + $(this._element).trigger(slideEvent) + + return slideEvent + } + + _setActiveIndicatorElement(element) { + if (this._indicatorsElement) { + $(this._indicatorsElement) + .find(Selector.ACTIVE) + .removeClass(ClassName.ACTIVE) + + let nextIndicator = this._indicatorsElement.children[ + this._getItemIndex(element) + ] + + if (nextIndicator) { + $(nextIndicator).addClass(ClassName.ACTIVE) + } + } + } + + _slide(direction, element) { + let activeElement = $(this._element).find(Selector.ACTIVE_ITEM)[0] + let nextElement = element || activeElement && + this._getItemByDirection(direction, activeElement) + + let isCycling = !!this._interval + + let directionalClassName = direction == Direction.NEXT ? + ClassName.LEFT : + ClassName.RIGHT + + if (nextElement && $(nextElement).hasClass(ClassName.ACTIVE)) { + this._isSliding = false + return + } + + let slideEvent = this._triggerSlideEvent(nextElement, directionalClassName) + if (slideEvent.isDefaultPrevented()) { + return + } + + if (!activeElement || !nextElement) { + // some weirdness is happening, so we bail + return + } + + this._isSliding = true + + if (isCycling) { + this.pause() + } + + this._setActiveIndicatorElement(nextElement) + + var slidEvent = $.Event(Event.SLID, { + relatedTarget: nextElement, + direction: directionalClassName + }) + + if (Util.supportsTransitionEnd() && + $(this._element).hasClass(ClassName.SLIDE)) { + + $(nextElement).addClass(direction) + + Util.reflow(nextElement) + + $(activeElement).addClass(directionalClassName) + $(nextElement).addClass(directionalClassName) + + $(activeElement) + .one(Util.TRANSITION_END, () => { + $(nextElement) + .removeClass(directionalClassName) + .removeClass(direction) + + $(nextElement).addClass(ClassName.ACTIVE) + + $(activeElement) + .removeClass(ClassName.ACTIVE) + .removeClass(direction) + .removeClass(directionalClassName) + + this._isSliding = false + + setTimeout(() => $(this._element).trigger(slidEvent), 0) + + }) + .emulateTransitionEnd(TRANSITION_DURATION) + + } else { + $(activeElement).removeClass(ClassName.ACTIVE) + $(nextElement).addClass(ClassName.ACTIVE) + + this._isSliding = false + $(this._element).trigger(slidEvent) + } + + if (isCycling) { + this.cycle() + } + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = $.extend({}, Defaults, $(this).data()) + + if (typeof config === 'object') { + $.extend(_config, config) + } + + let action = typeof config === 'string' ? config : _config.slide + + if (!data) { + data = new Carousel(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config == 'number') { + data.to(config) + + } else if (action) { + data[action]() + + } else if (_config.interval) { + data.pause() + data.cycle() + } + }) + } + + static _dataApiClickHandler(event) { + let selector = Util.getSelectorFromElement(this) + + if (!selector) { + return + } + + let target = $(selector)[0] + + if (!target || !$(target).hasClass(ClassName.CAROUSEL)) { + return + } + + let config = $.extend({}, $(target).data(), $(this).data()) + + let slideIndex = this.getAttribute('data-slide-to') + if (slideIndex) { + config.interval = false + } + + Carousel._jQueryInterface.call($(target), config) + + if (slideIndex) { + $(target).data(DATA_KEY).to(slideIndex) + } + + event.preventDefault() + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document) + .on(Event.CLICK, Selector.DATA_SLIDE, Carousel._dataApiClickHandler) + + $(window).on(Event.LOAD, function () { + $(Selector.DATA_RIDE).each(function () { + let $carousel = $(this) + Carousel._jQueryInterface.call($carousel, $carousel.data()) + }) + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Carousel._jQueryInterface + $.fn[NAME].Constructor = Carousel + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Carousel._jQueryInterface + } + + return Carousel + +})(jQuery) + +export default Carousel diff --git a/js/src/util.js b/js/src/util.js index abc548a45..c9ffbe555 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -60,7 +60,7 @@ const Util = (($) => { setTimeout(() => { if (!called) { - $(this).trigger(transition.end) + Util.triggerTransitionEnd(this) } }, duration) @@ -109,6 +109,10 @@ const Util = (($) => { new Function('bs', 'return bs')(element.offsetHeight) }, + triggerTransitionEnd(element) { + $(element).trigger(transition.end) + }, + supportsTransitionEnd() { return !!transition } diff --git a/js/tests/index.html b/js/tests/index.html index 29c84e2cc..0ba54e802 100644 --- a/js/tests/index.html +++ b/js/tests/index.html @@ -133,9 +133,9 @@ + - diff --git a/js/tests/unit/carousel.js b/js/tests/unit/carousel.js index 39d250598..a8a36ad32 100644 --- a/js/tests/unit/carousel.js +++ b/js/tests/unit/carousel.js @@ -56,13 +56,13 @@ $(function () { + '
  • ' + '' + '