41

Does anyone know how to open a bootstrap modal with vue 2.0? Before vue.js I would simply open the modal by using jQuery: $('#myModal').modal('show');

However, is there a proper way I should do this in Vue?

Thank you.

Trung Tran
  • 13,141
  • 42
  • 113
  • 200

9 Answers9

64

My code is based on the Michael Tranchida's answer.

Bootstrap 3 html:

<div id="app">
  <div v-if="showModal">
    <transition name="modal">
      <div class="modal-mask">
        <div class="modal-wrapper">
          <div class="modal-dialog">
            <div class="modal-content">
              <div class="modal-header">
                <button type="button" class="close" @click="showModal=false">
                  <span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title">Modal title</h4>
              </div>
              <div class="modal-body">
                modal body
              </div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </div>
  <button id="show-modal" @click="showModal = true">Show Modal</button>
</div>

Bootstrap 4 html:

<div id="app">
  <div v-if="showModal">
    <transition name="modal">
      <div class="modal-mask">
        <div class="modal-wrapper">
          <div class="modal-dialog" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <h5 class="modal-title">Modal title</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                  <span aria-hidden="true" @click="showModal = false">&times;</span>
                </button>
              </div>
              <div class="modal-body">
                <p>Modal body text goes here.</p>
              </div>
              <div class="modal-footer">
                <button type="button" class="btn btn-secondary" @click="showModal = false">Close</button>
                <button type="button" class="btn btn-primary">Save changes</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </div>
  <button @click="showModal = true">Click</button>
</div>

js:

new Vue({
  el: '#app',
  data: {
    showModal: false
  }
})

css:

.modal-mask {
  position: fixed;
  z-index: 9998;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, .5);
  display: table;
  transition: opacity .3s ease;
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

And in jsfiddle

Cristian Porto
  • 74
  • 1
  • 11
