41

I have a page for listing data from table using Vue.js and Laravel. Listing data is successful. Delete and edit function is going on. For that purpose I have added two <span> (glyphicon-pencil), <span> (glyphicon-trash). If both <span> are outside the <template>tooltip shows, otherwise it doesn't works. Do you know how bootstrap tooltip works inside Vue Js. Thanks.

page.blade.php

    <template id="tasks-template">
       <table class="table table-responsive table-bordered table-hover">
            <thead>
                   <tr>
                   <th>#</th>
                   <th>Id</th>
                   <th>Religion</th>
                   <th>Action</th>
                   <th>Created</th>
                   <td>Status</td>
               </tr>
           </thead>

      <tbody>
             <tr v-for="(index, task) in list">
             <td><input type="checkbox" id="checkbox" aria-label="checkbox" value="checkbox"></td>
             <td>@{{ index + 1 }}</td>
            <td>@{{ task.religion | capitalize }}</td>
           <td v-if="task.status == 'publish'">
                     <span class="glyphicon glyphicon-ok"></span>
           </td>
           <td v-else>
                     <span class="glyphicon glyphicon-remove"></span>
           </td>
           <td>@{{ task.created_at }}</td>
           <td>
               <span class="glyphicon glyphicon-pencil" aria-hidden="true" data-toggle="tooltip" data-placement="left" title="Edit"></span> 
               <span class="glyphicon glyphicon-trash" aria-hidden="true" data-toggle="tooltip" data-placement="right" title="Delete"></span>
           </td>
         </tr>
       </tbody>
        </table>
        </template>

        <tasks></tasks> 
@push('scripts')
    <script src="/js/script.js"></script>
@endpush 

scripts.js

$(function () {
    $('[data-toggle="tooltip"]').tooltip()
})


Vue.component('tasks', {

    template: '#tasks-template',

    data: function(){
        return{
            list: []
        };
    },

    created: function(){
        this.fetchTaskList();
    },

    methods: {
        fetchTaskList: function(){
            this.$http.get('/backend/religion/data', function(tasks){
                this.$set('list', tasks);
            });
        }
    }

});

new Vue({
   el: 'body'
});
Pierce O'Neill
  • 385
  • 9
  • 22
Sarath TS
  • 2,432
  • 6
  • 32
  • 79

11 Answers11

107

You can use this directive:

Vue.directive('tooltip', function(el, binding){
    $(el).tooltip({
             title: binding.value,
             placement: binding.arg,
             trigger: 'hover'             
         })
})

For example:

<span class="label label-default" v-tooltip:bottom="'Your tooltip text'">

Or you can also bind the tooltip text to a computed variable:

<span class="label label-default" v-tooltip:bottom="tooltipText">

And in your component script:

computed: {
    tooltipText: function() {
       // put your logic here to change the tooltip text
       return 'This is a computed tooltip'
    }
}
Ikbel
  • 7,721
  • 3
  • 34
  • 45
29

You need to run $('[data-toggle="tooltip"]').tooltip() AFTER the data loads from the server. To ensure the DOM is updated, you can use the nextTick function:

fetchTaskList: function(){
    this.$http.get('/backend/religion/data', function(tasks){
        this.$set('list', tasks);
        Vue.nextTick(function () {
            $('[data-toggle="tooltip"]').tooltip()
        })
    });
}

https://vuejs.org/api/#Vue-nextTick

Edit: a more complete and robust solution has been posted by Vitim.us below

Jeff
  • 24,623
  • 4
  • 69
  • 78
  • Great.. Thank you so much. – Sarath TS May 09 '16 at 16:13
  • 5
    This is not the right way to do it for a few reasons, this can break if the DOM reacts and re render a part of your view. You are not destroying tooltips after initialisation. This can break because nextTick is async, and something in between render and nextTick can change your DOM state. – Vitim.us Apr 06 '18 at 15:12
21

The right way to this, is making it a directive, so you can hook on the life cycle of a DOM element.

https://v2.vuejs.org/v2/guide/custom-directive.html

https://gist.github.com/victornpb/020d393f2f5b866437d13d49a4695b47

