Control modals using vue router
Asked Answered
S

2

25

I am working in the project developed with Vue 2 with VueRouter and I am trying to work with my modals controlled by my VueRouter!

I've done the following code

Main vue component: My normal components will be loaded on the default router-view and all my modals will be loaded on the modal router-view

<div id="app">
  <router-view v-if="!isLoading"></router-view>
  <router-view v-if="!isLoading" name="modal"></router-view>
</div>

RoutedModals Mixing As you can see on my beforeRouteEnter method I am checking if there is a previous "from" route (which means the user got the page navigating inside the app)... If it's I set that one as default component... if not (which means the user got directly from the URL) I set my dashboard as default and it will be opened behind my modal.

import Dashboard from 'modules/dashboard/components/Main.vue'
import { isNil } from 'lodash'

export default {
  data() {
      return {
        canAccessDirect: true,
        goBackTo: '/'
      }
    },
    beforeRouteEnter(to, from, next) {
      to.matched[0].components.default = isNil(from.matched[0]) ? Dashboard : from.matched[0].components.default

      next(vm => {
        if (!vm.canAccessDirect)
          vm.$router.push({
            name: 'dashboard.index'
          })

        vm.fetchRecords()
        vm.goBackTo = from.path
        window.jQuery(vm.$el).modal('show')
        window.jQuery(vm.$el).on('hide.bs.modal', () => {
          vm.$router.push(vm.goBackTo)
        })
      })
    },
    beforeRouteLeave(to, from, next) {
      setTimeout(() => {
        next()
      }, 200)
    },
    methods: {
      fetchRecords() {
        // Do list request
      }
    }
}

An example of my router object: The first route will open a modal on the router-view modal and the second will open only on the default router-view

{
  name: 'leads.quick-add',
  path: '/leads/quick-add',
  components: { modal: QuickAdd },
}, 
{
  name: 'leads.index',
  path: '/leads',
  component: Main,
},

It works great! The problem comes when I access my modal URL (does not matter if it's directly or navigating) and the default component has a child component! The child component get away on that case!

There is attached some screenshots to help you out understand what happens...

Image 1 enter image description here

Image 2 enter image description here

At Image 1 we can 2 components where the number 1 is my default component on my VueRouter and the number 2 is his child!

Ar the Image 2, after clicking on the + Quotation button the modal is loaded and the component number 2 getaway!

Any ideas on how to do it keeping the others components?

Just to be clear I want to do it by routing and no calling my modal manually!

########################## Edit I am trying to do something like that instead of check on beforeRouterEnter method:

{
  name: 'leads.show.quotations.create',
  path: '/leads/:id/quotations/create',
  components: {
    default: Show,
    'default.tab': Quotations,
    modal: Add
  },
  meta: {
    requiresAuth: true
  }
},

Where there is a sub-router-view but it does not work!

Thinking about possibilities I've added this issue on the github repo: https://github.com/vuejs/vue-router/issues/1030

Stuckey answered 20/12, 2016 at 19:23 Comment(4)
Did you find solution to this?Gayle
I have done something similar by having router point to a component that is only a modal, and that modal is in an active state. Then, setting the close button (and or save button) on the modal to router.back()Faith
Another one issue with assignment to components.default is that component looses its params.Balladry
Look into setting up a modal system that can operate independently and programmatically, perhaps using an event bus that controls any and all modals. Once that's setup, then the modal can be called from anywhere ie within a view (button press), via component state (mounted) or perhaps passing a parameter from a route (manual URL entry). This should help avoid wrestling with view content.Galatea
D
7

I did this in a project at work. It is quite simple actually, the hard part lies in mixing the jquery that you have there, and maybe the css to position a modal, since it's entry point is the router-view inside your component I recommend using the package portal-vue to move the modal content to the end of the body.

Here is a working exemple: https://codesandbox.io/s/vue-router-modal-j10t2

First create a Modal Component that receives it's child by props:

<template>
  <portal to="modal">
    <div class="modal-wrapper">
      <div class="overlay" @click="$router.back()"></div>
      <div class="modal">
        <!-- you can use v-bind to pass down all props -->
        <component :is="component" v-bind="$attrs"/>
      </div>
    </div>
  </portal>
</template>

<script>
export default {
  name: "Modal",
  props: ["component"],
};
</script>

<style scoped>
.modal-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}
.modal {
  position: absolute;
  z-index: 1;
  margin: auto;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  padding: 5em;
  border: 1px solid #ccc;
  border-radius: 1em;
  box-shadow: 0 0 1em #00000033;
}

.overlay {
  z-index: 1;
  position: absolute;
  width: 100vw;
  height: 100vh;
  background-color: #00000055;
}
</style>

Then you can use the props in routes to assign the content for a given route:

import Home from "./Home.vue";
import Modal from "./Modal.vue";
import Content from "./Content.vue";

const router = new VueRouter({
  routes: [
    {
      path: "/",
      component: Home,
      children: [
        {
          path: "create",
          component: Modal,
          props: {
            component: Content
          }
        }
      ]
    }
  ]
});

Since the modal is a child route of '/', the Home component needs to render the router-view:

<template>
  <div>
    Hi i'am home
    <router-view />
    <router-link to="/create">open modal</router-link>
  </div>
</template>

And the App.vue needs to render the portal target, so the modal goes to the end of your content (it makes positioning the modal a lot easier):

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" width="25%" />
    <router-view />
    <portal-target name="modal" />
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

And that's it

Dispeople answered 15/4, 2021 at 20:57 Comment(1)
Very elegant solution. Managing modal as main component and component to load as a prop in the routes is a neat ideaCountercurrent
E
1

I had a different approach that worked for me.

My use-case: I have a support link in the app header and is accessible throughout the site. Clicking the link I wanted a support contact form modal. I decided to set the to= to have a hash value:

<router-link to="#support" tag="button">Support</router-link>

In the global component where this link resides, I added a watcher:

methods:{
  showSupport(){
    this.support.dialog = true;
  }
},
watch: {
  '$route'(to) {
    if(to.hash == '#support'){
      this.showSupport();
    }
  }
}

My modal was persistent, so it required a manual close of the window and in that function, I went back in router history:

closeSupport(){
  this.support.dialog = false;
  this.$router.go(-1);
}

That seemed to work great for me. The only caveat is if the hash persists in the url (ie. domain.com/page#support), that will not allow Vue Router to fire a change event and the modal won't open again if it was once opened. But in most my cases the hash was removed from the url.

Cheers!

Enginery answered 6/3, 2023 at 6:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.