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).
-
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