Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sub-menu for dropdown #1455

Merged
merged 10 commits into from
Jan 27, 2021
Merged

Conversation

jonahtanjz
Copy link
Contributor

@jonahtanjz jonahtanjz commented Jan 21, 2021

What is the purpose of this pull request?

  • Documentation update
  • Bug fix
  • Feature addition or enhancement
  • Code maintenance
  • Others, please explain:

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:

<dropdown>
    <li>...</li> // normal dropdown item
    <li>...</li>
    <dropdown> // will be converted to submenu
        <li>...</li> // submenu item
        <li>...</li>
        <dropdown> // 2nd-level submenu
            <li>...</li> // 2nd-level submenu item
            <li>...</li>
        </dropdown>
    </dropdown>
</dropdown>

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: ☑️

  • Updated the documentation for feature additions and enhancements
  • Added tests for bug fixes or features
  • Linked all related issues
  • No blatantly unrelated changes
  • Pinged someone for a review!

@jonahtanjz
Copy link
Contributor Author

jonahtanjz commented Jan 21, 2021

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

Copy link
Contributor

@ang-zeyu ang-zeyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a review on the overall approach / issues:

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

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

  2. Really small nit: Make the submenu show on-hover as well? 🤔

@ang-zeyu
Copy link
Contributor

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

@jonahtanjz
Copy link
Contributor Author

@ang-zeyu I've made some changes accordingly.

I was thinking of using the https://v3.vuejs.org/guide/component-provide-inject.html#working-with-reactivity.

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 dropdown and submenu 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>a as these are user defined.

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

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.

@jonahtanjz jonahtanjz force-pushed the 212-dropdown-submenu branch from 716e2f9 to a26f606 Compare January 25, 2021 07:03
Copy link
Contributor

@ang-zeyu ang-zeyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 dropdown and submenu components.

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>a as 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:

dropdown-sub
(main / submenu closing on hover)

Untitled
(this is on mobile view)

Some suggestions:

@@ -13,6 +13,13 @@
</ul>
</slot>
</li>
<submenu v-else-if="isSubmenu" ref="submenu">
<slot slot="button" name=button></slot>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could do

<template v-for="(node, name) in $slots" :slot="name">
  <slot :name="name"></slot>
</template>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated :)

@@ -72,6 +79,7 @@ export default {
return toBoolean(this.disabled);
},
isLi () { return this.$parent._navbar || this.$parent.menu || this.$parent._tabset },
isSubmenu() { return this.$parent._dropdown || this.$parent._submenu },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. Have added this in :)

type: Boolean,
default: false,
},
'class': null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated :)

},
computed: {
classes() {
return [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's place this directly in the template only for consistency with other components'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. Updated :)

<include src="codeAndOutput.md" boilerplate >
<variable name="highlightStyle">html</variable>
<variable name="code">
<!-- Can simply nest the dropdown syntax -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<!-- Can simply nest the dropdown syntax -->
<!-- Nest the dropdown syntax to create dropdown submenus -->

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated :)

},
addClassIfSubmenu(a, li) {
let el = a.parentElement;
while (el != li) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!==

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated :)

},
methods: {
hideSubmenu() {
this.show = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use data() for properties that could change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated :)

}

@media (max-width: 767px) {
.dropdown-submenu > ul {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm can this be moved to the same media query below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated :)

@jonahtanjz
Copy link
Contributor Author

jonahtanjz commented Jan 25, 2021

@ang-zeyu Thanks for the suggestions and clarification 😄. I've updated it accordingly.

Some strange behaviours:
(main / submenu closing on hover)

For the main menu, it is closing because when the inner children of the submenu toggle is clicked/hovered, it triggers a blur event, which closes the main dropdown menu. I've fixed this by preventing this specific event from bubbling up.

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.

Copy link
Contributor

@ang-zeyu ang-zeyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple more nits:

@@ -112,7 +120,7 @@ export default {
showDropdownMenu() {
this.show = true;
$(this.$refs.dropdown).findChildren('ul').each(ul => ul.classList.toggle('show', true));
},
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright sure will open another PR for this 👍

disabledBool() {
return toBoolean(this.disabled);
},
showBool() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this (could replace with just show) and slots() dosen't seem used

Copy link
Contributor Author

@jonahtanjz jonahtanjz Jan 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Replaced all showBool with show

</script>

<style scoped>
.dropdown-submenu {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's indent these to fit the stylelint rules too (they've never been validated in our scripts for .vue files but should be)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated :)

@jonahtanjz
Copy link
Contributor Author

@ang-zeyu PR is updated accordingly :)

Copy link
Contributor

@ang-zeyu ang-zeyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm 👍

@ang-zeyu ang-zeyu added this to the v3.0 milestone Jan 27, 2021
@ang-zeyu ang-zeyu merged commit 3c3f295 into MarkBind:master Jan 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Drop-down menus in navbar: support sub-menu items
2 participants