The most common way of making a dropdown in Vue has always been this way:
<button>
or an <a>
) with a click event that will call a toggle method.v-if
directive that is bound to an active state.<template>
<div v-if="active">
<slot/>
</div>
</template>
<script>
export default {
name: 'AppDropdownContent',
}
</script>
Now, this way of doing is totally working and there is nothing wrong doing it. The problem is that you would need to define an active state on every component that has a dropdown. This kind of defeat the purpose of vue and the reusability of components.
I will show you my way of making dropdowns in a clean and reusable way.
I’m going to assume that you already have a vue project set up. If not, I recommend creating one using the vue-cli
We’ll start by scaffolding out the structure of our dropdown component:
<template>
<div id="app">
<AppDropdown>
<template slot="toggler">
<button>Toggle</button>
</template>
<AppDropdownContent>
<AppDropdownItem>Action 1</AppDropdownItem>
<AppDropdownItem>Action 2</AppDropdownItem>
<AppDropdownItem>Action 3</AppDropdownItem>
</AppDropdownContent>
</AppDropdown>
</div>
</template>
<script>
import AppDropdown from './components/AppDropdown.vue'
import AppDropdownContent from './components/AppDropdownContent.vue'
import AppDropdownItem from './components/AppDropdownItem.vue'
export default {
name: 'app',
components: {
AppDropdown,
AppDropdownContent,
AppDropdownItem,
}
}
</script>
We will then create 3 components:
AppDropdown
component, which will act as the wrapper component. It will contain both the toggler and the menu.AppDropdownContent
component, which will act as the toggable menu.AppDropdownItem
component, which will be the actionable item inside the menu.Let’s open the AppDropdown.vue
file and write some code.
In here we’ll define a <div>
that will contain the whole dropdown. We’ll add a slot with the name “toggler
” and a button inside of it which will act as the default button toggle if none is provided.
<template>
<div id="app">
<AppDropdown>
<template slot="toggler">
<button>Toggle</button>
</template>
<AppDropdownContent>
<AppDropdownItem>Action 1</AppDropdownItem>
<AppDropdownItem>Action 2</AppDropdownItem>
<AppDropdownItem>Action 3</AppDropdownItem>
</AppDropdownContent>
</AppDropdown>
</div>
</template>
<script>
import AppDropdown from './components/AppDropdown.vue'
import AppDropdownContent from './components/AppDropdownContent.vue'
import AppDropdownItem from './components/AppDropdownItem.vue'
export default {
name: 'app',
components: {
AppDropdown,
AppDropdownContent,
AppDropdownItem,
}
}
</script>
Now, let’s open the AppDropdownContent.vue
file.
We’ll add a <div>
which will wrap the menu and use the v-if
directive to only display it when it is active.
<template>
<div v-if="active">
<slot/>
</div>
</template>
<script>
export default {
name: 'AppDropdownContent',
}
</script>
You might ask yourself: where is the active
state coming from?
Here comes the fun part: We need the AppDropdownContent
component to receive that value in some way. The most logical way would be from the main AppDropdown
component itself. But how in the hell could we do this?
For that, we’ll use the amazing provide
and inject
features provided by Vue. In our case the AppDropdown
will “provide” the active
state and the AppDropdownContent
will “inject” it in itself.
Let’s go back to our AppDropdown.vue
file.
We’ll add a sharedState object to the data with the property active
in it, that will be set as false
by default. We’ll define a toggle()
method, that will switch the active
state. After that, we’ll add a @click
event to the main div that will call the toggle()
method. Finally, we’ll provide ()
the sharedState to every component inside the main AppDropdown
component.
<template>
<div @click="toggle">
<slot name="toggler">
<button>Toggle</button>
</slot>
<slot/>
</div>
</template>
<script>
export default {
name: 'AppDropdown',
provide () {
return {
sharedState: this.sharedState
}
},
data () {
return {
sharedState: {
active: false
}
}
},
methods: {
toggle () {
this.sharedState.active = !this.sharedState.active
}
}
}
</script>
In the AppDropdownContent
component, we now have the possibility to inject
the sharedState
provided by the AppDropdown
. Let’s create a computed property active
and set it’s value to the one sharedState
provides.
<template>
<div v-if="active">
<slot/>
</div>
</template>
<script>
export default {
name: 'AppDropdownContent',
inject: ['sharedState'],
computed: {
active () {
return this.sharedState.active
}
}
}
</script>
You now how a fully working dropdown component that is fully customizable.
But wouldn’t be nice if you didn’t need to close it by clicking on the toggle again and just click outside of it?
Fortunately, there is this great plugin called vue-clickaway
that allows us to do just this. Let’s install it: npm i vue-clickaway
NB: You might want to recompile your project after the install.
We’ll add the directive provided by vue-clickaway
the AppDropdown
. We’ll define an away()
method and call it when click away is triggered.
<template>
<div @click="toggle" v-on-clickaway="away">
<slot name="toggler">
<button>Toggle</button>
</slot>
<slot/>
</div>
</template>
<script>
import { mixin as clickaway } from 'vue-clickaway';
export default {
name: 'AppDropdown',
mixins: [ clickaway ],
provide () {
return {
sharedState: this.sharedState
}
},
data () {
return {
sharedState: {
active: false
}
}
},
methods: {
toggle () {
this.sharedState.active = !this.sharedState.active
},
away () {
this.sharedState.active = false
}
}
}
</script>
And that’s it! Now, you can simply add some styles to the dropdown and make it look shiny. Like this:
Thanks for reading. Keep Visiting. If you liked this post, share it with all of your programming buddies!
Further reading
Originally published on medium.com by @Victor Rønnow
#vue-js #javascript #web-development