aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorZac Echola <[email protected]>2015-04-28 09:45:16 -0500
committerZac Echola <[email protected]>2015-04-28 09:45:16 -0500
commit407e8dc9d8d8e28d456a37d9d69e1d3c89d6fbb4 (patch)
treed841221f51dd9852fbce1c562127158d35b8812d /js
parente46c998316359f3368d80736d7eb750d85dc4da5 (diff)
parente6438592e9ea28324ba1a4344e724cab28032208 (diff)
downloadbootstrap-407e8dc9d8d8e28d456a37d9d69e1d3c89d6fbb4.tar.xz
bootstrap-407e8dc9d8d8e28d456a37d9d69e1d3c89d6fbb4.zip
Merge branch 'master' of https://github.com/twbs/bootstrap into list_group_buttons
Diffstat (limited to 'js')
-rw-r--r--js/button.js8
-rw-r--r--js/dropdown.js79
-rw-r--r--js/tab.js2
-rw-r--r--js/tests/unit/button.js4
-rw-r--r--js/tests/unit/popover.js28
-rw-r--r--js/tests/unit/tooltip.js128
-rw-r--r--js/tooltip.js36
7 files changed, 214 insertions, 71 deletions
diff --git a/js/button.js b/js/button.js
index 7c7c021f9..8716aff4f 100644
--- a/js/button.js
+++ b/js/button.js
@@ -31,7 +31,7 @@
var val = $el.is('input') ? 'val' : 'html'
var data = $el.data()
- state = state + 'Text'
+ state += 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]())
@@ -56,8 +56,8 @@
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
- if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
- else $parent.find('.active').removeClass('active')
+ if ($input.prop('checked')) changed = false
+ if (!$input.prop('checked') || !this.$element.hasClass('active')) $parent.find('.active').removeClass('active')
}
if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
} else {
@@ -107,7 +107,7 @@
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
- e.preventDefault()
+ if (!$(e.target).is('input[type="radio"]')) e.preventDefault()
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
diff --git a/js/dropdown.js b/js/dropdown.js
index 1fbf74a31..8fd86a025 100644
--- a/js/dropdown.js
+++ b/js/dropdown.js
@@ -21,6 +21,40 @@
Dropdown.VERSION = '3.3.4'
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $this = $(this)
+ var $parent = getParent($this)
+ var relatedTarget = { relatedTarget: this }
+
+ if (!$parent.hasClass('open')) return
+
+ if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.attr('aria-expanded', 'false')
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+ })
+ }
+
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
@@ -70,7 +104,7 @@
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
- if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
+ if (!isActive && e.which != 27 || isActive && e.which == 27) {
if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click')
}
@@ -82,47 +116,13 @@
var index = $items.index(e.target)
- if (e.which == 38 && index > 0) index-- // up
- if (e.which == 40 && index < $items.length - 1) index++ // down
- if (!~index) index = 0
+ if (e.which == 38 && index > 0) index-- // up
+ if (e.which == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
$items.eq(index).trigger('focus')
}
- function clearMenus(e) {
- if (e && e.which === 3) return
- $(backdrop).remove()
- $(toggle).each(function () {
- var $this = $(this)
- var $parent = getParent($this)
- var relatedTarget = { relatedTarget: this }
-
- if (!$parent.hasClass('open')) return
-
- if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
-
- $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
-
- if (e.isDefaultPrevented()) return
-
- $this.attr('aria-expanded', 'false')
- $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
- })
- }
-
- function getParent($this) {
- var selector = $this.attr('data-target')
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
- }
-
- var $parent = selector && $(selector)
-
- return $parent && $parent.length ? $parent : $this.parent()
- }
-
// DROPDOWN PLUGIN DEFINITION
// ==========================
@@ -160,7 +160,6 @@
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
- .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
- .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
}(jQuery);
diff --git a/js/tab.js b/js/tab.js
index 935d5bc80..685bbc91f 100644
--- a/js/tab.js
+++ b/js/tab.js
@@ -65,7 +65,7 @@
var $active = container.find('> .active')
var transition = callback
&& $.support.transition
- && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
+ && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
function next() {
$active
diff --git a/js/tests/unit/button.js b/js/tests/unit/button.js
index 02312ebaf..ed7b248d4 100644
--- a/js/tests/unit/button.js
+++ b/js/tests/unit/button.js
@@ -167,13 +167,13 @@ $(function () {
assert.ok(!$btn2.find('input').prop('checked'), 'btn2 is not checked')
$btn2.find('input').trigger('click')
assert.ok(!$btn1.hasClass('active'), 'btn1 does not have active class')
- assert.ok(!$btn1.find('input').prop('checked'), 'btn1 is checked')
+ assert.ok(!$btn1.find('input').prop('checked'), 'btn1 is not checked')
assert.ok($btn2.hasClass('active'), 'btn2 has active class')
assert.ok($btn2.find('input').prop('checked'), 'btn2 is checked')
$btn2.find('input').trigger('click') // clicking an already checked radio should not un-check it
assert.ok(!$btn1.hasClass('active'), 'btn1 does not have active class')
- assert.ok(!$btn1.find('input').prop('checked'), 'btn1 is checked')
+ assert.ok(!$btn1.find('input').prop('checked'), 'btn1 is not checked')
assert.ok($btn2.hasClass('active'), 'btn2 has active class')
assert.ok($btn2.find('input').prop('checked'), 'btn2 is checked')
})
diff --git a/js/tests/unit/popover.js b/js/tests/unit/popover.js
index fbabab9e3..a25df3a58 100644
--- a/js/tests/unit/popover.js
+++ b/js/tests/unit/popover.js
@@ -259,4 +259,32 @@ $(function () {
assert.strictEqual($popover.data('bs.popover'), undefined, 'should not initialize the popover')
})
+ QUnit.test('should throw an error when template contains multiple top-level elements', function (assert) {
+ assert.expect(1)
+ assert.throws(function () {
+ $('<span data-toggle="popover" data-title="some title" data-content="some content">some text</span>')
+ .appendTo('#qunit-fixture')
+ .bootstrapPopover({ template: '<div>Foo</div><div>Bar</div>' })
+ .bootstrapPopover('show')
+ }, new Error('popover `template` option must consist of exactly 1 top-level element!'))
+ })
+
+ QUnit.test('should fire inserted event', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+
+ $('<a href="#">@Johann-S</a>')
+ .appendTo('#qunit-fixture')
+ .on('inserted.bs.popover', function () {
+ assert.notEqual($('.popover').length, 0, 'popover was inserted')
+ assert.ok(true, 'inserted event fired')
+ done()
+ })
+ .bootstrapPopover({
+ title: 'Test',
+ content: 'Test'
+ })
+ .bootstrapPopover('show')
+ })
+
})
diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js
index 57a59db18..bdcf0bbd0 100644
--- a/js/tests/unit/tooltip.js
+++ b/js/tests/unit/tooltip.js
@@ -139,6 +139,20 @@ $(function () {
.bootstrapTooltip('show')
})
+ QUnit.test('should fire inserted event', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+
+ $('<div title="tooltip title"/>')
+ .appendTo('#qunit-fixture')
+ .on('inserted.bs.tooltip', function () {
+ assert.notEqual($('.tooltip').length, 0, 'tooltip was inserted')
+ assert.ok(true, 'inserted event fired')
+ done()
+ })
+ .bootstrapTooltip('show')
+ })
+
QUnit.test('should fire shown event', function (assert) {
assert.expect(1)
var done = assert.async()
@@ -362,23 +376,19 @@ $(function () {
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
})
- QUnit.test('should be placed dynamically with the dynamic placement option', function (assert) {
+ QUnit.test('should be placed dynamically to viewport with the dynamic placement option', function (assert) {
assert.expect(6)
- var $style = $('<style> a[rel="tooltip"] { display: inline-block; position: absolute; } </style>')
+ var $style = $('<style> div[rel="tooltip"] { position: absolute; } #qunit-fixture { top: inherit; left: inherit } </style>').appendTo('head')
var $container = $('<div/>')
.css({
- position: 'absolute',
- overflow: 'hidden',
- width: 600,
- height: 400,
- top: 0,
- left: 0
+ position: 'relative',
+ height: '100%'
})
- .appendTo(document.body)
+ .appendTo('#qunit-fixture')
var $topTooltip = $('<div style="left: 0; top: 0;" rel="tooltip" title="Top tooltip">Top Dynamic Tooltip</div>')
.appendTo($container)
- .bootstrapTooltip({ placement: 'auto' })
+ .bootstrapTooltip({ placement: 'auto', viewport: '#qunit-fixture' })
$topTooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned to bottom')
@@ -388,7 +398,7 @@ $(function () {
var $rightTooltip = $('<div style="right: 0;" rel="tooltip" title="Right tooltip">Right Dynamic Tooltip</div>')
.appendTo($container)
- .bootstrapTooltip({ placement: 'right auto' })
+ .bootstrapTooltip({ placement: 'right auto', viewport: '#qunit-fixture' })
$rightTooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.left'), 'right positioned tooltip is dynamically positioned left')
@@ -398,7 +408,7 @@ $(function () {
var $leftTooltip = $('<div style="left: 0;" rel="tooltip" title="Left tooltip">Left Dynamic Tooltip</div>')
.appendTo($container)
- .bootstrapTooltip({ placement: 'auto left' })
+ .bootstrapTooltip({ placement: 'auto left', viewport: '#qunit-fixture' })
$leftTooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.right'), 'left positioned tooltip is dynamically positioned right')
@@ -436,6 +446,31 @@ $(function () {
$styles.remove()
})
+ QUnit.test('should position tip on top if viewport has enough space and is not parent', function (assert) {
+ assert.expect(2)
+ var styles = '<style>'
+ + '#section { height: 300px; border: 1px solid red; margin-top: 100px; }'
+ + 'div[rel="tooltip"] { width: 150px; border: 1px solid blue; }'
+ + '</style>'
+ var $styles = $(styles).appendTo('head')
+
+ var $container = $('<div id="section"/>').appendTo('#qunit-fixture')
+ var $target = $('<div rel="tooltip" title="tip"/>')
+ .appendTo($container)
+ .bootstrapTooltip({
+ placement: 'auto top',
+ viewport: '#qunit-fixture'
+ })
+
+ $target.bootstrapTooltip('show')
+ assert.ok($('.tooltip').is('.top'), 'top positioned tooltip is dynamically positioned to top')
+
+ $target.bootstrapTooltip('hide')
+ assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
+
+ $styles.remove()
+ })
+
QUnit.test('should position tip on bottom if the tip\'s dimension exceeds the viewport area and placement is "auto top"', function (assert) {
assert.expect(2)
var styles = '<style>'
@@ -719,6 +754,65 @@ $(function () {
$styles.remove()
})
+ QUnit.test('should get viewport element from function', function (assert) {
+ assert.expect(3)
+ var styles = '<style>'
+ + '.tooltip, .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ + '.container-viewport { position: absolute; top: 50px; left: 60px; width: 300px; height: 300px; }'
+ + 'a[rel="tooltip"] { position: fixed; }'
+ + '</style>'
+ var $styles = $(styles).appendTo('head')
+
+ var $container = $('<div class="container-viewport"/>').appendTo(document.body)
+ var $target = $('<a href="#" rel="tooltip" title="tip" style="top: 50px; left: 350px;"/>').appendTo($container)
+ $target
+ .bootstrapTooltip({
+ placement: 'bottom',
+ viewport: function ($element) {
+ assert.strictEqual($element[0], $target[0], 'viewport function was passed target as argument')
+ return ($element.closest('.container-viewport'))
+ }
+ })
+
+ $target.bootstrapTooltip('show')
+ var $tooltip = $container.find('.tooltip')
+ assert.strictEqual(Math.round($tooltip.offset().left), Math.round(60 + $container.width() - $tooltip[0].offsetWidth))
+
+ $target.bootstrapTooltip('hide')
+ assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
+
+ $container.remove()
+ $styles.remove()
+ })
+
+ QUnit.test('should not misplace the tip when the right edge offset is greater or equal than the viewport width', function (assert) {
+ assert.expect(2)
+ var styles = '<style>'
+ + '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }'
+ + '.container-viewport, .container-viewport *, .container-viewport *:before, .container-viewport *:after { box-sizing: border-box; }'
+ + '.tooltip, .tooltip .tooltip-inner { width: 50px; height: 50px; max-width: none; background: red; }'
+ + '.container-viewport { padding: 100px; margin-left: 100px; width: 100px; }'
+ + '</style>'
+ var $styles = $(styles).appendTo('head')
+
+ var $container = $('<div class="container-viewport"/>').appendTo(document.body)
+ var $target = $('<a href="#" rel="tooltip" title="tip">foobar</a>')
+ .appendTo($container)
+ .bootstrapTooltip({
+ viewport: '.container-viewport'
+ })
+
+ $target.bootstrapTooltip('show')
+ var $tooltip = $container.find('.tooltip')
+ assert.strictEqual(Math.round($tooltip.offset().left), Math.round($target.position().left + $target.width() / 2 - $tooltip[0].offsetWidth / 2))
+
+ $target.bootstrapTooltip('hide')
+ assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
+
+ $container.remove()
+ $styles.remove()
+ })
+
QUnit.test('should not error when trying to show an auto-placed tooltip that has been removed from the dom', function (assert) {
assert.expect(1)
var passed = true
@@ -1180,4 +1274,14 @@ $(function () {
assert.strictEqual($tooltip.data('bs.tooltip'), undefined, 'should not initialize the tooltip')
})
+ QUnit.test('should throw an error when template contains multiple top-level elements', function (assert) {
+ assert.expect(1)
+ assert.throws(function () {
+ $('<a href="#" data-toggle="tooltip" title="Another tooltip"></a>')
+ .appendTo('#qunit-fixture')
+ .bootstrapTooltip({ template: '<div>Foo</div><div>Bar</div>' })
+ .bootstrapTooltip('show')
+ }, new Error('tooltip `template` option must consist of exactly 1 top-level element!'))
+ })
+
})
diff --git a/js/tooltip.js b/js/tooltip.js
index 27367880f..af1483aba 100644
--- a/js/tooltip.js
+++ b/js/tooltip.js
@@ -50,7 +50,7 @@
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
- this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
+ this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
@@ -185,6 +185,7 @@
.data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+ this.$element.trigger('inserted.bs.' + this.type)
var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth
@@ -192,13 +193,12 @@
if (autoPlace) {
var orgPlacement = placement
- var $container = this.options.container ? $(this.options.container) : this.$element.parent()
- var containerDim = this.getPosition($container)
+ var viewportDim = this.getPosition(this.$viewport)
- placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
- placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
- placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
- placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
+ placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
+ placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
+ placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
+ placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement
$tip
@@ -239,8 +239,8 @@
if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0
- offset.top = offset.top + marginTop
- offset.left = offset.left + marginLeft
+ offset.top += marginTop
+ offset.left += marginLeft
// $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0
@@ -322,7 +322,7 @@
Tooltip.prototype.fixTitle = function () {
var $e = this.$element
- if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
+ if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
@@ -377,7 +377,7 @@
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset
- } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
+ } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
@@ -403,7 +403,13 @@
}
Tooltip.prototype.tip = function () {
- return (this.$tip = this.$tip || $(this.options.template))
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ if (this.$tip.length != 1) {
+ throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
+ }
+ }
+ return this.$tip
}
Tooltip.prototype.arrow = function () {
@@ -440,6 +446,12 @@
clearTimeout(this.timeout)
this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type)
+ if (that.$tip) {
+ that.$tip.detach()
+ }
+ that.$tip = null
+ that.$arrow = null
+ that.$viewport = null
})
}