MikeS
  • 1,058
  • 1
  • 10
  • 19
  • How to overflow-y with BS 3? – Felipe Thomas Mar 07 '19 at 13:37
  • 1
    To prevent form from submitting, add preventDefault() to button. ``. To prevent flickering of modal on load, add v-cloak: `
    ` and within css: `[v-cloak] { display:none; }`
    – yahermann Jan 07 '20 at 04:50
  • 1
    Excellent answer. Just to add, for Bootstrap 4, the key is NOT having the class "modal" div added at all. It pulls the dialog off screen and seems to require JS to open it, which of course could be done in `mounted()` event. Still, it seems too complicated an approach, and I find THIS solution cleaner. One, in fact can skip everything until ` – Vlad Feb 28 '20 at 16:43
  • 1
    I tried this and it doesn't seem to be playing the transition animation. I also tried [the example from the Vue 3 docs](https://codepen.io/team/Vue/pen/mdPoyvv) and it doesn't play the transition animation either. I'm not very experienced with CSS transitions. I looked into importing the Bootstrap JavaScript module (along with jQuery and Popper) and ugh, so much bloat for something so simple. (I could almost say that about Vue actually. Almost...) Screw it! The dialog will appear and disappear instantly. – Indiana Kernick Dec 23 '20 at 09:03
  • 1
    Ok I worked it out. You actually need to define a transition when using the `` element (which sounds pretty obvious now that I think about it!). See [the docs](https://v3.vuejs.org/guide/transitions-enterleave.html#transitioning-single-elements-components). You also need to put the `v-if` inside the ``. – Indiana Kernick Dec 23 '20 at 09:16
8

Tried to write a code that using VueJS transitions to operate native Bootsrap animations.

HTML:

    <div id="exampleModal">
      <!-- Button trigger modal-->
      <button class="btn btn-primary m-5" type="button" @click="showModal = !showModal">Launch demo modal</button>
      <!-- Modal-->
      <transition @enter="startTransitionModal" @after-enter="endTransitionModal" @before-leave="endTransitionModal" @after-leave="startTransitionModal">
        <div class="modal fade" v-if="showModal" ref="modal">
          <div class="modal-dialog" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
                <button class="close" type="button" @click="showModal = !showModal"><span aria-hidden="true">×</span></button>
              </div>
              <div class="modal-body">...</div>
              <div class="modal-footer">
                <button class="btn btn-secondary" @click="showModal = !showModal">Close</button>
                <button class="btn btn-primary" type="button">Save changes</button>
              </div>
            </div>
          </div>
        </div>
      </transition>
      <div class="modal-backdrop fade d-none" ref="backdrop"></div>
    </div>

Vue.JS:

    var vm = new Vue({
      el: "#exampleModal",
      data: {
        showModal: false,
      },
      methods: {
        startTransitionModal() {
          vm.$refs.backdrop.classList.toggle("d-block");
          vm.$refs.modal.classList.toggle("d-block");
        },
        endTransitionModal() {
          vm.$refs.backdrop.classList.toggle("show");
          vm.$refs.modal.classList.toggle("show");
        }
      }
    });

Example on Codepen if you are not familiar with Pug click View compiled HTML on a dropdown window in HTML section.

This is the basic example of how Modals works in Bootstrap. I'll appreciate if anyone will adopt it for general purposes.

Have a great code !

DolDurma
  • 15,753
  • 51
  • 198
  • 377
Andrey Horsev
  • 81
  • 2
  • 4
  • Uncaught TypeRrror: ```bash Uncaught TypeError: Cannot read properties of undefined (reading 'classList') ``` The modal has a `v-if` which means it will not exist once `showModal` is set to `false` Solution: Only call toggle on the modal ref if the ref is found ```js startTransitionModal() { this.$refs.backdrop.classList.toggle("d-block"); if (this.$refs.modal) this.$refs.modal.classList.toggle("d-block"); }, endTransitionModal() { this.$refs.backdrop.classList.toggle("show"); if (this.$refs.modal) this.$refs.modal.classList.toggle("show"); }, ``` Thank you. – Emmanuel Neni Jun 19 '22 at 17:47
4

I did an amalgam of the Vue.js Modal example and the Bootstrap 3.* live demo.

Basically, I used the Vue.js modal example but replaced (sorta) the Vue.js "html" part with the bootstrap modal html markup, save one thing (I think). I had to strip the outer div from the bootstrap 3, then it just worked, voila!

So the relevant code is regarding bootstrap. Just strip the outer div from the bootstrap markup and it should work. So...

ugh, a site for developers and i can't easily paste in code? This has been a serious continuing problem for me. Am i the only one? Based on history, I'm prolly an idiot and there's an easy way to paste in code, please advise. Every time i try, it's a horrible hack of formatting, at best. i'll provide a sample jsfiddle of how i did it if requested.

tony19
  • 125,647
  • 18
  • 229
  • 307
MikeT
  • 2,530
  • 25
  • 36
  • 3
    You can post code with indenting, backticks or embedding a fiddle. [Here is a detailed reference](https://stackoverflow.com/editing-help#code). The question editor has buttons to insert code too. – Cristian Porto Sep 13 '19 at 12:57
3

Using the $nextTick() function worked for me. It just waits until Vue has updated the DOM and then shows the modal:

HTML

<div v-if="is_modal_visible" id="modal" class="modal fade">...</div>

JS

{
  data: {
    isModalVisible: false,
  },
  methods: {
    showModal() {
      this.isModalVisible = true;
      this.$nextTick(() => {
        $('#modal').modal('show');
      });
    }
  },
}
Philippe Gerber
  • 17,457
  • 6
  • 45
  • 40
3

Here's the Vue way to open a Bootstrap modal..

Bootstrap 5 (2022)

Now that Bootstrap 5 no longer requires jQuery, it's easy to use the Bootstrap modal component modularly. You can simply use the data-bs attributes, or create a Vue wrapper component like this...

<bs-modal id="theModal">
        <button class="btn btn-info" slot="trigger"> Bootstrap modal </button>
        <div slot="target" class="modal" tabindex="-1">
                <div class="modal-dialog">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">Modal title</h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                        </div>
                        <div class="modal-body">
                            <p>Modal body text goes here.</p>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                            <button type="button" class="btn btn-primary">Save changes</button>
                        </div>
                    </div>
                </div>
        </div>
</bs-modal>


const { Modal } = bootstrap

const modal = Vue.component('bsModal', {
    template: `
        <div>
            <slot name="trigger"></slot>
            <slot name="target"></slot>
        </div>
    `,
    mounted() {
        var trigger = this.$slots['trigger'][0].elm
        var target = this.$slots['target'][0].elm
        trigger.addEventListener('click',()=>{
            var theModal = new Modal(target, {})
            theModal.show()
        })
    },
})

Bootstrap 5 Modal in Vue Demo

Bootstrap 4

Bootstrap 4 JS components require jQuery, but it's not necessary (or desirable) to use jQuery in Vue components. Instead manipulate the DOM using Vue...

   <a href="#reject" role="button" class="btn btn-primary" @click="toggle()">Launch modal</a>
   <div :class="modalClasses" class="fade" id="reject" role="dialog">
       <div class="modal-dialog">
             <div class="modal-content">
                  <div class="modal-header">
                    <h4 class="modal-title">Modal</h4>
                    <button type="button" class="close" @click="toggle()">&times;</button>
                  </div>
                  <div class="modal-body"> ... </div>
              </div>
       </div>
   </div>

var vm = new Vue({
  el: '#app',
  data () {
    return {
        modalClasses: ['modal','fade'],
    }
  },
  methods: {
    toggle() {
        document.body.className += ' modal-open'
        let modalClasses = this.modalClasses
    
        if (modalClasses.indexOf('d-block') > -1) {
            modalClasses.pop()
            modalClasses.pop()
    
            //hide backdrop
            let backdrop = document.querySelector('.modal-backdrop')
            document.body.removeChild(backdrop)
        }
        else {
            modalClasses.push('d-block')
            modalClasses.push('show')
    
            //show backdrop
            let backdrop = document.createElement('div')
            backdrop.classList = "modal-backdrop fade show"
            document.body.appendChild(backdrop)
        }
    }
  }
})

Bootstrap 4 Vue Modal Demo

Carol Skelly
  • 351,302
  • 90
  • 710
  • 624
0

My priority was to keep using Bootstrap code, since they made the effort to make the modal work, fixin' the scrollbars and all. I found existing proposals try to mimic that, but they go only part of the way. I didn't even want to leave it to chance: I just wanted to use actual bootstrap code.

Additionally, I wanted to have a procedural interface, e.g. calling dialog.show(gimme plenty of parameters here), not just toggling a variable somewhere (even if that variable could be a complex object).

I also wanted to have Vue's reactivity and component rendering for the actual dialog contents.

The problem to solve was that Vue refuses to cooperate if it finds component's DOM to have been manipulated externally. So, basically, I moved the outer div declaring the modal itself, out of the component and registered the component such that I also gain procedural access to the dialogs.

Code like this is possible:

window.mydialog.yesNo('Question', 'Do you like this dialog?')

On to the solution.

main.html (basically just the outer div wrapping our component):

<div class="modal fade" id="df-modal-handler" tabindex="-1" role="dialog" aria-hidden="true">
  <df-modal-handler/>
</div>

component-template.html (the rest of the modal):

<script type="text/x-template" id="df-modal-handler-template">
  <div :class="'modal-dialog ' + sizeClass" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">{{ title }}</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body" v-html="body"/>
      <div class="modal-footer">
        <button type="button" v-for="button in buttons" :class="button.classes" v-bind="button.arias"
                @click.stop="buttonClick(button, callback)">{{ button.text }}
        </button>
      </div>
    </div>
  </div>
</script>

component-def.js - contains logic for showing & manipulating the dialog, also supports dialog stacks in case you make a mistake and invoke two dialogs in sequence:

  Vue.component('df-modal-handler', {
    template: `#df-modal-handler-template`,
    props: {},
    data() {
      return {
        dialogs: [],
        initialEventAssignDone: false,
      };
    },
    computed: {
      bootstrapDialog() { return document.querySelector('#df-modal-handler'); },
      currentDialog() { return this.dialogs.length ? this.dialogs[this.dialogs.length - 1] : null; },
      sizeClass() {
        let dlg = this.currentDialog;
        if (!dlg) return 'modal-sm';
        if (dlg.large || ['large', 'lg', 'modal-lg'].includes(dlg.size)) return 'modal-lg';
        else if (dlg.small || ['small', 'sm', 'modal-sm'].includes(dlg.size)) return 'modal-sm';
        return '';
      },
      title() { return this.currentDialog ? this.currentDialog.title : 'No dialogs to show!'; },
      body() { return this.currentDialog ? this.currentDialog.body : 'No dialogs have been invoked'; },
      callback() { return this.currentDialog ? this.currentDialog.callback : null; },
      buttons() {
        const self = this;
        let res = this.currentDialog && this.currentDialog.buttons ? this.currentDialog.buttons : [{close: 'default'}];
        return res.map(value => {
          if (value.close == 'default') value = {
            text: 'Close',
            classes: 'btn btn-secondary',
            data_return: 'close'
          };
          else if (value.yes == 'default') value = {
            text: 'Yes',
            classes: 'btn btn-primary',
            data_return: 'yes'
          };
          else if (value.no == 'default') value = {
            text: 'No',
            classes: 'btn btn-secondary',
            data_return: 'no'
          };
          value.arias = value.arias || {};
          let clss = (value.classes || '').split(' ');
          if (clss.indexOf('btn') == -1) clss.push('btn');
          value.classes = clss.join(' ');
          return value;
        });
      },
    },
    created() {
      // make our API available
      window.mydialog = this;
    },
    methods: {
      show: function show() {
        const self = this;
        if (!self.initialEventAssignDone) {
          // created is too soon. if we try to do this there, the dialog won't even show.
          self.initialEventAssignDone = true;
          $(self.bootstrapDialog).on('hide.bs.modal', function (event) {
            let callback = null;
            if (self.dialogs.length) callback = self.dialogs.pop().callback;
            if (self.dialogs.length) event.preventDefault();
            if (callback && callback.df_called !== true) callback(null);
          });
        }
        $(self.bootstrapDialog).modal('show');
      },
      hide: function hide() {
        $(this.bootstrapDialog).modal('hide');
      },
      buttonClick(button, callback) {
        if (callback) { callback(button.data_return); callback.df_called = true; }
        else console.log(button);
        this.hide();
      },
      yesNo(title, question, callback) {
        this.dialogs.push({
          title: title, body: question, buttons: [{yes: 'default'}, {no: 'default'}], callback: callback
        });
        this.show();
      },
    },
  });

