26

I am using a dropdown menu components in vuejs to make normal dropdown menu. My code is for dropdown component is :

<template>
    <span class="dropdown" :class="{shown: state}">
        <a href="#" @click.prevent="toggleDropdown" class="dropdown-toggle">toggle menu</a>
            <div class="dropdown-menu" v-show="state">
                <ul class="list-unstyled">
                    <slot></slot>
                </ul>
            </div>
        </transition>
    </span>
</template>

<script>
export default {
    name: 'dropdown',
    data () {
        return {
            state: false
        }
    },
    methods: {
        toggleDropdown (e) {
            this.state = !this.state;
        }
    }
}
</script>

Now I am importing the dropdown component in my VUE app at various place by using following code in the template

<dropdown>
    <li>
         Action
    </li>
</dropdown>

Now that is working fine but I want that only one dropdown should be active at the same time.

I have done little research and found that i can use plugins like https://github.com/davidnotplay/vue-my-dropdown but I don't want to use that. Again i have also studied how the above example works but I want to implement this dropdown functionality in such a way that my dropdown component would take care of all event related to dropdown. So can you help me how to achieve that?

4 Answers 4

58

I know it's quite an old question but I think the best way to do that without any external plugins is to add a click listener to mounted lifecycle hook (and remove it on beforeDestroy hook) and filter the clicks on your component so it only hides when clicked outside.

<template>
    <span class="dropdown" :class="{shown: state}">
      <a href="#" @click.prevent="toggleDropdown" class="dropdown-toggle">toggle menu</a>
            <div class="dropdown-menu" v-show="state">
                <ul class="list-unstyled">
                    <slot></slot>
                </ul>
            </div>
        <transition/>
    </span>
</template>

<script>
export default {
  name: 'dropdown',
  data () {
    return {
      state: false
    }
  },
  methods: {
    toggleDropdown (e) {
      this.state = !this.state
    },
    close (e) {
      if (!this.$el.contains(e.target)) {
        this.state = false
      }
    }
  },
  mounted () {
    document.addEventListener('click', this.close)
  },
  beforeDestroy () {
    document.removeEventListener('click',this.close)
  }
}
</script>
Sign up to request clarification or add additional context in comments.

2 Comments

I've used this approach before. It works well when put in a mixin or directive. The less plugins the better, esp when they are so simple.
such a great approach! thank you
3

In Vue 3 the following should work

Note that the @click.stop on the dropdown trigger and on the dropdown content prevents the document event from executing the close function.

<template>
  <div class="dropdown" :class="{'is-active': dropdown.active.value}">
    <div class="dropdown-trigger">
      <button class="button" @click.stop="dropdown.active.value = !dropdown.active.value">
        Toggle
      </button>
    </div>
    <div class="dropdown-menu" role="filter">
      <div class="dropdown-content" @click.stop>
        <!-- dropdown items -->
      </div>
    </div>
  </div>
</template>

<script>

import { defineComponent, ref, onMounted, onBeforeUnmount } from "vue";

export default defineComponent({
  name: "Dropdown",
  setup(){
    const dropdown = {
      active: ref(false),
      close: () => {
        dropdown.active.value = false
      }
    }

    onBeforeUnmount(() => {
      document.removeEventListener('click', dropdown.close)
    })

    onMounted(() => {
      document.addEventListener('click', dropdown.close)
    })

    return {
      dropdown
    }
  }
})

</script>

This example uses bulma but off course you don't need to.

7 Comments

I wrote the codes in the style you said but it didn't work, is it possible to write code without making define Component?
@Liza Yes you can. In this example I use the Composition API. But you can also choose to use the Options API. You can find the difference here: vuejs.org/guide/introduction.html#api-styles
i run it normally but it didn't work for me
codesandbox I added below but it didn't work
Here is your codesandbox with the fix. You forgot to put the .stop after the toggle click. codesandbox.io/s/vue3-fixed-header-scroll-forked-w7qz1b?file=/…
|
2

Have a look at vue-clickaway.(Link)

Sometimes you need to detect clicks outside of the element (to close a modal window or hide a dropdown select). There is no native event for that, and Vue.js does not cover you either. This is why vue-clickaway exists. Please check out the demo before reading further.

1 Comment

@Shubham it may solve the problem but i want to minimize the dependency on any external component/plugin/resource . Anyways i will try to implement similar functionality, thanks
0

You can make use of the blur event, e.g. if you add a method:

close() {
    setTimeout(() => {
        this.state = false;
    }, 200);
}

And set the blur event for your link:

<a href="#" @click.prevent="toggleDropdown" @blur="close">toggle menu</a>

Then the dropdown will get hidden whenever your toggle link loses focus. The setTimeout is necessary because Javascript click events occur after blur, which would result in your dropdown links being not clickable. To work around this issue, delay the menu hiding a little bit.

1 Comment

Yup but this may not the perfect solution. Additionally we have to bind blur event to the parent wrapper i think because user may take some mili-seconds to select from the actions from dropdown

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.