How to close Bootstrap 5's modal with back button on Vue project

jprumekso

Jalu Pujo Rumekso

Posted on August 25, 2021

How to close Bootstrap 5's modal with back button on Vue project

Say you are building a spa using Bootstrap 5 and Vue. And one of its functionality is displaying a list of product through a modal. And you want the modal to get closed when the user press the back button, just like how most mobile app behave.

How do you do that? Let me tell you.

Before we dive into the technical implementation, let's understand the logic first.

The logic is simple. When the user press back button while the modal is open, we want to tell Vue to close the modal instead of navigating back to the previous route.

From that simple logic now we know that we need to know four things:

  • First, when the user press back button.
  • Second, the Bootstrap modal's state (open/close).
  • Third, how to prevent Vue from going away to previous route.
  • Finally, how to close Bootstrap's modal programmatically.

Alright, let's get into it.

Project Setup

First, let's open the terminal and run vue create modal-demo to create a new Vue project. After the setup is complete, install Bootstrap using npm install bootstrap command. To include Boostrap's css add this line inside the style block of App.vue: @import "~bootstrap/dist/css/bootstrap.min.css".

Now let's add a bare bone component to work with. I'll name it ModalDemo and place it in the views folder since it's a page-level component.



<template>
  <button
    data-bs-toggle="modal"
    data-bs-target="#theModal"
    class="btn btn-primary"
  >
    Open Modal
  </button>
  <div id="theModal" 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>
</template>


Enter fullscreen mode Exit fullscreen mode


export default {
  name: 'ModalDemo',
};


Enter fullscreen mode Exit fullscreen mode


import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/modal-demo',
    name: 'ModalDemo',
    component: () => import('@/views/ModalDemo.vue'),
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});


Enter fullscreen mode Exit fullscreen mode

As you can see, our component consist of a modal and a button to toggle it. The button has data-bs-toggle and data-bs-target to activate the modal on button click.

Then, we registered the component to Vue Router's routes. So now we have three components. The ModalDemo and the Home and About which is included by default when we create our project using vue create command. Now let's open the App.vue to add a link to our ModalDemo component.



// App.vue

<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/modal-demo">Modal Demo</router-link>
  </div>
  <router-view />
</template>


Enter fullscreen mode Exit fullscreen mode

So here's what we gonna do to test the 'close bootstrap modal on back button' functionality. First we want to visit the home route (/). Then click the 'Modal Demo' link. Then click the button to open up the modal. Then, while the modal is open, we'll click the back button.

bootstrap-modal-back-button-vue

As you can see, by default Vue Router will take us to the previous route when we press the back button while the modal is open. Also it leaves the modal overlay open. And those are not what we want, right?

Alright, let's tackle our first task.

Detecting Back Button event on Vue

Well, honestly we can't detect when the user press the back button since there is no such an event. However, thanks to Vue Router's beforeRouteLeave navigation guard, we can execute code at the time when the user is about to navigate away from the currently rendered component. Whether the user wants to go back, forward, or to completely another route, the beforeRouteLeave got you covered.

So let's add the beforeRouteLeave navigation guard to our ModalDemo component definition.



beforeRouteLeave(to, from, next) {
  // check modal state and prevent navigation here
  next()
}


Enter fullscreen mode Exit fullscreen mode

The beforeRouteLeave takes three argument: to, from, and next. The first twos give us information about the route that we are going to navigate to and from respectively. The next is a function that we need to execute to tell Vue whether we are allowing the navigation to occur. And it need to be executed once.

Alright, at this point that's all what we need to know about the role of beforeRouteLeave. For more information just check the official doc.

Identifying the state of Bootstrap 5's modal.

Lucky for us, Bootstrap 5 offers an out of the box functionality to check the modal's state via the Modal instance.

Here's how you want to instantiate a Bootstrap 5's Modal in Vue's component:



import { Modal } from 'bootstrap';

export default {

  ...

  data() {
    return {
      theModal: null
    }
  },

  ...

  mounted() {
   this.theModal = new Modal(document.getElementById('theModal'));
  }

}


Enter fullscreen mode Exit fullscreen mode

We begin with importing the Modal and instantiate it at the mounted hook. Why the mounted? Because at the mounted we can access the DOM required for Modal instantiation.

Here we instantiate the modal by providing the Modal constructor with a modal element by an id of theModal (so make sure that at the template definition you have a valid modal markup with an id of 'theModal' or named it anything you want). Then we assign it to theModal data's prop so now our modal's props and methods is accessible from any place in the component code via this.theModal.

Note: you can optionally pass the second argument to the Modal constructor, which is modal's option object. Check here for further information about modal's option object.

Alright, now if you check theModal from Vue devtools we know that it has several properties and the one we're interested with is the _isShown prop. Its value is true when the modal is shown and vice versa.

bootstrap-5-modal-instance

Alright, now let's use it as a condition at our beforeRouteLeave navigation guard.



beforeRouteLeave(to, from, next) {

  if(this.theModal._isShown) {
    // cancel navigation here
  } else {
    // allow to navigate away as usual
    next()
  }

}


Enter fullscreen mode Exit fullscreen mode

Note: The line of theModal._isShown will likely cause the eslint to throw 'no-dangle-underscore' error. To fix it we need to add an exception for the _isShown. We can do so by adding this rule: 'no-underscore-dangle': ["error", { "allow": ["_isShown"] }] to the rules prop of eslintrc.js.



// eslintrc.js

rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-underscore-dangle': ["error", { "allow": ["_isShown"] }]
  },


Enter fullscreen mode Exit fullscreen mode

Alright, now can execute the real route cancelation!

Preventing Vue from Navigating Away

To prevent the app from navigating away we need to pass false to the next() method.



beforeRouteLeave(to, from, next) {

  // When the modal is shown
  if(this.theModal._isShown) {
    // cancel navigation
    next(false);
  } else {
    // allow to navigate away as usual
    next();
  }

}


Enter fullscreen mode Exit fullscreen mode

Next, we just need to close the modal!

Close Bootstrap 5's Modal Programmatically

Now in Bootstrap 5, you want to call the hide() method on the modal instance to hide the modal.



beforeRouteLeave() {

  // When the modal is shown
  if(this.theModal._isShown) {
    // cancel navigation
    next(false);
    this.theModal.hide();
  } else {
    // allow to navigate away as usual
    next();
  }

}


Enter fullscreen mode Exit fullscreen mode

Alright, now try to open the modal and click the back button. As you can see, the app won't going anywhere except to close down the modal. It works!

close-bootstrap-modal-with-browser-back-buttton

That's it! Thanks for reading

Note: Sorry for poor gif. I create it using Peek on Ubuntu laptop. If you know a better way to do this task, feel free to leave your comment out. Thank you.

💖 💪 🙅 🚩
jprumekso
Jalu Pujo Rumekso

Posted on August 25, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related