loading...
Cover image for Handling Dialogs with Vue Router

Handling Dialogs with Vue Router

berniwittmann profile image Bernhard Wittmann ・2 min read

In the first part of this little series on Vue Router Architecture I wrote about handling nested routes with Vue Router. It would maybe easier to follow if you have read that already, but here is a very short summary:
That tutorial introduced an EmptyRouterView component for handling the nested routes.

But we can also use this component for handling dialogs like this one:

By handling the visibility of dialogs with vue router we can easily toggle them by navigating to the corresponding urls. And in my opinion this results in a more clean codebase. So how do we do it?

Step 1: Update our EmptyRouterView Component

First we need to edit our EmptyRouterView component:

<template>
  <div>
    <router-view name="dialog"></router-view>
    <router-view></router-view>
  </div>
</template>

The component now contains two router-views. The lower one (the default one) is used for displaying the background (or the normal page in case no dialog should be shown). Meanwhile the one for the dialog will contain the content of our dialog.

Step 2: Create a component for our dialog

Now we have to create a component for our dialog:

template>
  <div class="dialog">
    <div class="dialog__inner">
      <h3>My Dialog</h3>
      <p>Content</p>
      <a class="btn" @click="close">Close</a>
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    close () {
      this.$router.back()
    }
  }
}
</script>

<style lang="scss">
.dialog {
  # Styling for the background overlay of the dialog

  &__inner {
    # Styling for the dialog itself
  }
}
</style>

This is a pretty simple component, however you need some styling to make it look like a dialog (you could also use bootstraps classes for it, or something else). Maybe you noticed that I also included a back button already. We can simply use the router.back() method for navigating one page back to close the modal, since we toggle the visibility with the routes from Vue-Router.

Step 3: Update the Route Config

Last but not least we have to specify the route configuration for Vue Router in our router.js config:

routes: [{
    path: '/nested,
    component: EmptyRouterView,
    children: [{
        name: 'nested',
        path: '',
        component: MyPageView
    }, {
        name: 'nested.dialog',
        path: 'dialog',
        components: {
            default: MyPageView,
            dialog: MyDialogComponent
        }
    }]
}]

For our dialog route with name nested.dialog at the url /nested/dialog we specify the components MyPageView as the background and MyDialogComponent for the dialog itself. Since this route is a child of a route that has the EmptyRouterView component, these components directly fill the router-views specified in our EmptyRouterView Component.

This was already it. Now we can navigate to our route at /nested/dialog and you'll see the dialog:

You can also play around with it in the repo i created:

GitHub logo BerniWittmann / vue-router-architecture

My Architecture approach on Vue Router

My Vue Router Architecture Approach

This repository should demonstrate my basic approach on Vue Router Architecture and nested routes. It also displays the possibility of handling dialogs with Vue Router

I use a view called EmptyRouterView (which just contains a router view, and one for the dialog) to achieve a clean way to structure my nested routing configuration and an easy way to handle dialogs.

The corresponding Blog Post can be found on dev.to

Part 1: Route Handling

Part 2: Dialog Handling

Project setup

npm install

Compiles and hot-reloads for development

npm run serve

Got any questions?

Don't hesitate to drop me an email to dev@bernhardwittmann.com

That's it for now. Feel free to let me know your thoughts on this 😁

Posted on by:

berniwittmann profile

Bernhard Wittmann

@berniwittmann

Student - Developer - Productivity Tool Junkie - Privacy Enthusiast - Astronomy Geek

Discussion

markdown guide
 

Thanks for this. This is helpful.

One question: What if I wanted multiple dialogs to pop-up on any/all pages? How would you DRY up your routes.js?

 

I think this approach of using vue router couples the logic of the modals Reiniger to the url/page structure. E.g. A User Edit Modal will always be rendered only on the User Page (since the reference is strongly connected via the routes.js declaration)

I guess you want a more flexible approach (like displaying any modal on any page). This can be done with vuex (you would move the logic of which dialogs should be displayed to vuex) and then somewhere in your app (probably the root component) you could do something like this

<template>
    <dialog-1 v-if="dialog1visible"/>
    <dialog-2 v-if="dialog2Visible"/>

    // the App itself or your root router-view
    <router-view/>
</template>

You can of course combine both approaches and tailor it to your needs

If you want to get a little more info, I'd be glad to elaborate on this :)

 