Do note that this solution creates one single dialog instance in the DOM and re-uses that for all your dialog needs. There are no transitions (yet), so the UX isn't too great when there are multiple active dialogs. It's bad practice anyway, but I wanted it covered because you never know...

Dialog body is actually a v-html, so just instantiate your component with some parameters to have it draw the body itself.

velis
  • 8,747
  • 4
  • 44
  • 64
0

I create button with params for modal and simply trigger click()

document.getElementById('modalOpenBtn').click()

<a id="modalOpenBtn" data-toggle="modal" data-target="#Modal">open modal</a>

<div class="modal" id="Modal" tabindex="-1" role="dialog" aria-labelledby="orderSubmitModalLabel" aria-hidden="true">...</div>
-2

modal doc

Vue.component('modal', {
      template: '#modal-template'
    })
    
    // start app
    new Vue({
      el: '#app',
      data: {
        showModal: false
      }
    })

<script type="text/x-template" id="modal-template">
  <transition name="modal">
    <div class="modal-mask">
      <div class="modal-wrapper">
        <div class="modal-container">

          <div class="modal-header">
            <slot name="header">
              default header
            </slot>
          </div>

          <div class="modal-body">
            <slot name="body">
              default body
            </slot>
          </div>

          <div class="modal-footer">
            <slot name="footer">
              default footer
              <button class="modal-default-button" @click="$emit('close')">
                OK
              </button>
            </slot>
          </div>
        </div>
      </div>
    </div>
  </transition>
