How to have a page with or without q-drawer



  • This is my first post so sorry if this isn’t the right place to ask. But I’m quite new to Vue.js and Quasar so I’m struggling a bit on how to layout my page. With the use of the q-layout builder I generated the following:

    87d5963b-7c91-47d9-b135-01135a23ee76-image.png

    The q-drawer on the lieft should be depending on the tab selection in q-tabs. So when a user clicks ‘tickets’ it should show the q-drawer with the options you see on the left in the screenshot. But when the user is on the ‘Home’ page it should not show the q-drawer, and for ‘Links’ the q-drawer might have different content.

    MainLayout.vue

    <template>
      <q-layout view="hHh lpR fFf">
        <q-header elevated class="bg-primary text-white" height-hint="98">
          <q-toolbar>
            <q-btn flat @click="drawer = !drawer" round dense icon="menu" />
    
            <q-toolbar-title>
              <!-- <q-avatar>
                <img src="https://cdn.quasar.dev/logo/svg/quasar-logo.svg" />
              </q-avatar>-->
              HIP - HeidelbergCement IT Portal
            </q-toolbar-title>
          </q-toolbar>
    
          <q-tabs align="left">
            <q-route-tab to="/" label="Home" />
            <q-route-tab to="/tickets" label="Tickets" />
            <q-route-tab to="/links" label="Links" />
            <q-route-tab to="/about" label="About" />
          </q-tabs>
        </q-header>
    
        <q-drawer
          v-model="drawer"
          show-if-above
          :mini="miniState"
          @mouseover="miniState = false"
          @mouseout="miniState = true"
          :width="200"
          :breakpoint="500"
          bordered
          content-class="bg-grey-3"
        >
          <q-scroll-area class="fit">
            <q-list padding class="menu-list">
              <q-item clickable v-ripple>
                <q-item-section avatar>
                  <q-icon name="mail_outline" />
                </q-item-section>
    
                <q-item-section>My tickets</q-item-section>
              </q-item>
    
              <q-item clickable v-ripple>
                <q-item-section avatar>
                  <q-icon name="send" />
                </q-item-section>
    
                <q-item-section>New ticket</q-item-section>
              </q-item>
    
              <q-item active clickable v-ripple>
                <q-item-section avatar>
                  <q-icon name="star" />
                </q-item-section>
    
                <q-item-section>Follow up</q-item-section>
              </q-item>
            </q-list>
          </q-scroll-area>
        </q-drawer>
    
        <q-page-container>
          <router-view />
        </q-page-container>
    
        <q-footer elevated class="bg-priary text-light text-right q-pr-md">
          version {{ version }}
        </q-footer>
      </q-layout>
    </template>
    

    I was wondering what the best way of doing this would be. Is it best to create 1 row with 2 columns for each page and add the drawer to the first column with its specicif content for that page? Would the animation effect still work in that case?

    The thing is that a normal ‘index.vue’ page for example does not have q-laout as that is defined in the ‘MainLayout.vue’ and I think that is what is used by the q-drawer to define its position…

    Index.vue

    <template>
      <q-page>
        <h3>Welcome to the coolest IT portal in the company!</h3>
        <h4>Get stuff done, hassle free!</h4>
      </q-page>
    </template>
    

    Thank you for putting me on the right path.



  • @DarkLite1 The simplest solution is to use v-if for drawer items and drawer itself (home tab). The shared state variable (tab name) should be kept in vuex store. Both components - tabs and drawer could have access to this variable. This is a nice way to have shared variable (active tab):

    https://vuex.vuejs.org/guide/forms.html#two-way-computed-property

    Above code could be put in vue mixin and imported both in drawer and tabs components.



  • I’m not yet familiar with Vuex but I think from what I can understand allready that your way of doing it is the cleanest. So in layman terms that means that the content of the drawer is decided by which tab is active. The active tab is known in the Vuex store and as such the drawer’s content can be changed on tab switch or simply hidden if there is no content.

    Thank you very much for the helpful feedback. I’ll dig into the store now.



  • You don’t need v-if for the drawer’s visibility. You just need to control the v-model of the drawer appropriately. Something like on Page X, allow for it to toggle as it should. For Page Y, always keep it false. Oh, and however you do the “calculation”, you’ll also need it to hide show the drawer button too and that could be in a v-if. 😉

    Scott



  • Thank you Scott for the extra info. Currently looking into Vuex as that is a requirement to get it all to work together properly.



  • @qyloxe I was wondering if keeping the active tab, which represents a page in the routes.js, in a Vuex state variable is the best solution. Say for example you save the activeTab in the vuex store on a click:

    <q-route-tab to="/tickets" label="Tickets" @click="activeTab='tickets'" />
    

    The above example will work when clicking on the q-tab but not when you go directly to the page with copy pasting a url like http://localhost:8080/#/tickets. At that moment there is no click event for Vue to set the store state variable.

    So I was wondering, is the router not aware what page is loaded? Isn’t this stored somewhere? It would seem logical that the router would know what the active page is, that is the one I need to set the drawer to show/hide or its content.

    Have to look into this. Any tips are wlcome 🙂



  • Yeah, so you’re going to need to “find” a piece of data related to the page itself to make the decisions on. Maybe use the route path? Or set meta data on the page within the route? I’m just guessing though.

    https://router.vuejs.org/guide/advanced/meta.html#route-meta-fields

    Scott



  • Depending on the route would be the best way to go. But how does the <q-tabs> component know whcih tab is active? As the active tab is underlined correctly every time. It needs to be the same logic I suppose.



  • If the tab is being set properly via the router, then there you have it. Use the QTab’s v-model to set your drawer logic too.

    Scott



  • That’s some clever thinking! I’ll try that approach it seems to the the best one. I was already looking at lifecycles too. Like say in the ‘Mounted’ section of a page set the Vuex state variable. I don’t even know if this would’ve been possible as a page is not a component I believe. Sorry for the confusion, I’m still learning.

    Thank you for the great help. Really appreciate it Scott.



  • Well, it’s all about the “reactiveness” of Vue, which is very clever. 🙂 You should always try to think in terms of how to “hook” into the data reactiveness in as many situations as you can, This is a paradigm change, where, in the old days prior to reactive UI frameworks, you had to somehow always “trigger” the actions you wanted to happen yourself. Thinking in the sense of reactive programming is often missed by a lot of devs coming to Vue or React. It’s one of the first concepts I learned in order to understand the greatness of having a Vue or React using data flow to “react” to (and of course where the name React came from 🙂 ).

    https://en.wikipedia.org/wiki/Reactive_programming

    Scott



  • Thank you Scott, that’s valuable advice. As a newbie it will take me a while before I got ‘the right mind set’. But I’m sure with lots of failing will also come some success. So thanks for being so helpful. In any case,

    I was doubting between Nuxt and Quasar because I wanted to have some easier development than with plain Vue.js. I fell a bit in love with bootstrap along the way too, but Quasar adopted the same techniques so that will be very similar. I love the classes and components that are availble in Quasar out of the box. Quasar just seemed more an all in one solution than Nuxt. Might be wrong on that but I know my bosses at work. It starts with a simple website and then a few months down the road they might say “Could you make it a mobile app?” or something similar. So i think this is the right framework.

    Thank you again for takng the time to help me out. Stay home, stay safe.



  • PS: I never get email notifications from the forum upon replies although I marked is a ‘Watched’. Checked my mail address and it’s correct. Just a heads-up that there might be something wrong with the forum.



  • One last question with regards to the basic layout/design. Suppose you have your ‘routes.js’ configured like this:

    const routes = [
      {
        path: '/',
        component: () => import('layouts/MainLayout.vue'),
        children: [
          { path: '', component: () => import('pages/Index.vue') },
          { path: 'about', component: () => import('pages/About.vue') },
          { path: 'links', component: () => import('pages/Links.vue') }
        ]
      },
      {
        path: '/tickets',
        component: () => import('layouts/MainLayout.vue'),
        children: [
          { path: '', component: () => import('pages/tickets/Index.vue') },
          { path: 'myTickets', component: () => import('pages/tickets/MyTickets.vue') },
          { path: 'newTicket', component: () => import('pages/tickets/NewTicket.vue') },
          { path: 'followUp', component: () => import('pages/tickets/FollowUp.vue') },
        ]
      },
    

    In this case we’re using the same component ‘MainLayout.vue’ for both parent paths. Given that we want to have a custom drawer we could decicde to create a new ‘layout’ containing the custom side bar just for ‘Tickets’ for example. However, as this side bar will be having the same location. styling, … on all pages it is better to have this in the ‘MainLayout.vue’ as we discussed before and populate/set the drawer depening on the route (or q-tabs v-model).

    One question here: if we use the same ‘MainLayout.vue’ component for both parent folders does it get reloaded when changing routes between say ‘/’ and ‘/tickets/myTickets’? Because that would be unnessary as the layout is the same, only the drawer’s content is updated and the page component is changed/loaded. What I mean is there’s no reasyon to reload the header for example.



  • Correct. Unless the layout is going to change majorly between sections of your app, you should stick to one layout, if possible. It is why it is chock full of features too. 🙂

    Thanks for the bit about the emails here in the forum. We’ll check on it.

    Scott



  • @DarkLite1 said in How to have a page with or without q-drawer:

    One question here: if we use the same ‘MainLayout.vue’ component for both parent folders does it get reloaded when changing routes between say ‘/’ and ‘/tickets/myTickets’? Because that would be unnessary as the layout is the same, only the drawer’s content is updated and the page component is changed/loaded. What I mean is there’s no reasyon to reload the header for example.

    I can only guess but this one could be helpful:
    https://router.vuejs.org/guide/essentials/nested-routes.html

    You can have one layout which you register as main “route” layout and the subpath routes could be registered to specific areas of this layout. Be it for example tabs.

    Routing could be misleading and not obvious, it is VERY helpful to register global hooks and just “console.log” what is happening when you change routes:
    https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards

    In case of specific components, when you need to “react” on route changes in some specific way, there is a possibility to “watch” for route changes in ANY component. It is in documentation, but this post is very recommended:
    https://blog.webf.zone/vue-router-the-missing-manual-ce51c21430b0?gi=b45747cfc34f

    Oh, and two another pitfalls:

    1. the “keep-alive” - there are a situtations, when you prefer to have one component assigned to subroutes “kept” in memory by Vue. This helps.
    2. async components - this could be nasty, and again, if you want to mix that with router, there are some solutions (hacks), where you need to use this:
      https://vuejs.org/v2/api/#Vue-nextTick


  • Thanks guys, really appreciate the input. I’ve also read the suggestion from qylozee about nested routes and taking into account Scott’s remark about keeping it in one layout if possible then I think this router setup would be more suited to have the header and footer not re-rerended on route changes because they all use the same MainLayout:

    const routes = [
      {
        path: '/',
        component: () => import('layouts/MainLayout.vue'),
        children: [
          { path: '', component: () => import('pages/Index.vue') },
          { path: 'about', component: () => import('pages/About.vue') },
          { path: 'links', component: () => import('pages/Links.vue') },
          { path: 'tickets/myTickets', component: () => import('pages/tickets/MyTickets.vue') },
          { path: 'tickets/newTicket', component: () => import('pages/tickets/NewTicket.vue') },
          { path: 'tickets/followUp', component: () => import('pages/tickets/FollowUp.vue') },
        ]
      },
    


  • I’ve been struggling with the keeping the navigation link on top of the site active when a link in the drawer (side bar navigation) had been clicked. As this would call another page the Vue router simply removed the ‘active’ class from the tab and only set it in the drawer:

    <q-tabs v-model="activeTab" align="left">
            <q-route-tab
              v-for="link in navigationLinks"
              :key="link.name"
              :name="link.name"
              :to="link.to"
              :label="link.label"
              :class="{ 'my-menu-link': link.name === activeTab }"
              exact
            />
          </q-tabs>
    

    I thought about fixing this with using a simple <q-tab> instead of a <q-route-tab> like so:

    <q-tabs v-model="activeTab" align="left">
            <q-tab
              v-for="link in navigationLinks"
              :key="link.name"
              :name="link.name"
              :to="link.to"
              :label="link.label"
              :class="{ 'my-menu-link': link.name === activeTab }"
              exact
            />
          </q-tabs>
    

    Luckily for me this works just fine, except when you copy/paste a link in the addrees bar of the browser. To solved this, I read the docus on hooks as posted by qyloxe. Thanks for the that by the way. So now I’m planning to keep the tab ‘Tickets’ in the header toolbar active if a child route has been called with the string ‘/tickets/’ in its link.

    router.beforeEach((to, from, next) => {
    // match the string in the link and set the vuex store state accordingly
    })
    

    Oops… while testing this further I noticed the pages are not reached but the tab is colored and stays colored 🙂 Up to the drawingboard again to get this right. Better some pain now than having bigger app issues later on.

    faa4a05f-af03-4a07-aacb-acb0450f223a-image.png

    It would be great if <q-router-tab v-model=“activeTab”> would be able to keep its value, also on moving away from the route. Something like “if activeTab is set, you can’t set it to null”. That way it’s easy to keep the correct tab highlighted.



  • I’ll do it the other way around using a hook in the router that will set the acrive classes on the <q-router-tab> whenever the route is matched. It will be easier Ithink 🙂

    q-router-link--exact-active 
    q-router-link--active 
    q-tab--active 
    


  • Would you be interested in making a codesandbox with some of your code to test the navigation and to collaborate on? https://codesandbox.quasar.dev

    Scott


Log in to reply