0👍
To fix this I had to make two changes:
First is in the event listener. When I tested it, I got an exception since e.target.parentNode.classList
was undefined (when element has no classes). The error is when trying to access function contains
of undefined
.
So I made a variable and checked for truthy value on it. If it is undefined, it wouldn’t contain the desired class, so you could close the menu.
The next and main problem was this part of the code:
for (var i = 0; i < this.dropDowns.length; i++) {
this.DropDowns
is an Object
, not an Array
. Objects don’t have length, so you can’t use dropDowns.length
.
Instead, you’d extract the object keys and loop them, then access the values using the key name.
I used Array.prototype.forEach
instead of the loop for a more functional approach, and this is the final result:
new Vue({
el: '#menu',
mounted: function() {
var self = this
window.addEventListener('click', function(e) {
var classList = e.target.parentNode.classList;
//Check if classList is a valid value. If classList is undefined, contains would throw an error.
if (!classList || !classList.contains('menu__link--toggle')) {
self.close()
}
}, false)
},
data: {
dropDowns: {
ranking: {
open: false
}
}
},
methods: {
toggle: function(dropdownName) {
this.dropDowns[dropdownName].open = !this.dropDowns[dropdownName].open;
},
close: function() {
var dropDowns = this.dropDowns;
Object.keys(dropDowns).forEach(function(key, index) {
dropDowns[key].open = false;
});
}
}
})
body {
margin: 2rem 0;
}
ul {
list-style: none;
}
.menu {
display: flex;
}
.menu__item {
position: relative;
padding-right: 3rem;
}
.menu__link {
text-transform: uppercase;
}
.menu__icon {
margin: 0 !important;
}
.open .dropdown-menu {
display: block;
}
.dropdown-menu {
font-size: 0.9rem;
position: absolute;
min-width: 130px;
top: 2.2rem;
display: none;
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.dropdown-menu__item:first-child .dropdown-menu__link {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.dropdown-menu__item:last-child .dropdown-menu__link {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.dropdown-menu__link {
display: block;
padding: 1rem;
color: blue;
background-color: #fafafa;
}
.dropdown-menu__link:hover {
color: green;
background-color: #ccc;
}
<script src="https://unpkg.com/vue@2.4.2/dist/vue.min.js"></script>
<div id="menu" class="row">
<ul class="menu">
<li class="menu__item">
<a class="menu__link" href="#">Home</a>
</li>
<li class="menu__item menu__item--dropdown" v-on:click="toggle('ranking')" v-bind:class="{'open' : dropDowns.ranking.open}">
<a class="menu__link menu__link--toggle" href="#">
<span>Rangliste</span>
<i class="menu__icon fa fa-angle-down"></i>
</a>
<ul class="dropdown-menu">
<li class="dropdown-menu__item">
<a class="dropdown-menu__link" href="#">Aktuelle Runde</a>
</li>
<li class="dropdown-menu__item">
<a class="dropdown-menu__link" href="#">Siegerliste</a>
</li>
</ul>
</li>
<li class="menu__item">
<a class="menu__link" href="#">Belegungskalender</a>
</li>
</ul>
<pre>{{ dropDowns }}</pre>
</div>
I’d recommend that you turn this menu into a component. As it is right now, that is non-reusable instance, and you’ll surely need to use this dropdown in more places in your project.