From e949505b89ca146e3af0cf735e100c82703f1cda Mon Sep 17 00:00:00 2001
From: Adrien Siami <adrien.siami@dimelo.com>
Date: Wed, 25 Mar 2015 14:46:21 +0100
Subject: [PATCH] Allow viewport option to be a function

Closes #16151 by merging a rebased version of it that adds docs and 1 more assertion.
---
 docs/_includes/js/popovers.html |  3 ++-
 docs/_includes/js/tooltips.html |  3 ++-
 js/tests/unit/tooltip.js        | 31 +++++++++++++++++++++++++++++++
 js/tooltip.js                   |  2 +-
 4 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/docs/_includes/js/popovers.html b/docs/_includes/js/popovers.html
index dadddafc34..a782a12047 100644
--- a/docs/_includes/js/popovers.html
+++ b/docs/_includes/js/popovers.html
@@ -233,10 +233,11 @@ sagittis lacus vel augue laoreet rutrum faucibus.">
         </tr>
         <tr>
           <td>viewport</td>
-          <td>string | object</td>
+          <td>string | object | function</td>
           <td>{ selector: 'body', padding: 0 }</td>
           <td>
             <p>Keeps the popover within the bounds of this element. Example: <code>viewport: '#viewport'</code> or <code>{ "selector": "#viewport", "padding": 0 }</code></p>
+            <p>If a function is given, it is called with the triggering element DOM node as its only argument. The <code>this</code> context is set to the popover instance.</p>
           </td>
        </tr>
       </tbody>
diff --git a/docs/_includes/js/tooltips.html b/docs/_includes/js/tooltips.html
index a8914b180e..5b399a5f38 100644
--- a/docs/_includes/js/tooltips.html
+++ b/docs/_includes/js/tooltips.html
@@ -199,10 +199,11 @@ $('#example').tooltip(options)
         </tr>
         <tr>
           <td>viewport</td>
-          <td>string | object</td>
+          <td>string | object | function</td>
           <td>{ selector: 'body', padding: 0 }</td>
           <td>
             <p>Keeps the tooltip within the bounds of this element. Example: <code>viewport: '#viewport'</code> or <code>{ "selector": "#viewport", "padding": 0 }</code></p>
+            <p>If a function is given, it is called with the triggering element DOM node as its only argument. The <code>this</code> context is set to the tooltip instance.</p>
           </td>
         </tr>
       </tbody>
diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js
index 8086631c89..0cb964e9b6 100644
--- a/js/tests/unit/tooltip.js
+++ b/js/tests/unit/tooltip.js
@@ -733,6 +733,37 @@ $(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 error when trying to show an auto-placed tooltip that has been removed from the dom', function (assert) {
     assert.expect(1)
     var passed = true
diff --git a/js/tooltip.js b/js/tooltip.js
index bbff2cdec8..b2d775938a 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!')
-- 
GitLab