[Vuejs]-Migrating from vue1 to vue2

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.

Leave a comment