Custom QCollapsible title content (excluding the toggle button)



  • My use case is having a side-nav with linkable (router-link) collapsible titles so essentially it would be just like a file explorer or menu pattern. Example here (OpenUI5).
    0_1511676484760_side-nav.png



  • My solution to this was to rebuild the q-collapsible. I do not have the time to go in detail at the moment.
    The behavior was unlimited nesting of the colapsible.

    Here is the code:

    // Usage in layout
    
    <sidebar-menu-item v-for="child in pages" :key="'/' + child.path.join('/')" :item="child" group="/" :itemlist="pages"></sidebar-menu-item>
    
    // SideBarMenuItem.vue
    
    <template lang="html">
      <div>
        <q-item class="q-item-link relative-position" v-ripple v-if="!item.children" @click="goToPage()">
          <q-item-main :label="$t(item.name)" />
        </q-item>
        <template v-else>
          <!-- Missing some sidelink classes, close panel after click -->
          <!-- <q-side-link item to="/">link</q-side-link> -->
          <q-item class="q-item-link relative-position" v-ripple @click="toggle() || goToPage()">
            <q-item-main :label="$t(item.name)"/>
            <q-item-tile
            @click.stop="toggle()"
            icon="keyboard_arrow_down"
            class="transition-generic"
            :class="{'rotate-180': item.expanded}"
            ></q-item-tile>
          </q-item>
          <q-slide-transition>
            <div v-show="item.expanded">
              <div class="q-collapsible-sub-item relative-position indent">
                <sidebar-menu-item v-for="child in item.children" :key="'/' + child.path.join('/')"  :item="child" :group="'/' + item.path.join('/')" :itemlist="itemlist"></sidebar-menu-item>
              </div>
            </div>
          </q-slide-transition>
        </template>
      </div>
    </template>
    
    <script>
    import SidebarMenuItem from '@/SidebarMenuItem'
    import {
      Events,
      QList,
      QItem,
      QItemMain,
      QItemSide,
      QItemTile,
      QSlideTransition,
      Ripple
    } from 'quasar'
    
    const eventName = 'q:collapsible:close'
    
    export default {
      name: 'SidebarMenuItem',
      i18nOptions: { namespaces: ['comon'], keyPrefix: 'pages' },
      directives: {
        Ripple
      },
      components: {
        SidebarMenuItem,
        QList,
        QItem,
        QItemMain,
        QItemSide,
        QItemTile,
        QSlideTransition
      },
      props: {
        item: {
          required: true,
          type: Object
        },
        itemlist: {
          required: true,
          type: Array
        },
        group: {
          required: true,
          type: String
        }
      },
      inject: ['layout'],
      watch: {
        'item.expanded' (val) {
          if (val && this.group) {
            Events.$emit(eventName, this)
          }
          if (!val && this.item.children) {
            this.collapse(this.item.children)
          }
        }
      },
      methods: {
        goToPage () {
          this.collapse(this.itemlist)
          this.layout.hideRight(() => {
            this.$router.push(`/${this.item.path.join('/')}`)
          })
        },
        toggle () {
          this.item.expanded = !this.item.expanded
          return this.item.expanded
        },
        collapse (items) {
          items.forEach(item => {
            item.expanded = false
            if (item.children) {
              this.collapse(item.children)
            }
          })
        },
        __eventHandler (comp) {
          if (this.group && this !== comp && comp.group === this.group) {
            this.item.expanded = false
          }
        }
      },
      created () {
        Events.$on(eventName, this.__eventHandler)
      },
      beforeDestroy () {
        Events.$off(eventName, this.__eventHandler)
      }
    }
    </script>
    
    <style lang="stylus" scoped>
    @import '~variables'
    .q-item
      color $primary
      user-select none
    .q-item.active,
    .q-item.router-link-active,
    .q-item:hover
      background-color rgba($secondary, 0.3)
      color $secondary
    </style>
    
    
    

    Play around with that, also take a look at the source for the collapsible.

    I’ll be glad to answer any questions later on



  • Thank you @benoitranque for the quick response. I ended up with this:

    <template>
      <div :class="[{'q-collapsible': this.$slots.content}, {'q-collapsible-closed': this.$slots.content && !expanded}, {'q-collapsible-opened': this.$slots.content && expanded}]">
        <q-item v-bind="this.$props">
          <slot/>
          <q-item-side right v-if="this.$slots.content" @click.stop="toggle" :icon="iconClass? ``: `keyboard_arrow_down`" />
        </q-item>
        <q-slide-transition v-if="this.$slots.content">
          <div v-show="expanded">
            <div class="q-collapsible-sub-item relative-position">
              <slot name="content"/>
            </div>
          </div>
        </q-slide-transition>
      </div>
    </template>
    
    <script>
    import {
      QIcon,
      QList,
      QItem,
      QSideLink,
      QItemMain,
      QItemSide,
      QSlideTransition,
      QCollapsible
    } from 'quasar'
    
    // const eventName = 'q:collapsible:close'
    
    export default {
      mixins: [QSideLink, QCollapsible],
      name: 'QCompositeItem',
      components: {
        QIcon,
        QList,
        QItem,
        QSideLink,
        QItemMain,
        QItemSide,
        QSlideTransition
      },
      data () {
        return {
          expanded: this.opened || false
        }
      },
      props: ['iconClass'],
      methods: {
        toggle () {
          this.expanded = !this.expanded
        }
      },
      mounted () {
        this.iconToggle = true
      }
    }
    </script>
    

    And I use it like this:

            <q-composite-item :to="{name: 'catalog'}">
              <q-item-side class="fio-inspection" />
              <q-item-main label="Service Catalog" />
              <template slot="content">
                <q-side-link item :to="{name: 'wish_list'}" key="wishlist">
                  <q-item-main label="Wish List" />
                </q-side-link>
              </template>
            </q-composite-item>
            <q-composite-item label="empty item">
              <q-item-side left class="fio-inspection" />
              <q-item-main label="Empty Label" />
            </q-composite-item>
    

    This enables me to use item as a dynamic component that will know to link and/or collapse based on the “to” attribute and “content” slot. Also it reuses QCollapsible and QItem through mixins so there is not much code to upkeep.



  • One really annoying bug I have is that on narrow resolutions when the sidebar is hovered in click on the modal background (layout content portion) when I’m on the parent-nav-item route, the app unexpectedly routes to the the sub-nav-item route


Log in to reply
 

Looks like your connection to Quasar Framework was lost, please wait while we try to reconnect.