diff options
| author | Mark Otto <[email protected]> | 2014-10-26 22:31:59 -0700 |
|---|---|---|
| committer | Mark Otto <[email protected]> | 2014-10-26 22:31:59 -0700 |
| commit | d6b0f45fb711989cb8ac32f6717d6920ef5c68e0 (patch) | |
| tree | 48f08aacd9aadf5cf6fe9aaeeebfa6eb7fab26d0 /js | |
| parent | d1660ad0788fa4e9b0d072323c1d70f9c6f5dbf2 (diff) | |
| parent | 66bb0b4fc963fec42e7168f40b18703d3f31bfa8 (diff) | |
| download | bootstrap-d6b0f45fb711989cb8ac32f6717d6920ef5c68e0.tar.xz bootstrap-d6b0f45fb711989cb8ac32f6717d6920ef5c68e0.zip | |
Merge branch 'master' into derp
Conflicts:
Gruntfile.js
dist/css/bootstrap-theme.css
dist/css/bootstrap-theme.css.map
dist/css/bootstrap-theme.min.css
dist/css/bootstrap.css
dist/css/bootstrap.css.map
dist/css/bootstrap.min.css
docs/_includes/components/dropdowns.html
docs/_includes/components/media.html
docs/_includes/components/navs.html
docs/_includes/components/progress-bars.html
docs/_includes/components/responsive-embed.html
docs/_includes/css/buttons.html
docs/_includes/css/forms.html
docs/_includes/css/less.html
docs/_includes/css/overview.html
docs/_includes/css/responsive-utilities.html
docs/_includes/customizer-variables.html
docs/_includes/getting-started/browser-device-support.html
docs/_includes/getting-started/grunt.html
docs/_includes/getting-started/template.html
docs/_includes/header.html
docs/_includes/js/alerts.html
docs/_includes/js/buttons.html
docs/_includes/js/carousel.html
docs/_includes/js/collapse.html
docs/_includes/js/dropdowns.html
docs/_includes/js/modal.html
docs/_includes/js/popovers.html
docs/_includes/js/scrollspy.html
docs/_includes/js/tabs.html
docs/_includes/js/tooltips.html
docs/_includes/nav/components.html
docs/_includes/nav/getting-started.html
docs/_layouts/default.html
docs/about.html
docs/assets/css/docs.min.css
docs/assets/css/src/docs.css
docs/assets/js/customize.min.js
docs/assets/js/docs.min.js
docs/assets/js/raw-files.min.js
docs/browser-bugs.html
docs/components.html
docs/components/navbar.md
docs/css.html
docs/dist/css/bootstrap-theme.css
docs/dist/css/bootstrap-theme.css.map
docs/dist/css/bootstrap-theme.min.css
docs/dist/css/bootstrap.css
docs/dist/css/bootstrap.css.map
docs/dist/css/bootstrap.min.css
docs/examples/blog/index.html
docs/examples/carousel/index.html
docs/examples/cover/index.html
docs/examples/dashboard/index.html
docs/examples/grid/index.html
docs/examples/jumbotron-narrow/index.html
docs/examples/jumbotron/index.html
docs/examples/justified-nav/index.html
docs/examples/navbar-fixed-top/index.html
docs/examples/navbar-static-top/index.html
docs/examples/navbar/index.html
docs/examples/non-responsive/index.html
docs/examples/offcanvas/index.html
docs/examples/signin/index.html
docs/examples/starter-template/index.html
docs/examples/sticky-footer-navbar/index.html
docs/examples/sticky-footer/index.html
docs/examples/theme/index.html
docs/examples/tooltip-viewport/index.html
docs/getting-started.html
docs/javascript.html
docs/migration.html
less/_animation.less
less/_modal.less
less/_navbar.less
less/_variables.less
less/glyphicons.less
less/navs.less
less/panels.less
less/progress-bars.less
Diffstat (limited to 'js')
| -rw-r--r-- | js/button.js | 2 | ||||
| -rw-r--r-- | js/carousel.js | 9 | ||||
| -rw-r--r-- | js/collapse.js | 14 | ||||
| -rw-r--r-- | js/dropdown.js | 10 | ||||
| -rw-r--r-- | js/modal.js | 20 | ||||
| -rw-r--r-- | js/popover.js | 14 | ||||
| -rw-r--r-- | js/scrollspy.js | 16 | ||||
| -rw-r--r-- | js/tab.js | 37 | ||||
| -rw-r--r-- | js/tests/index.html | 5 | ||||
| -rw-r--r-- | js/tests/unit/button.js | 20 | ||||
| -rw-r--r-- | js/tests/unit/carousel.js | 101 | ||||
| -rw-r--r-- | js/tests/unit/collapse.js | 35 | ||||
| -rw-r--r-- | js/tests/unit/modal.js | 4 | ||||
| -rw-r--r-- | js/tests/unit/popover.js | 48 | ||||
| -rw-r--r-- | js/tests/unit/scrollspy.js | 69 | ||||
| -rw-r--r-- | js/tests/unit/tab.js | 101 | ||||
| -rw-r--r-- | js/tests/unit/tooltip.js | 271 | ||||
| -rw-r--r-- | js/tests/visual/dropdown.html | 41 | ||||
| -rw-r--r-- | js/tooltip.js | 35 |
19 files changed, 746 insertions, 106 deletions
diff --git a/js/button.js b/js/button.js index b3e944c59..901e47a6e 100644 --- a/js/button.js +++ b/js/button.js @@ -60,6 +60,8 @@ else $parent.find('.active').removeClass('active') } if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) } if (changed) this.$element.toggleClass('active') diff --git a/js/carousel.js b/js/carousel.js index 65cc7b912..b68899009 100644 --- a/js/carousel.js +++ b/js/carousel.js @@ -14,7 +14,7 @@ // ========================= var Carousel = function (element, options) { - this.$element = $(element).on('keydown.bs.carousel', $.proxy(this.keydown, this)) + this.$element = $(element) this.$indicators = this.$element.find('.carousel-indicators') this.options = options this.paused = @@ -23,7 +23,9 @@ this.$active = this.$items = null - this.options.pause == 'hover' && this.$element + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) } @@ -35,7 +37,8 @@ Carousel.DEFAULTS = { interval: 5000, pause: 'hover', - wrap: true + wrap: true, + keyboard: true } Carousel.prototype.keydown = function (e) { diff --git a/js/collapse.js b/js/collapse.js index d7d56bdd5..abbf25f2a 100644 --- a/js/collapse.js +++ b/js/collapse.js @@ -38,17 +38,21 @@ Collapse.prototype.show = function () { if (this.transitioning || this.$element.hasClass('in')) return + var activesData + var actives = this.$parent && this.$parent.find('> .panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + var startEvent = $.Event('show.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return - var actives = this.$parent && this.$parent.find('> .panel').children('.in, .collapsing') - if (actives && actives.length) { - var hasData = actives.data('bs.collapse') - if (hasData && hasData.transitioning) return Plugin.call(actives, 'hide') - hasData || actives.data('bs.collapse', null) + activesData || actives.data('bs.collapse', null) } var dimension = this.dimension() diff --git a/js/dropdown.js b/js/dropdown.js index 756fc21f5..7388f8124 100644 --- a/js/dropdown.js +++ b/js/dropdown.js @@ -55,7 +55,7 @@ } Dropdown.prototype.keydown = function (e) { - if (!/(38|40|27)/.test(e.keyCode)) return + if (!/(38|40|27|32)/.test(e.which)) return var $this = $(this) @@ -67,7 +67,7 @@ var $parent = getParent($this) var isActive = $parent.hasClass('open') - if (!isActive || (isActive && e.keyCode == 27)) { + if ((!isActive && e.which != 27) || (isActive && e.which == 27)) { if (e.which == 27) $parent.find(toggle).trigger('focus') return $this.trigger('click') } @@ -77,10 +77,10 @@ if (!$items.length) return - var index = $items.index($items.filter(':focus')) + var index = $items.index(e.target) - if (e.keyCode == 38 && index > 0) index-- // up - if (e.keyCode == 40 && index < $items.length - 1) index++ // down + 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') diff --git a/js/modal.js b/js/modal.js index d0426b01b..6e2ac7e74 100644 --- a/js/modal.js +++ b/js/modal.js @@ -107,9 +107,6 @@ this.isShown = false - this.$body.removeClass('modal-open') - - this.resetScrollbar() this.escape() $(document).off('focusin.bs.modal') @@ -150,6 +147,8 @@ var that = this this.$element.hide() this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetScrollbar() that.$element.trigger('hidden.bs.modal') }) } @@ -167,14 +166,13 @@ var doAnimate = $.support.transition && animate this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') - .appendTo(this.$body) - - this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { - if (e.target !== e.currentTarget) return - this.options.backdrop == 'static' - ? this.$element[0].focus.call(this.$element[0]) - : this.hide.call(this) - }, this)) + .prependTo(this.$element) + .on('click.dismiss.bs.modal', $.proxy(function (e) { + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus.call(this.$element[0]) + : this.hide.call(this) + }, this)) if (doAnimate) this.$backdrop[0].offsetWidth // force reflow diff --git a/js/popover.js b/js/popover.js index 87b8d12de..c69be715f 100644 --- a/js/popover.js +++ b/js/popover.js @@ -86,12 +86,18 @@ function Plugin(option) { return this.each(function () { - var $this = $(this) - var data = $this.data('bs.popover') - var options = typeof option == 'object' && option + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + var selector = options && options.selector if (!data && option == 'destroy') return - if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (selector) { + if (!data) $this.data('bs.popover', (data = {})) + if (!data[selector]) data[selector] = new Popover(this, options) + } else { + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + } if (typeof option == 'string') data[option]() }) } diff --git a/js/scrollspy.js b/js/scrollspy.js index 841df1878..430b5d6aa 100644 --- a/js/scrollspy.js +++ b/js/scrollspy.js @@ -65,7 +65,6 @@ return ($href && $href.length && $href.is(':visible') - && $el.is(':visible') && [[$href[offsetMethod]().top + offsetBase, href]]) || null }) .sort(function (a, b) { return a[0] - b[0] }) @@ -92,8 +91,9 @@ return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) } - if (activeTarget && scrollTop <= offsets[0]) { - return activeTarget != (i = targets[0]) && this.activate(i) + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() } for (i = offsets.length; i--;) { @@ -107,9 +107,7 @@ ScrollSpy.prototype.activate = function (target) { this.activeTarget = target - $(this.selector) - .parentsUntil(this.options.target, '.active') - .removeClass('active') + this.clear() var selector = this.selector + '[data-target="' + target + '"],' + @@ -128,6 +126,12 @@ active.trigger('activate.bs.scrollspy') } + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + // SCROLLSPY PLUGIN DEFINITION // =========================== @@ -33,22 +33,30 @@ if ($this.parent('li').hasClass('active')) return - var previous = $ul.find('.active:last a')[0] - var e = $.Event('show.bs.tab', { - relatedTarget: previous + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] }) - $this.trigger(e) + $previous.trigger(hideEvent) + $this.trigger(showEvent) - if (e.isDefaultPrevented()) return + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return var $target = $(selector) this.activate($this.closest('li'), $ul) this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) $this.trigger({ type: 'shown.bs.tab', - relatedTarget: previous + relatedTarget: $previous[0] }) }) } @@ -63,9 +71,15 @@ $active .removeClass('active') .find('> .dropdown-menu > .active') - .removeClass('active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) - element.addClass('active') + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) if (transition) { element[0].offsetWidth // reflow for transition @@ -75,7 +89,12 @@ } if (element.parent('.dropdown-menu')) { - element.closest('li.dropdown').addClass('active') + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) } callback && callback() diff --git a/js/tests/index.html b/js/tests/index.html index 1c025cf76..194f531aa 100644 --- a/js/tests/index.html +++ b/js/tests/index.html @@ -10,6 +10,11 @@ <!-- QUnit --> <link rel="stylesheet" href="vendor/qunit.css" media="screen"> <script src="vendor/qunit.js"></script> + <style> + #qunit-tests > li.pass { + display: none;/* Make it easier to see failing tests is Sauce screencasts */ + } + </style> <script> // See https://github.com/axemclion/grunt-saucelabs#test-result-details-with-qunit var log = [] diff --git a/js/tests/unit/button.js b/js/tests/unit/button.js index bd431d546..73747cdd4 100644 --- a/js/tests/unit/button.js +++ b/js/tests/unit/button.js @@ -85,7 +85,7 @@ $(function () { }) test('should toggle active', function () { - var $btn = $('<button class="btn">mdo</button>') + var $btn = $('<button class="btn" data-toggle="button">mdo</button>') ok(!$btn.hasClass('active'), 'btn does not have active class') $btn.bootstrapButton('toggle') ok($btn.hasClass('active'), 'btn has class active') @@ -102,6 +102,24 @@ $(function () { ok($btn.hasClass('active'), 'btn has class active') }) + test('should toggle aria-pressed', function () { + var $btn = $('<button class="btn" data-toggle="button" aria-pressed="false">redux</button>') + equal($btn.attr('aria-pressed'), 'false', 'btn aria-pressed state is false') + $btn.bootstrapButton('toggle') + equal($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true') + }) + + test('should toggle aria-pressed when btn children are clicked', function () { + var $btn = $('<button class="btn" data-toggle="button" aria-pressed="false">redux</button>') + var $inner = $('<i/>') + $btn + .append($inner) + .appendTo('#qunit-fixture') + equal($btn.attr('aria-pressed'), 'false', 'btn aria-pressed state is false') + $inner.click() + equal($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true') + }) + test('should toggle active when btn children are clicked within btn-group', function () { var $btngroup = $('<div class="btn-group" data-toggle="buttons"/>') var $btn = $('<button class="btn">fat</button>') diff --git a/js/tests/unit/carousel.js b/js/tests/unit/carousel.js index 3f9e61a34..6f0b9642f 100644 --- a/js/tests/unit/carousel.js +++ b/js/tests/unit/carousel.js @@ -398,4 +398,105 @@ $(function () { strictEqual($template.find('.item')[1], $template.find('.active')[0], 'second item active') }) + + test('should go to previous item if left arrow key is pressed', function () { + var templateHTML = '<div id="myCarousel" class="carousel" data-interval="false">' + + '<div class="carousel-inner">' + + '<div id="first" class="item">' + + '<img alt="">' + + '</div>' + + '<div id="second" class="item active">' + + '<img alt="">' + + '</div>' + + '<div id="third" class="item">' + + '<img alt="">' + + '</div>' + + '</div>' + + '</div>' + var $template = $(templateHTML) + + $template.bootstrapCarousel() + + strictEqual($template.find('.item')[1], $template.find('.active')[0], 'second item active') + + $template.trigger($.Event('keydown', { which: 37 })) + + strictEqual($template.find('.item')[0], $template.find('.active')[0], 'first item active') + }) + + test('should go to next item if right arrow key is pressed', function () { + var templateHTML = '<div id="myCarousel" class="carousel" data-interval="false">' + + '<div class="carousel-inner">' + + '<div id="first" class="item active">' + + '<img alt="">' + + '</div>' + + '<div id="second" class="item">' + + '<img alt="">' + + '</div>' + + '<div id="third" class="item">' + + '<img alt="">' + + '</div>' + + '</div>' + + '</div>' + var $template = $(templateHTML) + + $template.bootstrapCarousel() + + strictEqual($template.find('.item')[0], $template.find('.active')[0], 'first item active') + + $template.trigger($.Event('keydown', { which: 39 })) + + strictEqual($template.find('.item')[1], $template.find('.active')[0], 'second item active') + }) + + test('should support disabling the keyboard navigation', function () { + var templateHTML = '<div id="myCarousel" class="carousel" data-interval="false" data-keyboard="false">' + + '<div class="carousel-inner">' + + '<div id="first" class="item active">' + + '<img alt="">' + + '</div>' + + '<div id="second" class="item">' + + '<img alt="">' + + '</div>' + + '<div id="third" class="item">' + + '<img alt="">' + + '</div>' + + '</div>' + + '</div>' + var $template = $(templateHTML) + + $template.bootstrapCarousel() + + strictEqual($template.find('.item')[0], $template.find('.active')[0], 'first item active') + + $template.trigger($.Event('keydown', { which: 39 })) + + strictEqual($template.find('.item')[0], $template.find('.active')[0], 'first item still active after right arrow press') + + $template.trigger($.Event('keydown', { which: 37 })) + + strictEqual($template.find('.item')[0], $template.find('.active')[0], 'first item still active after left arrow press') + }) + + test('should only add mouseenter and mouseleave listeners when not on mobile', function () { + var isMobile = 'ontouchstart' in document.documentElement + var templateHTML = '<div id="myCarousel" class="carousel" data-interval="false" data-pause="hover">' + + '<div class="carousel-inner">' + + '<div id="first" class="item active">' + + '<img alt="">' + + '</div>' + + '<div id="second" class="item">' + + '<img alt="">' + + '</div>' + + '<div id="third" class="item">' + + '<img alt="">' + + '</div>' + + '</div>' + + '</div>' + var $template = $(templateHTML).bootstrapCarousel() + + $.each(['mouseover', 'mouseout'], function (i, type) { + strictEqual(type in $._data($template[0], 'events'), !isMobile, 'does' + (isMobile ? ' not' : '') + ' listen for ' + type + ' events') + }) + }) }) diff --git a/js/tests/unit/collapse.js b/js/tests/unit/collapse.js index 825f79360..c1d78575c 100644 --- a/js/tests/unit/collapse.js +++ b/js/tests/unit/collapse.js @@ -263,4 +263,39 @@ $(function () { $target3.click() }) + test('should not fire show event if show is prevented because other element is still transitioning', function () { + stop() + + var accordionHTML = '<div id="accordion">' + + '<div class="panel"/>' + + '<div class="panel"/>' + + '</div>' + var showFired = false + var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.panel') + + var $target1 = $('<a data-toggle="collapse" href="#body1" data-parent="#accordion"/>').appendTo($groups.eq(0)) + + $('<div id="body1" class="collapse"/>') + .appendTo($groups.eq(0)) + .on('show.bs.collapse', function () { + showFired = true + }) + + var $target2 = $('<a data-toggle="collapse" href="#body2" data-parent="#accordion"/>').appendTo($groups.eq(1)) + var $body2 = $('<div id="body2" class="collapse"/>').appendTo($groups.eq(1)) + + $target2.click() + + $body2 + .toggleClass('in collapsing') + .data('bs.collapse').transitioning = 1 + + $target1.click() + + setTimeout(function () { + ok(!showFired, 'show event didn\'t fire') + start() + }, 1) + }) + }) diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js index 2262465a4..efd478b20 100644 --- a/js/tests/unit/modal.js +++ b/js/tests/unit/modal.js @@ -141,7 +141,7 @@ $(function () { notEqual($('#modal-test').length, 0, 'modal insterted into dom') $('.contents').click() ok($('#modal-test').is(':visible'), 'modal visible') - $('#modal-test').click() + $('#modal-test .modal-backdrop').click() }) .on('hidden.bs.modal', function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') @@ -196,7 +196,7 @@ $(function () { $('<div id="modal-test"><div class="contents"/></div>') .on('shown.bs.modal', function () { triggered = 0 - $('#modal-test').click() + $('#modal-test .modal-backdrop').click() }) .on('hide.bs.modal', function () { triggered += 1 diff --git a/js/tests/unit/popover.js b/js/tests/unit/popover.js index 5cb4cafdd..466ebace2 100644 --- a/js/tests/unit/popover.js +++ b/js/tests/unit/popover.js @@ -30,7 +30,7 @@ $(function () { }) test('should render popover element', function () { - var $popover = $('<a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>') + var $popover = $('<a href="#" title="mdo" data-content="https://twitter.com/mdo">@mdo</a>') .appendTo('#qunit-fixture') .bootstrapPopover('show') @@ -40,7 +40,7 @@ $(function () { }) test('should store popover instance in popover data object', function () { - var $popover = $('<a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>').bootstrapPopover() + var $popover = $('<a href="#" title="mdo" data-content="https://twitter.com/mdo">@mdo</a>').bootstrapPopover() ok($popover.data('bs.popover'), 'popover instance exists') }) @@ -173,4 +173,48 @@ $(function () { ok(!$._data($popover[0], 'events').mouseover && !$._data($popover[0], 'events').mouseout, 'popover does not have any events') }) + test('should render popover element using delegated selector', function () { + var $div = $('<div><a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a></div>') + .appendTo('#qunit-fixture') + .bootstrapPopover({ + selector: 'a', + trigger: 'click' + }) + + $div.find('a').click() + notEqual($('.popover').length, 0, 'popover was inserted') + + $div.find('a').click() + equal($('.popover').length, 0, 'popover was removed') + }) + + test('should render popover elements using different delegated selectors on the same node', function () { + var popoverHTML = '<div>' + + '<a href="#" class="first" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>' + + '<a href="#" class="second" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>' + + '</div>' + + var $div = $(popoverHTML) + .appendTo('#qunit-fixture') + .bootstrapPopover({ + selector: 'a.first', + trigger: 'click' + }) + .bootstrapPopover({ + selector: 'a.second', + trigger: 'click' + }) + + $div.find('a.first').click() + notEqual($('.popover').length, 0, 'first popover was inserted') + + $div.find('a.first').click() + equal($('.popover').length, 0, 'first popover removed') + + $div.find('a.second').click() + notEqual($('.popover').length, 0, 'second popover was inserted') + + $div.find('a.second').click() + equal($('.popover').length, 0, 'second popover removed') + }) }) diff --git a/js/tests/unit/scrollspy.js b/js/tests/unit/scrollspy.js index c071d0f65..0c9081491 100644 --- a/js/tests/unit/scrollspy.js +++ b/js/tests/unit/scrollspy.js @@ -29,22 +29,6 @@ $(function () { strictEqual($scrollspy[0], $el[0], 'collection contains element') }) - // Does not work properly ATM, #13500 will fix this - test('should switch "active" class on scroll', function () { - var topbarHTML = '<div class="topbar">' - + '<div class="topbar-inner">' - + '<div class="container">' - + '<h3><a href="#">Bootstrap</a></h3>' - + '<li><a href="#masthead">Overview</a></li>' - + '</ul>' - + '</div>' - + '</div>' - + '</div>' - var $topbar = $(topbarHTML).bootstrapScrollspy() - - ok($topbar.find('.active', true)) - }) - test('should only switch "active" class on current target', function () { stop() @@ -77,9 +61,9 @@ $(function () { var $section = $(sectionHTML).appendTo('#qunit-fixture') var $scrollspy = $section - .show() - .find('#scrollspy-example') - .bootstrapScrollspy({ target: '#ss-target' }) + .show() + .find('#scrollspy-example') + .bootstrapScrollspy({ target: '#ss-target' }) $scrollspy.on('scroll.bs.scrollspy', function () { ok($section.hasClass('active'), '"active" class still on root node') @@ -89,7 +73,7 @@ $(function () { $scrollspy.scrollTop(350) }) - test('middle navigation option correctly selected when large offset is used', function () { + test('should correctly select middle navigation option when large offset is used', function () { stop() var sectionHTML = '<div id="header" style="height: 500px;"></div>' @@ -107,8 +91,8 @@ $(function () { + '</div>' var $section = $(sectionHTML).appendTo('#qunit-fixture') var $scrollspy = $section - .show() - .filter('#content') + .show() + .filter('#content') $scrollspy.bootstrapScrollspy({ target: '#navigation', offset: $scrollspy.position().top }) @@ -158,4 +142,45 @@ $(function () { .then(function () { return testElementIsActiveAfterScroll('#li-2', '#div-2') }) }) + test('should clear selection if above the first section', function () { + stop() + + var sectionHTML = '<div id="header" style="height: 500px;"></div>' + + '<nav id="navigation" class="navbar">' + + '<ul class="nav navbar-nav">' + + '<li class="active"><a id="one-link" href="#one">One</a></li>' + + '<li><a id="two-link" href="#two">Two</a></li>' + + '<li><a id="three-link" href="#three">Three</a></li>' + + '</ul>' + + '</nav>' + $(sectionHTML).appendTo('#qunit-fixture') + + var scrollspyHTML = '<div id="content" style="height: 200px; overflow-y: auto;">' + + '<div id="spacer" style="height: 100px;"/>' + + '<div id="one" style="height: 100px;"/>' + + '<div id="two" style="height: 100px;"/>' + + '<div id="three" style="height: 100px;"/>' + + '<div id="spacer" style="height: 100px;"/>' + + '</div>' + var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture') + + $scrollspy + .bootstrapScrollspy({ + target: '#navigation', + offset: $scrollspy.position().top + }) + .one('scroll.bs.scrollspy', function () { + strictEqual($('.active').length, 1, '"active" class on only one element present') + strictEqual($('.active').has('#two-link').length, 1, '"active" class on second section') + + $scrollspy + .one('scroll.bs.scrollspy', function () { + strictEqual($('.active').length, 0, 'selection cleared') + start() + }) + .scrollTop(0) + }) + .scrollTop(201) + }) + }) diff --git a/js/tests/unit/tab.js b/js/tests/unit/tab.js index 8e50614ec..9b2a18d57 100644 --- a/js/tests/unit/tab.js +++ b/js/tests/unit/tab.js @@ -101,4 +101,105 @@ $(function () { .bootstrapTab('show') }) + test('should fire hide and hidden events', function () { + stop() + + var tabsHTML = '<ul class="tabs">' + + '<li><a href="#home">Home</a></li>' + + '<li><a href="#profile">Profile</a></li>' + + '</ul>' + + $(tabsHTML) + .find('li:first a') + .on('hide.bs.tab', function () { + ok(true, 'hide event fired') + }) + .bootstrapTab('show') + .end() + .find('li:last a') + .bootstrapTab('show') + + $(tabsHTML) + .find('li:first a') + .on('hidden.bs.tab', function () { + ok(true, 'hidden event fired') + start() + }) + .bootstrapTab('show') + .end() + .find('li:last a') + .bootstrapTab('show') + }) + + test('should not fire hidden when hide is prevented', function () { + stop() + + var tabsHTML = '<ul class="tabs">' + + '<li><a href="#home">Home</a></li>' + + '<li><a href="#profile">Profile</a></li>' + + '</ul>' + + $(tabsHTML) + .find('li:first a') + .on('hide.bs.tab', function (e) { + e.preventDefault() + ok(true, 'hide event fired') + start() + }) + .on('hidden.bs.tab', function () { + ok(false, 'hidden event fired') + }) + .bootstrapTab('show') + .end() + .find('li:last a') + .bootstrapTab('show') + }) + + test('hide and hidden events contain correct relatedTarget', function () { + stop() + + var tabsHTML = '<ul class="tabs">' + + '<li><a href="#home">Home</a></li>' + + '<li><a href="#profile">Profile</a></li>' + + '</ul>' + + $(tabsHTML) + .find('li:first a') + .on('hide.bs.tab', function (e) { + equal(e.relatedTarget.hash, '#profile', 'references correct element as relatedTarget') + }) + .on('hidden.bs.tab', function (e) { + equal(e.relatedTarget.hash, '#profile', 'references correct element as relatedTarget') + start() + }) + .bootstrapTab('show') + .end() + .find('li:last a') + .bootstrapTab('show') + }) + + test('selected tab should have aria-expanded', function () { + var tabsHTML = '<ul class="nav nav-tabs">' + + '<li class="active"><a href="#home" toggle="tab" aria-expanded="true">Home</a></li>' + + '<li><a href="#profile" toggle="tab" aria-expanded="false">Profile</a></li>' + + '</ul>' + var $tabs = $(tabsHTML).appendTo('#qunit-fixture') + + $tabs.find('li:first a').bootstrapTab('show') + equal($tabs.find('.active a').attr('aria-expanded'), 'true', 'shown tab has aria-expanded = true') + equal($tabs.find('li:not(.active) a').attr('aria-expanded'), 'false', 'hidden tab has aria-expanded = false') + + $tabs.find('li:last a').click() + equal($tabs.find('.active a').attr('aria-expanded'), 'true', 'after click, shown tab has aria-expanded = true') + equal($tabs.find('li:not(.active) a').attr('aria-expanded'), 'false', 'after click, hidden tab has aria-expanded = false') + + $tabs.find('li:first a').bootstrapTab('show') + equal($tabs.find('.active a').attr('aria-expanded'), 'true', 'shown tab has aria-expanded = true') + equal($tabs.find('li:not(.active) a').attr('aria-expanded'), 'false', 'hidden tab has aria-expanded = false') + + $tabs.find('li:first a').click() + equal($tabs.find('.active a').attr('aria-expanded'), 'true', 'after second show event, shown tab still has aria-expanded = true') + equal($tabs.find('li:not(.active) a').attr('aria-expanded'), 'false', 'after second show event, hidden tab has aria-expanded = false') + }) + }) diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js index c75924e9f..351dd61cb 100644 --- a/js/tests/unit/tooltip.js +++ b/js/tests/unit/tooltip.js @@ -235,6 +235,37 @@ $(function () { equal($('.tooltip').length, 0, 'tooltip was removed from dom') }) + test('should show tooltips with different delegate selectors on the same node on click', function () { + var tooltipHTML = '<div>' + + '<a href="#" class="first" rel="tooltip" title="First delegated tooltip"/>' + + '<a href="#" class="second" rel="tooltip" title="Second delegated tooltip"/>' + + '</div>' + + var $div = $(tooltipHTML) + .append() + .appendTo('#qunit-fixture') + .bootstrapTooltip({ + selector: 'a.first[rel="tooltip"]', + trigger: 'click' + }) + .bootstrapTooltip({ + selector: 'a.second[rel="tooltip"]', + trigger: 'click' + }) + + $div.find('a.first').click() + ok($('.tooltip').is('.fade.in'), 'first tooltip is faded in') + + $div.find('a.first').click() + equal($('.tooltip').length, 0, 'first tooltip was removed from dom') + + $div.find('a.second').click() + ok($('.tooltip').is('.fade.in'), 'second tooltip is faded in') + + $div.find('a.second').click() + equal($('.tooltip').length, 0, 'second tooltip was removed from dom') + }) + test('should show tooltip when toggle is called', function () { $('<a href="#" rel="tooltip" title="tooltip on toggle"/>') .appendTo('#qunit-fixture') @@ -382,6 +413,164 @@ $(function () { $style.remove() }) + test('should position tip on top if viewport has enough space and placement is "auto top"', function () { + var styles = '<style>' + + 'body { padding-top: 100px; }' + + '#section { height: 300px; border: 1px solid red; padding-top: 50px }' + + '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: '#section' + }) + + $target.bootstrapTooltip('show') + ok($('.tooltip').is('.top'), 'top positioned tooltip is dynamically positioned to top') + + $target.bootstrapTooltip('hide') + equal($('.tooltip').length, 0, 'tooltip removed from dom') + + $styles.remove() + }) + + test('should position tip on bottom if the tip\'s dimension exceeds the viewport area and placement is "auto top"', function () { + var styles = '<style>' + + 'body { padding-top: 100px; }' + + '#section { height: 300px; border: 1px solid red; }' + + '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: '#section' + }) + + $target.bootstrapTooltip('show') + ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned to bottom') + + $target.bootstrapTooltip('hide') + equal($('.tooltip').length, 0, 'tooltip removed from dom') + + $styles.remove() + }) + + test('should display the tip on top whenever scrollable viewport has enough room if the given placement is "auto top"', function () { + var styles = '<style>' + + '#scrollable-div { height: 200px; overflow: auto; }' + + '.tooltip-item { margin: 200px 0 400px; width: 150px; }' + + '</style>' + var $styles = $(styles).appendTo('head') + + var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture') + var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>') + .appendTo($container) + .bootstrapTooltip({ + placement: 'top auto', + viewport: '#scrollable-div' + }) + + $('#scrollable-div').scrollTop(100) + + $target.bootstrapTooltip('show') + ok($('.tooltip').is('.fade.top.in'), 'has correct classes applied') + + $target.bootstrapTooltip('hide') + equal($('.tooltip').length, 0, 'tooltip removed from dom') + + $styles.remove() + }) + + test('should display the tip on bottom whenever scrollable viewport doesn\'t have enough room if the given placement is "auto top"', function () { + var styles = '<style>' + + '#scrollable-div { height: 200px; overflow: auto; }' + + '.tooltip-item { padding: 200px 0 400px; width: 150px; }' + + '</style>' + var $styles = $(styles).appendTo('head') + + var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture') + var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>') + .appendTo($container) + .bootstrapTooltip({ + placement: 'top auto', + viewport: '#scrollable-div' + }) + + $('#scrollable-div').scrollTop(200) + + $target.bootstrapTooltip('show') + ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied') + + $target.bootstrapTooltip('hide') + equal($('.tooltip').length, 0, 'tooltip removed from dom') + + $styles.remove() + }) + + test('should display the tip on bottom whenever scrollable viewport has enough room if the given placement is "auto bottom"', function () { + var styles = '<style>' + + '#scrollable-div { height: 200px; overflow: auto; }' + + '.spacer { height: 400px; }' + + '.spacer:first-child { height: 200px; }' + + '.tooltip-item { width: 150px; }' + + '</style>' + var $styles = $(styles).appendTo('head') + + var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture') + var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>') + .appendTo($container) + .before('<div class="spacer"/>') + .after('<div class="spacer"/>') + .bootstrapTooltip({ + placement: 'bottom auto', + viewport: '#scrollable-div' + }) + + $('#scrollable-div').scrollTop(200) + + $target.bootstrapTooltip('show') + ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied') + + $target.bootstrapTooltip('hide') + equal($('.tooltip').length, 0, 'tooltip removed from dom') + + $styles.remove() + }) + + test('should display the tip on top whenever scrollable viewport doesn\'t have enough room if the given placement is "auto bottom"', function () { + var styles = '<style>' + + '#scrollable-div { height: 200px; overflow: auto; }' + + '.tooltip-item { margin-top: 400px; width: 150px; }' + + '</style>' + var $styles = $(styles).appendTo('head') + + var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture') + var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>') + .appendTo($container) + .bootstrapTooltip({ + placement: 'bottom auto', + viewport: '#scrollable-div' + }) + + $('#scrollable-div').scrollTop(400) + + $target.bootstrapTooltip('show') + ok($('.tooltip').is('.fade.top.in'), 'has correct classes applied') + + $target.bootstrapTooltip('hide') + equal($('.tooltip').length, 0, 'tooltip removed from dom') + + $styles.remove() + }) + test('should adjust the tip\'s top position when up against the top of the viewport', function () { var styles = '<style>' + '.tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }' @@ -609,7 +798,7 @@ $(function () { }, 0) }) - test('should show tooltip if leave event hasn\'t occured before delay expires', function () { + test('should show tooltip if leave event hasn\'t occurred before delay expires', function () { stop() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') @@ -777,6 +966,47 @@ $(function () { $circle.bootstrapTooltip('show') }) + test('should correctly determine auto placement based on container rather than parent', function () { + stop() + + var styles = '<style>' + + '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }' + + '.tooltip { position: absolute; display: block; font-size: 12px; line-height: 1.4; }' + + '.tooltip .tooltip-inner { max-width: 200px; padding: 3px 8px; font-family: Helvetica; text-align: center; }' + + '#trigger-parent {' + + ' position: fixed;' + + ' top: 100px;' + + ' right: 17px;' + + '}' + + '</style>' + var $styles = $(styles).appendTo('head') + + $('#qunit-fixture').append('<span id="trigger-parent"><a id="tt-trigger" title="If a_larger_text is written here, it won\'t fit using older broken version of BS">HOVER OVER ME</a></span>') + var $trigger = $('#tt-trigger') + + $trigger + .on('shown.bs.tooltip', function () { + var $tip = $('.tooltip-inner') + var tipXrightEdge = $tip.offset().left + $tip.width() + var triggerXleftEdge = $trigger.offset().left + ok(tipXrightEdge < triggerXleftEdge, 'tooltip with auto left placement, when near the right edge of the viewport, gets left placement') + $trigger.bootstrapTooltip('hide') + }) + .on('hidden.bs.tooltip', function () { + $styles.remove() + $(this).remove() + equal($('.tooltip').length, 0, 'tooltip removed from dom') + start() + }) + .bootstrapTooltip({ + container: 'body', + placement: 'auto left', + trigger: 'manual' + }) + + $trigger.bootstrapTooltip('show') + }) + test('should not reload the tooltip on subsequent mouseenter events', function () { var titleHtml = function () { var uid = $.fn.bootstrapTooltip.Constructor.prototype.getUID('tooltip') @@ -859,6 +1089,7 @@ $(function () { .on('hidden.bs.tooltip', function () { $styles.remove() $(this).remove() + equal($('.tooltip').length, 0, 'tooltip removed from dom') start() }) .bootstrapTooltip({ @@ -869,4 +1100,42 @@ $(function () { .bootstrapTooltip('show') }) + test('should correctly position tooltips on transformed elements', function () { + var styleProps = document.documentElement.style + if (!('transform' in styleProps) && !('webkitTransform' in styleProps) && !('msTransform' in styleProps)) { + expect(0) + return + } + + stop() + + var styles = '<style>' + + '#qunit-fixture { top: 0; left: 0; }' + + '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }' + + '.tooltip { position: absolute; }' + + '.tooltip .tooltip-inner { width: 24px; height: 24px; font-family: Helvetica; }' + + '#target { position: absolute; top: 100px; left: 50px; width: 100px; height: 200px; -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); }' + + '</style>' + var $styles = $(styles).appendTo('head') + + var $element = $('<div id="target" title="1"/>').appendTo('#qunit-fixture') + + $element + .on('shown.bs.tooltip', function () { + var offset = $('.tooltip').offset() + $styles.remove() + ok(Math.abs(offset.left - 88) <= 1, 'tooltip has correct horizontal location') + ok(Math.abs(offset.top - 126) <= 1, 'tooltip has correct vertical location') + $element.bootstrapTooltip('hide') + start() + }) + .bootstrapTooltip({ + container: 'body', + placement: 'top', + trigger: 'manual' + }) + + $element.bootstrapTooltip('show') + }) + }) diff --git a/js/tests/visual/dropdown.html b/js/tests/visual/dropdown.html index 455a4f571..6c7f52b2f 100644 --- a/js/tests/visual/dropdown.html +++ b/js/tests/visual/dropdown.html @@ -28,21 +28,21 @@ <li class="dropdown"> <a id="drop1" href="#" role="button" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a> <ul class="dropdown-menu" role="menu" aria-labelledby="drop1"> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Another action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Something else here</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Something else here</a></li> <li role="presentation" class="divider"></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Separated link</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> <li class="dropdown"> <a href="#" id="drop2" role="button" class="dropdown-toggle" data-toggle="dropdown">Dropdown 2 <b class="caret"></b></a> <ul class="dropdown-menu" role="menu" aria-labelledby="drop2"> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Another action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Something else here</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Something else here</a></li> <li role="presentation" class="divider"></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Separated link</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> </ul> @@ -50,11 +50,11 @@ <li id="fat-menu" class="dropdown"> <a href="#" id="drop3" role="button" class="dropdown-toggle" data-toggle="dropdown">Dropdown 3 <b class="caret"></b></a> <ul class="dropdown-menu" role="menu" aria-labelledby="drop3"> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Another action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Something else here</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Something else here</a></li> <li role="presentation" class="divider"></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Separated link</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> </ul> @@ -67,21 +67,21 @@ <li class="dropdown"> <a id="drop4" role="button" data-toggle="dropdown" href="#">Dropdown <b class="caret"></b></a> <ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4"> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Another action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Something else here</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Something else here</a></li> <li role="presentation" class="divider"></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Separated link</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> <li class="dropdown"> <a id="drop5" role="button" data-toggle="dropdown" href="#">Dropdown 2 <b class="caret"></b></a> <ul id="menu2" class="dropdown-menu" role="menu" aria-labelledby="drop5"> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Another action</a></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Something else here</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Something else here</a></li> <li role="presentation" class="divider"></li> - <li role="presentation"><a role="menuitem" tabindex="-1" href="http://twitter.com/fat">Separated link</a></li> + <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> </ul> @@ -92,6 +92,7 @@ <script src="../vendor/jquery.min.js"></script> <script src="../../transition.js"></script> <script src="../../dropdown.js"></script> +<script src="../../collapse.js"></script> </body> </html> diff --git a/js/tooltip.js b/js/tooltip.js index 7194b5d01..6af0e7d72 100644 --- a/js/tooltip.js +++ b/js/tooltip.js @@ -188,13 +188,13 @@ if (autoPlace) { var orgPlacement = placement - var $parent = this.$element.parent() - var parentDim = this.getPosition($parent) + var $container = this.options.container ? $(this.options.container) : this.$element.parent() + var containerDim = this.getPosition($container) - placement = placement == 'bottom' && pos.top + pos.height + actualHeight - parentDim.scroll > parentDim.height ? 'top' : - placement == 'top' && pos.top - parentDim.scroll - actualHeight < 0 ? 'bottom' : - placement == 'right' && pos.right + actualWidth > parentDim.width ? 'left' : - placement == 'left' && pos.left - actualWidth < parentDim.left ? 'right' : + 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 $tip @@ -207,8 +207,11 @@ this.applyPlacement(calculatedOffset, placement) var complete = function () { + var prevHoverState = that.hoverState that.$element.trigger('shown.bs.' + that.type) that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) } $.support.transition && this.$tip.hasClass('fade') ? @@ -329,7 +332,6 @@ var el = $element[0] var isBody = el.tagName == 'BODY' - var isSvg = window.SVGElement && el instanceof window.SVGElement var elRect = el.getBoundingClientRect() if (elRect.width == null) { @@ -338,10 +340,7 @@ } var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } - var outerDims = isSvg ? {} : { - width: isBody ? $(window).width() : $element.outerWidth(), - height: isBody ? $(window).height() : $element.outerHeight() - } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null return $.extend({}, elRect, scroll, outerDims, elOffset) } @@ -446,12 +445,18 @@ function Plugin(option) { return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tooltip') - var options = typeof option == 'object' && option + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + var selector = options && options.selector if (!data && option == 'destroy') return - if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (selector) { + if (!data) $this.data('bs.tooltip', (data = {})) + if (!data[selector]) data[selector] = new Tooltip(this, options) + } else { + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + } if (typeof option == 'string') data[option]() }) } |
