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>
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
Image 2
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
Share Improve this question edited Dec 27, 2018 at 10:07 p u 1,4551 gold badge20 silver badges30 bronze badges asked Dec 20, 2016 at 19:23 Gustavo BissolliGustavo Bissolli 1,5713 gold badges23 silver badges36 bronze badges 4- Did you find solution to this? – troynt Commented Mar 28, 2017 at 6:10
- 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() – Jackson Miller Commented Dec 27, 2018 at 22:54
- Another one issue with assignment to components.default is that component looses its params. – Vaulter Commented Sep 2, 2019 at 23:30
- 4 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. – Kalnode Commented Apr 5, 2020 at 12:50
2 Answers
Reset to default 7I 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
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!