]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Accept elements as the tooltip / popover content 17402/head
authorGleb Mazovetskiy <glex.spb@gmail.com>
Sun, 30 Aug 2015 23:57:16 +0000 (00:57 +0100)
committerGleb Mazovetskiy <glex.spb@gmail.com>
Mon, 31 Aug 2015 00:06:13 +0000 (01:06 +0100)
When a DOM node is passed to an HTML tooltip, the `title` node is only
moved if it is not already in the tooltip. Otherwise, `empty()` is used
instead of `detach()` before appending the `title` to avoid memory
leaks. If a DOM node is passed to a plain text tooltip, its text is
copied via jQuery `.text()`.

Replaces `.detach()` with `.empty()`, as `.detach()` is almost never
useful but instead leaks memory. The difference between `empty` and
`detach` is that the latter keeps all the attached jQuery events/data.
However, since we do not return the previous children, the user would
have to keep these themselves, thus they can `detach()` if necessary.

This is a port of https://github.com/twbs/bootstrap/pull/14552 to v4.

docs/components/popovers.md
docs/components/tooltips.md
js/src/popover.js
js/src/tooltip.js
js/tests/unit/popover.js
js/tests/unit/tooltip.js

index 98672d23e9bb6f6465112e923ec502a671839098..f48d860eadd9424fb7a086154ef9d9b9f54aed1b 100644 (file)
@@ -193,7 +193,7 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
       </tr>
       <tr>
         <td>content</td>
-        <td>string | function</td>
+        <td>string | element | function</td>
         <td>''</td>
         <td>
           <p>Default content value if <code>data-content</code> attribute isn't present.</p>
@@ -245,7 +245,7 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
       </tr>
       <tr>
         <td>title</td>
-        <td>string | function</td>
+        <td>string | element | function</td>
         <td>''</td>
         <td>
           <p>Default title value if <code>title</code> attribute isn't present.</p>
index 65bd2703b8dea733ea6f6b8a1d6dd23c1de68b3e..f2936469b710cac8c29fcac4864bd61be0de22f0 100644 (file)
@@ -203,7 +203,7 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
       </tr>
       <tr>
         <td>title</td>
-        <td>string | function</td>
+        <td>string | element | function</td>
         <td>''</td>
         <td>
           <p>Default title value if <code>title</code> attribute isn't present.</p>
