From edb221a20ceabebd427e27d0432e94a227717217 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 22 Nov 2013 11:58:53 -0800 Subject: [PATCH] Add tooltip `viewport` option, respect bounds of the viewport --- docs/javascript.html | 16 +++++++ examples/tooltips/viewport.html | 71 +++++++++++++++++++++++++++++ js/tests/unit/tooltip.js | 65 +++++++++++++++++++++++++- js/tooltip.js | 81 +++++++++++++++++++++------------ 4 files changed, 203 insertions(+), 30 deletions(-) create mode 100644 examples/tooltips/viewport.html diff --git a/docs/javascript.html b/docs/javascript.html index d7035d6612..9961a28519 100644 --- a/docs/javascript.html +++ b/docs/javascript.html @@ -1027,6 +1027,22 @@ $('#example').tooltip(options)

Appends the tooltip to a specific element. Example: container: 'body'

+ + viewport + string + 'body' + +

Keeps the tooltip within the bounds of this element. Example: viewport: '#viewport'

+ + + + viewportPadding + number + 0 + +

Pixel distance to keep the tooltip from the edges of the viewport element. Example: viewportPadding: 10

+ + diff --git a/examples/tooltips/viewport.html b/examples/tooltips/viewport.html new file mode 100644 index 0000000000..6805056f2e --- /dev/null +++ b/examples/tooltips/viewport.html @@ -0,0 +1,71 @@ + + + + + + + + + + + Tooltip Viewport Example for Bootstrap + + + + + + + + + + + + + + + + + + + +
There is a button down there ↓
+ + + + + + + + + + + diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js index d921bee7fd..94b881bd71 100644 --- a/js/tests/unit/tooltip.js +++ b/js/tests/unit/tooltip.js @@ -338,10 +338,10 @@ $(function () { }) test('should add position class before positioning so that position-specific styles are taken into account', function () { - $('head').append('') + $('head').append('') var container = $('
').appendTo('body'), - target = $('') + target = $('') .appendTo(container) .tooltip({placement: 'right'}) .tooltip('show'), @@ -349,6 +349,67 @@ $(function () { ok( Math.round(target.offset().top + (target[0].offsetHeight / 2) - (tooltip[0].offsetHeight / 2)) === Math.round(tooltip.offset().top) ) target.tooltip('hide') + $('head #test').remove() + }) + + test('should adjust the tip\'s top when up against the top of the viewport', function () { + $('head').append('') + + var container = $('
').appendTo('body'), + target = $('') + .appendTo(container) + .tooltip({placement: 'right', viewportPadding: 12}) + .tooltip('show'), + tooltip = container.find('.tooltip') + + ok( Math.round(tooltip.offset().top) === 12 ) + target.tooltip('hide') + $('head #test').remove() + }) + + test('should adjust the tip\'s top when up against the bottom of the viewport', function () { + $('head').append('') + + var container = $('
').appendTo('body'), + target = $('') + .appendTo(container) + .tooltip({placement: 'right', viewportPadding: 12}) + .tooltip('show'), + tooltip = container.find('.tooltip') + + ok( Math.round(tooltip.offset().top) === Math.round($(window).height() - 12 - tooltip[0].offsetHeight) ) + target.tooltip('hide') + $('head #test').remove() + }) + + test('should adjust the tip\'s left when up against the left of the viewport', function () { + $('head').append('') + + var container = $('
').appendTo('body'), + target = $('') + .appendTo(container) + .tooltip({placement: 'bottom', viewportPadding: 12}) + .tooltip('show'), + tooltip = container.find('.tooltip') + + ok( Math.round(tooltip.offset().left) === 12 ) + target.tooltip('hide') + $('head #test').remove() + }) + + test('should adjust the tip\'s left when up against the right of the viewport', function () { + $('head').append('') + + var container = $('
').appendTo('body'), + target = $('') + .appendTo(container) + .tooltip({placement: 'bottom', viewportPadding: 12}) + .tooltip('show'), + tooltip = container.find('.tooltip') + + ok( Math.round(tooltip.offset().left) === Math.round($(window).width() - 12 - tooltip[0].offsetWidth) ) + target.tooltip('hide') + $('head #test').remove() }) test('tooltip title test #1', function () { diff --git a/js/tooltip.js b/js/tooltip.js index a976bbf1e6..2bc718d633 100644 --- a/js/tooltip.js +++ b/js/tooltip.js @@ -34,7 +34,9 @@ title: '', delay: 0, html: false, - container: false + container: false, + viewport: 'body', + viewportPadding: 0 } Tooltip.prototype.init = function (type, element, options) { @@ -42,6 +44,7 @@ this.type = type this.$element = $(element) this.options = this.getOptions(options) + this.$viewport = $(this.options.viewport) var triggers = this.options.trigger.split(' ') @@ -157,18 +160,14 @@ var actualHeight = $tip[0].offsetHeight if (autoPlace) { + var orgPlacement = placement var $parent = this.$element.parent() + var parentDim = this.getElementDimensions($parent) - var orgPlacement = placement - var docScroll = document.documentElement.scrollTop || document.body.scrollTop - var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth() - var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight() - var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left - - placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' : - placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' : - placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' : - placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' : + 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 $tip @@ -228,29 +227,22 @@ var actualHeight = $tip[0].offsetHeight if (placement == 'top' && actualHeight != height) { - replace = true offset.top = offset.top + height - actualHeight } - if (/bottom|top/.test(placement)) { - var delta = 0 - - if (offset.left < 0) { - delta = offset.left * -2 - offset.left = 0 + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) - $tip.offset(offset) - - actualWidth = $tip[0].offsetWidth - actualHeight = $tip[0].offsetHeight - } + if (delta.left) + offset.left = offset.left + delta.left + else + offset.top = offset.top + delta.top - this.replaceArrow(delta - width + actualWidth, actualWidth, 'left') - } else { - this.replaceArrow(actualHeight - height, actualHeight, 'top') - } + var arrowDelta = delta.left * 2 - (delta.left ? width + actualWidth : height + actualHeight) + var arrowPosition = delta.left ? 'left' : 'top' + var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight' - if (replace) $tip.offset(offset) + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition) } Tooltip.prototype.replaceArrow = function (delta, dimension, position) { @@ -316,6 +308,39 @@ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = {} + var viewportPadding = this.options.viewportPadding + var viewportDimensions = this.getElementDimensions(this.$viewport) + + if (/right|left/.test(placement)) { + if (pos.top + actualHeight - viewportDimensions.scroll + viewportPadding > viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.height + viewportDimensions.scroll - actualHeight - viewportPadding - pos.top + } else if (pos.top - viewportDimensions.scroll - viewportPadding < 0) { // top overflow + delta.top = viewportDimensions.scroll + viewportPadding - pos.top + } + } else { + if (pos.left + actualWidth + viewportPadding > viewportDimensions.width) { // right overflow + delta.left = viewportDimensions.width - actualWidth - viewportPadding - pos.left + } else if (pos.left - viewportPadding < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left + viewportPadding - pos.left + } + } + + return delta + } + + Tooltip.prototype.getElementDimensions = function ($element) { + var isBody = $element[0].tagName == 'BODY' + return { + scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(), + width: isBody ? window.innerWidth : $element.outerWidth(), + height: isBody ? window.innerHeight : $element.outerHeight(), + left: isBody ? 0 : $element.offset().left + } } Tooltip.prototype.getTitle = function () { -- 2.47.2