/**
 * Enable Bootstrap tooltips using Vue directive
 * @author Vitim.us
 * @see https://gist.github.com/victornpb/020d393f2f5b866437d13d49a4695b47
 * @example
 *   <button v-tooltip="foo">Hover me</button>
 *   <button v-tooltip.click="bar">Click me</button>
 *   <button v-tooltip.html="baz">Html</button>
 *   <button v-tooltip:top="foo">Top</button>
 *   <button v-tooltip:left="foo">Left</button>
 *   <button v-tooltip:right="foo">Right</button>
 *   <button v-tooltip:bottom="foo">Bottom</button>
 *   <button v-tooltip:auto="foo">Auto</button>
 *   <button v-tooltip:auto.html="clock" @click="clock = Date.now()">Updating</button>
 *   <button v-tooltip:auto.html.live="clock" @click="clock = Date.now()">Updating Live</button>
 */
Vue.directive('tooltip', {
  bind: function bsTooltipCreate(el, binding) {
    let trigger;
    if (binding.modifiers.focus || binding.modifiers.hover || binding.modifiers.click) {
      const t = [];
      if (binding.modifiers.focus) t.push('focus');
      if (binding.modifiers.hover) t.push('hover');
      if (binding.modifiers.click) t.push('click');
      trigger = t.join(' ');
    }
    $(el).tooltip({
      title: binding.value,
      placement: binding.arg,
      trigger: trigger,
      html: binding.modifiers.html
    });
  },
  update: function bsTooltipUpdate(el, binding) {
    const $el = $(el);
    $el.attr('title', binding.value).tooltip('fixTitle');

    const data = $el.data('bs.tooltip');
    if (binding.modifiers.live) { // update live without flickering (but it doesn't reposition)
      if (data.$tip) {
        if (data.options.html) data.$tip.find('.tooltip-inner').html(binding.value);
        else data.$tip.find('.tooltip-inner').text(binding.value);
      }
    } else {
      if (data.inState.hover || data.inState.focus || data.inState.click) $el.tooltip('show');
    }
  },
  unbind(el, binding) {
    $(el).tooltip('destroy');
  },
});


//DEMO
new Vue({
  el: '#app',
  data: {
    foo: "Hi",
    bar: "There",
    baz: "<b>Hi</b><br><i>There</i>",
    clock: '00:00',
  },
  mounted() {
    setInterval(() => this.clock = new Date().toLocaleTimeString(), 1000);
  }
});
<link href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.js"></script>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>


<div id="app">
  <h4>Bootstrap tooltip with Vue.js Directive</h4>
  <br>
  <button v-tooltip="foo">Hover me</button>
  <button v-tooltip.click="bar">Click me</button>
  <button v-tooltip.html="baz">Html</button>
  <br>
  <button v-tooltip:top="foo">Top</button>
  <button v-tooltip:left="foo">Left</button>
  <button v-tooltip:right="foo">Right</button>
  <button v-tooltip:bottom="foo">Bottom</button>
  <button v-tooltip:auto="foo">Auto</button>

  <button v-tooltip:auto.html="clock" @click="clock = 'Long text test <b>bold</b>'+Date.now()">Updating</button>

  <button v-tooltip:auto.html.live="clock" @click="clock = 'Long text test  <b>bold</b>'+Date.now()">Updating Live</button>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
Vitim.us
  • 20,746
  • 15
  • 92
  • 109
11

I tried to use the solution posted by Vitim.us but I ran into some issues (unexpected/unset values). Here's my fixed and shortened version:

import Vue from 'vue'

const bsTooltip = (el, binding) => {
  const t = []

  if (binding.modifiers.focus) t.push('focus')
  if (binding.modifiers.hover) t.push('hover')
  if (binding.modifiers.click) t.push('click')
  if (!t.length) t.push('hover')

  $(el).tooltip({
    title: binding.value,
    placement: binding.arg || 'top',
    trigger: t.join(' '),
    html: !!binding.modifiers.html,
  });
}

Vue.directive('tooltip', {
  bind: bsTooltip,
  update: bsTooltip,
  unbind (el) {
    $(el).tooltip('dispose')
  }
});

