From 0724bd91ff81b5eca0addce0c336c72b3ec10be5 Mon Sep 17 00:00:00 2001 From: fat Date: Thu, 7 May 2015 12:48:22 -0700 Subject: es6 alert :| --- js/src/alert.js | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ js/src/util.js | 118 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 js/src/alert.js create mode 100644 js/src/util.js (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js new file mode 100644 index 000000000..bd12b1a55 --- /dev/null +++ b/js/src/alert.js @@ -0,0 +1,168 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): alert.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import Util from 'util' + + +/** + * -------------------------------------------------------------------------- + * Constants + * -------------------------------------------------------------------------- + */ + +const NAME = 'alert' +const VERSION = '4.0.0' +const DATA_KEY = 'bs.alert' +const JQUERY_NO_CONFLICT = $.fn[NAME] +const TRANSITION_DURATION = 150 + +const Selector = { + DISMISS : '[data-dismiss="alert"]' +} + +const Event = { + CLOSE : 'close.bs.alert', + CLOSED : 'closed.bs.alert', + CLICK : 'click.bs.alert.data-api' +} + +const ClassName = { + ALERT : 'alert', + FADE : 'fade', + IN : 'in' +} + + +/** + * -------------------------------------------------------------------------- + * Class Definition + * -------------------------------------------------------------------------- + */ + +export class Alert { + + constructor(element) { + if (element) { + this.element = element + } + } + + + // public + + close(element) { + let rootElement = this._getRootElement(element) + let customEvent = this._triggerCloseEvent(rootElement) + + if (customEvent.isDefaultPrevented()) { + return + } + + this._removeElement(rootElement) + } + + + // private + + _getRootElement(element) { + let parent = false + let selector = Util.getSelectorFromElement(element) + + if (selector) { + parent = $(selector)[0] + } + + if (!parent) { + parent = $(element).closest('.' + ClassName.ALERT)[0] + } + + return parent + } + + _triggerCloseEvent(element) { + var closeEvent = $.Event(Event.CLOSE) + $(element).trigger(closeEvent) + return closeEvent + } + + _removeElement(element) { + $(element).removeClass(ClassName.IN) + + if (!Util.supportsTransitionEnd() || !$(element).hasClass(ClassName.FADE)) { + this._destroyElement(element) + return + } + + $(element) + .one(Util.TRANSITION_END, this._destroyElement.bind(this, element)) + .emulateTransitionEnd(TRANSITION_DURATION) + } + + _destroyElement(element) { + $(element) + .detach() + .trigger(Event.CLOSED) + .remove() + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new Alert(this) + $element.data(DATA_KEY, data) + } + + if (config === 'close') { + data[config](this) + } + }) + } + + static _handleDismiss(alertInstance) { + return function (event) { + if (event) { + event.preventDefault() + } + + alertInstance.close(this) + } + } + +} + + +/** + * -------------------------------------------------------------------------- + * Data Api implementation + * -------------------------------------------------------------------------- + */ + +$(document).on( + Event.CLICK, + Selector.DISMISS, + Alert._handleDismiss(new Alert()) +) + + +/** + * -------------------------------------------------------------------------- + * jQuery + * -------------------------------------------------------------------------- + */ + +$.fn[NAME] = Alert._jQueryInterface +$.fn[NAME].Constructor = Alert +$.fn[NAME].noConflict = function () { + $.fn[NAME] = Alert._JQUERY_NO_CONFLICT + return Alert._jQueryInterface +} diff --git a/js/src/util.js b/js/src/util.js new file mode 100644 index 000000000..e9542149e --- /dev/null +++ b/js/src/util.js @@ -0,0 +1,118 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): util.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + +/** + * -------------------------------------------------------------------------- + * Public Util Api + * -------------------------------------------------------------------------- + */ + +var Util = { + + TRANSITION_END: 'bsTransitionEnd', + + getUID(prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + }, + + getSelectorFromElement(element) { + let selector = element.getAttribute('data-target') + + if (!selector) { + selector = element.getAttribute('href') || '' + selector = /^#[a-z]/i.test(selector) ? selector : null + } + + return selector + }, + + reflow(element) { + new Function('bs', 'return bs')(element.offsetHeight) + }, + + supportsTransitionEnd() { + return !!transition + } + +} + +export default Util + + +/** + * -------------------------------------------------------------------------- + * Private TransitionEnd Helpers + * -------------------------------------------------------------------------- + */ + +let transition = false + +const TransitionEndEvent = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' +} + +function getSpecialTransitionEndEvent() { + return { + bindType: transition.end, + delegateType: transition.end, + handle: function (event) { + if ($(event.target).is(this)) { + return event.handleObj.handler.apply(this, arguments) + } + } + } +} + +function transitionEndTest() { + if (window.QUnit) { + return false + } + + let el = document.createElement('bootstrap') + + for (var name in TransitionEndEvent) { + if (el.style[name] !== undefined) { + return { end: TransitionEndEvent[name] } + } + } + + return false +} + +function transitionEndEmulator(duration) { + let called = false + + $(this).one(Util.TRANSITION_END, function () { + called = true + }) + + setTimeout(() => { + if (!called) { + $(this).trigger(transition.end) + } + }, duration) + + return this +} + +function setTransitionEndSupport() { + transition = transitionEndTest() + + $.fn.emulateTransitionEnd = transitionEndEmulator + + if (Util.supportsTransitionEnd()) { + $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent() + } +} + +setTransitionEndSupport() -- cgit v1.2.3 From c3a79b1a8c2fa8d7fc8edcd3e626dad8b45d5dc3 Mon Sep 17 00:00:00 2001 From: fat Date: Thu, 7 May 2015 16:34:28 -0700 Subject: change the export pattern to protect against leaking globals --- js/src/alert.js | 238 +++++++++++++++++++++++++++++--------------------------- js/src/util.js | 166 ++++++++++++++++++++------------------- 2 files changed, 210 insertions(+), 194 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index bd12b1a55..d69ad8910 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,3 +1,6 @@ +import Util from './util' + + /** * -------------------------------------------------------------------------- * Bootstrap (v4.0.0): alert.js @@ -5,164 +8,171 @@ * -------------------------------------------------------------------------- */ -import Util from 'util' +const Alert = (() => { -/** - * -------------------------------------------------------------------------- - * Constants - * -------------------------------------------------------------------------- - */ + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ -const NAME = 'alert' -const VERSION = '4.0.0' -const DATA_KEY = 'bs.alert' -const JQUERY_NO_CONFLICT = $.fn[NAME] -const TRANSITION_DURATION = 150 + const NAME = 'alert' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.alert' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 150 -const Selector = { - DISMISS : '[data-dismiss="alert"]' -} + const Selector = { + DISMISS : '[data-dismiss="alert"]' + } -const Event = { - CLOSE : 'close.bs.alert', - CLOSED : 'closed.bs.alert', - CLICK : 'click.bs.alert.data-api' -} + const Event = { + CLOSE : 'close.bs.alert', + CLOSED : 'closed.bs.alert', + CLICK : 'click.bs.alert.data-api' + } -const ClassName = { - ALERT : 'alert', - FADE : 'fade', - IN : 'in' -} + const ClassName = { + ALERT : 'alert', + FADE : 'fade', + IN : 'in' + } -/** - * -------------------------------------------------------------------------- - * Class Definition - * -------------------------------------------------------------------------- - */ + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ -export class Alert { + class Alert { - constructor(element) { - if (element) { - this.element = element + constructor(element) { + if (element) { + this.element = element + } } - } - // public + // public + + close(element) { + let rootElement = this._getRootElement(element) + let customEvent = this._triggerCloseEvent(rootElement) - close(element) { - let rootElement = this._getRootElement(element) - let customEvent = this._triggerCloseEvent(rootElement) + if (customEvent.isDefaultPrevented()) { + return + } - if (customEvent.isDefaultPrevented()) { - return + this._removeElement(rootElement) } - this._removeElement(rootElement) - } + // private - // private + _getRootElement(element) { + let parent = false + let selector = Util.getSelectorFromElement(element) - _getRootElement(element) { - let parent = false - let selector = Util.getSelectorFromElement(element) + if (selector) { + parent = $(selector)[0] + } - if (selector) { - parent = $(selector)[0] + if (!parent) { + parent = $(element).closest('.' + ClassName.ALERT)[0] + } + + return parent } - if (!parent) { - parent = $(element).closest('.' + ClassName.ALERT)[0] + _triggerCloseEvent(element) { + var closeEvent = $.Event(Event.CLOSE) + $(element).trigger(closeEvent) + return closeEvent } - return parent - } + _removeElement(element) { + $(element).removeClass(ClassName.IN) - _triggerCloseEvent(element) { - var closeEvent = $.Event(Event.CLOSE) - $(element).trigger(closeEvent) - return closeEvent - } + if (!Util.supportsTransitionEnd() || + !$(element).hasClass(ClassName.FADE)) { + this._destroyElement(element) + return + } - _removeElement(element) { - $(element).removeClass(ClassName.IN) + $(element) + .one(Util.TRANSITION_END, this._destroyElement.bind(this, element)) + .emulateTransitionEnd(TRANSITION_DURATION) + } - if (!Util.supportsTransitionEnd() || !$(element).hasClass(ClassName.FADE)) { - this._destroyElement(element) - return + _destroyElement(element) { + $(element) + .detach() + .trigger(Event.CLOSED) + .remove() } - $(element) - .one(Util.TRANSITION_END, this._destroyElement.bind(this, element)) - .emulateTransitionEnd(TRANSITION_DURATION) - } - _destroyElement(element) { - $(element) - .detach() - .trigger(Event.CLOSED) - .remove() - } + // static + static _jQueryInterface(config) { + return this.each(function () { + let $element = $(this) + let data = $element.data(DATA_KEY) - // static + if (!data) { + data = new Alert(this) + $element.data(DATA_KEY, data) + } - static _jQueryInterface(config) { - return this.each(function () { - let $element = $(this) - let data = $element.data(DATA_KEY) + if (config === 'close') { + data[config](this) + } + }) + } - if (!data) { - data = new Alert(this) - $element.data(DATA_KEY, data) - } + static _handleDismiss(alertInstance) { + return function (event) { + if (event) { + event.preventDefault() + } - if (config === 'close') { - data[config](this) + alertInstance.close(this) } - }) + } + } - static _handleDismiss(alertInstance) { - return function (event) { - if (event) { - event.preventDefault() - } - alertInstance.close(this) - } - } + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ -} + $(document).on( + Event.CLICK, + Selector.DISMISS, + Alert._handleDismiss(new Alert()) + ) -/** - * -------------------------------------------------------------------------- - * Data Api implementation - * -------------------------------------------------------------------------- - */ + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ -$(document).on( - Event.CLICK, - Selector.DISMISS, - Alert._handleDismiss(new Alert()) -) + $.fn[NAME] = Alert._jQueryInterface + $.fn[NAME].Constructor = Alert + $.fn[NAME].noConflict = function () { + $.fn[NAME] = Alert._JQUERY_NO_CONFLICT + return Alert._jQueryInterface + } + return Alert -/** - * -------------------------------------------------------------------------- - * jQuery - * -------------------------------------------------------------------------- - */ +})() -$.fn[NAME] = Alert._jQueryInterface -$.fn[NAME].Constructor = Alert -$.fn[NAME].noConflict = function () { - $.fn[NAME] = Alert._JQUERY_NO_CONFLICT - return Alert._jQueryInterface -} +export default Alert diff --git a/js/src/util.js b/js/src/util.js index e9542149e..68205edef 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -5,114 +5,120 @@ * -------------------------------------------------------------------------- */ +const Util = (() => { -/** - * -------------------------------------------------------------------------- - * Public Util Api - * -------------------------------------------------------------------------- - */ -var Util = { + /** + * ------------------------------------------------------------------------ + * Private TransitionEnd Helpers + * ------------------------------------------------------------------------ + */ - TRANSITION_END: 'bsTransitionEnd', + let transition = false - getUID(prefix) { - do prefix += ~~(Math.random() * 1000000) - while (document.getElementById(prefix)) - return prefix - }, + const TransitionEndEvent = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } - getSelectorFromElement(element) { - let selector = element.getAttribute('data-target') + function getSpecialTransitionEndEvent() { + return { + bindType: transition.end, + delegateType: transition.end, + handle: function (event) { + if ($(event.target).is(this)) { + return event.handleObj.handler.apply(this, arguments) + } + } + } + } - if (!selector) { - selector = element.getAttribute('href') || '' - selector = /^#[a-z]/i.test(selector) ? selector : null + function transitionEndTest() { + if (window.QUnit) { + return false } - return selector - }, + let el = document.createElement('bootstrap') - reflow(element) { - new Function('bs', 'return bs')(element.offsetHeight) - }, + for (var name in TransitionEndEvent) { + if (el.style[name] !== undefined) { + return { end: TransitionEndEvent[name] } + } + } - supportsTransitionEnd() { - return !!transition + return false } -} - -export default Util - + function transitionEndEmulator(duration) { + let called = false -/** - * -------------------------------------------------------------------------- - * Private TransitionEnd Helpers - * -------------------------------------------------------------------------- - */ + $(this).one(Util.TRANSITION_END, function () { + called = true + }) -let transition = false - -const TransitionEndEvent = { - WebkitTransition : 'webkitTransitionEnd', - MozTransition : 'transitionend', - OTransition : 'oTransitionEnd otransitionend', - transition : 'transitionend' -} - -function getSpecialTransitionEndEvent() { - return { - bindType: transition.end, - delegateType: transition.end, - handle: function (event) { - if ($(event.target).is(this)) { - return event.handleObj.handler.apply(this, arguments) + setTimeout(() => { + if (!called) { + $(this).trigger(transition.end) } - } - } -} + }, duration) -function transitionEndTest() { - if (window.QUnit) { - return false + return this } - let el = document.createElement('bootstrap') + function setTransitionEndSupport() { + transition = transitionEndTest() + + $.fn.emulateTransitionEnd = transitionEndEmulator - for (var name in TransitionEndEvent) { - if (el.style[name] !== undefined) { - return { end: TransitionEndEvent[name] } + if (Util.supportsTransitionEnd()) { + $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent() } } - return false -} -function transitionEndEmulator(duration) { - let called = false + /** + * -------------------------------------------------------------------------- + * Public Util Api + * -------------------------------------------------------------------------- + */ - $(this).one(Util.TRANSITION_END, function () { - called = true - }) + let Util = { - setTimeout(() => { - if (!called) { - $(this).trigger(transition.end) - } - }, duration) + TRANSITION_END: 'bsTransitionEnd', + + getUID(prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + }, - return this -} + getSelectorFromElement(element) { + let selector = element.getAttribute('data-target') -function setTransitionEndSupport() { - transition = transitionEndTest() + if (!selector) { + selector = element.getAttribute('href') || '' + selector = /^#[a-z]/i.test(selector) ? selector : null + } + + return selector + }, - $.fn.emulateTransitionEnd = transitionEndEmulator + reflow(element) { + new Function('bs', 'return bs')(element.offsetHeight) + }, + + supportsTransitionEnd() { + return !!transition + } - if (Util.supportsTransitionEnd()) { - $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent() } -} -setTransitionEndSupport() + setTransitionEndSupport() + + return Util + +})() + +export default Util -- cgit v1.2.3 From 660505188241418ffda53b5eb848defecd5f57e1 Mon Sep 17 00:00:00 2001 From: fat Date: Thu, 7 May 2015 17:07:38 -0700 Subject: button -> es6 --- js/src/alert.js | 8 +-- js/src/button.js | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ js/src/util.js | 4 +- 3 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 js/src/button.js (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index d69ad8910..67a1ceda4 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -8,7 +8,7 @@ import Util from './util' * -------------------------------------------------------------------------- */ -const Alert = (() => { +const Alert = (($) => { /** @@ -58,6 +58,8 @@ const Alert = (() => { // public close(element) { + element = element || this.element + let rootElement = this._getRootElement(element) let customEvent = this._triggerCloseEvent(rootElement) @@ -167,12 +169,12 @@ const Alert = (() => { $.fn[NAME] = Alert._jQueryInterface $.fn[NAME].Constructor = Alert $.fn[NAME].noConflict = function () { - $.fn[NAME] = Alert._JQUERY_NO_CONFLICT + $.fn[NAME] = JQUERY_NO_CONFLICT return Alert._jQueryInterface } return Alert -})() +})(jQuery) export default Alert diff --git a/js/src/button.js b/js/src/button.js new file mode 100644 index 000000000..7e9344923 --- /dev/null +++ b/js/src/button.js @@ -0,0 +1,158 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): button.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Button = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'button' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.button' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 150 + + const ClassName = { + ACTIVE : 'active', + BUTTON : 'btn', + FOCUS : 'focus' + } + + const Selector = { + DATA_TOGGLE_CARROT : '[data-toggle^="button"]', + DATA_TOGGLE : '[data-toggle="buttons"]', + INPUT : 'input', + ACTIVE : '.active', + BUTTON : '.btn' + } + + const Event = { + CLICK : 'click.bs.button.data-api', + FOCUS_BLUR : 'focus.bs.button.data-api blur.bs.button.data-api' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Button { + + constructor(element) { + this.element = element + } + + // public + + toggle() { + let triggerChangeEvent = true + let rootElement = $(this.element).closest( + Selector.DATA_TOGGLE + )[0] + + if (rootElement) { + let input = $(this.element).find(Selector.INPUT)[0] + + if (input) { + if (input.type === 'radio') { + if (input.checked && + $(this.element).hasClass(ClassName.ACTIVE)) { + triggerChangeEvent = false + + } else { + let activeElement = $(rootElement).find(Selector.ACTIVE)[0] + + if (activeElement) { + $(activeElement).removeClass(ClassName.ACTIVE) + } + } + } + + if (triggerChangeEvent) { + input.checked = !$(this.element).hasClass(ClassName.ACTIVE) + $(this.element).trigger('change') + } + } + } else { + this.element.setAttribute('aria-pressed', + !$(this.element).hasClass(ClassName.ACTIVE)) + } + + if (triggerChangeEvent) { + $(this.element).toggleClass(ClassName.ACTIVE) + } + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + + if (!data) { + data = new Button(this) + $(this).data(DATA_KEY, data) + } + + if (config === 'toggle') { + data[config]() + } + }) + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document) + .on(Event.CLICK, Selector.DATA_TOGGLE_CARROT, function (event) { + event.preventDefault() + + let button = event.target + + if (!$(button).hasClass(ClassName.BUTTON)) { + button = $(button).closest(Selector.BUTTON) + } + + Button._jQueryInterface.call($(button), 'toggle') + }) + .on(Event.FOCUS_BLUR, Selector.DATA_TOGGLE_CARROT, function (event) { + var button = $(event.target).closest(Selector.BUTTON)[0] + $(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type)) + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Button._jQueryInterface + $.fn[NAME].Constructor = Button + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Button._jQueryInterface + } + + return Button + +})(jQuery) + +export default Button diff --git a/js/src/util.js b/js/src/util.js index 68205edef..abc548a45 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -5,7 +5,7 @@ * -------------------------------------------------------------------------- */ -const Util = (() => { +const Util = (($) => { /** @@ -119,6 +119,6 @@ const Util = (() => { return Util -})() +})(jQuery) export default Util -- cgit v1.2.3 From 1b183e2ff796fc22aba8a8cac074a306c083d018 Mon Sep 17 00:00:00 2001 From: fat Date: Thu, 7 May 2015 22:26:40 -0700 Subject: carousel -> es6 --- js/src/alert.js | 6 +- js/src/button.js | 18 +-- js/src/carousel.js | 426 +++++++++++++++++++++++++++++++++++++++++++++++++++++ js/src/util.js | 6 +- 4 files changed, 442 insertions(+), 14 deletions(-) create mode 100644 js/src/carousel.js (limited to 'js/src') 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 } -- cgit v1.2.3 From 8bab38bb7176d2716b72664dbafcc326824a2ee9 Mon Sep 17 00:00:00 2001 From: fat Date: Sat, 9 May 2015 23:00:59 -0700 Subject: add collapse --- js/src/alert.js | 2 +- js/src/collapse.js | 345 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 js/src/collapse.js (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index e5e8eeacb..a712154ab 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -80,7 +80,7 @@ const Alert = (($) => { } if (!parent) { - parent = $(element).closest('.' + ClassName.ALERT)[0] + parent = $(element).closest(`.${ClassName.ALERT}`)[0] } return parent diff --git a/js/src/collapse.js b/js/src/collapse.js new file mode 100644 index 000000000..00ae29097 --- /dev/null +++ b/js/src/collapse.js @@ -0,0 +1,345 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): collapse.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Collapse = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'collapse' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.collapse' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 600 + + const Defaults = { + toggle : true, + parent : null + } + + const Event = { + SHOW : 'show.bs.collapse', + SHOWN : 'shown.bs.collapse', + HIDE : 'hide.bs.collapse', + HIDDEN : 'hidden.bs.collapse', + CLICK : 'click.bs.collapse.data-api' + } + + const ClassName = { + IN : 'in', + COLLAPSE : 'collapse', + COLLAPSING : 'collapsing', + COLLAPSED : 'collapsed' + } + + const Dimension = { + WIDTH : 'width', + HEIGHT : 'height' + } + + const Selector = { + ACTIVES : '.panel > .in, .panel > .collapsing', + DATA_TOGGLE : '[data-toggle="collapse"]' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Collapse { + + constructor(element, config) { + + this._isTransitioning = false + this._element = element + this._config = $.extend({}, Defaults, config) + this._triggerArray = $.makeArray($( + `[data-toggle="collapse"][href="#${element.id}"],` + + `[data-toggle="collapse"][data-target="#${element.id}"]` + )) + + this._parent = this._config.parent ? this._getParent() : null + + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._element, this._triggerArray) + } + + if (this._config.toggle) { + this.toggle() + } + + } + + // public + + toggle() { + if ($(this._element).hasClass(ClassName.IN)) { + this.hide() + } else { + this.show() + } + } + + show() { + if (this._isTransitioning || + $(this._element).hasClass(ClassName.IN)) { + return + } + + let activesData + let actives + + if (this._parent) { + actives = $.makeArray($(Selector.ACTIVES)) + if (!actives.length) { + actives = null + } + } + + if (actives) { + activesData = $(actives).data(DATA_KEY) + if (activesData && activesData._isTransitioning) { + return + } + } + + let startEvent = $.Event(Event.SHOW) + $(this._element).trigger(startEvent) + if (startEvent.isDefaultPrevented()) { + return + } + + if (actives) { + Collapse._jQueryInterface.call($(actives), 'hide') + if (!activesData) { + $(actives).data(DATA_KEY, null) + } + } + + let dimension = this._getDimension() + + $(this._element) + .removeClass(ClassName.COLLAPSE) + .addClass(ClassName.COLLAPSING) + + this._element.style[dimension] = 0 + this._element.setAttribute('aria-expanded', true) + + if (this._triggerArray.length) { + $(this._triggerArray) + .removeClass(ClassName.COLLAPSED) + .attr('aria-expanded', true) + } + + this.setTransitioning(true) + + let complete = () => { + $(this._element) + .removeClass(ClassName.COLLAPSING) + .addClass(ClassName.COLLAPSE) + .addClass(ClassName.IN) + + this._element.style[dimension] = '' + + this.setTransitioning(false) + + $(this._element).trigger(Event.SHOWN) + } + + if (!Util.supportsTransitionEnd()) { + complete() + return + } + + let scrollSize = 'scroll' + + (dimension[0].toUpperCase() + + dimension.slice(1)) + + $(this._element) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(TRANSITION_DURATION) + + this._element.style[dimension] = this._element[scrollSize] + 'px' + } + + hide() { + if (this._isTransitioning || + !$(this._element).hasClass(ClassName.IN)) { + return + } + + let startEvent = $.Event(Event.HIDE) + $(this._element).trigger(startEvent) + if (startEvent.isDefaultPrevented()) { + return + } + + let dimension = this._getDimension() + let offsetDimension = dimension === Dimension.WIDTH ? + 'offsetWidth' : 'offsetHeight' + + this._element.style[dimension] = this._element[offsetDimension] + 'px' + + Util.reflow(this._element) + + $(this._element) + .addClass(ClassName.COLLAPSING) + .removeClass(ClassName.COLLAPSE) + .removeClass(ClassName.IN) + + this._element.setAttribute('aria-expanded', false) + + if (this._triggerArray.length) { + $(this._triggerArray) + .addClass(ClassName.COLLAPSED) + .attr('aria-expanded', false) + } + + this.setTransitioning(true) + + let complete = () => { + this.setTransitioning(false) + $(this._element) + .removeClass(ClassName.COLLAPSING) + .addClass(ClassName.COLLAPSE) + .trigger(Event.HIDDEN) + } + + this._element.style[dimension] = 0 + + if (!Util.supportsTransitionEnd()) { + return complete() + } + + $(this._element) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(TRANSITION_DURATION) + } + + setTransitioning(isTransitioning) { + this._isTransitioning = isTransitioning + } + + + // private + + _getDimension() { + let hasWidth = $(this._element).hasClass(Dimension.WIDTH) + return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT + } + + _getParent() { + let parent = $(this._config.parent)[0] + let selector = + `[data-toggle="collapse"][data-parent="${this._config.parent}"]` + + $(parent).find(selector).each((i, element) => { + this._addAriaAndCollapsedClass( + Collapse._getTargetFromElement(element), + [element] + ) + }) + + return parent + } + + _addAriaAndCollapsedClass(element, triggerArray) { + if (element) { + let isOpen = $(element).hasClass(ClassName.IN) + element.setAttribute('aria-expanded', isOpen) + + if (triggerArray.length) { + $(triggerArray) + .toggleClass(ClassName.COLLAPSED, !isOpen) + .attr('aria-expanded', isOpen) + } + } + } + + + // static + + static _getTargetFromElement(element) { + let selector = Util.getSelectorFromElement(element) + return selector ? $(selector)[0] : null + } + + static _jQueryInterface(config) { + return this.each(function () { + let $this = $(this) + let data = $this.data(DATA_KEY) + let _config = $.extend( + {}, + Defaults, + $this.data(), + typeof config === 'object' && config + ) + + if (!data && _config.toggle && /show|hide/.test(config)) { + _config.toggle = false + } + + if (!data) { + data = new Collapse(this, _config) + $this.data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config]() + } + }) + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document).on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + event.preventDefault() + + let target = Collapse._getTargetFromElement(this) + + let data = $(target).data(DATA_KEY) + let config = data ? 'toggle' : $(this).data() + + Collapse._jQueryInterface.call($(target), config) + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Collapse._jQueryInterface + $.fn[NAME].Constructor = Collapse + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Collapse._jQueryInterface + } + + return Collapse + +})(jQuery) + +export default Collapse -- cgit v1.2.3 From bbb97a8660639002e70b1786e595ef9171bfecc6 Mon Sep 17 00:00:00 2001 From: fat Date: Sun, 10 May 2015 13:47:11 -0700 Subject: add dropdown --- js/src/dropdown.js | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 js/src/dropdown.js (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js new file mode 100644 index 000000000..c5e29d8f1 --- /dev/null +++ b/js/src/dropdown.js @@ -0,0 +1,261 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Dropdown = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'dropdown' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.dropdown' + const JQUERY_NO_CONFLICT = $.fn[NAME] + + const Event = { + HIDE   : 'hide.bs.dropdown', + HIDDEN   : 'hidden.bs.dropdown', + SHOW   : 'show.bs.dropdown', + SHOWN   : 'shown.bs.dropdown', + CLICK   : 'click.bs.dropdown', + KEYDOWN   : 'keydown.bs.dropdown.data-api', + CLICK_DATA : 'click.bs.dropdown.data-api' + } + + const ClassName = { + BACKDROP : 'dropdown-backdrop', + DISABLED : 'disabled', + OPEN : 'open' + } + + const Selector = { + BACKDROP : '.dropdown-backdrop', + DATA_TOGGLE : '[data-toggle="dropdown"]', + FORM_CHILD : '.dropdown form', + ROLE_MENU : '[role="menu"]', + ROLE_LISTBOX : '[role="listbox"]', + NAVBAR_NAV : '.navbar-nav', + VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, ' + + '[role="listbox"] li:not(.disabled) a' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Dropdown { + + constructor(element) { + $(element).on(Event.CLICK, this.toggle) + } + + // public + + toggle() { + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return + } + + let parent = Dropdown._getParentFromElement(this) + let isActive = $(parent).hasClass(ClassName.OPEN) + + Dropdown._clearMenus() + + if (isActive) { + return false + } + + if ('ontouchstart' in document.documentElement && + (!$(parent).closest(Selector.NAVBAR_NAV).length)) { + + // if mobile we use a backdrop because click events don't delegate + let dropdown = document.createElement('div') + dropdown.className = ClassName.BACKDROP + $(dropdown).insertBefore(this) + $(dropdown).on('click', Dropdown._clearMenus) + } + + let relatedTarget = { relatedTarget : this } + let showEvent = $.Event(Event.SHOW, relatedTarget) + + $(parent).trigger(showEvent) + + if (showEvent.isDefaultPrevented()) { + return + } + + this.focus() + this.setAttribute('aria-expanded', 'true') + + $(parent).toggleClass(ClassName.OPEN) + $(parent).trigger(Event.SHOWN, relatedTarget) + + return false + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + + if (!data) { + $(this).data(DATA_KEY, (data = new Dropdown(this))) + } + + if (typeof config === 'string') { + data[config].call(this) + } + }) + } + + static _clearMenus(event) { + if (event && event.which === 3) { + return + } + + let backdrop = $(Selector.BACKDROP)[0] + if (backdrop) { + backdrop.parentNode.removeChild(backdrop) + } + + let toggles = $.makeArray($(Selector.DATA_TOGGLE)) + + for (let i = 0; i < toggles.length; i++) { + let parent = Dropdown._getParentFromElement(toggles[i]) + let relatedTarget = { relatedTarget : toggles[i] } + + if (!$(parent).hasClass(ClassName.OPEN)) { + continue + } + + if (event && event.type === 'click' && + (/input|textarea/i.test(event.target.tagName)) && + ($.contains(parent, event.target))) { + continue + } + + let hideEvent = $.Event(Event.HIDE, relatedTarget) + $(parent).trigger(hideEvent) + if (hideEvent.isDefaultPrevented()) { + continue + } + + toggles[i].setAttribute('aria-expanded', 'false') + + $(parent) + .removeClass(ClassName.OPEN) + .trigger(Event.HIDDEN, relatedTarget) + } + } + + static _getParentFromElement(element) { + let parent + let selector = Util.getSelectorFromElement(element) + + if (selector) { + parent = $(selector)[0] + } + + return parent || element.parentNode + } + + static _dataApiKeydownHandler(event) { + if (!/(38|40|27|32)/.test(event.which) || + /input|textarea/i.test(event.target.tagName)) { + return + } + + event.preventDefault() + event.stopPropagation() + + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return + } + + let parent = Dropdown._getParentFromElement(this) + let isActive = $(parent).hasClass(ClassName.OPEN) + + if ((!isActive && event.which !== 27) || + (isActive && event.which === 27)) { + + if (event.which === 27) { + let toggle = $(parent).find(Selector.DATA_TOGGLE)[0] + $(toggle).trigger('focus') + } + + $(this).trigger('click') + return + } + + let items = $.makeArray($(Selector.VISIBLE_ITEMS)) + + items = items.filter((item) => { + return item.offsetWidth || item.offsetHeight + }) + + if (!items.length) { + return + } + + let index = items.indexOf(event.target) + + if (event.which === 38 && index > 0) index-- // up + if (event.which === 40 && index < items.length - 1) index++ // down + if (!~index) index = 0 + + items[index].focus() + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document) + .on(Event.KEYDOWN, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) + .on(Event.CLICK_DATA, Dropdown._clearMenus) + .on(Event.CLICK_DATA, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) + .on(Event.CLICK_DATA, Selector.FORM_CHILD, function (e) { + e.stopPropagation() + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Dropdown._jQueryInterface + $.fn[NAME].Constructor = Dropdown + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Dropdown._jQueryInterface + } + + return Dropdown + +})(jQuery) + +export default Dropdown -- cgit v1.2.3 From ca9c850ebbfc880dc5021b451ffc9fa12184ff87 Mon Sep 17 00:00:00 2001 From: fat Date: Sun, 10 May 2015 19:45:38 -0700 Subject: add getters for Version and Default where applicable add modal my gawd --- js/src/alert.js | 7 + js/src/button.js | 8 + js/src/carousel.js | 15 +- js/src/collapse.js | 18 +- js/src/dropdown.js | 8 + js/src/modal.js | 492 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 541 insertions(+), 7 deletions(-) create mode 100644 js/src/modal.js (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index a712154ab..57a59746a 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -53,6 +53,13 @@ const Alert = (($) => { } + // getters + + static get VERSION() { + return VERSION + } + + // public close(element) { diff --git a/js/src/button.js b/js/src/button.js index 0f1dab2af..7520de2f6 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -52,6 +52,14 @@ const Button = (($) => { this._element = element } + + // getters + + static get VERSION() { + return VERSION + } + + // public toggle() { diff --git a/js/src/carousel.js b/js/src/carousel.js index 08476d666..bce51d7fd 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -23,7 +23,7 @@ const Carousel = (($) => { const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 600 - const Defaults = { + const Default = { interval : 5000, keyboard : true, slide : false, @@ -72,7 +72,6 @@ const Carousel = (($) => { class Carousel { constructor(element, config) { - this._items = null this._interval = null this._activeElement = null @@ -85,7 +84,17 @@ const Carousel = (($) => { this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0] this._addEventListeners() + } + + + // getters + + static get VERSION() { + return VERSION + } + static get Default() { + return Default } @@ -331,7 +340,7 @@ const Carousel = (($) => { static _jQueryInterface(config) { return this.each(function () { let data = $(this).data(DATA_KEY) - let _config = $.extend({}, Defaults, $(this).data()) + let _config = $.extend({}, Default, $(this).data()) if (typeof config === 'object') { $.extend(_config, config) diff --git a/js/src/collapse.js b/js/src/collapse.js index 00ae29097..ac506b86b 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -23,7 +23,7 @@ const Collapse = (($) => { const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 600 - const Defaults = { + const Default = { toggle : true, parent : null } @@ -63,10 +63,9 @@ const Collapse = (($) => { class Collapse { constructor(element, config) { - this._isTransitioning = false this._element = element - this._config = $.extend({}, Defaults, config) + this._config = $.extend({}, Default, config) this._triggerArray = $.makeArray($( `[data-toggle="collapse"][href="#${element.id}"],` + `[data-toggle="collapse"][data-target="#${element.id}"]` @@ -81,9 +80,20 @@ const Collapse = (($) => { if (this._config.toggle) { this.toggle() } + } + + + // getters + static get VERSION() { + return VERSION } + static get Default() { + return Default + } + + // public toggle() { @@ -284,7 +294,7 @@ const Collapse = (($) => { let data = $this.data(DATA_KEY) let _config = $.extend( {}, - Defaults, + Default, $this.data(), typeof config === 'object' && config ) diff --git a/js/src/dropdown.js b/js/src/dropdown.js index c5e29d8f1..3135ca73a 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -62,6 +62,14 @@ const Dropdown = (($) => { $(element).on(Event.CLICK, this.toggle) } + + // getters + + static get VERSION() { + return VERSION + } + + // public toggle() { diff --git a/js/src/modal.js b/js/src/modal.js new file mode 100644 index 000000000..b2e8fd77f --- /dev/null +++ b/js/src/modal.js @@ -0,0 +1,492 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Modal = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'modal' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.modal' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 300 + const BACKDROP_TRANSITION_DURATION = 150 + + const Default = { + backdrop : true, + keyboard : true, + show : true + } + + const Event = { + HIDE   : 'hide.bs.modal', + HIDDEN   : 'hidden.bs.modal', + SHOW   : 'show.bs.modal', + SHOWN   : 'shown.bs.modal', + DISMISS : 'click.dismiss.bs.modal', + KEYDOWN : 'keydown.dismiss.bs.modal', + FOCUSIN : 'focusin.bs.modal', + RESIZE : 'resize.bs.modal', + CLICK : 'click.bs.modal.data-api', + MOUSEDOWN : 'mousedown.dismiss.bs.modal', + MOUSEUP : 'mouseup.dismiss.bs.modal' + } + + const ClassName = { + BACKDROP : 'modal-backdrop', + OPEN : 'modal-open', + FADE : 'fade', + IN : 'in' + } + + const Selector = { + DIALOG : '.modal-dialog', + DATA_TOGGLE : '[data-toggle="modal"]', + DATA_DISMISS : '[data-dismiss="modal"]', + SCROLLBAR_MEASURER : 'modal-scrollbar-measure' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Modal { + + constructor(element, config) { + this._config = config + this._element = element + this._dialog = $(element).find(Selector.DIALOG)[0] + this._backdrop = null + this._isShown = false + this._isBodyOverflowing = false + this._ignoreBackdropClick = false + this._originalBodyPadding = 0 + this._scrollbarWidth = 0 + } + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + + // public + + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + let showEvent = $.Event(Event.SHOW, { + relatedTarget: relatedTarget + }) + + $(this._element).trigger(showEvent) + + if (this._isShown || showEvent.isDefaultPrevented()) { + return + } + + this._isShown = true + + this._checkScrollbar() + this._setScrollbar() + + $(document.body).addClass(ClassName.OPEN) + + this._setEscapeEvent() + this._setResizeEvent() + + $(this._element).on( + Event.DISMISS, + Selector.DATA_DISMISS, + this.hide.bind(this) + ) + + $(this._dialog).on(Event.MOUSEDOWN, () => { + $(this._element).one(Event.MOUSEUP, (event) => { + if ($(event.target).is(this._element)) { + that._ignoreBackdropClick = true + } + }) + }) + + this._showBackdrop( + this._showElement.bind(this, relatedTarget) + ) + } + + hide(event) { + if (event) { + event.preventDefault() + } + + let hideEvent = $.Event(Event.HIDE) + + $(this._element).trigger(hideEvent) + + if (!this._isShown || hideEvent.isDefaultPrevented()) { + return + } + + this._isShown = false + + this._setEscapeEvent() + this._setResizeEvent() + + $(document).off(Event.FOCUSIN) + + $(this._element).removeClass(ClassName.IN) + + $(this._element).off(Event.DISMISS) + $(this._dialog).off(Event.MOUSEDOWN) + + if (Util.supportsTransitionEnd() && + ($(this._element).hasClass(ClassName.FADE))) { + + $(this._element) + .one(Util.TRANSITION_END, this._hideModal.bind(this)) + .emulateTransitionEnd(TRANSITION_DURATION) + } else { + this._hideModal() + } + } + + + // private + + _showElement(relatedTarget) { + let transition = Util.supportsTransitionEnd() && + $(this._element).hasClass(ClassName.FADE) + + if (!this._element.parentNode || + (this._element.parentNode.nodeType !== Node.ELEMENT_NODE)) { + // don't move modals dom position + document.body.appendChild(this._element) + } + + this._element.style.display = 'block' + this._element.scrollTop = 0 + + if (transition) { + Util.reflow(this._element) + } + + $(this._element).addClass(ClassName.IN) + + this._enforceFocus() + + let shownEvent = $.Event(Event.SHOWN, { + relatedTarget: relatedTarget + }) + + let transitionComplete = () => { + this._element.focus() + $(this._element).trigger(shownEvent) + } + + if (transition) { + $(this._dialog) + .one(Util.TRANSITION_END, transitionComplete) + .emulateTransitionEnd(TRANSITION_DURATION) + } else { + transitionComplete() + } + } + + _enforceFocus() { + $(document) + .off(Event.FOCUSIN) // guard against infinite focus loop + .on(Event.FOCUSIN, (event) => { + if (this._element !== event.target && + (!$(this._element).has(event.target).length)) { + this._element.focus() + } + }) + } + + _setEscapeEvent() { + if (this._isShown && this._config.keyboard) { + $(this._element).on(Event.KEYDOWN, (event) => { + if (event.which === 27) { + this.hide() + } + }) + + } else if (!this._isShown) { + $(this._element).off(Event.KEYDOWN) + } + } + + _setResizeEvent() { + if (this._isShown) { + $(window).on(Event.RESIZE, this._handleUpdate.bind(this)) + } else { + $(window).off(Event.RESIZE) + } + } + + _hideModal() { + this._element.style.display = 'none' + this._showBackdrop(() => { + $(document.body).removeClass(ClassName.OPEN) + this._resetAdjustments() + this._resetScrollbar() + $(this._element).trigger(Event.HIDDEN) + }) + } + + _removeBackdrop() { + if (this._backdrop) { + $(this._backdrop).remove() + this._backdrop = null + } + } + + _showBackdrop(callback) { + let animate = $(this._element).hasClass(ClassName.FADE) ? + ClassName.FADE : '' + + if (this._isShown && this._config.backdrop) { + let doAnimate = Util.supportsTransitionEnd() && animate + + this._backdrop = document.createElement('div') + this._backdrop.className = ClassName.BACKDROP + + if (animate) { + $(this._backdrop).addClass(animate) + } + + $(this._backdrop).appendTo(this.$body) + + $(this._element).on(Event.DISMISS, (event) => { + if (this._ignoreBackdropClick) { + this._ignoreBackdropClick = false + return + } + if (event.target !== event.currentTarget) { + return + } + if (this._config.backdrop === 'static') { + this._element.focus() + } else { + this.hide() + } + }) + + if (doAnimate) { + Util.reflow(this._backdrop) + } + + $(this._backdrop).addClass(ClassName.IN) + + if (!callback) { + return + } + + if (!doAnimate) { + callback() + return + } + + $(this._backdrop) + .one(Util.TRANSITION_END, callback) + .emulateTransitionEnd(BACKDROP_TRANSITION_DURATION) + + } else if (!this._isShown && this._backdrop) { + $(this._backdrop).removeClass(ClassName.IN) + + let callbackRemove = () => { + this._removeBackdrop() + if (callback) { + callback() + } + } + + if (Util.supportsTransitionEnd() && + ($(this._element).hasClass(ClassName.FADE))) { + $(this._backdrop) + .one(Util.TRANSITION_END, callbackRemove) + .emulateTransitionEnd(BACKDROP_TRANSITION_DURATION) + } else { + callbackRemove() + } + + } else if (callback) { + callback() + } + } + + + // ---------------------------------------------------------------------- + // the following methods are used to handle overflowing modals + // todo (fat): these should probably be refactored out of modal.js + // ---------------------------------------------------------------------- + + _handleUpdate() { + this._adjustDialog() + } + + _adjustDialog() { + let isModalOverflowing = + this._element.scrollHeight > document.documentElement.clientHeight + + if (!this._isBodyOverflowing && isModalOverflowing) { + this._element.style.paddingLeft = this._scrollbarWidth + 'px' + } + + if (this._isBodyOverflowing && !isModalOverflowing) { + this._element.style.paddingRight = this._scrollbarWidth + 'px' + } + } + + _resetAdjustments() { + this._element.style.paddingLeft = '' + this._element.style.paddingRight = '' + } + + _checkScrollbar() { + let fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + let documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = + documentElementRect.right - Math.abs(documentElementRect.left) + } + this._isBodyOverflowing = document.body.clientWidth < fullWindowWidth + this._scrollbarWidth = this._getScrollbarWidth() + } + + _setScrollbar() { + let bodyPadding = parseInt( + $(document.body).css('padding-right') || 0, + 10 + ) + + this._originalBodyPadding = document.body.style.paddingRight || '' + + if (this._isBodyOverflowing) { + document.body.style.paddingRight = + bodyPadding + this._scrollbarWidth + 'px' + } + } + + _resetScrollbar() { + document.body.style.paddingRight = this._originalBodyPadding + } + + _getScrollbarWidth() { // thx d.walsh + let scrollDiv = document.createElement('div') + scrollDiv.className = Selector.SCROLLBAR_MEASURER + document.body.appendChild(scrollDiv) + let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + document.body.removeChild(scrollDiv) + return scrollbarWidth + } + + + // static + + static _jQueryInterface(config, relatedTarget) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = $.extend( + {}, + Modal.Default, + $(this).data(), + typeof config === 'object' && config + ) + + if (!data) { + data = new Modal(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config](relatedTarget) + + } else if (_config.show) { + data.show(relatedTarget) + } + }) + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document).on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + let target + let selector = Util.getSelectorFromElement(this) + + if (selector) { + target = $(selector)[0] + } + + let config = $(target).data(DATA_KEY) ? + 'toggle' : $.extend({}, $(target).data(), $(this).data()) + + if (this.tagName === 'A') { + event.preventDefault() + } + + let $target = $(target).one(Event.SHOW, (showEvent) => { + if (showEvent.isDefaultPrevented()) { + // only register focus restorer if modal will actually get shown + return + } + + $target.one(Event.HIDDEN, () => { + if ($(this).is(':visible')) { + this.focus() + } + }) + }) + + Modal._jQueryInterface.call($(target), config, this) + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Modal._jQueryInterface + $.fn[NAME].Constructor = Modal + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Modal._jQueryInterface + } + + return Modal + +})(jQuery) + +export default Modal -- cgit v1.2.3 From 2d91494d96f8161f1bcce6589081880b72706154 Mon Sep 17 00:00:00 2001 From: fat Date: Mon, 11 May 2015 12:05:35 -0700 Subject: scrollspy es6 --- js/src/scrollspy.js | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 js/src/scrollspy.js (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js new file mode 100644 index 000000000..985da708d --- /dev/null +++ b/js/src/scrollspy.js @@ -0,0 +1,275 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const ScrollSpy = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'scrollspy' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.scrollspy' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 150 + + const Defaults = { + offset : 10 + } + + const Event = { + ACTIVATE : 'activate.bs.scrollspy', + SCROLL : 'scroll.bs.scrollspy', + LOAD : 'load.bs.scrollspy.data-api' + } + + const ClassName = { + DROPDOWN_MENU : 'dropdown-menu', + ACTIVE : 'active' + } + + const Selector = { + DATA_SPY : '[data-spy="scroll"]', + ACTIVE : '.active', + LI_DROPDOWN : 'li.dropdown', + LI : 'li' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class ScrollSpy { + + constructor(element, config) { + this._scrollElement = element.tagName === 'BODY' ? window : element + this._config = $.extend({}, Defaults, config) + this._selector = `${this._config.target || ''} .nav li > a` + this._offsets = [] + this._targets = [] + this._activeTarget = null + this._scrollHeight = 0 + + $(this._scrollElement).on(Event.SCROLL, this._process.bind(this)) + + this.refresh() + this._process() + } + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + + // public + + refresh() { + let offsetMethod = 'offset' + let offsetBase = 0 + + if (this._scrollElement !== this._scrollElement.window) { + offsetMethod = 'position' + offsetBase = this._getScrollTop() + } + + this._offsets = [] + this._targets = [] + + this._scrollHeight = this._getScrollHeight() + + let targets = $.makeArray($(this._selector)) + + targets + .map((element) => { + let target + let targetSelector = Util.getSelectorFromElement(element) + + if (targetSelector) { + target = $(targetSelector)[0] + } + + if (target && (target.offsetWidth || target.offsetHeight)) { + // todo (fat): remove sketch reliance on jQuery position/offset + return [ + $(target)[offsetMethod]().top + offsetBase, + targetSelector + ] + } + }) + .filter((item) => item) + .sort((a, b) => a[0] - b[0]) + .forEach((item) => { + this._offsets.push(item[0]) + this._targets.push(item[1]) + }) + } + + + // private + + _getScrollTop() { + return this._scrollElement === window ? + this._scrollElement.scrollY : this._scrollElement.scrollTop + } + + _getScrollHeight() { + return this._scrollElement.scrollHeight || Math.max( + document.body.scrollHeight, + document.documentElement.scrollHeight + ) + } + + _process() { + let scrollTop = this._getScrollTop() + this._config.offset + let scrollHeight = this._getScrollHeight() + let maxScroll = this._config.offset + + scrollHeight + - this._scrollElement.offsetHeight + + if (this._scrollHeight !== scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + let target = this._targets[this._targets.length - 1] + + if (this._activeTarget !== target) { + this._activate(target) + } + } + + if (this._activeTarget && scrollTop < this._offsets[0]) { + this._activeTarget = null + this._clear() + return + } + + for (let i = this._offsets.length; i--;) { + let isActiveTarget = this._activeTarget !== this._targets[i] + && scrollTop >= this._offsets[i] + && (this._offsets[i + 1] === undefined || + scrollTop < this._offsets[i + 1]) + + if (isActiveTarget) { + this._activate(this._targets[i]) + } + } + } + + _activate(target) { + this._activeTarget = target + + this._clear() + + let selector = + `${this._selector}[data-target="${target}"],` + + `${this._selector}[href="${target}"]` + + // todo (fat): getting all the raw li's up the tree is not great. + let parentListItems = $(selector).parents(Selector.LI) + + for (let i = parentListItems.length; i--;) { + $(parentListItems[i]).addClass(ClassName.ACTIVE) + + let itemParent = parentListItems[i].parentNode + + if (itemParent && $(itemParent).hasClass(ClassName.DROPDOWN_MENU)) { + let closestDropdown = $(itemParent) + .closest(Selector.LI_DROPDOWN)[0] + $(closestDropdown).addClass(ClassName.ACTIVE) + } + } + + $(this._scrollElement).trigger(Event.ACTIVATE, { + relatedTarget: target + }) + } + + _clear() { + let activeParents = $(this._selector).parentsUntil( + this._config.target, + Selector.ACTIVE + ) + + for (let i = activeParents.length; i--;) { + $(activeParents[i]).removeClass(ClassName.ACTIVE) + } + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = typeof config === 'object' && config || null + + if (!data) { + data = new ScrollSpy(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config]() + } + }) + } + + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(window).on(Event.LOAD, function () { + let scrollSpys = $.makeArray($(Selector.DATA_SPY)) + + for (let i = scrollSpys.length; i--;) { + let $spy = $(scrollSpys[i]) + ScrollSpy._jQueryInterface.call($spy, $spy.data()) + } + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = ScrollSpy._jQueryInterface + $.fn[NAME].Constructor = ScrollSpy + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return ScrollSpy._jQueryInterface + } + + return ScrollSpy + +})(jQuery) + +export default ScrollSpy -- cgit v1.2.3 From 8eee78ca15f51dc7e7d514497078bfd7c012ac21 Mon Sep 17 00:00:00 2001 From: fat Date: Mon, 11 May 2015 12:29:06 -0700 Subject: tab es6 --- js/src/scrollspy.js | 9 +- js/src/tab.js | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 js/src/tab.js (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 985da708d..b66f7bb88 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -17,11 +17,10 @@ const ScrollSpy = (($) => { * ------------------------------------------------------------------------ */ - const NAME = 'scrollspy' - const VERSION = '4.0.0' - const DATA_KEY = 'bs.scrollspy' - const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 150 + const NAME = 'scrollspy' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.scrollspy' + const JQUERY_NO_CONFLICT = $.fn[NAME] const Defaults = { offset : 10 diff --git a/js/src/tab.js b/js/src/tab.js new file mode 100644 index 000000000..4668ff9e6 --- /dev/null +++ b/js/src/tab.js @@ -0,0 +1,278 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): tab.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Tab = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'tab' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.tab' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 150 + + const Event = { + HIDE : 'hide.bs.tab', + HIDDEN : 'hidden.bs.tab', + SHOW : 'show.bs.tab', + SHOWN : 'shown.bs.tab', + CLICK : 'click.bs.tab.data-api' + } + + const ClassName = { + DROPDOWN_MENU : 'dropdown-menu', + ACTIVE : 'active', + FADE : 'fade', + IN : 'in' + } + + const Selector = { + A : 'a', + LI : 'li', + LI_DROPDOWN : 'li.dropdown', + UL : 'ul:not(.dropdown-menu)', + FADE_CHILD : '> .fade', + ACTIVE : '.active', + ACTIVE_CHILD : '> .active', + DATA_TOGGLE : '[data-toggle="tab"], [data-toggle="pill"]', + DROPDOWN_ACTIVE_CHILD : '> .dropdown-menu > .active' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Tab { + + constructor(element) { + this._element = element + } + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + + // public + + show() { + if (this._element.parentNode && + (this._element.parentNode.nodeType == Node.ELEMENT_NODE) && + ($(this._element).parent().hasClass(ClassName.ACTIVE))) { + return + } + + let target + let previous + let ulElement = $(this._element).closest(Selector.UL)[0] + let selector = Util.getSelectorFromElement(this._element) + + if (ulElement) { + previous = $.makeArray($(ulElement).find(Selector.ACTIVE)) + previous = previous[previous.length - 1] + + if (previous) { + previous = $(previous).find(Selector.A)[0] + } + } + + let hideEvent = $.Event(Event.HIDE, { + relatedTarget: this._element + }) + + let showEvent = $.Event(Event.SHOW, { + relatedTarget: previous + }) + + if (previous) { + $(previous).trigger(hideEvent) + } + + $(this._element).trigger(showEvent) + + if (showEvent.isDefaultPrevented() || + (hideEvent.isDefaultPrevented())) { + return + } + + if (selector) { + target = $(selector)[0] + } + + this._activate( + $(this._element).closest(Selector.LI)[0], + ulElement + ) + + let complete = () => { + let hiddenEvent = $.Event(Event.HIDDEN, { + relatedTarget: this._element + }) + + let shownEvent = $.Event(Event.SHOWN, { + relatedTarget: previous + }) + + $(previous).trigger(hiddenEvent) + $(this._element).trigger(shownEvent) + } + + if (target) { + this._activate(target, target.parentNode, complete) + } else { + complete() + } + } + + + // private + + _activate(element, container, callback) { + let active = $(container).find(Selector.ACTIVE_CHILD)[0] + let isTransitioning = callback + && Util.supportsTransitionEnd() + && ((active && $(active).hasClass(ClassName.FADE)) + || !!$(container).find(Selector.FADE_CHILD)[0]) + + let complete = this._transitionComplete.bind( + this, element, active, isTransitioning, callback) + + if (active && isTransitioning) { + $(active) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(TRANSITION_DURATION) + + } else { + complete() + } + + if (active) { + $(active).removeClass(ClassName.IN) + } + } + + _transitionComplete(element, active, isTransitioning, callback) { + if (active) { + $(active).removeClass(ClassName.ACTIVE) + + let dropdownChild = $(active).find( + Selector.DROPDOWN_ACTIVE_CHILD + )[0] + if (dropdownChild) { + $(dropdownChild).removeClass(ClassName.ACTIVE) + } + + let activeToggle = $(active).find(Selector.DATA_TOGGLE)[0] + if (activeToggle) { + activeToggle.setAttribute('aria-expanded', false) + } + } + + $(element).addClass(ClassName.ACTIVE) + + let elementToggle = $(element).find(Selector.DATA_TOGGLE)[0] + if (elementToggle) { + elementToggle.setAttribute('aria-expanded', true) + } + + if (isTransitioning) { + Util.reflow(element) + $(element).addClass(ClassName.IN) + } else { + $(element).removeClass(ClassName.FADE) + } + + if (element.parentNode && + ($(element.parentNode).hasClass(ClassName.DROPDOWN_MENU))) { + + let dropdownElement = $(element).closest(Selector.LI_DROPDOWN)[0] + if (dropdownElement) { + $(dropdownElement).addClass(ClassName.ACTIVE) + } + + elementToggle = $(element).find(Selector.DATA_TOGGLE)[0] + if (elementToggle) { + elementToggle.setAttribute('aria-expanded', true) + } + } + + if (callback) { + callback() + } + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let $this = $(this) + let data = $this.data(DATA_KEY) + + if (!data) { + data = data = new Tab(this) + $this.data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config]() + } + }) + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document) + .on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + event.preventDefault() + Tab._jQueryInterface.call($(this), 'show') + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Tab._jQueryInterface + $.fn[NAME].Constructor = Tab + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Tab._jQueryInterface + } + + return Tab + +})(jQuery) + +export default Tab -- cgit v1.2.3 From 3452e8dc8336c7a4151bcccdb9d3d4202f06f294 Mon Sep 17 00:00:00 2001 From: fat Date: Mon, 11 May 2015 23:32:37 -0700 Subject: rewritten tooltip + tether integration and death to our positioner jank --- js/src/scrollspy.js | 2 +- js/src/tooltip.js | 619 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 js/src/tooltip.js (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index b66f7bb88..0ab8804c6 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -22,7 +22,7 @@ const ScrollSpy = (($) => { const DATA_KEY = 'bs.scrollspy' const JQUERY_NO_CONFLICT = $.fn[NAME] - const Defaults = { + const Default = { offset : 10 } diff --git a/js/src/tooltip.js b/js/src/tooltip.js new file mode 100644 index 000000000..4c09a5baf --- /dev/null +++ b/js/src/tooltip.js @@ -0,0 +1,619 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): tooltip.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const ToolTip = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'tooltip' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.tooltip' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 150 + const CLASS_PREFIX = 'bs-tether' + + const Default = { + animation : true, + template : '', + trigger : 'hover focus', + title : '', + delay : 0, + html : false, + selector : false, + attachment : 'top', + offset : '0 0', + constraints : null + } + + const HorizontalMirror = { + LEFT : 'right', + CENTER : 'center', + RIGHT : 'left' + } + + const VerticalMirror = { + TOP : 'bottom', + MIDDLE : 'middle', + BOTTOM : 'top' + } + + const VerticalDefault = { + LEFT : 'middle', + CENTER : 'bottom', + RIGHT : 'middle' + } + + const HorizontalDefault = { + TOP : 'center', + MIDDLE : 'left', + BOTTOM : 'center' + } + + const HoverState = { + IN : 'in', + OUT : 'out' + } + + const Event = { + HIDE : 'hide.bs.tooltip', + HIDDEN : 'hidden.bs.tooltip', + SHOW : 'show.bs.tooltip', + SHOWN : 'shown.bs.tooltip', + INSERTED : 'inserted.bs.tooltip', + CLICK : 'click.bs.tooltip', + FOCUSIN : 'focusin.bs.tooltip', + FOCUSOUT : 'focusout.bs.tooltip', + MOUSEENTER : 'mouseenter.bs.tooltip', + MOUSELEAVE : 'mouseleave.bs.tooltip' + } + + const ClassName = { + FADE : 'fade', + IN : 'in' + } + + const Selector = { + TOOLTIP : '.tooltip', + TOOLTIP_INNER : '.tooltip-inner', + TOOLTIP_ARROW : '.tooltip-arrow' + } + + const TetherClass = { + element : false, + enabled : false + } + + const Trigger = { + HOVER : 'hover', + FOCUS : 'focus', + CLICK : 'click', + MANUAL : 'manual' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Tooltip { + + constructor(element, config) { + + // private + this._isEnabled = true + this._timeout = 0 + this._hoverState = '' + this._activeTrigger = {} + + // protected + this.element = element + this.config = this._getConfig(config) + this.tip = null + this.tether = null + + this._setListeners() + + } + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + + // public + + enable() { + this._isEnabled = true + } + + disable() { + this._isEnabled = false + } + + toggleEnabled() { + this._isEnabled = !this._isEnabled + } + + toggle(event) { + let context = this + + if (event) { + context = $(event.currentTarget).data(DATA_KEY) + + if (!context) { + context = new this.constructor( + event.currentTarget, + this._getDelegateConfig() + ) + $(event.currentTarget).data(DATA_KEY, context) + } + + context._activeTrigger.click = !context._activeTrigger.click + + if (context._isWithActiveTrigger()) { + context._enter(null, context) + } else { + context._leave(null, context) + } + + } else { + $(context.getTipElement()).hasClass(ClassName.IN) ? + context._leave(null, context) : + context._enter(null, context) + } + } + + destroy() { + clearTimeout(this._timeout) + this.hide(() => { + $(this.element) + .off(Selector.TOOLTIP) + .removeData(DATA_KEY) + }) + } + + show() { + let showEvent = $.Event(Event.SHOW) + + if (this.isWithContent() && this._isEnabled) { + $(this.element).trigger(showEvent) + + let isInTheDom = $.contains( + this.element.ownerDocument.documentElement, + this.element + ) + + if (showEvent.isDefaultPrevented() || !isInTheDom) { + return + } + + let tip = this.getTipElement() + let tipId = Util.getUID(NAME) + + tip.setAttribute('id', tipId) + this.element.setAttribute('aria-describedby', tipId) + + this.setContent() + + if (this.config.animation) { + $(tip).addClass(ClassName.FADE) + } + + let attachment = typeof this.config.attachment === 'function' ? + this.config.attachment.call(this, tip, this.element) : + this.config.attachment + + attachment = this.getAttachment(attachment) + + $(tip).data(DATA_KEY, this) + + this.element.parentNode.insertBefore(tip, this.element.nextSibling) + $(this.element).trigger(Event.INSERTED) + + this.tether = new Tether({ + element : this.tip, + target : this.element, + attachment : attachment, + classes : TetherClass, + classPrefix : CLASS_PREFIX, + offset : this.config.offset, + constraints : this.config.constraints + }) + + Util.reflow(tip) + this.tether.position() + + $(tip).addClass(ClassName.IN) + + let complete = () => { + let prevHoverState = this._hoverState + this._hoverState = null + + $(this.element).trigger(Event.SHOWN) + + if (prevHoverState === HoverState.OUT) { + this._leave(null, this) + } + } + + Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE) ? + $(this.tip) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) : + complete() + } + } + + hide(callback) { + let tip = this.getTipElement() + let hideEvent = $.Event(Event.HIDE) + let complete = () => { + if (this._hoverState !== HoverState.IN && tip.parentNode) { + tip.parentNode.removeChild(tip) + } + + this.element.removeAttribute('aria-describedby') + $(this.element).trigger(Event.HIDDEN) + this.cleanupTether() + + if (callback) { + callback() + } + } + + $(this.element).trigger(hideEvent) + + if (hideEvent.isDefaultPrevented()) { + return + } + + $(tip).removeClass(ClassName.IN) + + if (Util.supportsTransitionEnd() && + ($(this.tip).hasClass(ClassName.FADE))) { + + $(tip) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(TRANSITION_DURATION) + + } else { + complete() + } + + this._hoverState = '' + } + + + // protected + + isWithContent() { + return !!this.getTitle() + } + + getTipElement() { + return (this.tip = this.tip || $(this.config.template)[0]) + } + + getAttachment(attachmentString) { + let attachmentArray = attachmentString.split(' ') + let normalizedAttachment = {} + + if (!attachmentArray.length) { + throw new Error('Tooltip requires attachment') + } + + for (let attachment of attachmentArray) { + attachment = attachment.toUpperCase() + + if (HorizontalMirror[attachment]) { + normalizedAttachment.horizontal = HorizontalMirror[attachment] + } + + if (VerticalMirror[attachment]) { + normalizedAttachment.vertical = VerticalMirror[attachment] + } + } + + if (!normalizedAttachment.horizontal && + (!normalizedAttachment.vertical)) { + throw new Error('Tooltip requires valid attachment') + } + + if (!normalizedAttachment.horizontal) { + normalizedAttachment.horizontal = + HorizontalDefault[normalizedAttachment.vertical.toUpperCase()] + } + + if (!normalizedAttachment.vertical) { + normalizedAttachment.vertical = + VerticalDefault[normalizedAttachment.horizontal.toUpperCase()] + } + + return [ + normalizedAttachment.vertical, + normalizedAttachment.horizontal + ].join(' ') + } + + setContent() { + let tip = this.getTipElement() + let title = this.getTitle() + let method = this.config.html ? 'innerHTML' : 'innerText' + + $(tip).find(Selector.TOOLTIP_INNER)[0][method] = title + + $(tip) + .removeClass(ClassName.FADE) + .removeClass(ClassName.IN) + + this.cleanupTether() + } + + getTitle() { + let title = this.element.getAttribute('data-original-title') + + if (!title) { + title = typeof this.config.title === 'function' ? + this.config.title.call(this.element) : + this.config.title + } + + return title + } + + removeTetherClasses(i, css) { + return ((css.baseVal || css).match( + new RegExp(`(^|\\s)${CLASS_PREFIX}-\\S+`, 'g')) || [] + ).join(' ') + } + + cleanupTether() { + if (this.tether) { + this.tether.destroy() + + // clean up after tether's junk classes + // remove after they fix issue + // (https://github.com/HubSpot/tether/issues/36) + $(this.element).removeClass(this.removeTetherClasses) + $(this.tip).removeClass(this.removeTetherClasses) + } + } + + + // private + + _setListeners() { + let triggers = this.config.trigger.split(' ') + + triggers.forEach((trigger) => { + if (trigger === 'click') { + $(this.element).on( + Event.CLICK, + this.config.selector, + this.toggle.bind(this) + ) + + } else if (trigger !== Trigger.MANUAL) { + let eventIn = trigger == Trigger.HOVER ? + Event.MOUSEENTER : Event.FOCUSIN + let eventOut = trigger == Trigger.HOVER ? + Event.MOUSELEAVE : Event.FOCUSOUT + + $(this.element) + .on( + eventIn, + this.config.selector, + this._enter.bind(this) + ) + .on( + eventOut, + this.config.selector, + this._leave.bind(this) + ) + } + }) + + if (this.config.selector) { + this.config = $.extend({}, this.config, { + trigger : 'manual', + selector : '' + }) + } else { + this._fixTitle() + } + } + + _fixTitle() { + let titleType = typeof this.element.getAttribute('data-original-title') + if (this.element.getAttribute('title') || + (titleType !== 'string')) { + this.element.setAttribute( + 'data-original-title', + this.element.getAttribute('title') || '' + ) + this.element.setAttribute('title', '') + } + } + + _enter(event, context) { + context = context || $(event.currentTarget).data(DATA_KEY) + + if (!context) { + context = new this.constructor( + event.currentTarget, + this._getDelegateConfig() + ) + $(event.currentTarget).data(DATA_KEY, context) + } + + if (event) { + context._activeTrigger[ + event.type == 'focusin' ? Trigger.FOCUS : Trigger.HOVER + ] = true + } + + if ($(context.getTipElement()).hasClass(ClassName.IN) || + (context._hoverState === HoverState.IN)) { + context._hoverState = HoverState.IN + return + } + + clearTimeout(context._timeout) + + context._hoverState = HoverState.IN + + if (!context.config.delay || !context.config.delay.show) { + context.show() + return + } + + context._timeout = setTimeout(() => { + if (context._hoverState === HoverState.IN) { + context.show() + } + }, context.config.delay.show) + } + + _leave(event, context) { + context = context || $(event.currentTarget).data(DATA_KEY) + + if (!context) { + context = new this.constructor( + event.currentTarget, + this._getDelegateConfig() + ) + $(event.currentTarget).data(DATA_KEY, context) + } + + if (event) { + context._activeTrigger[ + event.type == 'focusout' ? Triger.FOCUS : Trigger.HOVER + ] = false + } + + if (context._isWithActiveTrigger()) { + return + } + + clearTimeout(context._timeout) + + context._hoverState = HoverState.OUT + + if (!context.config.delay || !context.config.delay.hide) { + context.hide() + return + } + + context._timeout = setTimeout(() => { + if (context._hoverState === HoverState.OUT) { + context.hide() + } + }, context.config.delay.hide) + } + + _isWithActiveTrigger() { + for (let trigger in this._activeTrigger) { + if (this._activeTrigger[trigger]) { + return true + } + } + + return false + } + + _getConfig(config) { + config = $.extend({}, Default, $(this.element).data(), config) + + if (config.delay && typeof config.delay === 'number') { + config.delay = { + show : config.delay, + hide : config.delay + } + } + + return config + } + + _getDelegateConfig() { + let config = {} + + if (this.config) { + for (let key in this.config) { + let value = this.config[key] + if (Default[key] !== value) { + config[key] = value + } + } + } + + return config + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = typeof config === 'object' ? + config : null + + if (!data && /destroy|hide/.test(config)) { + return + } + + if (!data) { + data = new Tooltip(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config]() + } + }) + } + + } + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Tooltip._jQueryInterface + $.fn[NAME].Constructor = Tooltip + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Tooltip._jQueryInterface + } + + return Tooltip + +})(jQuery) + +export default Tooltip -- cgit v1.2.3 From a58febf71a5eac2161ce2db08c7d723755ed1163 Mon Sep 17 00:00:00 2001 From: fat Date: Tue, 12 May 2015 14:28:11 -0700 Subject: popover passing as well --- js/src/popover.js | 178 ++++++++++++++++++++++++++++++++++++++++++++++++ js/src/scrollspy.js | 2 +- js/src/tooltip.js | 191 +++++++++++++++++++++++----------------------------- 3 files changed, 263 insertions(+), 108 deletions(-) create mode 100644 js/src/popover.js (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js new file mode 100644 index 000000000..6b14a2983 --- /dev/null +++ b/js/src/popover.js @@ -0,0 +1,178 @@ +import Tooltip from './tooltip' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): popover.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Popover = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'popover' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.popover' + const JQUERY_NO_CONFLICT = $.fn[NAME] + + const Default = $.extend({}, Tooltip.Default, { + placement : 'right', + trigger : 'click', + content : '', + template : '' + }) + + const ClassName = { + FADE : 'fade', + IN : 'in' + } + + const Selector = { + TITLE : '.popover-title', + CONTENT : '.popover-content', + ARROW : '.popover-arrow' + } + + const Event = { + HIDE : 'hide.bs.popover', + HIDDEN : 'hidden.bs.popover', + SHOW : 'show.bs.popover', + SHOWN : 'shown.bs.popover', + INSERTED : 'inserted.bs.popover', + CLICK : 'click.bs.popover', + FOCUSIN : 'focusin.bs.popover', + FOCUSOUT : 'focusout.bs.popover', + MOUSEENTER : 'mouseenter.bs.popover', + MOUSELEAVE : 'mouseleave.bs.popover' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Popover extends Tooltip { + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + static get NAME() { + return NAME + } + + static get DATA_KEY() { + return DATA_KEY + } + + static get Event() { + return Event + } + + + // overrides + + isWithContent() { + return this.getTitle() || this._getContent() + } + + getTipElement() { + return (this.tip = this.tip || $(this.config.template)[0]) + } + + setContent() { + let tip = this.getTipElement() + let title = this.getTitle() + let content = this._getContent() + let titleElement = $(tip).find(Selector.TITLE)[0] + + if (titleElement) { + titleElement[ + this.config.html ? 'innerHTML' : 'innerText' + ] = title + } + + // we use append for html objects to maintain js events + $(tip).find(Selector.CONTENT).children().detach().end()[ + this.config.html ? + (typeof content === 'string' ? 'html' : 'append') : 'text' + ](content) + + $(tip) + .removeClass(ClassName.FADE) + .removeClass(ClassName.IN) + + this.cleanupTether() + } + + // private + + _getContent() { + return this.element.getAttribute('data-content') + || (typeof this.config.content == 'function' ? + this.config.content.call(this.element) : + this.config.content) + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = typeof config === 'object' ? config : null + + if (!data && /destroy|hide/.test(config)) { + return + } + + if (!data) { + data = new Popover(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config]() + } + }) + } + } + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Popover._jQueryInterface + $.fn[NAME].Constructor = Popover + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Popover._jQueryInterface + } + + return Popover + +})(jQuery) + +export default Popover diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 0ab8804c6..e41a3ae12 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -55,7 +55,7 @@ const ScrollSpy = (($) => { constructor(element, config) { this._scrollElement = element.tagName === 'BODY' ? window : element - this._config = $.extend({}, Defaults, config) + this._config = $.extend({}, Default, config) this._selector = `${this._config.target || ''} .nav li > a` this._offsets = [] this._targets = [] diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 4c09a5baf..a04085130 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -8,7 +8,7 @@ import Util from './util' * -------------------------------------------------------------------------- */ -const ToolTip = (($) => { +const Tooltip = (($) => { /** @@ -34,33 +34,16 @@ const ToolTip = (($) => { delay : 0, html : false, selector : false, - attachment : 'top', + placement : 'top', offset : '0 0', constraints : null } - const HorizontalMirror = { - LEFT : 'right', - CENTER : 'center', - RIGHT : 'left' - } - - const VerticalMirror = { - TOP : 'bottom', - MIDDLE : 'middle', - BOTTOM : 'top' - } - - const VerticalDefault = { - LEFT : 'middle', - CENTER : 'bottom', - RIGHT : 'middle' - } - - const HorizontalDefault = { - TOP : 'center', - MIDDLE : 'left', - BOTTOM : 'center' + const AttachmentMap = { + TOP : 'bottom center', + RIGHT : 'middle left', + BOTTOM : 'top center', + LEFT : 'middle right' } const HoverState = { @@ -88,8 +71,7 @@ const ToolTip = (($) => { const Selector = { TOOLTIP : '.tooltip', - TOOLTIP_INNER : '.tooltip-inner', - TOOLTIP_ARROW : '.tooltip-arrow' + TOOLTIP_INNER : '.tooltip-inner' } const TetherClass = { @@ -120,12 +102,12 @@ const ToolTip = (($) => { this._timeout = 0 this._hoverState = '' this._activeTrigger = {} + this._tether = null // protected this.element = element this.config = this._getConfig(config) this.tip = null - this.tether = null this._setListeners() @@ -142,6 +124,19 @@ const ToolTip = (($) => { return Default } + static get NAME() { + return NAME + } + + static get DATA_KEY() { + return DATA_KEY + } + + static get Event() { + return Event + } + + // public @@ -159,16 +154,17 @@ const ToolTip = (($) => { toggle(event) { let context = this + let dataKey = this.constructor.DATA_KEY if (event) { - context = $(event.currentTarget).data(DATA_KEY) + context = $(event.currentTarget).data(dataKey) if (!context) { context = new this.constructor( event.currentTarget, this._getDelegateConfig() ) - $(event.currentTarget).data(DATA_KEY, context) + $(event.currentTarget).data(dataKey, context) } context._activeTrigger.click = !context._activeTrigger.click @@ -190,13 +186,19 @@ const ToolTip = (($) => { clearTimeout(this._timeout) this.hide(() => { $(this.element) - .off(Selector.TOOLTIP) - .removeData(DATA_KEY) + .off(`.${this.constructor.NAME}`) + .removeData(this.constructor.DATA_KEY) + + if (this.tip) { + $(this.tip).detach() + } + + this.tip = null }) } show() { - let showEvent = $.Event(Event.SHOW) + let showEvent = $.Event(this.constructor.Event.SHOW) if (this.isWithContent() && this._isEnabled) { $(this.element).trigger(showEvent) @@ -211,7 +213,7 @@ const ToolTip = (($) => { } let tip = this.getTipElement() - let tipId = Util.getUID(NAME) + let tipId = Util.getUID(this.constructor.NAME) tip.setAttribute('id', tipId) this.element.setAttribute('aria-describedby', tipId) @@ -222,19 +224,20 @@ const ToolTip = (($) => { $(tip).addClass(ClassName.FADE) } - let attachment = typeof this.config.attachment === 'function' ? - this.config.attachment.call(this, tip, this.element) : - this.config.attachment + let placement = typeof this.config.placement === 'function' ? + this.config.placement.call(this, tip, this.element) : + this.config.placement - attachment = this.getAttachment(attachment) + let attachment = this._getAttachment(placement) - $(tip).data(DATA_KEY, this) + $(tip) + .data(this.constructor.DATA_KEY, this) + .appendTo(document.body) - this.element.parentNode.insertBefore(tip, this.element.nextSibling) - $(this.element).trigger(Event.INSERTED) + $(this.element).trigger(this.constructor.Event.INSERTED) - this.tether = new Tether({ - element : this.tip, + this._tether = new Tether({ + element : tip, target : this.element, attachment : attachment, classes : TetherClass, @@ -244,7 +247,7 @@ const ToolTip = (($) => { }) Util.reflow(tip) - this.tether.position() + this._tether.position() $(tip).addClass(ClassName.IN) @@ -252,7 +255,7 @@ const ToolTip = (($) => { let prevHoverState = this._hoverState this._hoverState = null - $(this.element).trigger(Event.SHOWN) + $(this.element).trigger(this.constructor.Event.SHOWN) if (prevHoverState === HoverState.OUT) { this._leave(null, this) @@ -269,14 +272,14 @@ const ToolTip = (($) => { hide(callback) { let tip = this.getTipElement() - let hideEvent = $.Event(Event.HIDE) + let hideEvent = $.Event(this.constructor.Event.HIDE) let complete = () => { if (this._hoverState !== HoverState.IN && tip.parentNode) { tip.parentNode.removeChild(tip) } this.element.removeAttribute('aria-describedby') - $(this.element).trigger(Event.HIDDEN) + $(this.element).trigger(this.constructor.Event.HIDDEN) this.cleanupTether() if (callback) { @@ -317,47 +320,6 @@ const ToolTip = (($) => { return (this.tip = this.tip || $(this.config.template)[0]) } - getAttachment(attachmentString) { - let attachmentArray = attachmentString.split(' ') - let normalizedAttachment = {} - - if (!attachmentArray.length) { - throw new Error('Tooltip requires attachment') - } - - for (let attachment of attachmentArray) { - attachment = attachment.toUpperCase() - - if (HorizontalMirror[attachment]) { - normalizedAttachment.horizontal = HorizontalMirror[attachment] - } - - if (VerticalMirror[attachment]) { - normalizedAttachment.vertical = VerticalMirror[attachment] - } - } - - if (!normalizedAttachment.horizontal && - (!normalizedAttachment.vertical)) { - throw new Error('Tooltip requires valid attachment') - } - - if (!normalizedAttachment.horizontal) { - normalizedAttachment.horizontal = - HorizontalDefault[normalizedAttachment.vertical.toUpperCase()] - } - - if (!normalizedAttachment.vertical) { - normalizedAttachment.vertical = - VerticalDefault[normalizedAttachment.horizontal.toUpperCase()] - } - - return [ - normalizedAttachment.vertical, - normalizedAttachment.horizontal - ].join(' ') - } - setContent() { let tip = this.getTipElement() let title = this.getTitle() @@ -384,43 +346,43 @@ const ToolTip = (($) => { return title } - removeTetherClasses(i, css) { - return ((css.baseVal || css).match( - new RegExp(`(^|\\s)${CLASS_PREFIX}-\\S+`, 'g')) || [] - ).join(' ') - } - cleanupTether() { - if (this.tether) { - this.tether.destroy() + if (this._tether) { + this._tether.destroy() // clean up after tether's junk classes // remove after they fix issue // (https://github.com/HubSpot/tether/issues/36) - $(this.element).removeClass(this.removeTetherClasses) - $(this.tip).removeClass(this.removeTetherClasses) + $(this.element).removeClass(this._removeTetherClasses) + $(this.tip).removeClass(this._removeTetherClasses) } } // private + _getAttachment(placement) { + return AttachmentMap[placement.toUpperCase()] + } + _setListeners() { let triggers = this.config.trigger.split(' ') triggers.forEach((trigger) => { if (trigger === 'click') { $(this.element).on( - Event.CLICK, + this.constructor.Event.CLICK, this.config.selector, this.toggle.bind(this) ) } else if (trigger !== Trigger.MANUAL) { let eventIn = trigger == Trigger.HOVER ? - Event.MOUSEENTER : Event.FOCUSIN + this.constructor.Event.MOUSEENTER : + this.constructor.Event.FOCUSIN let eventOut = trigger == Trigger.HOVER ? - Event.MOUSELEAVE : Event.FOCUSOUT + this.constructor.Event.MOUSELEAVE : + this.constructor.Event.FOCUSOUT $(this.element) .on( @@ -446,6 +408,12 @@ const ToolTip = (($) => { } } + _removeTetherClasses(i, css) { + return ((css.baseVal || css).match( + new RegExp(`(^|\\s)${CLASS_PREFIX}-\\S+`, 'g')) || [] + ).join(' ') + } + _fixTitle() { let titleType = typeof this.element.getAttribute('data-original-title') if (this.element.getAttribute('title') || @@ -459,14 +427,16 @@ const ToolTip = (($) => { } _enter(event, context) { - context = context || $(event.currentTarget).data(DATA_KEY) + let dataKey = this.constructor.DATA_KEY + + context = context || $(event.currentTarget).data(dataKey) if (!context) { context = new this.constructor( event.currentTarget, this._getDelegateConfig() ) - $(event.currentTarget).data(DATA_KEY, context) + $(event.currentTarget).data(dataKey, context) } if (event) { @@ -498,19 +468,21 @@ const ToolTip = (($) => { } _leave(event, context) { - context = context || $(event.currentTarget).data(DATA_KEY) + let dataKey = this.constructor.DATA_KEY + + context = context || $(event.currentTarget).data(dataKey) if (!context) { context = new this.constructor( event.currentTarget, this._getDelegateConfig() ) - $(event.currentTarget).data(DATA_KEY, context) + $(event.currentTarget).data(dataKey, context) } if (event) { context._activeTrigger[ - event.type == 'focusout' ? Triger.FOCUS : Trigger.HOVER + event.type == 'focusout' ? Trigger.FOCUS : Trigger.HOVER ] = false } @@ -545,7 +517,12 @@ const ToolTip = (($) => { } _getConfig(config) { - config = $.extend({}, Default, $(this.element).data(), config) + config = $.extend( + {}, + this.constructor.Default, + $(this.element).data(), + config + ) if (config.delay && typeof config.delay === 'number') { config.delay = { @@ -563,7 +540,7 @@ const ToolTip = (($) => { if (this.config) { for (let key in this.config) { let value = this.config[key] - if (Default[key] !== value) { + if (this.constructor.Default[key] !== value) { config[key] = value } } -- cgit v1.2.3 From ab1578465aee4a776412b48f16bfefca79381919 Mon Sep 17 00:00:00 2001 From: fat Date: Tue, 12 May 2015 16:52:54 -0700 Subject: grunt test-js, grunt dist-js now working --- js/src/carousel.js | 8 ++++---- js/src/modal.js | 8 ++++---- js/src/scrollspy.js | 2 +- js/src/tab.js | 10 ++++++++-- js/src/tooltip.js | 6 +++--- 5 files changed, 20 insertions(+), 14 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index bce51d7fd..8c9b45247 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -139,7 +139,7 @@ const Carousel = (($) => { if (this._config.interval && !this._isPaused) { this._interval = setInterval( - this.next.bind(this), this._config.interval + $.proxy(this.next, this), this._config.interval ) } } @@ -177,14 +177,14 @@ const Carousel = (($) => { _addEventListeners() { if (this._config.keyboard) { $(this._element) - .on('keydown.bs.carousel', this._keydown.bind(this)) + .on('keydown.bs.carousel', $.proxy(this._keydown, 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)) + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) } } diff --git a/js/src/modal.js b/js/src/modal.js index b2e8fd77f..300ea2a7f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -121,7 +121,7 @@ const Modal = (($) => { $(this._element).on( Event.DISMISS, Selector.DATA_DISMISS, - this.hide.bind(this) + $.proxy(this.hide, this) ) $(this._dialog).on(Event.MOUSEDOWN, () => { @@ -133,7 +133,7 @@ const Modal = (($) => { }) this._showBackdrop( - this._showElement.bind(this, relatedTarget) + $.proxy(this._showElement, this, relatedTarget) ) } @@ -166,7 +166,7 @@ const Modal = (($) => { ($(this._element).hasClass(ClassName.FADE))) { $(this._element) - .one(Util.TRANSITION_END, this._hideModal.bind(this)) + .one(Util.TRANSITION_END, $.proxy(this._hideModal, this)) .emulateTransitionEnd(TRANSITION_DURATION) } else { this._hideModal() @@ -241,7 +241,7 @@ const Modal = (($) => { _setResizeEvent() { if (this._isShown) { - $(window).on(Event.RESIZE, this._handleUpdate.bind(this)) + $(window).on(Event.RESIZE, $.proxy(this._handleUpdate, this)) } else { $(window).off(Event.RESIZE) } diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index e41a3ae12..763d133e8 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -62,7 +62,7 @@ const ScrollSpy = (($) => { this._activeTarget = null this._scrollHeight = 0 - $(this._scrollElement).on(Event.SCROLL, this._process.bind(this)) + $(this._scrollElement).on(Event.SCROLL, $.proxy(this._process, this)) this.refresh() this._process() diff --git a/js/src/tab.js b/js/src/tab.js index 4668ff9e6..f6b174ce2 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -156,8 +156,14 @@ const Tab = (($) => { && ((active && $(active).hasClass(ClassName.FADE)) || !!$(container).find(Selector.FADE_CHILD)[0]) - let complete = this._transitionComplete.bind( - this, element, active, isTransitioning, callback) + let complete = $.proxy( + this._transitionComplete, + this, + element, + active, + isTransitioning, + callback + ) if (active && isTransitioning) { $(active) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index a04085130..1bd018c0f 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -373,7 +373,7 @@ const Tooltip = (($) => { $(this.element).on( this.constructor.Event.CLICK, this.config.selector, - this.toggle.bind(this) + $.proxy(this.toggle, this) ) } else if (trigger !== Trigger.MANUAL) { @@ -388,12 +388,12 @@ const Tooltip = (($) => { .on( eventIn, this.config.selector, - this._enter.bind(this) + $.proxy(this._enter, this) ) .on( eventOut, this.config.selector, - this._leave.bind(this) + $.proxy(this._leave, this) ) } }) -- cgit v1.2.3 From f8b2569ec8956a1f4d09fe6fc9865bd200ecde43 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 12:48:34 -0700 Subject: implement global dispose method --- js/src/alert.js | 15 +++++++++---- js/src/button.js | 16 ++++++++++---- js/src/carousel.js | 37 ++++++++++++++++++++++++-------- js/src/collapse.js | 26 +++++++++++++++++------ js/src/dropdown.js | 45 +++++++++++++++++++++++++++------------ js/src/modal.js | 61 +++++++++++++++++++++++++++++++++++------------------ js/src/popover.js | 25 +++++++++++++--------- js/src/scrollspy.js | 25 ++++++++++++++++++---- js/src/tab.js | 19 +++++++++++------ js/src/tooltip.js | 53 ++++++++++++++++++++++++++++------------------ 10 files changed, 224 insertions(+), 98 deletions(-) (limited to 'js/src') diff --git a/js/src/alert.js b/js/src/alert.js index 57a59746a..eda74d706 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -20,6 +20,8 @@ const Alert = (($) => { const NAME = 'alert' const VERSION = '4.0.0' const DATA_KEY = 'bs.alert' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 150 @@ -28,9 +30,9 @@ const Alert = (($) => { } const Event = { - CLOSE : 'close.bs.alert', - CLOSED : 'closed.bs.alert', - CLICK : 'click.bs.alert.data-api' + CLOSE : `close${EVENT_KEY}`, + CLOSED : `closed${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -75,6 +77,11 @@ const Alert = (($) => { this._removeElement(rootElement) } + dispose() { + $.removeData(this._element, DATA_KEY) + this._element = null + } + // private @@ -159,7 +166,7 @@ const Alert = (($) => { */ $(document).on( - Event.CLICK, + Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert()) ) diff --git a/js/src/button.js b/js/src/button.js index 7520de2f6..8210e8ae0 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -17,6 +17,8 @@ const Button = (($) => { const NAME = 'button' const VERSION = '4.0.0' const DATA_KEY = 'bs.button' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 150 @@ -35,8 +37,9 @@ const Button = (($) => { } const Event = { - CLICK : 'click.bs.button.data-api', - FOCUS_BLUR : 'focus.bs.button.data-api blur.bs.button.data-api' + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, + FOCUS_BLUR_DATA_API : `focus${EVENT_KEY}${DATA_API_KEY} ` + + `blur${EVENT_KEY}${DATA_API_KEY}` } @@ -101,6 +104,11 @@ const Button = (($) => { } } + dispose() { + $.removeData(this._element, DATA_KEY) + this._element = null + } + // static @@ -129,7 +137,7 @@ const Button = (($) => { */ $(document) - .on(Event.CLICK, Selector.DATA_TOGGLE_CARROT, function (event) { + .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, function (event) { event.preventDefault() let button = event.target @@ -140,7 +148,7 @@ const Button = (($) => { Button._jQueryInterface.call($(button), 'toggle') }) - .on(Event.FOCUS_BLUR, Selector.DATA_TOGGLE_CARROT, function (event) { + .on(Event.FOCUS_BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, function (event) { var button = $(event.target).closest(Selector.BUTTON)[0] $(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type)) }) diff --git a/js/src/carousel.js b/js/src/carousel.js index 8c9b45247..a4b6da298 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -20,6 +20,8 @@ const Carousel = (($) => { const NAME = 'carousel' const VERSION = '4.0.0' const DATA_KEY = 'bs.carousel' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 600 @@ -37,10 +39,13 @@ const Carousel = (($) => { } const Event = { - SLIDE : 'slide.bs.carousel', - SLID : 'slid.bs.carousel', - CLICK : 'click.bs.carousel.data-api', - LOAD : 'load' + SLIDE : `slide${EVENT_KEY}`, + SLID : `slid${EVENT_KEY}`, + KEYDOWN : `keydown${EVENT_KEY}`, + MOUSEENTER : `mouseenter${EVENT_KEY}`, + MOUSELEAVE : `mouseleave${EVENT_KEY}`, + LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -171,20 +176,34 @@ const Carousel = (($) => { this._slide(direction, this._items[index]) } + dispose() { + $(this._element).off(EVENT_KEY) + $.removeData(this._element, DATA_KEY) + + this._items = null + this._config = null + this._element = null + this._interval = null + this._isPaused = null + this._isSliding = null + this._activeElement = null + this._indicatorsElement = null + } + // private _addEventListeners() { if (this._config.keyboard) { $(this._element) - .on('keydown.bs.carousel', $.proxy(this._keydown, this)) + .on(Event.KEYDOWN, $.proxy(this._keydown, this)) } if (this._config.pause == 'hover' && !('ontouchstart' in document.documentElement)) { $(this._element) - .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) - .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + .on(Event.MOUSEENTER, $.proxy(this.pause, this)) + .on(Event.MOUSELEAVE, $.proxy(this.cycle, this)) } } @@ -405,9 +424,9 @@ const Carousel = (($) => { */ $(document) - .on(Event.CLICK, Selector.DATA_SLIDE, Carousel._dataApiClickHandler) + .on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler) - $(window).on(Event.LOAD, function () { + $(window).on(Event.LOAD_DATA_API, function () { $(Selector.DATA_RIDE).each(function () { let $carousel = $(this) Carousel._jQueryInterface.call($carousel, $carousel.data()) diff --git a/js/src/collapse.js b/js/src/collapse.js index ac506b86b..ded623448 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -20,6 +20,8 @@ const Collapse = (($) => { const NAME = 'collapse' const VERSION = '4.0.0' const DATA_KEY = 'bs.collapse' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 600 @@ -29,11 +31,11 @@ const Collapse = (($) => { } const Event = { - SHOW : 'show.bs.collapse', - SHOWN : 'shown.bs.collapse', - HIDE : 'hide.bs.collapse', - HIDDEN : 'hidden.bs.collapse', - CLICK : 'click.bs.collapse.data-api' + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -110,8 +112,8 @@ const Collapse = (($) => { return } - let activesData let actives + let activesData if (this._parent) { actives = $.makeArray($(Selector.ACTIVES)) @@ -244,6 +246,16 @@ const Collapse = (($) => { this._isTransitioning = isTransitioning } + dispose() { + $.removeData(this._element, DATA_KEY) + + this._config = null + this._parent = null + this._element = null + this._triggerArray = null + this._isTransitioning = null + } + // private @@ -323,7 +335,7 @@ const Collapse = (($) => { * ------------------------------------------------------------------------ */ - $(document).on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { event.preventDefault() let target = Collapse._getTargetFromElement(this) diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 3135ca73a..bae0f7adb 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -20,16 +20,18 @@ const Dropdown = (($) => { const NAME = 'dropdown' const VERSION = '4.0.0' const DATA_KEY = 'bs.dropdown' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const Event = { - HIDE   : 'hide.bs.dropdown', - HIDDEN   : 'hidden.bs.dropdown', - SHOW   : 'show.bs.dropdown', - SHOWN   : 'shown.bs.dropdown', - CLICK   : 'click.bs.dropdown', - KEYDOWN   : 'keydown.bs.dropdown.data-api', - CLICK_DATA : 'click.bs.dropdown.data-api' + HIDE   : `hide${EVENT_KEY}`, + HIDDEN   : `hidden${EVENT_KEY}`, + SHOW   : `show${EVENT_KEY}`, + SHOWN   : `shown${EVENT_KEY}`, + CLICK   : `click${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, + KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -59,7 +61,9 @@ const Dropdown = (($) => { class Dropdown { constructor(element) { - $(element).on(Event.CLICK, this.toggle) + this._element = element + + this._addEventListeners() } @@ -114,6 +118,19 @@ const Dropdown = (($) => { return false } + dispose() { + $.removeData(this._element, DATA_KEY) + $(this._element).off(EVENT_KEY) + this._element = null + } + + + // private + + _addEventListeners() { + $(this._element).on(Event.CLICK, this.toggle) + } + // static @@ -239,12 +256,12 @@ const Dropdown = (($) => { */ $(document) - .on(Event.KEYDOWN, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) - .on(Event.KEYDOWN, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) - .on(Event.KEYDOWN, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) - .on(Event.CLICK_DATA, Dropdown._clearMenus) - .on(Event.CLICK_DATA, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) - .on(Event.CLICK_DATA, Selector.FORM_CHILD, function (e) { + .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) + .on(Event.CLICK_DATA_API, Dropdown._clearMenus) + .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) + .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, function (e) { e.stopPropagation() }) diff --git a/js/src/modal.js b/js/src/modal.js index 300ea2a7f..d0e1ec21b 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -20,6 +20,8 @@ const Modal = (($) => { const NAME = 'modal' const VERSION = '4.0.0' const DATA_KEY = 'bs.modal' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 300 const BACKDROP_TRANSITION_DURATION = 150 @@ -31,17 +33,17 @@ const Modal = (($) => { } const Event = { - HIDE   : 'hide.bs.modal', - HIDDEN   : 'hidden.bs.modal', - SHOW   : 'show.bs.modal', - SHOWN   : 'shown.bs.modal', - DISMISS : 'click.dismiss.bs.modal', - KEYDOWN : 'keydown.dismiss.bs.modal', - FOCUSIN : 'focusin.bs.modal', - RESIZE : 'resize.bs.modal', - CLICK : 'click.bs.modal.data-api', - MOUSEDOWN : 'mousedown.dismiss.bs.modal', - MOUSEUP : 'mouseup.dismiss.bs.modal' + HIDE   : `hide${EVENT_KEY}`, + HIDDEN   : `hidden${EVENT_KEY}`, + SHOW   : `show${EVENT_KEY}`, + SHOWN   : `shown${EVENT_KEY}`, + FOCUSIN : `focusin${EVENT_KEY}`, + RESIZE : `resize${EVENT_KEY}`, + CLICK_DISMISS : `click.dismiss${EVENT_KEY}`, + KEYDOWN_DISMISS : `keydown.dismiss${EVENT_KEY}`, + MOUSEUP_DISMISS : `mouseup.dismiss${EVENT_KEY}`, + MOUSEDOWN_DISMISS : `mousedown.dismiss${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -119,13 +121,13 @@ const Modal = (($) => { this._setResizeEvent() $(this._element).on( - Event.DISMISS, + Event.CLICK_DISMISS, Selector.DATA_DISMISS, $.proxy(this.hide, this) ) - $(this._dialog).on(Event.MOUSEDOWN, () => { - $(this._element).one(Event.MOUSEUP, (event) => { + $(this._dialog).on(Event.MOUSEDOWN_DISMISS, () => { + $(this._element).one(Event.MOUSEUP_DISMISS, (event) => { if ($(event.target).is(this._element)) { that._ignoreBackdropClick = true } @@ -159,8 +161,8 @@ const Modal = (($) => { $(this._element).removeClass(ClassName.IN) - $(this._element).off(Event.DISMISS) - $(this._dialog).off(Event.MOUSEDOWN) + $(this._element).off(Event.CLICK_DISMISS) + $(this._dialog).off(Event.MOUSEDOWN_DISMISS) if (Util.supportsTransitionEnd() && ($(this._element).hasClass(ClassName.FADE))) { @@ -173,6 +175,25 @@ const Modal = (($) => { } } + dispose() { + $.removeData(this._element, DATA_KEY) + + $(window).off(EVENT_KEY) + $(document).off(EVENT_KEY) + $(this._element).off(EVENT_KEY) + $(this._backdrop).off(EVENT_KEY) + + this._config = null + this._element = null + this._dialog = null + this._backdrop = null + this._isShown = null + this._isBodyOverflowing = null + this._ignoreBackdropClick = null + this._originalBodyPadding = null + this._scrollbarWidth = null + } + // private @@ -228,14 +249,14 @@ const Modal = (($) => { _setEscapeEvent() { if (this._isShown && this._config.keyboard) { - $(this._element).on(Event.KEYDOWN, (event) => { + $(this._element).on(Event.KEYDOWN_DISMISS, (event) => { if (event.which === 27) { this.hide() } }) } else if (!this._isShown) { - $(this._element).off(Event.KEYDOWN) + $(this._element).off(Event.KEYDOWN_DISMISS) } } @@ -280,7 +301,7 @@ const Modal = (($) => { $(this._backdrop).appendTo(this.$body) - $(this._element).on(Event.DISMISS, (event) => { + $(this._element).on(Event.CLICK_DISMISS, (event) => { if (this._ignoreBackdropClick) { this._ignoreBackdropClick = false return @@ -440,7 +461,7 @@ const Modal = (($) => { * ------------------------------------------------------------------------ */ - $(document).on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { let target let selector = Util.getSelectorFromElement(this) diff --git a/js/src/popover.js b/js/src/popover.js index 6b14a2983..eab4c7e63 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -20,6 +20,7 @@ const Popover = (($) => { const NAME = 'popover' const VERSION = '4.0.0' const DATA_KEY = 'bs.popover' + const EVENT_KEY = `.${DATA_KEY}` const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = $.extend({}, Tooltip.Default, { @@ -44,16 +45,16 @@ const Popover = (($) => { } const Event = { - HIDE : 'hide.bs.popover', - HIDDEN : 'hidden.bs.popover', - SHOW : 'show.bs.popover', - SHOWN : 'shown.bs.popover', - INSERTED : 'inserted.bs.popover', - CLICK : 'click.bs.popover', - FOCUSIN : 'focusin.bs.popover', - FOCUSOUT : 'focusout.bs.popover', - MOUSEENTER : 'mouseenter.bs.popover', - MOUSELEAVE : 'mouseleave.bs.popover' + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + INSERTED : `inserted${EVENT_KEY}`, + CLICK : `click${EVENT_KEY}`, + FOCUSIN : `focusin${EVENT_KEY}`, + FOCUSOUT : `focusout${EVENT_KEY}`, + MOUSEENTER : `mouseenter${EVENT_KEY}`, + MOUSELEAVE : `mouseleave${EVENT_KEY}` } @@ -88,6 +89,10 @@ const Popover = (($) => { return Event } + static get EVENT_KEY() { + return EVENT_KEY + } + // overrides diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 763d133e8..d1be3a45f 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -20,6 +20,8 @@ const ScrollSpy = (($) => { const NAME = 'scrollspy' const VERSION = '4.0.0' const DATA_KEY = 'bs.scrollspy' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { @@ -27,9 +29,9 @@ const ScrollSpy = (($) => { } const Event = { - ACTIVATE : 'activate.bs.scrollspy', - SCROLL : 'scroll.bs.scrollspy', - LOAD : 'load.bs.scrollspy.data-api' + ACTIVATE : `activate${EVENT_KEY}`, + SCROLL : `scroll${EVENT_KEY}`, + LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -54,6 +56,7 @@ const ScrollSpy = (($) => { class ScrollSpy { constructor(element, config) { + this._element = element this._scrollElement = element.tagName === 'BODY' ? window : element this._config = $.extend({}, Default, config) this._selector = `${this._config.target || ''} .nav li > a` @@ -123,6 +126,20 @@ const ScrollSpy = (($) => { }) } + dispose() { + $.removeData(this._element, DATA_KEY) + $(this._scrollElement).off(EVENT_KEY) + + this._element = null + this._scrollElement = null + this._config = null + this._selector = null + this._offsets = null + this._targets = null + this._activeTarget = null + this._scrollHeight = null + } + // private @@ -244,7 +261,7 @@ const ScrollSpy = (($) => { * ------------------------------------------------------------------------ */ - $(window).on(Event.LOAD, function () { + $(window).on(Event.LOAD_DATA_API, function () { let scrollSpys = $.makeArray($(Selector.DATA_SPY)) for (let i = scrollSpys.length; i--;) { diff --git a/js/src/tab.js b/js/src/tab.js index f6b174ce2..61c062d89 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -20,15 +20,17 @@ const Tab = (($) => { const NAME = 'tab' const VERSION = '4.0.0' const DATA_KEY = 'bs.tab' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 150 const Event = { - HIDE : 'hide.bs.tab', - HIDDEN : 'hidden.bs.tab', - SHOW : 'show.bs.tab', - SHOWN : 'shown.bs.tab', - CLICK : 'click.bs.tab.data-api' + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -146,6 +148,11 @@ const Tab = (($) => { } } + dispose() { + $.removeClass(this._element, DATA_KEY) + this._element = null + } + // private @@ -258,7 +265,7 @@ const Tab = (($) => { */ $(document) - .on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { event.preventDefault() Tab._jQueryInterface.call($(this), 'show') }) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 1bd018c0f..42639895e 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -20,6 +20,7 @@ const Tooltip = (($) => { const NAME = 'tooltip' const VERSION = '4.0.0' const DATA_KEY = 'bs.tooltip' + const EVENT_KEY = `.${DATA_KEY}` const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 150 const CLASS_PREFIX = 'bs-tether' @@ -52,16 +53,16 @@ const Tooltip = (($) => { } const Event = { - HIDE : 'hide.bs.tooltip', - HIDDEN : 'hidden.bs.tooltip', - SHOW : 'show.bs.tooltip', - SHOWN : 'shown.bs.tooltip', - INSERTED : 'inserted.bs.tooltip', - CLICK : 'click.bs.tooltip', - FOCUSIN : 'focusin.bs.tooltip', - FOCUSOUT : 'focusout.bs.tooltip', - MOUSEENTER : 'mouseenter.bs.tooltip', - MOUSELEAVE : 'mouseleave.bs.tooltip' + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + INSERTED : `inserted${EVENT_KEY}`, + CLICK : `click${EVENT_KEY}`, + FOCUSIN : `focusin${EVENT_KEY}`, + FOCUSOUT : `focusout${EVENT_KEY}`, + MOUSEENTER : `mouseenter${EVENT_KEY}`, + MOUSELEAVE : `mouseleave${EVENT_KEY}` } const ClassName = { @@ -136,6 +137,9 @@ const Tooltip = (($) => { return Event } + static get EVENT_KEY() { + return EVENT_KEY + } // public @@ -182,19 +186,28 @@ const Tooltip = (($) => { } } - destroy() { + dispose() { clearTimeout(this._timeout) - this.hide(() => { - $(this.element) - .off(`.${this.constructor.NAME}`) - .removeData(this.constructor.DATA_KEY) - if (this.tip) { - $(this.tip).detach() - } + this.cleanupTether() - this.tip = null - }) + $.removeData(this.element, this.constructor.DATA_KEY) + + $(this.element).off(this.constructor.EVENT_KEY) + + if (this.tip) { + $(this.tip).remove() + } + + this._isEnabled = null + this._timeout = null + this._hoverState = null + this._activeTrigger = null + this._tether = null + + this.element = null + this.config = null + this.tip = null } show() { -- cgit v1.2.3 From da495ee24c239ef9c9c154670b1a641745ac147f Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 12:55:11 -0700 Subject: address https://github.com/twbs/bootstrap/pull/16135 --- js/src/scrollspy.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index d1be3a45f..3f4a145a1 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -25,7 +25,8 @@ const ScrollSpy = (($) => { const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { - offset : 10 + offset : 10, + method : 'auto' } const Event = { @@ -46,6 +47,11 @@ const ScrollSpy = (($) => { LI : 'li' } + const OffsetMethod = { + OFFSET : 'offset', + POSITION : 'position' + } + /** * ------------------------------------------------------------------------ @@ -86,13 +92,14 @@ const ScrollSpy = (($) => { // public refresh() { - let offsetMethod = 'offset' - let offsetBase = 0 + let autoMethod = this._scrollElement !== this._scrollElement.window ? + OffsetMethod.POSITION : OffsetMethod.OFFSET - if (this._scrollElement !== this._scrollElement.window) { - offsetMethod = 'position' - offsetBase = this._getScrollTop() - } + let offsetMethod = this._config.method === 'auto' ? + autoMethod : this._config.method + + let offsetBase = offsetMethod === OffsetMethod.POSITION ? + this._getScrollTop() : 0 this._offsets = [] this._targets = [] -- cgit v1.2.3 From 7ef0e52fd042da2fdf107a3347abab3486a67790 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 13:22:26 -0700 Subject: add "focus" option for turning off modal focusing #16050 --- js/src/modal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index d0e1ec21b..88a3c8950 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -29,6 +29,7 @@ const Modal = (($) => { const Default = { backdrop : true, keyboard : true, + focus : true, show : true } @@ -223,7 +224,7 @@ const Modal = (($) => { }) let transitionComplete = () => { - this._element.focus() + if (this._config.focus) this._element.focus() $(this._element).trigger(shownEvent) } -- cgit v1.2.3 From b0d142334f0d15e63577b28e2d7045c2199dcd6a Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 13:43:56 -0700 Subject: fix #15301 --- js/src/modal.js | 2 +- js/src/scrollspy.js | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 88a3c8950..084c4ec3a 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -217,7 +217,7 @@ const Modal = (($) => { $(this._element).addClass(ClassName.IN) - this._enforceFocus() + if (this._config.focus) this._enforceFocus() let shownEvent = $.Event(Event.SHOWN, { relatedTarget: relatedTarget diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 3f4a145a1..bb639f91b 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -26,7 +26,8 @@ const ScrollSpy = (($) => { const Default = { offset : 10, - method : 'auto' + method : 'auto', + target : '' } const Event = { @@ -43,8 +44,9 @@ const ScrollSpy = (($) => { const Selector = { DATA_SPY : '[data-spy="scroll"]', ACTIVE : '.active', + LI : 'li', LI_DROPDOWN : 'li.dropdown', - LI : 'li' + NAV_ANCHORS : '.nav li > a' } const OffsetMethod = { @@ -64,8 +66,8 @@ const ScrollSpy = (($) => { constructor(element, config) { this._element = element this._scrollElement = element.tagName === 'BODY' ? window : element - this._config = $.extend({}, Default, config) - this._selector = `${this._config.target || ''} .nav li > a` + this._config = this._getConfig(config) + this._selector = `${this._config.target} ${Selector.NAV_ANCHORS}` this._offsets = [] this._targets = [] this._activeTarget = null @@ -150,6 +152,21 @@ const ScrollSpy = (($) => { // private + _getConfig(config) { + config = $.extend({}, Default, config) + + if (typeof config.target !== 'string') { + let id = $(config.target).attr('id') + if (!id) { + id = Util.getUID(NAME) + $(config.target).attr('id', id) + } + config.target = `#${id}` + } + + return config + } + _getScrollTop() { return this._scrollElement === window ? this._scrollElement.scrollY : this._scrollElement.scrollTop -- cgit v1.2.3 From eaab1def7af7d7e1ab32ff69d043b46e2815ca22 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 14:46:50 -0700 Subject: add simple type checker implementation --- js/src/carousel.js | 16 +++++++++++++++- js/src/collapse.js | 14 +++++++++++++- js/src/modal.js | 15 ++++++++++++++- js/src/popover.js | 8 ++++++++ js/src/scrollspy.js | 8 ++++++++ js/src/tab.js | 4 ---- js/src/tooltip.js | 25 ++++++++++++++++++++++++- js/src/util.js | 28 ++++++++++++++++++++++++++++ 8 files changed, 110 insertions(+), 8 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index a4b6da298..c11f0a599 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -33,6 +33,14 @@ const Carousel = (($) => { wrap : true } + const DefaultType = { + interval : '(number|boolean)', + keyboard : 'boolean', + slide : '(boolean|string)', + pause : '(string|boolean)', + wrap : 'boolean' + } + const Direction = { NEXT : 'next', PREVIOUS : 'prev' @@ -84,7 +92,7 @@ const Carousel = (($) => { this._isPaused = false this._isSliding = false - this._config = config + this._config = this._getConfig(config) this._element = $(element)[0] this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0] @@ -193,6 +201,12 @@ const Carousel = (($) => { // private + _getConfig(config) { + config = $.extend({}, Default, config) + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + _addEventListeners() { if (this._config.keyboard) { $(this._element) diff --git a/js/src/collapse.js b/js/src/collapse.js index ded623448..6a5fcd854 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -30,6 +30,11 @@ const Collapse = (($) => { parent : null } + const DefaultType = { + toggle : 'boolean', + parent : '(string|null)' + } + const Event = { SHOW : `show${EVENT_KEY}`, SHOWN : `shown${EVENT_KEY}`, @@ -67,7 +72,7 @@ const Collapse = (($) => { constructor(element, config) { this._isTransitioning = false this._element = element - this._config = $.extend({}, Default, config) + this._config = this._getConfig(config) this._triggerArray = $.makeArray($( `[data-toggle="collapse"][href="#${element.id}"],` + `[data-toggle="collapse"][data-target="#${element.id}"]` @@ -259,6 +264,13 @@ const Collapse = (($) => { // private + _getConfig(config) { + config = $.extend({}, Default, config) + config.toggle = !!config.toggle // coerce string values + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + _getDimension() { let hasWidth = $(this._element).hasClass(Dimension.WIDTH) return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT diff --git a/js/src/modal.js b/js/src/modal.js index 084c4ec3a..2ca603b23 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -33,6 +33,13 @@ const Modal = (($) => { show : true } + const DefaultType = { + backdrop : '(boolean|string)', + keyboard : 'boolean', + focus : 'boolean', + show : 'boolean' + } + const Event = { HIDE   : `hide${EVENT_KEY}`, HIDDEN   : `hidden${EVENT_KEY}`, @@ -71,7 +78,7 @@ const Modal = (($) => { class Modal { constructor(element, config) { - this._config = config + this._config = this._getConfig(config) this._element = element this._dialog = $(element).find(Selector.DIALOG)[0] this._backdrop = null @@ -198,6 +205,12 @@ const Modal = (($) => { // private + _getConfig(config) { + config = $.extend({}, Default, config) + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + _showElement(relatedTarget) { let transition = Util.supportsTransitionEnd() && $(this._element).hasClass(ClassName.FADE) diff --git a/js/src/popover.js b/js/src/popover.js index eab4c7e63..31c7a3ae1 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -33,6 +33,10 @@ const Popover = (($) => { + '
' }) + const DefaultType = $.extend({}, Tooltip.DefaultType, { + content : '(string|function)' + }) + const ClassName = { FADE : 'fade', IN : 'in' @@ -93,6 +97,10 @@ const Popover = (($) => { return EVENT_KEY } + static get DefaultType() { + return DefaultType + } + // overrides diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index bb639f91b..a407511f6 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -30,6 +30,12 @@ const ScrollSpy = (($) => { target : '' } + const DefaultType = { + offset : 'number', + method : 'string', + target : '(string|element)' + } + const Event = { ACTIVATE : `activate${EVENT_KEY}`, SCROLL : `scroll${EVENT_KEY}`, @@ -164,6 +170,8 @@ const ScrollSpy = (($) => { config.target = `#${id}` } + Util.typeCheckConfig(NAME, config, DefaultType) + return config } diff --git a/js/src/tab.js b/js/src/tab.js index 61c062d89..4d8d7dec8 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -72,10 +72,6 @@ const Tab = (($) => { return VERSION } - static get Default() { - return Default - } - // public diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 42639895e..5d62e154a 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -37,7 +37,20 @@ const Tooltip = (($) => { selector : false, placement : 'top', offset : '0 0', - constraints : null + constraints : [] + } + + const DefaultType = { + animation : 'boolean', + template : 'string', + title : '(string|function)', + trigger : 'string', + delay : '(number|object)', + html : 'boolean', + selector : '(string|boolean)', + placement : '(string|function)', + offset : 'string', + constraints : 'array' } const AttachmentMap = { @@ -141,6 +154,10 @@ const Tooltip = (($) => { return EVENT_KEY } + static get DefaultType() { + return DefaultType + } + // public @@ -544,6 +561,12 @@ const Tooltip = (($) => { } } + Util.typeCheckConfig( + NAME, + config, + this.constructor.DefaultType + ) + return config } diff --git a/js/src/util.js b/js/src/util.js index c9ffbe555..86bea6578 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -23,6 +23,15 @@ const Util = (($) => { transition : 'transitionend' } + // shoutout AngusCroll (https://goo.gl/pxwQGp) + function toType(obj) { + return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() + } + + function isElement(obj) { + return (obj[0] || obj).nodeType; + } + function getSpecialTransitionEndEvent() { return { bindType: transition.end, @@ -115,6 +124,25 @@ const Util = (($) => { supportsTransitionEnd() { return !!transition + }, + + typeCheckConfig(componentName, config, configTypes) { + + for (let property in configTypes) { + let expectedTypes = configTypes[property] + let value = config[property] + let valueType + + if (value && isElement(value)) valueType = 'element' + else valueType = toType(value) + + if (!new RegExp(expectedTypes).test(valueType)) { + throw new Error( + `${componentName.toUpperCase()}: ` + + `Option "${property}" provided type "${valueType}" ` + + `but expected type "${expectedTypes}".`) + } + } } } -- cgit v1.2.3 From 6b2b0ed32f485103f58fe42057e93a175e14bc2a Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 14:52:46 -0700 Subject: al tests passing, dist rebuilt, w/typechecker --- js/src/collapse.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 6a5fcd854..e911c98d1 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -27,12 +27,12 @@ const Collapse = (($) => { const Default = { toggle : true, - parent : null + parent : '' } const DefaultType = { toggle : 'boolean', - parent : '(string|null)' + parent : 'string' } const Event = { -- cgit v1.2.3