{"id":360706,"date":"2024-05-21T00:58:32","date_gmt":"2024-05-21T00:58:32","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=360706"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=360706","title":{"rendered":"<span>All the features of modals for Vue<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>I often see how the topic of modal windows is raised in Discrod, on Reddis, and even on Habr. If you go to the official discord channel <code>Vue<\/code> and turn on the search for the word <em>modal<\/em>, you can find that questions are asked every day. Although what can be difficult in modal windows? Create the <code>isOpen<\/code> variable, add <code>v-if<\/code> and that&#8217;s it. This is what I see in <strong>90%<\/strong> of projects. But is this approach so convenient &#8212; definitely not.<\/p>\n<p>A couple of years ago, I decided to deal with modals once and for all. This article will explain how developers use modal windows, how elegantly add them to your project. All the described approaches were collected into a single library <code>jenesius-vue-modal<\/code> and described on <a href=\"https:\/\/github.com\/Jenesius\/vue-modal\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>.<\/p>\n<p>I searched the official Discrod channel for messages related to <code>modal<\/code> and <code>dialog<\/code> and highlighted 4 main topics that developers raise:<\/p>\n<ul>\n<li>\n<p>how to open a modal window<\/p>\n<\/li>\n<li>\n<p>how to open a modal window on top of the previous one<\/p>\n<\/li>\n<li>\n<p>how to return a value from a modal window<\/p>\n<\/li>\n<li>\n<p>how to attach a modal window to a specific <code>route<\/code><\/p>\n<\/li>\n<\/ul>\n<p>Great! We have requirements, now it&#8217;s time to fulfill them. This article it will be divided into 4 parts, each dedicated to one of the above points. Let&#8217;s start.<\/p>\n<h3>How to open a modal window<\/h3>\n<p>It has always been inconvenient for me to insert a modal window component directly into another component. It looks like this:<\/p>\n<pre><code class=\"javascript\">&lt;!-- widget-user-card.vue --> &lt;template>     &lt;div class = \"user\">         &lt;!--user-card-->         &lt;button @click = \"openUserModal\">&lt;\/button>         &lt;!--modal-->         &lt;modal-user :id = \"userId\" v-if = \"isOpen\"\/>     &lt;\/div> &lt;\/template> &lt;script setup>  const props = defineProps(['userId']) const isOpen = ref(false);  function openUserModal() {     isOpen.value = true; }  &lt;\/script> <\/code><\/pre>\n<p>If we have an application with one modal window, then this approach will suit us. But in other situations, this overloads the component, which is why their volume begins to grow.<\/p>\n<p>In the <code>vue<\/code> of the third version, <code>teleport<\/code> was added to render components in another part of our application, but this clutters up our file even more. In our project, we added a new abstraction and passed it to a component, which then `teleported&#187; to the place we needed.<\/p>\n<p>Now let&#8217;s try to make it more elegant and convenient. As can be seen from the requirements, we sometimes need to display multiple windows. Therefore, we will create a dynamic queue in which active modal windows will be stored. We will also describe the <code>openModal<\/code> function that will be used to open these modal windows:<\/p>\n<pre><code class=\"javascript\">const modalQueue = reactive([]);  function openModal(component, props) {     \/\/ We need close all opened modals before add new.     modalQueue.splice(0, modalQueue.length);     modalQueue.push({component, props}) } <\/code><\/pre>\n<p>The component to be displayed and the <code>props<\/code> to be installed in it are passed to the function to open the modal window. Also, do not forget that we need to close all previously opened modal windows.<\/p>\n<p>The functionality is implemented, now we will create a component: a container in which this <code>modalQueue<\/code> will be displayed:<\/p>\n<pre><code class=\"javascript\">&lt;!--modal-container.vue--> &lt;template>     &lt;component         v-for = \"item in modalQueue\"          :is = \"item.component\"         v-bind = \"item.props\"     \/> &lt;\/template> <\/code><\/pre>\n<p>I have removed the description of the <code>CSS<\/code> classes, the darkening and all other secondary details. Here we see the most important thing:<\/p>\n<ul>\n<li>\n<p>Displaying all components from modalQueue<\/p>\n<\/li>\n<li>\n<p>Transfer of all <code>props<\/code> via &#8216;v-bind`<\/p>\n<\/li>\n<\/ul>\n<p>We also need to add this container to our application. I prefer to add it to the very end of the <code>App.vue<\/code> components so that modal windows are always on top of other components.<\/p>\n<p>Now let&#8217;s update our <code>widget-user-card<\/code> file:<\/p>\n<pre><code class=\"javascript\">&lt;!-- widget-user-card.vue --> &lt;template>     &lt;div class = \"user\">         &lt;!--user-card-->         &lt;button @click = \"openUserModal\">&lt;\/button>     &lt;\/div> &lt;\/template> &lt;script setup> const props = defineProps(['userId'])  function openUserModal() {     openModal(ModalUser, { id: props.userId }) } &lt;\/script> <\/code><\/pre>\n<p>It looks like this: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/20e\/34c\/1e4\/20e34c1e49aeace7683b3238070637cb.gif\" alt=\"example open modal\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/20e\/34c\/1e4\/20e34c1e49aeace7683b3238070637cb.gif\"\/><\/p>\n<div><figcaption>example open modal<\/figcaption><\/div>\n<\/figure>\n<p>We got rid of unnecessary logic in the component, and the code became cleaner. We don&#8217;t have to keep reactivity when passing <code>props<\/code> to a function, because the modal window is a new layer of logic. But nothing prevents us from passing the <code>computed<\/code> variable there.<\/p>\n<h3>How to open multiple modal windows<\/h3>\n<p>Since we have chosen a reactive array in advance to store modal windows, we simply need to add new data to the end to show a new window. Let&#8217;s add the <code>pushModal<\/code> function, which will do the<br \/> same as <code>openModal<\/code>, but without clearing the array:<\/p>\n<pre><code class=\"javascript\">function pushModal(component, props) {     modalQueue.push({component, props}) } <\/code><\/pre>\n<p>It looks like this: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/cc3\/80e\/426\/cc380e426ad17fc75f65e28b137819fa.gif\" alt=\"example push modal\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/cc3\/80e\/426\/cc380e426ad17fc75f65e28b137819fa.gif\"\/><\/p>\n<div><figcaption>example push modal<\/figcaption><\/div>\n<\/figure>\n<p>I can also highlight another approach: only the last modal window is always shown on the page, and the rest are hidden with preservation internal state.<\/p>\n<h3>How to return a value from a modal window<\/h3>\n<p>This is the most popular question I&#8217;ve come across, because the previous two are intuitive. If we are talking about the return value of modal windows, we must first understand their essence. By default, modal windows are treated as a separate logic layer with its own data model. This approach is convenient and makes the development of a web application with modal windows safe. I think about the same. A modal window is a separate logical layer that accepts input parameters and interacting with them in some way. However, there are cases when the modal window is only part of the process.<\/p>\n<p>The first thing that comes to mind is to pass a <code>callback<\/code> that will be called by the modal window itself at the end of the process.<\/p>\n<pre><code class=\"javascript\">openModal(ModalSelectUser, {     resolve(userId) {         \/\/ Do something     } }) <\/code><\/pre>\n<p>Callback-and that&#8217;s cool, but for me, linear code using <code>Promise<\/code> is more convenient. For this reason, I implemented the function for returning the value as follows:<\/p>\n<pre><code class=\"javascript\">function promptModal(component, props) {     return new Promise(resolve => {         pushModal(component, {             ...props,             resolve         })     }) } <\/code><\/pre>\n<p>As a <code>callback<\/code> we always pass <code>resolve<\/code> as props and call it already inside the modal window:<\/p>\n<pre><code class=\"javascript\">&lt;!--modal-select-user.vue--> &lt;template>     &lt;!-- -->     &lt;button @click = \"handleSelect\">&lt;\/button> &lt;\/template> &lt;script setup> const props = defineProps(['resolve'])  function handleSelect() {     props.resolve(someData); } &lt;\/script> <\/code><\/pre>\n<p>It looks like this: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/9c9\/ee9\/054\/9c9ee9054a182fa795c76c746ec74957.gif\" alt=\"example-prompt-modal\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/9c9\/ee9\/054\/9c9ee9054a182fa795c76c746ec74957.gif\"\/><\/p>\n<div><figcaption>example-prompt-modal<\/figcaption><\/div>\n<\/figure>\n<p>The most simplified example in which the component returns data by sending it to `resolve&#8217;. Example of calling this function:<\/p>\n<pre><code class=\"javascript\">const userId = await promptModal(ModalSelectUser) <\/code><\/pre>\n<p>For me, this approach looks somehow fresher.<\/p>\n<h3>How to attach a modal window to a specific route<\/h3>\n<p>And finally integration with &#8216;vue-roter&#8217;. The main task: when the user switches to <code>\/user\/5<\/code>, display the modal window of the user card.The first thing that comes to mind: in the <code>user-card<\/code> component at the time of onMount open a modal window, close it at the moment of unMount. This will<br \/> work great.<\/p>\n<p>Let&#8217;s highlight what problems we can expect here and what needs to be taken into account:<\/p>\n<ul>\n<li>\n<p>Updating components in <code>onBeforeRouteUpdate<\/code>. If we have a transition from <code>user\/4<\/code> to <code>user\/8<\/code>, onMount will not be called.<\/p>\n<\/li>\n<li>\n<p>If the modal window was closed, you need to go back a step in the vue-router. You can return to the previous route by closing the modal directly, or you can use the <em>&#171;back&#187;<\/em> key on your device. In the second case, it is necessary to control that we do not leave immediately two steps back (clicking on <em>&#171;back&#187;<\/em>, closing modal).<\/p>\n<\/li>\n<\/ul>\n<p>This is not the whole list. You can add window closing handlers to it. For example, if we add hooks to modal windows that will prohibit closing until the user accepts <em>&#171;Consent to data processing&#187;<\/em>, the transition to the desired route should not occur.<\/p>\n<p>We are implementing a basic wrapper function, which we will pass to `Router&#8217; when we initialize our application. And gradually we will fill it:<\/p>\n<pre><code class=\"javascript\">function useModalRouter(component) {     return {         setup() {             \/\/             return () => null         }     } } <\/code><\/pre>\n<p>When initializing <code>route<\/code>, we will wrap modal windows with this function:<\/p>\n<pre><code class=\"javascript\">const routes = [     {         path: \"\/users\",         component: WidgetUserList,         children: [             {                 path: \":user-id\",                 component: useModalRouter(ModalUser) \/\/ Here             }         ]     } ] <\/code><\/pre>\n<p>When switching to <code>\/users\/5<\/code>, we will not create or install anything. That&#8217;s why the <code>setup<\/code> function returns null. Now we need to display a modal window.<\/p>\n<pre><code class=\"javascript\">function useModalRouter(component) {     return {         setup() {             function init() {                 openModal(component)             }             onMounte(init)             onBeforeRouteUpdate(init)             onBeforeRouteLeave(popModal);              return () => null         }     } } <\/code><\/pre>\n<p>We will also add the <code>popModal<\/code> method to close the last open modal window:<\/p>\n<pre><code class=\"javascript\">function popModal() {     modalQueue.pop(); } <\/code><\/pre>\n<p>If you try to do through the entire set of hooks <code>onMount<\/code>, <code>onUnmount<\/code>, <code>onBeforeRouteUpdate<\/code>, we will create <em>frankenstein&#8217;s monster<\/em>. Also in the example above there is a problem with the transmission of props. We need to solve this somehow. Let&#8217;s change our approach and review each change <code>router<\/code>. Yes, this approach may not seem optimal, but we will immediately solve two problems:<\/p>\n<ul>\n<li>\n<p>integration with vue-router<\/p>\n<\/li>\n<li>\n<p>closing the modal window when switching to another route.<\/p>\n<\/li>\n<\/ul>\n<p>Eventually we will implement something similar to this:<\/p>\n<pre><code class=\"javascript\">router.afterEach(async (to) => {     closeModal(); \/\/ [1]     const modalComponent = findModal(to); \/\/ [2]     if (modal) await modalComponent.initialize(); \/\/ [3] }) <\/code><\/pre>\n<p>Let&#8217;s take a closer look at what we are doing in this handler:<\/p>\n<ul>\n<li>\n<p>[1] close all modal windows before switching to a new route<\/p>\n<\/li>\n<li>\n<p>[2] We are looking for a modal component. To do this, we implemented the <code>findModal<\/code> function:<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"javascript\">function findModal(routerLocation) {     for(let i = routerLocation.matched.length - 1; i >= 0; i--) {         const components = routerLocation.matched[i].components;          const a = Object.values(components).find(route => route._isModal);         if (a) return a;     }     return null; } <\/code><\/pre>\n<p>To briefly explain what this function does: it looks for a wrapper that was created using <code>useModalRouter<\/code> and returns it. If you delve into the topic, then the algorithm is as follows:<\/p>\n<ol>\n<li>\n<p>For the current route, we get all the matches described for the current route in routes.<\/p>\n<\/li>\n<li>\n<p>We get the component object that were specified for rendering<\/p>\n<\/li>\n<li>\n<p>we are looking for those among them that have the <code>_isModal<\/code> flag set.<\/p>\n<\/li>\n<\/ol>\n<p>Stop! It is unlikely that <code>Vue<\/code> has such properties. That&#8217;s right, we&#8217;re expanding the <code>useModalRouter<\/code> method, now it looks like this:<\/p>\n<pre><code class=\"javascript\">function useModalRouter(component) {     return {         setup() { return null },         _isModal: true     } } <\/code><\/pre>\n<p>We return to the <code>afterEach<\/code> hook at position <strong>[3]<\/strong>. The <code>initialize<\/code> property is also not in the returned object, so we also add it:<\/p>\n<pre><code class=\"javascript\">function useModalRouter(component) {     return {         initialize() {             const params = computed(() => router.currentRoute.value.params);             openModal(component, params);         }     } } <\/code><\/pre>\n<p>Now, if a user enters the <code>route<\/code> for which a modal window should be opened, the search and initialization process will take place. Also pay attention to props. Here we pass them as a <code>computed<\/code> variable. This is not a problem for me, because in a modal container, Vue will independently transform <code>v-bind = \"props\"<\/code> to a normal form.<\/p>\n<p>It would not be possible to show how it works in gif. How integration with vue-router works can be viewed on <a href=\"https:\/\/codesandbox.io\/s\/vue-modal-router-n9rn94\" rel=\"noopener noreferrer nofollow\">sandbox<\/a>.<\/p>\n<h3>Why do we write our own?<\/h3>\n<p>There are several libraries for modal windows to work with, but they do not provide even half of the functionality described above. I just wanted to show that working with modal windows can be pleasant and simple. What I described above is the foundation for this  <a href=\"https:\/\/github.com\/Jenesius\/vue-modal\" rel=\"noopener noreferrer nofollow\">libraries<\/a>. For a couple of years, I have collected functionality in it that covers all my needs when working with modal windows. Added a large number of tests and described the documentation. Perhaps it will also be useful for someone.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/775224\/\"> https:\/\/habr.com\/ru\/articles\/775224\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>I often see how the topic of modal windows is raised in Discrod, on Reddis, and even on Habr. If you go to the official discord channel <code>Vue<\/code> and turn on the search for the word <em>modal<\/em>, you can find that questions are asked every day. Although what can be difficult in modal windows? Create the <code>isOpen<\/code> variable, add <code>v-if<\/code> and that&#8217;s it. This is what I see in <strong>90%<\/strong> of projects. But is this approach so convenient &#8212; definitely not.<\/p>\n<p>A couple of years ago, I decided to deal with modals once and for all. This article will explain how developers use modal windows, how elegantly add them to your project. All the described approaches were collected into a single library <code>jenesius-vue-modal<\/code> and described on <a href=\"https:\/\/github.com\/Jenesius\/vue-modal\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>.<\/p>\n<p>I searched the official Discrod channel for messages related to <code>modal<\/code> and <code>dialog<\/code> and highlighted 4 main topics that developers raise:<\/p>\n<ul>\n<li>\n<p>how to open a modal window<\/p>\n<\/li>\n<li>\n<p>how to open a modal window on top of the previous one<\/p>\n<\/li>\n<li>\n<p>how to return a value from a modal window<\/p>\n<\/li>\n<li>\n<p>how to attach a modal window to a specific <code>route<\/code><\/p>\n<\/li>\n<\/ul>\n<p>Great! We have requirements, now it&#8217;s time to fulfill them. This article it will be divided into 4 parts, each dedicated to one of the above points. Let&#8217;s start.<\/p>\n<h3>How to open a modal window<\/h3>\n<p>It has always been inconvenient for me to insert a modal window component directly into another component. It looks like this:<\/p>\n<pre><code class=\"javascript\">&lt;!-- widget-user-card.vue --> &lt;template>     &lt;div class = \"user\">         &lt;!--user-card-->         &lt;button @click = \"openUserModal\">&lt;\/button>         &lt;!--modal-->         &lt;modal-user :id = \"userId\" v-if = \"isOpen\"\/>     &lt;\/div> &lt;\/template> &lt;script setup>  const props = defineProps(['userId']) const isOpen = ref(false);  function openUserModal() {     isOpen.value = true; }  &lt;\/script> <\/code><\/pre>\n<p>If we have an application with one modal window, then this approach will suit us. But in other situations, this overloads the component, which is why their volume begins to grow.<\/p>\n<p>In the <code>vue<\/code> of the third version, <code>teleport<\/code> was added to render components in another part of our application, but this clutters up our file even more. In our project, we added a new abstraction and passed it to a component, which then `teleported&#187; to the place we needed.<\/p>\n<p>Now let&#8217;s try to make it more elegant and convenient. As can be seen from the requirements, we sometimes need to display multiple windows. Therefore, we will create a dynamic queue in which active modal windows will be stored. We will also describe the <code>openModal<\/code> function that will be used to open these modal windows:<\/p>\n<pre><code class=\"javascript\">const modalQueue = reactive([]);  function openModal(component, props) {     \/\/ We need close all opened modals before add new.     modalQueue.splice(0, modalQueue.length);     modalQueue.push({component, props}) } <\/code><\/pre>\n<p>The component to be displayed and the <code>props<\/code> to be installed in it are passed to the function to open the modal window. Also, do not forget that we need to close all previously opened modal windows.<\/p>\n<p>The functionality is implemented, now we will create a component: a container in which this <code>modalQueue<\/code> will be displayed:<\/p>\n<pre><code class=\"javascript\">&lt;!--modal-container.vue--> &lt;template>     &lt;component         v-for = \"item in modalQueue\"          :is = \"item.component\"         v-bind = \"item.props\"     \/> &lt;\/template> <\/code><\/pre>\n<p>I have removed the description of the <code>CSS<\/code> classes, the darkening and all other secondary details. Here we see the most important thing:<\/p>\n<ul>\n<li>\n<p>Displaying all components from modalQueue<\/p>\n<\/li>\n<li>\n<p>Transfer of all <code>props<\/code> via &#8216;v-bind`<\/p>\n<\/li>\n<\/ul>\n<p>We also need to add this container to our application. I prefer to add it to the very end of the <code>App.vue<\/code> components so that modal windows are always on top of other components.<\/p>\n<p>Now let&#8217;s update our <code>widget-user-card<\/code> file:<\/p>\n<pre><code class=\"javascript\">&lt;!-- widget-user-card.vue --> &lt;template>     &lt;div class = \"user\">         &lt;!--user-card-->         &lt;button @click = \"openUserModal\">&lt;\/button>     &lt;\/div> &lt;\/template> &lt;script setup> const props = defineProps(['userId'])  function openUserModal() {     openModal(ModalUser, { id: props.userId }) } &lt;\/script> <\/code><\/pre>\n<p>It looks like this: <\/p>\n<figure class=\"\">\n<div><figcaption>example open modal<\/figcaption><\/div>\n<\/figure>\n<p>We got rid of unnecessary logic in the component, and the code became cleaner. We don&#8217;t have to keep reactivity when passing <code>props<\/code> to a function, because the modal window is a new layer of logic. But nothing prevents us from passing the <code>computed<\/code> variable there.<\/p>\n<h3>How to open multiple modal windows<\/h3>\n<p>Since we have chosen a reactive array in advance to store modal windows, we simply need to add new data to the end to show a new window. Let&#8217;s add the <code>pushModal<\/code> function, which will do the<br \/> same as <code>openModal<\/code>, but without clearing the array:<\/p>\n<pre><code class=\"javascript\">function pushModal(component, props) {     modalQueue.push({component, props}) } <\/code><\/pre>\n<p>It looks like this: <\/p>\n<figure class=\"\">\n<div><figcaption>example push modal<\/figcaption><\/div>\n<\/figure>\n<p>I can also highlight another approach: only the last modal window is always shown on the page, and the rest are hidden with preservation internal state.<\/p>\n<h3>How to return a value from a modal window<\/h3>\n<p>This is the most popular question I&#8217;ve come across, because the previous two are intuitive. If we are talking about the return value of modal windows, we must first understand their essence. By default, modal windows are treated as a separate logic layer with its own data model. This approach is convenient and makes the development of a web application with modal windows safe. I think about the same. A modal window is a separate logical layer that accepts input parameters and interacting with them in some way. However, there are cases when the modal window is only part of the process.<\/p>\n<p>The first thing that comes to mind is to pass a <code>callback<\/code> that will be called by the modal window itself at the end of the process.<\/p>\n<pre><code class=\"javascript\">openModal(ModalSelectUser, {     resolve(userId) {         \/\/ Do something     } }) <\/code><\/pre>\n<p>Callback-and that&#8217;s cool, but for me, linear code using <code>Promise<\/code> is more convenient. For this reason, I implemented the function for returning the value as follows:<\/p>\n<pre><code class=\"javascript\">function promptModal(component, props) {     return new Promise(resolve => {         pushModal(component, {             ...props,             resolve         })     }) } <\/code><\/pre>\n<p>As a <code>callback<\/code> we always pass <code>resolve<\/code> as props and call it already inside the modal window:<\/p>\n<pre><code class=\"javascript\">&lt;!--modal-select-user.vue--> &lt;template>     &lt;!-- -->     &lt;button @click = \"handleSelect\">&lt;\/button> &lt;\/template> &lt;script setup> const props = defineProps(['resolve'])  function handleSelect() {     props.resolve(someData); } &lt;\/script> <\/code><\/pre>\n<p>It looks like this: <\/p>\n<figure class=\"\">\n<div><figcaption>example-prompt-modal<\/figcaption><\/div>\n<\/figure>\n<p>The most simplified example in which the component returns data by sending it to `resolve&#8217;. Example of calling this function:<\/p>\n<pre><code class=\"javascript\">const userId = await promptModal(ModalSelectUser) <\/code><\/pre>\n<p>For me, this approach looks somehow fresher.<\/p>\n<h3>How to attach a modal window to a specific route<\/h3>\n<p>And finally integration with &#8216;vue-roter&#8217;. The main task: when the user switches to <code>\/user\/5<\/code>, display the modal window of the user card.The first thing that comes to mind: in the <code>user-card<\/code> component at the time of onMount open a modal window, close it at the moment of unMount. This will<br \/> work great.<\/p>\n<p>Let&#8217;s highlight what problems we can expect here and what needs to be taken into account:<\/p>\n<ul>\n<li>\n<p>Updating components in <code>onBeforeRouteUpdate<\/code>. If we have a transition from <code>user\/4<\/code> to <code>user\/8<\/code>, onMount will not be called.<\/p>\n<\/li>\n<li>\n<p>If the modal window was closed, you need to go back a step in the vue-router. You can return to the previous route by closing the modal directly, or you can use the <em>&#171;back&#187;<\/em> key on your device. In the second case, it is necessary to control that we do not leave immediately two steps back (clicking on <em>&#171;back&#187;<\/em>, closing modal).<\/p>\n<\/li>\n<\/ul>\n<p>This is not the whole list. You can add window closing handlers to it. For example, if we add hooks to modal windows that will prohibit closing until the user accepts <em>&#171;Consent to data processing&#187;<\/em>, the transition to the desired route should not occur.<\/p>\n<p>We are implementing a basic wrapper function, which we will pass to `Router&#8217; when we initialize our application. And gradually we will fill it:<\/p>\n<pre><code class=\"javascript\">function useModalRouter(component) {     return {         setup() {             \/\/             return () => null         }     } } <\/code><\/pre>\n<p>When initializing <code>route<\/code>, we will wrap modal windows with this function:<\/p>\n<pre><code class=\"javascript\">const routes = [     {         path: \"\/users\",         component: WidgetUserList,         children: [             {                 path: \":user-id\",                 component: useModalRouter(ModalUser) \/\/ Here             }         ]     } ] <\/code><\/pre>\n<p>When switching to <code>\/users\/5<\/code>, we will not create or install anything. That&#8217;s why the <code>setup<\/code> function returns null. Now we need to display a modal window.<\/p>\n<pre><code class=\"javascript\">function useModalRouter(component) {     return {         setup() {             function init() {                 openModal(component)             }             onMounte(init)             onBeforeRouteUpdate(init)             onBeforeRouteLeave(popModal);              return () => null         }     } } <\/code><\/pre>\n<p>We will also add the <code>popModal<\/code> method to close the last open modal window:<\/p>\n<pre><code class=\"javascript\">function popModal() {     modalQueue.pop(); } <\/code><\/pre>\n<p>If you try to do through the entire set of hooks <code>onMount<\/code>, <code>onUnmount<\/code>, <code>onBeforeRouteUpdate<\/code>, we will create <em>frankenstein&#8217;s monster<\/em>. Also in the example above there is a problem with the transmission of props. We need to solve this somehow. Let&#8217;s change our approach and review each change <code>router<\/code>. Yes, this approach may not seem optimal, but we will immediately solve two problems:<\/p>\n<ul>\n<li>\n<p>integration with vue-router<\/p>\n<\/li>\n<li>\n<p>closing the modal window when switching to another route.<\/p>\n<\/li>\n<\/ul>\n<p>Eventually we will implement something similar to this:<\/p>\n<pre><code class=\"javascript\">router.afterEach(async (to) => {     closeModal(); \/\/ [1]     const modalComponent = findModal(to); \/\/ [2]     if (modal) await modalComponent.initialize(); \/\/ [3] }) <\/code><\/pre>\n<p>Let&#8217;s take a closer look at what we are doing in this handler:<\/p>\n<ul>\n<li>\n<p>[1] close all modal windows before switching to a new route<\/p>\n<\/li>\n<li>\n<p>[2] We are looking for a modal component. To do this, we implemented the <code>findModal<\/code> function:<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"javascript\">function findModal(routerLocation) {     for(let i = routerLocation.matched.length - 1; i >= 0; i--) {         const components = routerLocation.matched[i].components;          const a = Object.values(components).find(route => route._isModal);         if (a) return a;     }     return<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-360706","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/360706","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=360706"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/360706\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=360706"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=360706"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=360706"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}