Search code examples
listvue.jsquasar

Successively bind list item values, from list to page, in a layout rendered within another layout


The best example to illustrate what I am trying to develop is a desktop email application.

On the left there is a vertical menu (on a quasar q-drawer).

Next, also on the left, there is a mailing list (on a quasar q-list within a q-drawer).

When each item is selected, the corresponding content is displayed on the right (on a quasar q-page).

Expected operation:

The list is loaded once and when I successively select the various items in the list, only the content on the right should be used and the content updated according to the id sent as a parameter in the request.

Note that the list component is only rendered once; that is, it is not rendered again each time a item is selected from the list and remains visible while the content is displayed on the right

The problem:

When I select the first item in the mailing list it works correctly and as expected, the mail content is displayed on the q-page.

When I select a second item from the list it doesn't work anymore and the following error is displayed on the console:

Uncaught (in promise) NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated", message: "Navigating to current location ("/mailcontent") is not allowed", stack: "Error at new NavigationDuplicated (webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"}

I would appreciate suggestions on how to resolve this issue.

The following code is intended to illustrate the problem in the main part:

Routes: secondlayout is the child of another layout

const routes = [
  {
    path: "/index",
    component: () => import("layouts/AppLayout.vue"),
    children: [
      { path: "/home", component: () => import("pages/Home.vue") },
      {
        path: "secondlayout",
        component: () =>  import("Layouts/MailsPlace.vue"),
        children: [
            { path: "/mailcontent",  name: 'mailcontent', component: () => import("pages/MailContent.vue") },
        ]
      }
    ]
 }
];

Second layout where the email application (list and content) is rendered with q-drawer and router-view

<template>
    <q-layout view="lhh LpR lff" container class=" myclass shadow-2 window-height" >

        <q-drawer
            style="full-height"
            v-model="drawerLeft"
            :width="500"
            :breakpoint="700"
            elevated
            content-class="bg-grey-1"
          >
        <q-scroll-area 
            class="fit" 
            style="margin-top:80px">

             <q-list separator padding>
                  <q-separator />
                        <list-mails 
                            v-for="(mail, index) in mails" 
                            :mail="mail"                                                 
                            :key="mail.id_mail"
                            :id="index">
                        </list-mails>
                    <q-separator />
            </q-list> 
        </q-scroll-area>
      </q-drawer>

      <q-page-container>          
           <router-view></router-view>          
      </q-page-container>

</template>

<script>

export default {
  data () {
    return {        
      mails: {},

      drawerRight: false,
    }
  },

/*  watch: {
    $route(to, from) {
      console.log('after', this.$route.path);
    }
  },   
  beforeRouteUpdate(to, from, next) {
    console.log('before', this.$route.path);
    next();
  },*/          


    components: {            
        'list-mails': require("pages/ListMails.vue").default,
    },

   created: function() {
       this.listMails()
    },

    methods: {        
        listMails(){
           this.$axios.get("/listmails")        
            .then(response => {             
              if (response.data.success) {
                  this.mails = response.data.mails.data;
              } else {
                 showErrorNotify('msg');
              }
            })
            .catch(error => {         
              showErrorMessage(error.message); 
            });
        }
    }
</script>

Mail list item with mailitemclick method

<template>
        <q-item 
            clickable 
            v-ripple 
            exact 
            @click="mailitemclick(mail.id_mail)"
         >
          <q-item-section>
               <q-item-label side lines="2"> {{ mail.title_mail }}</q-item-label>
          </q-item-section>
      </q-item>

</template>

<script>
    export default { 
      props: ["mail"],
       methods:{
            mailitemclick(id){                
                 this.$router.push({
                     name: 'mailcontent', 
                     params: {id:id}
                });
            }
        }
    }
</script>

Mail content

<template>            
      <q-page class="fit row wrap justify-center tems-start content-start" style="overflow: hidden;">
          <div style="padding:5px; margin:0px 0px 20px 0px; min-width: 650px;  max-width: 700px;" >
              <q-item>
                <q-item-label class="titulo"> {{ mail.title_mail }} </q-item-label>
                 <div v-html="mail.content_mail"></div>
                </q-item>
            </div>
      </q-page>
</template>

<script>

export default {
  name: 'mailcontent',
  data() {
  return { 
    mail: {},
    };
  },

  created() {
        this.$axios.get(`/mailcontent/${this.$route.params.id}`)                
        .then(response => {              
          if (response.data.success) {
              this.mail = response.data.mail[0])
          } else {
                 showErrorNotify('msg');
          }
        })
        .catch(error => {         
          showErrorMessage(error.message); 
        });
    }
}
</script>

Solution

  • This happened to me when I had a router-link pointing to the same route. e.g. /products/1.

    The user is able to click on the products, but if a product was already clicked (and the component view was already loaded) and the user attempts to click it again, the error/warning shows in the console.

    You can solve this by adding catch block.

        methods: {
            mailitemclick(id) {
                this.$router.push({
                    name: 'mailcontent',
                    params: {'id': id}
                }).catch(err => {});
    
            }
        },
    

    But in the mail-content, you need to use watch for calling function and in mounted for first-time calling.

    Temp Example -

        data() {
            return {
                mail: {},
                test_mails: {
                    12: {
                        content_mail: '<div>test 12<div>'
                    },
                    122:{
                        content_mail: '<div>test 122<div>'
                    }
                }
            }
        },
        mounted() {
            this.mail = this.test_mails[this.$route.params.id]
        },
        watch:{
            '$route':function () {
                this.mail = this.test_mails[this.$route.params.id]
            }
        }
    

    OR

    You can use :to in list-mail to avoild click and catch -

    <q-item
        clickable
        v-ripple
        exact
        :to="'/mailcontent/'+mail.id_mail"
      >
        <q-item-section>
          <q-item-label side lines="2"> {{ mail.title_mail }}</q-item-label>
        </q-item-section>
      </q-item>
    
    
    children: [
      { path: '', component: () => import('pages/Index.vue') },
      {
        path: "secondlayout",
        component: () =>  import("layouts/mail-place.vue"),
        children: [
            { path: "/mailcontent/:id",  name: 'mailcontent', component: () => import("pages/mail-content.vue") },
        ]
      }
    ]