To use it with Nuxt.js you can create a plugin:

Put the above code in a file, e.g. /plugins/bs-tooltips.js and register it in your nuxt.config.js.

plugins: [
    '~/plugins/bs-tooltips.js'
],

Now this works:

<button v-tooltip="'Tooltip text'">Hover me</button>
<button v-tooltip.click="Tooltip text">Click me</button>
<button v-tooltip.html="Tooltip text">Html</button>
<button v-tooltip:bottom="Tooltip text">Bottom</button>
<button v-tooltip:auto="Tooltip text">Auto</button>
Markus Kottländer
  • 8,228
  • 4
  • 37
  • 61
  • 2
    THIS is the best answer! – Vlad Jun 01 '20 at 23:44
  • 1
    this is the nearest answer to what i'm looking for on how to integrate data-bs-tooltips on nuxt…… but i get the following error from the plugin file code "$ is not defined" get bootstrap says to initialize i only to use this: ***const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))*** but have no idea where that should go – HenryAveMedi May 10 '23 at 11:56
10

A easyway to use bootstrap tooltip in vuejs

Install boostrap, jquery and popper.js

for jquery, bootstrap and popper.js add below code in main.js

import 'popper.js'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min.js'
import jQuery from 'jquery'
//global declaration of jquery
global.jQuery = jQuery
global.$ = jQuery

$(() => {
  $('#app').tooltip({
    selector: '[data-toggle="tooltip"]'
  })
})

if you use eslint in vuejs, please dont forgot to add below code in .eslintrc.js file

env: {
  browser: true,
  "jquery": true
}

And dont forgot to Re-Compile the vuejs

  • 1
    This is an easier way to make it work, but compared with the directive, this might not perfect. – Honghao Z May 25 '19 at 21:09
  • I received "Uncaught ReferenceError: global is not defined", so this solution doesn't work for vite, jquery, vue3, bootstrap 4 – Alexey Sh. Feb 27 '23 at 11:14
1

Inspered by the code of @Brendan

Same solution wihtout jQuery. Tested with Laravel 9, Bootstrap 5 and Vue 3.