index 99e48e64f9e3143998fb69e002a33bd779d9e53e..b8b24a1c4c3036992d6e0c9a7f893a04759b7f81 100644 (file)
@@ -34,7 +34,7 @@ const Popover = (($) => {
   })
 
   const DefaultType = $.extend({}, Tooltip.DefaultType, {
-    content : '(string|function)'
+    content : '(string|element|function)'
   })
 
   const ClassName = {
@@ -113,24 +113,13 @@ const Popover = (($) => {
     }
 
     setContent() {
-      let tip          = this.getTipElement()
-      let title        = this.getTitle()
-      let content      = this._getContent()
-      let $titleElement = $(tip).find(Selector.TITLE)
-
-      if ($titleElement) {
-        $titleElement[
-          this.config.html ? 'html' : 'text'
-        ](title)
-      }
+      let $tip = $(this.getTipElement())
 
       // we use append for html objects to maintain js events
-      $(tip).find(Selector.CONTENT).children().detach().end()[
-        this.config.html ?
-          (typeof content === 'string' ? 'html' : 'append') : 'text'
-      ](content)
+      this.setElementContent($tip.find(Selector.TITLE), this.getTitle())
+      this.setElementContent($tip.find(Selector.CONTENT), this._getContent())
 
-      $(tip)
+      $tip
         .removeClass(ClassName.FADE)
         .removeClass(ClassName.IN)
 
index aa5c739457619a93128b4c378449fa159621cdd6..151cd6f515ea9c7148488284570db36c1ad1c930 100644 (file)
@@ -43,7 +43,7 @@ const Tooltip = (($) => {
   const DefaultType = {
     animation   : 'boolean',
     template    : 'string',
-    title       : '(string|function)',
+    title       : '(string|element|function)',
     trigger     : 'string',
     delay       : '(number|object)',
     html        : 'boolean',
@@ -356,19 +356,33 @@ const Tooltip = (($) => {
     }
 
     setContent() {
-      let tip    = this.getTipElement()
-      let title  = this.getTitle()
-      let method = this.config.html ? 'html' : 'text'
+      let $tip = $(this.getTipElement())
 
-      $(tip).find(Selector.TOOLTIP_INNER)[method](title)
+      this.setElementContent($tip.find(Selector.TOOLTIP_INNER), this.getTitle())
 
-      $(tip)
+      $tip
         .removeClass(ClassName.FADE)
         .removeClass(ClassName.IN)
 
       this.cleanupTether()
     }
 
+    setElementContent($element, content) {
+      let html = this.config.html
+      if (typeof content === 'object' && (content.nodeType || content.jquery)) {
+        // content is a DOM node or a jQuery
+        if (html) {
+          if (!$(content).parent().is($element)) {
+            $element.empty().append(content)
+          }
+        } else {
+          $element.text($(content).text())
+        }
+      } else {
+        $element[html ? 'html' : 'text'](content)
+      }
+    }
+
     getTitle() {
       let title = this.element.getAttribute('data-original-title')
 
index 8347e9f03452d6e5a66b1cec20138520101f1a3d..894468695ce2c989eae6745aac23224126278137 100644 (file)
@@ -86,6 +86,42 @@ $(function () {
     assert.strictEqual($('.popover').length, 0, 'popover was removed')
   })
 
+  QUnit.test('should allow DOMElement title and content (html: true)', function (assert) {
+    assert.expect(5)
+    var title = document.createTextNode('@glebm <3 writing tests')
+    var content = $('<i>¯\\_(ツ)_/¯</i>').get(0)
+    var $popover = $('<a href="#" rel="tooltip"/>')
+      .appendTo('#qunit-fixture')
+      .bootstrapPopover({ html: true, title: title, content: content })
+
+    $popover.bootstrapPopover('show')
+
+    assert.notEqual($('.popover').length, 0, 'popover inserted')
+    assert.strictEqual($('.popover .popover-title').text(), '@glebm <3 writing tests', 'title inserted')
+    assert.ok($.contains($('.popover').get(0), title), 'title node moved, not copied')
+    // toLowerCase because IE8 will return <I>...</I>
+    assert.strictEqual($('.popover .popover-content').html().toLowerCase(), '<i>¯\\_(ツ)_/¯</i>', 'content inserted')
+    assert.ok($.contains($('.popover').get(0), content), 'content node moved, not copied')
+  })
+
+  QUnit.test('should allow DOMElement title and content (html: false)', function (assert) {
+    assert.expect(5)
+    var title = document.createTextNode('@glebm <3 writing tests')
+    var content = $('<i>¯\\_(ツ)_/¯</i>').get(0)
+    var $popover = $('<a href="#" rel="tooltip"/>')
+      .appendTo('#qunit-fixture')
+      .bootstrapPopover({ title: title, content: content })
+
+    $popover.bootstrapPopover('show')
+
+    assert.notEqual($('.popover').length, 0, 'popover inserted')
+    assert.strictEqual($('.popover .popover-title').text(), '@glebm <3 writing tests', 'title inserted')
+    assert.ok(!$.contains($('.popover').get(0), title), 'title node copied, not moved')
+    assert.strictEqual($('.popover .popover-content').html(), '¯\\_(ツ)_/¯', 'content inserted')
+    assert.ok(!$.contains($('.popover').get(0), content), 'content node copied, not moved')
+  })
+
+
   QUnit.test('should not duplicate HTML object', function (assert) {
     assert.expect(6)
     var $div = $('<div/>').html('loves writing tests (╯°□°)╯︵ ┻━┻')
index f4deb29f8ec20a51ad6ef962187462bfd53f15e2..934e26b9e0da5342d6bc77eaf71e1dab0b060e0a 100644 (file)
@@ -119,6 +119,35 @@ $(function () {
     assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
   })
 
+  QUnit.test('should allow DOMElement title (html: false)', function (assert) {
+    assert.expect(3)
+    var title = document.createTextNode('<3 writing tests')
+    var $tooltip = $('<a href="#" rel="tooltip"/>')
+      .appendTo('#qunit-fixture')
+      .bootstrapTooltip({ title: title })
+
+    $tooltip.bootstrapTooltip('show')
+
+    assert.notEqual($('.tooltip').length, 0, 'tooltip inserted')
+    assert.strictEqual($('.tooltip').text(), '<3 writing tests', 'title inserted')
+    assert.ok(!$.contains($('.tooltip').get(0), title), 'title node copied, not moved')
+  })
+
+  QUnit.test('should allow DOMElement title (html: true)', function (assert) {
+    assert.expect(3)
+    var title = document.createTextNode('<3 writing tests')
+    var $tooltip = $('<a href="#" rel="tooltip"/>')
+      .appendTo('#qunit-fixture')
+      .bootstrapTooltip({ html: true, title: title })
+
+    $tooltip.bootstrapTooltip('show')
+
+    assert.notEqual($('.tooltip').length, 0, 'tooltip inserted')
+    assert.strictEqual($('.tooltip').text(), '<3 writing tests', 'title inserted')
+    assert.ok($.contains($('.tooltip').get(0), title), 'title node moved, not copied')
+  })
+
+
   QUnit.test('should respect custom classes', function (assert) {
     assert.expect(2)
     var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>')