diff --git a/js/dist/carousel.js b/js/dist/carousel.js index 87b5cc086..d12b95331 100644 --- a/js/dist/carousel.js +++ b/js/dist/carousel.js @@ -35,6 +35,14 @@ var Carousel = (function ($) { wrap: true }; + var DefaultType = { + interval: '(number|boolean)', + keyboard: 'boolean', + slide: '(boolean|string)', + pause: 'string', + wrap: 'boolean' + }; + var Direction = { NEXT: 'next', PREVIOUS: 'prev' @@ -86,7 +94,7 @@ var Carousel = (function ($) { 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]; @@ -187,10 +195,17 @@ var Carousel = (function ($) { this._indicatorsElement = null; } }, { - key: '_addEventListeners', + key: '_getConfig', // private + value: function _getConfig(config) { + config = $.extend({}, Default, config); + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + } + }, { + key: '_addEventListeners', value: function _addEventListeners() { if (this._config.keyboard) { $(this._element).on(Event.KEYDOWN, $.proxy(this._keydown, this)); diff --git a/js/dist/carousel.js.map b/js/dist/carousel.js.map index da3cb6c58..a1f88ef45 100644 Binary files a/js/dist/carousel.js.map and b/js/dist/carousel.js.map differ diff --git a/js/dist/collapse.js b/js/dist/collapse.js index 0d014da2e..596e8f9f7 100644 --- a/js/dist/collapse.js +++ b/js/dist/collapse.js @@ -32,6 +32,11 @@ var Collapse = (function ($) { parent: null }; + var DefaultType = { + toggle: 'boolean', + parent: '(string|null)' + }; + var Event = { SHOW: 'show' + EVENT_KEY, SHOWN: 'shown' + EVENT_KEY, @@ -69,7 +74,7 @@ var Collapse = (function ($) { 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 + '"]'))); this._parent = this._config.parent ? this._getParent() : null; @@ -230,10 +235,18 @@ var Collapse = (function ($) { this._isTransitioning = null; } }, { - key: '_getDimension', + key: '_getConfig', // private + value: function _getConfig(config) { + config = $.extend({}, Default, config); + config.toggle = !!config.toggle; + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + } + }, { + key: '_getDimension', value: function _getDimension() { var hasWidth = $(this._element).hasClass(Dimension.WIDTH); return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT; diff --git a/js/dist/collapse.js.map b/js/dist/collapse.js.map index 8f50a03f3..af69b6d5c 100644 Binary files a/js/dist/collapse.js.map and b/js/dist/collapse.js.map differ diff --git a/js/dist/modal.js b/js/dist/modal.js index 80a493777..2a6387725 100644 --- a/js/dist/modal.js +++ b/js/dist/modal.js @@ -35,6 +35,13 @@ var Modal = (function ($) { show: true }; + var DefaultType = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + focus: 'boolean', + show: 'boolean' + }; + var Event = { HIDE: 'hide' + EVENT_KEY, HIDDEN: 'hidden' + EVENT_KEY, @@ -73,7 +80,7 @@ var Modal = (function ($) { function Modal(element, config) { _classCallCheck(this, Modal); - this._config = config; + this._config = this._getConfig(config); this._element = element; this._dialog = $(element).find(Selector.DIALOG)[0]; this._backdrop = null; @@ -184,10 +191,17 @@ var Modal = (function ($) { this._scrollbarWidth = null; } }, { - key: '_showElement', + key: '_getConfig', // private + value: function _getConfig(config) { + config = $.extend({}, Default, config); + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + } + }, { + key: '_showElement', value: function _showElement(relatedTarget) { var _this2 = this; diff --git a/js/dist/modal.js.map b/js/dist/modal.js.map index 247fbab7d..5d10cd6d3 100644 Binary files a/js/dist/modal.js.map and b/js/dist/modal.js.map differ diff --git a/js/dist/popover.js b/js/dist/popover.js index b9ef35586..8a30917c3 100644 --- a/js/dist/popover.js +++ b/js/dist/popover.js @@ -34,6 +34,10 @@ var Popover = (function ($) { template: '' }); + var DefaultType = $.extend({}, Tooltip.DefaultType, { + content: '(string|function)' + }); + var ClassName = { FADE: 'fade', IN: 'in' @@ -148,6 +152,11 @@ var Popover = (function ($) { get: function () { return EVENT_KEY; } + }, { + key: 'DefaultType', + get: function () { + return DefaultType; + } }, { key: '_jQueryInterface', diff --git a/js/dist/popover.js.map b/js/dist/popover.js.map index 5f29d837d..edc130101 100644 Binary files a/js/dist/popover.js.map and b/js/dist/popover.js.map differ diff --git a/js/dist/scrollspy.js b/js/dist/scrollspy.js index 47774a95b..b214f86ee 100644 --- a/js/dist/scrollspy.js +++ b/js/dist/scrollspy.js @@ -32,6 +32,12 @@ var ScrollSpy = (function ($) { target: '' }; + var DefaultType = { + offset: 'number', + method: 'string', + target: '(string|element)' + }; + var Event = { ACTIVATE: 'activate' + EVENT_KEY, SCROLL: 'scroll' + EVENT_KEY, @@ -155,6 +161,8 @@ var ScrollSpy = (function ($) { config.target = '#' + id; } + Util.typeCheckConfig(NAME, config, DefaultType); + return config; } }, { diff --git a/js/dist/scrollspy.js.map b/js/dist/scrollspy.js.map index 3f2a3c41f..bde96eda3 100644 Binary files a/js/dist/scrollspy.js.map and b/js/dist/scrollspy.js.map differ diff --git a/js/dist/tab.js b/js/dist/tab.js index d2935869d..95c561940 100644 --- a/js/dist/tab.js +++ b/js/dist/tab.js @@ -219,11 +219,6 @@ var Tab = (function ($) { get: function () { return VERSION; } - }, { - key: 'Default', - get: function () { - return Default; - } }, { key: '_jQueryInterface', diff --git a/js/dist/tab.js.map b/js/dist/tab.js.map index 9e7b58298..82a86ecb1 100644 Binary files a/js/dist/tab.js.map and b/js/dist/tab.js.map differ diff --git a/js/dist/tooltip.js b/js/dist/tooltip.js index b4db8deca..864e0bb76 100644 --- a/js/dist/tooltip.js +++ b/js/dist/tooltip.js @@ -37,7 +37,20 @@ var Tooltip = (function ($) { selector: false, placement: 'top', offset: '0 0', - constraints: null + constraints: [] + }; + + var 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' }; var AttachmentMap = { @@ -476,6 +489,8 @@ var Tooltip = (function ($) { }; } + Util.typeCheckConfig(NAME, config, this.constructor.DefaultType); + return config; } }, { @@ -527,6 +542,11 @@ var Tooltip = (function ($) { get: function () { return EVENT_KEY; } + }, { + key: 'DefaultType', + get: function () { + return DefaultType; + } }, { key: '_jQueryInterface', diff --git a/js/dist/tooltip.js.map b/js/dist/tooltip.js.map index ce5ce2d80..e823424cb 100644 Binary files a/js/dist/tooltip.js.map and b/js/dist/tooltip.js.map differ diff --git a/js/dist/util.js b/js/dist/util.js index f3654eacf..a135c3f38 100644 --- a/js/dist/util.js +++ b/js/dist/util.js @@ -24,6 +24,15 @@ var Util = (function ($) { 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, @@ -116,6 +125,21 @@ var Util = (function ($) { supportsTransitionEnd: function supportsTransitionEnd() { return !!transition; + }, + + typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) { + + for (var property in configTypes) { + var expectedTypes = configTypes[property]; + var value = config[property]; + var valueType = undefined; + + 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 + '".')); + } + } } }; diff --git a/js/dist/util.js.map b/js/dist/util.js.map index 34275a5b7..7c73c492e 100644 Binary files a/js/dist/util.js.map and b/js/dist/util.js.map differ 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}".`) + } + } } } diff --git a/js/tests/unit/carousel.js b/js/tests/unit/carousel.js index a8a36ad32..95345c39e 100644 --- a/js/tests/unit/carousel.js +++ b/js/tests/unit/carousel.js @@ -32,6 +32,36 @@ $(function () { assert.strictEqual($carousel[0], $el[0], 'collection contains element') }) + QUnit.test('should type check config options', function (assert) { + var message + var expectedMessage = 'CAROUSEL: Option "interval" provided type "string" but expected type "(number|boolean)".' + var config = { + interval: 'fat sux' + } + + try { + $('
').bootstrapCarousel(config) + } catch (e) { + message = e.message + } + + assert.ok(message === expectedMessage, 'correct error message') + + config = { + keyboard: $('div') + } + expectedMessage = 'CAROUSEL: Option "keyboard" provided type "element" but expected type "boolean".' + + try { + $('
').bootstrapCarousel(config) + } catch (e) { + message = e.message + } + + assert.ok(message === expectedMessage, 'correct error message') + }) + + QUnit.test('should not fire slid when slide is prevented', function (assert) { assert.expect(1) var done = assert.async()