Support sub-menu for dropdown#1455
Conversation
|
@ang-zeyu this is ready for review and do let me know on how this can be improved. If the implementation is fine, will proceed to add in tests and documentation as well. |
ang-zeyu
left a comment
There was a problem hiding this comment.
Just a review on the overall approach / issues:
- I was thinking of using the https://v3.vuejs.org/guide/component-provide-inject.html#working-with-reactivity.
e.g.
you could do
<dropdown>
<div class="some-wrapper">
<dropdown></dropdown> // would still work when nested
</div>
</dropdown>
the approach here is more consistent with vue-strap's, but as you might have observed its a rather low level approach that ties itself heavily to the dom. (note vue-strap didn't have this api at the time)
But I would be ok with accepting this as quite a bit of work has been done already. If you're up for it, let's change this over to provide/inject (could even make a separate PR to shift the original implementation to it first if it makes things easier)
-
Positioning of submenus needs to be dynamic (we can assume right position first then flip it over to the left otherwise). (note the submenu may overflow over the screen currently) You could explore the popperjs library to do this (bootstrap-vue uses this internally for its popovers,dropdowns...)
-
Really small nit: Make the submenu show on-hover as well? 🤔
|
One more note since you're tackling #980 as well: you may want to finish shifting the mobile navbar design to it first, as dropdowns contained within navbars look / behave quite differently - less work to do here |
|
@ang-zeyu I've made some changes accordingly.
I didn't use provide/inject in the end as when I was trying to implement this, I managed to remove all the unnecessary communication between the
I tried out popperjs but the auto positioning didn't work very well/a little buggy. I switched to using a modified bootstrap javascript which similarly solves this issue. Hopefully that's fine ;) I've also added tests and updated the docs. |
716e2f9 to
a26f606
Compare
ang-zeyu
left a comment
There was a problem hiding this comment.
I didn't use provide/inject in the end as when I was trying to implement this, I managed to remove all the unnecessary communication between the
dropdownandsubmenucomponents.
Not so much for removing unnecessary communication but just allowing parent / child communication across different compilation scopes.
Nice work isolating the implementation as much as possible - only have one not-so-beneficial suggestion where this can be used now. (see comment below)
You could take a look at the following for examples how it could be used more beneficially:
- bv components
- our own
<quiz>/<question>/<q-option>components
Not sure where else to use it as the rest of the search of the virtual DOM seems necessary, for example listening for clicks on
li>aas these are user defined.
yes this is still necessary unfortunately, due to our use of standard dom elements.
I tried out popperjs but the auto positioning didn't work very well/a little buggy. I switched to using a modified bootstrap javascript which similarly solves this issue. Hopefully that's fine ;)
Seems ok too 👍
Some strange behaviours:

(main / submenu closing on hover)
Some suggestions:
| </slot> | ||
| </li> | ||
| <submenu v-else-if="isSubmenu" ref="submenu"> | ||
| <slot slot="button" name=button></slot> |
There was a problem hiding this comment.
could do
<template v-for="(node, name) in $slots" :slot="name">
<slot :name="name"></slot>
</template>
There was a problem hiding this comment.
Thanks! Updated :)
| return toBoolean(this.disabled); | ||
| }, | ||
| isLi () { return this.$parent._navbar || this.$parent.menu || this.$parent._tabset }, | ||
| isSubmenu() { return this.$parent._dropdown || this.$parent._submenu }, |
There was a problem hiding this comment.
simple example where provide / inject could be used to remove the $parent dependency, but not so beneficial:
provide: { hasParentDropdown: true }
inject: ['hasParentDropdown']
isSubmenu() { return this.hasParentDropdown}
There was a problem hiding this comment.
Thanks for the clarification. Have added this in :)
| type: Boolean, | ||
| default: false, | ||
| }, | ||
| 'class': null, |
There was a problem hiding this comment.
Thanks! Updated :)
| }, | ||
| computed: { | ||
| classes() { | ||
| return [ |
There was a problem hiding this comment.
let's place this directly in the template only for consistency with other components'
There was a problem hiding this comment.
Alright. Updated :)
| <include src="codeAndOutput.md" boilerplate > | ||
| <variable name="highlightStyle">html</variable> | ||
| <variable name="code"> | ||
| <!-- Can simply nest the dropdown syntax --> |
There was a problem hiding this comment.
| <!-- Can simply nest the dropdown syntax --> | |
| <!-- Nest the dropdown syntax to create dropdown submenus --> |
| }, | ||
| addClassIfSubmenu(a, li) { | ||
| let el = a.parentElement; | ||
| while (el != li) { |
There was a problem hiding this comment.
Thanks! Updated :)
| }, | ||
| methods: { | ||
| hideSubmenu() { | ||
| this.show = false; |
There was a problem hiding this comment.
let's use data() for properties that could change
| } | ||
|
|
||
| @media (max-width: 767px) { | ||
| .dropdown-submenu > ul { |
There was a problem hiding this comment.
hmm can this be moved to the same media query below?
There was a problem hiding this comment.
Thanks! Updated :)
|
@ang-zeyu Thanks for the suggestions and clarification 😄. I've updated it accordingly.
For the main menu, it is closing because when the inner children of the submenu toggle is clicked/hovered, it triggers a As for the submenu closing on hover, it is because when the user hovers over the submenu toggle, it will mirror a click. That is the reason why the second hover will close the submenu. One workaround is to only allow submenus to be opened by hovering over and not by clicking. This is the current implementation for now. |
| this.show = true; | ||
| $(this.$refs.dropdown).findChildren('ul').each(ul => ul.classList.toggle('show', true)); | ||
| }, | ||
| } |
There was a problem hiding this comment.
let's correct these in another PR; we could enable linting for dropdown.vue as well. (the unlinted ones are files that haven't changed much from vue-strap)
There was a problem hiding this comment.
Alright sure will open another PR for this 👍
| disabledBool() { | ||
| return toBoolean(this.disabled); | ||
| }, | ||
| showBool() { |
There was a problem hiding this comment.
this (could replace with just show) and slots() dosen't seem used
There was a problem hiding this comment.
Thanks! Replaced all showBool with show
| </script> | ||
|
|
||
| <style scoped> | ||
| .dropdown-submenu { |
There was a problem hiding this comment.
let's indent these to fit the stylelint rules too (they've never been validated in our scripts for .vue files but should be)
|
@ang-zeyu PR is updated accordingly :) |

What is the purpose of this pull request?
Overview of changes:
Resolves #212.
Sub-menus can be added by nesting dropdowns so that users do not have to remember a new syntax. A new component
<submenu>is created to handle the logic.<dropdown>component is edited such that if a<dropdown>is nested, it will be converted to a<submenu>. Sub-menus can also contain sub-menus using the same syntax for multiple levels. Currently, sub-menus will appear to the right of a dropdown on desktop version and becomes an accordion on mobile.Anything you'd like to highlight / discuss:
Implementation.
Testing instructions:
Sub-menus can be added by nesting
<dropdown>.For example:
A dropdown with 2 levels of submenu will be created.
Proposed commit message: (wrap lines at 72 characters)
Add sub-menu support for dropdown
Checklist: ☑️