Hey I have a similar but even more complicated case. I want to build a gallery that works like dribbble.com/ . You have different routes with different galleries/filters/sorting represented in the route and then when you open a work URL changes to direct link to work (so it could be shared and it's a unique link for SEO purposes). Something like /work/{id}. But everything under modal stays the same saving the context for the user. For now I made it with nested views and PREVIOUS_ROUTE/work/{id} is added at the end. But that's not ideal for SEO (there are many links to the same work) and usability (what if user shares this link and then the work disappears from the gallery it was before or goes to 107 page in that gallery). The only idea I have now is to just handle it myself programatically with wildcard route and writing the whole logic myself.

I think the problem you're having is that you are loosing the filter/sorting information.
Your gallery retrieves this information from the url (like /gallery/illustrations?filter=today&sort=best, yet when a single work is displayed you want to remove this information in the url (/work/1231314). How should the page know what gallery to display below...

I have the following idea to solve this problem:
We need to store the filter sorting information for the gallery somewhere. What if the sorting/filter information of the url is stored somewhere in the localStorage, when you visit a gallery page and updated accordingly. Then the information is safely stored in the local storage, but it can still be controlled via the url.
When the user now navigates to a work url, the component responsible for rendering the gallery can load the sorting information from localstorage and still render gallery from before.

Yet this approach has the caveat, that you have to handle, when no gallery information is present (could be the case when a user goes to a work url, without having visited any gallery before). But this could be solved with some appropriate defaults like showing some default gallery.

Also this approach for information storage should allow you to apply the idea of the dialog of this article again more easily and you are able to separate the work logic from the gallery logic, since you can just reuse the gallery component.

I hope I could express my idea adequately, and if you have more questions just let me know and i will try to express my idea an another way or maybe provide some code snippets to make it more clear :)
And please let me know, whether it works out ;)

Well how to store the data is not the problem, I get that. I'm using vuex all the way. Question is how to "trick" vue-router so it will not destroy the underlying layer with gallery when the path changes. Maybe there are a better way then just writing huge component that would handle everything manually and making it a "*" route.

The thing is you don't have to worry about destroying the underlying gallery since the gallery does not depend on the route itself anymore (only indirectly).

I just adjusted the example code to showcase that this can indeed work. Just see this branch: github.com/BerniWittmann/vue-route...

You can navigate to localhost:8080/nested/one?foo=test which resembles the gallery and see the query parameters rendered (because they have been stored in the state in the beforeEnter route guard). When you click on the show dialog button the url changes to /dialog which resembles the work page. You will see, that the page below did not change. It still has the query parameters rendered and also it was not even rerendered at all, which you can see at the timestamp.
As you'll see the back/close button will also work as expected and you can easily specify a default behavior when the local storage is empty on the work page. Hope that helps ;)

Thank you, now I get your idea :) Really appreciate your going to such a length trying to help me!

No worries, I‘m glad to help :)

For everybody who would like me google and find your article first:
github.com/vuejs/vue-router/issues...
This is a known issue, there are future request in and it's planned to be addressed by vue-router.
There are also a lot of hacky solutions for this in that issue.

 

Thanks. Vuex is a good alternative.

I started implementing your first proposal and discovered what you mentioned about specific dialogs only being needed in one place.

One gotcha I got stuck on for a bit was I had children of children routes and didn't realize that each one needed a router-view to render their children.

Thanks again

Yes that‘s due to the architecture of cue router

You‘re welcome

 

This was very mind oppenning, thanks ;-)

BTW
Since dialog is expected to be on top, code would be more semantinc if you put router-view named dialog at the end in template.

<router-view></router-view>
<router-view name="dialog"></router-view>
 

Yes you're right, that this would be better semantically. However, since you need to apply some css to make it look like a dialog it's not that big of a deal

 
 

This approach is clean, however it doesn't look like it supports providing a custom action for the dialog, especially if the function resides in the sibling component of the dialog.

 

I‘m sorry, but I seem to not understand what you mean by custom action and by the function you mentioned. Maybe you could explain a little more about what you would like to achieve/mean?

 

What I meant by custom action is when you implement a confirm or cancel action to the dialog aside from closing it. I can't figure out yet how to provide a function from MyPageView to MyDialogComponent without using vuex.

Ah I see. The whole idea of this approach is not to create a single MyDialogComponent that can be used in several locations, but to create several dialogs for different purposes. This ties the dialogs closer to the business logic if you will.

However you can control sone behavior via url parameters or some query string in the url.

Let’s use an example:
The goal is to create a dialog that pops up for confirmation after editing a form. This form can either trigger a create or an update request.
Therefore the dialog should display some text and a button Text depending on the action type. The confirm button will then either trigger a create or an update request.

The approach outlined in the article would propose two separate dialogs living in their own vue router routes eg /confirm/edit and /confirm/create (You can still avoid code duplication by using components)

You could still use one dialog, that would render content or do stuff conditionally depending on the url, but that would probably violate the single purpose idea

Let me know whether this could clarify things. If not I can also put together a code snippet. Just let me know

This made much more sense in relation to the business logic.

I'm actually using this approach for a reusable notification/error dialog, which, upon reading your reply, seems like not the best use case for this pattern. I may not use it right now, but I will definitely adapt this where it's appropriate. Thank you for taking the time to respond

Yes, in that case some more reusable dialog would probably by the way to go :)
Of course, such discussions are the key for this dev community and I'm glad to help

 

Thank for the article, this is what I was looking for !

Just a question, I tried to implement in my projet:
I have in my nested page a long list of articles (coming from my store fetched at "created"). When I scroll and click to an article to display the dialog, I'm losing my scroll position at my selected article.
Do you have some tricks to keep the scroll position of the child component in the background of the dialog ?

 

You can control the scrolling behavior with vue router. Take a look at the docs