diff options
Diffstat (limited to 'js/src')
| -rw-r--r-- | js/src/alert.js | 6 | ||||
| -rw-r--r-- | js/src/button.js | 18 | ||||
| -rw-r--r-- | js/src/carousel.js | 426 | ||||
| -rw-r--r-- | js/src/util.js | 6 |
4 files changed, 442 insertions, 14 deletions
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 } |