</script>

<!-- app -->
<div id="app">
  <button id="show-modal" @click="showModal = true">Show Modal</button>
  <!-- use the modal component, pass in the prop -->
  <modal v-if="showModal" @close="showModal = false">
    <!--
      you can use custom content here to overwrite
      default content
    -->
    <h3 slot="header">custom header</h3>
  </modal>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
-2

From https://getbootstrap.com/docs/4.0/getting-started/javascript/#programmatic-api

$('#myModal').modal('show')

You can do this from a Vue method and it works just fine.

Andrew Koster
  • 1,550
  • 1
  • 21
  • 31
  • 1
    Your answer attracted a down-vote, but no reason was given. I use your answer in a web-application, and I have the impression that opening dialogs like this causes a memory leak. But I have no solid evidence for that. It would be great if someone could explain why this answer does not work. – rakensi Mar 24 '20 at 09:47
  • 3
    Just using the programmatic API will not cause a memory leak. The call here will only show an existing modal attached to an element. However, if it's not working in code you're using, chances are you're encountering issues where the DOM element that the modal was originally attached to no longer exists due to a Vue re-render. That's the main issue with using traditional Javascript and jQuery-based libraries: They rely on the DOM elements not changing all the time, which is an invalid assumption with reactive frameworks like Vue. – Joe C. Apr 24 '20 at 15:29
  • It would be nice if Bootstrap adapted to reactive frameworks, it's not like Vue is the only one or even the most popular one. – Andrew Koster Apr 25 '20 at 20:17
  • This likely got a down vote because it's jquery not vue. – Mark Brown Aug 21 '20 at 00:05
  • 1
    Bootstrap 4 is implemented with jQuery. That jQuery code is directly from the Bootstrap documentation. – Andrew Koster Aug 21 '20 at 02:36