Vue.directive('tooltip', {
    mounted(el, binding) {  
        el.setAttribute('data-toggle', 'tooltip')
        
        new bootstrap.Tooltip(el,{
            title: binding.value,
            placement: binding.arg,
            trigger: 'hover'
        })
Rifton007
  • 291
  • 1
  • 5
  • 24
0

If using Typescript Vue class components, install the jquery types:

npm install --save @types/jquery

And declare the directive like this in your Vue file, outside your component:

Vue.directive('tooltip', function(el, binding) {
    ($(el) as any).tooltip({
           title: binding.value,
           placement: binding.arg,
           trigger: 'hover'             
    })
});

Then use the sample HTML/bindings from @Ikbel's answer.

seagulledge
  • 314
  • 1
  • 2
  • 7
0

You will find all the CDNs here without requirement of any registration of directive. Libray was made by Guillaume Chau

body {
  font-family: sans-serif;
  margin: 42px;
}

.tooltip {
  display: block !important;
  z-index: 10000;
}

.tooltip .tooltip-inner {
  background: black;
  color: white;
  border-radius: 16px;
  padding: 5px 10px 4px;
}

.tooltip .tooltip-arrow {
  width: 0;
  height: 0;
  border-style: solid;
  position: absolute;
  margin: 5px;
  border-color: black;
}

.tooltip[x-placement^="top"] {
  margin-bottom: 5px;
}

.tooltip[x-placement^="top"] .tooltip-arrow {
  border-width: 5px 5px 0 5px;
  border-left-color: transparent !important;
  border-right-color: transparent !important;
  border-bottom-color: transparent !important;
  bottom: -5px;
  left: calc(50% - 5px);
  margin-top: 0;
  margin-bottom: 0;
}

.tooltip[x-placement^="bottom"] {
  margin-top: 5px;
}

.tooltip[x-placement^="bottom"] .tooltip-arrow {
  border-width: 0 5px 5px 5px;
  border-left-color: transparent !important;
  border-right-color: transparent !important;
  border-top-color: transparent !important;
  top: -5px;
  left: calc(50% - 5px);
  margin-top: 0;
  margin-bottom: 0;
}

.tooltip[x-placement^="right"] {
  margin-left: 5px;
}

.tooltip[x-placement^="right"] .tooltip-arrow {
  border-width: 5px 5px 5px 0;
  border-left-color: transparent !important;
  border-top-color: transparent !important;
  border-bottom-color: transparent !important;
  left: -5px;
  top: calc(50% - 5px);
  margin-left: 0;
  margin-right: 0;
}

.tooltip[x-placement^="left"] {
  margin-right: 5px;
}

.tooltip[x-placement^="left"] .tooltip-arrow {
  border-width: 5px 0 5px 5px;
  border-top-color: transparent !important;
  border-right-color: transparent !important;
  border-bottom-color: transparent !important;
  right: -5px;
  top: calc(50% - 5px);
  margin-left: 0;
  margin-right: 0;
}

.tooltip[aria-hidden='true'] {
  visibility: hidden;
  opacity: 0;
  transition: opacity .15s, visibility .15s;
}

.tooltip[aria-hidden='false'] {
  visibility: visible;
  opacity: 1;
  transition: opacity .15s;
}

<script src="https://unpkg.com/popper.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/v-tooltip@2.0.2"></script>

<div id="app">
  <p><input v-model="message" placeholder="Message" /></p>
  <p><span v-tooltip="message">{{ message }}</span></p>
</div>

new Vue({
  el: '#app',
   data: {
    message: 'Hello Vue.js!'
  }
})

If the tooltip does not show on your page, most probably it is conflicting with some other library or the order of placing the CDNs on the page is not proper. In case your tooltip is being cut off on Chrome (will work in FireFox) due to conflict with bootstrap library, add max-width:100% to tooltip-inner to the v-tooltip style

.tooltip .tooltip-inner {
  **max-width:100%;**
  background: black;
  color: white;
  border-radius: 16px;
  padding: 5px 10px 4px;
}
veritas
  • 378
  • 1
  • 6
  • 16
0

I used a combination of different answers.

Prerequisites: bootstrap, jQuery, vue

'~/directives/tooltip.js'

export default {
  bind (el, binding) {
    const jQuery = require('jquery')
    const $ = jQuery

    el.setAttribute('data-toggle', 'tooltip')

    $(el).tooltip({
      title: binding.value,
      placement: binding.arg,
      trigger: 'hover'
    })
  }
}

in component:

    <button
      v-tooltip:bottom="tooltipText"
    >
      Click Me!
    </button>
export default {
  directives: {
    tooltip
  },
  data() {
    return {
      tooltipText: 'tooltip!'
    }
  // ...
}
Brendan
  • 125
  • 2
  • 14
0

A slight variation on the above answers for Vue3, using a global directive and also destroying the tooltip before unmount as they otherwise sometimes linger. In my case, I had already added data variables with tooltip properties throughout so left these as is vs. passing into the directive.

const app = createApp(App)
    .use(pinia)
    .use(router)

app.directive('tooltip', {
    mounted: (el) => {
        $(el).tooltip();
    },
    beforeUnmount: (el) => {
        $(el).tooltip('dispose')
    }
})

app.mount("#app");

And in use:

<button v-tooltip
    data-toggle="tooltip"
    data-placement="top"
    data-original-title="Hide option">
    <i aria-hidden="true" class="fas fa-eye-slash"></i>
</button>
Jaimerine
  • 31
  • 4
-3

You should use this syntax, put it on index.html or on your general js file

$(function () {
  $('body').tooltip({
      selector: '[data-toggle="tooltip"]'
  });
});
Yul94
  • 53
  • 3
  • 2
    No, this should be done the Vue way. You are assuming that the items on the page won't change. – Ikbel Jun 16 '18 at 15:43
  • Have you tried it ? Even After ajax or dom update it will be OK. Because jquery selector is the body. – Yul94 Jun 17 '18 at 17:43