4

In a Vue-component, I have a menu like this:

<ul class="menu-outer-wrapper">
  <li><a href="/foo-1">Foo 1</a></li>
  <li class="has-children">
    <a href="/foo-2">Foo 2</a>
    <ul>
      <li><a href="/child-1">Child 1</a></li>
      <li><a href="/child-2">Child 2</a></li>
      <li><a href="/child-3">Child 3</a></li>
    </ul>
  </li>
  <li><a href="/foo-5">Foo 5</a></li>
  <li class="has-children">
    <a href="/foo-6">Foo 6</a>
    <ul>
      <li><a href="/child-1">Child 1</a></li>
      <li><a href="/child-2">Child 2</a></li>
    </ul>
  </li>
  <li><a href="/foo-7">Foo 7</a></li>
  <li><a href="/foo-8">Foo 8</a></li>
</ul>

And I would like to add the class hovered to the li.has-children-elements upon hover (mouseenter) (to be able to make some nicer animations for the children of that dropdown. And remove that class on mouseleave.

I know that there are options to do this with pure CSS, - but controlling delays and soft fade-in's are a pain (and become very messy very fast, without adding some classes).

I imagined doing something like this:

  ...
  mounted(){
    let liWithChildren = document.querySelectorAll( '.menu-outer-wrapper > li.has-children' );
    liWithChildren.forEach( (event, window) => {
      // Somehow add the class to the hovered element here.
      // But I don't know how. Or if it's a good/bad idea (performance-wise). 
    } 
  }

But is that the way to go? And can I do it without using data (since the menu is dynamically generated by a CMS-system.


Update 1

I'm trying to keep my markdown readable. So I would like to avoid something like this:

<ul class="menu-outer-wrapper">
  <li :class="[ { 'hovered' : someVar } ]">
    <a href="/foo-1">Foo 1</a>
  </li>
  <li :class="[ { 'hovered' : someVar }, 'has-children' ]">
    <a href="/foo-2">Foo 2</a>
    <ul>
      <li><a href="/child-1">Child 1</a></li>
      <li><a href="/child-2">Child 2</a></li>
      <li><a href="/child-3">Child 3</a></li>
    </ul>
  </li>
  <li :class="[ { 'hovered' : someVar } ]">
    <a href="/foo-3">Foo 2</a>
  </li>
...
...
...

Both since it won't gel with the dynamically generated menu. And also since it add a lot of noise to the markdown.


Update 2

I simplified the example, to make it easier to digest. But due to the comments I figured I would elaborate on the dynamic generated menu. I'm making it something like this:

<nav id="secondary-menu" v-if="secondaryMenu">
  <ul>
    <li
      :class="[ { 'has-children': r.children } ]"
      v-for="(r, r_key, r_index) in secondaryMenu">
      <a :href="r.url" :title="r.title">
        {{ r.title }}
      </a>
      <ul class="children" v-if="r.children">
        <li v-for="(c1, c1_key, c1_index) in r.children">
          <a :href="c1.url" :title="c1.title">
            {{ c1.title }}
          </a>
        </li>
      </ul>
    </li>
  </ul>
</nav>
3
  • Vue should have baked in event bindings available for elements so you could define mouseenter and mouseleave events Commented Dec 31, 2020 at 21:16
  • vuejs.org/v2/guide/events.html <= you can make two event handlers, that toggle a data model flag true/false. And your component can use that data model variable to know if the element needs to have, or not have, a class (vuejs.org/v2/guide/class-and-style.html) Commented Dec 31, 2020 at 21:17
  • A secondary point about why you should do it with the built in vue methodology is because of how Vue uses the virtual dom and such to compute the differences and build the views for you. If you change the view outside of the typical vue patterns, you have to account for any time that vue may regenerate the view, completely destroying any custom changes you have made. (vuejsdevelopers.com/2017/02/21/vue-js-virtual-dom) Commented Dec 31, 2020 at 21:29

1 Answer 1

3

You just need the @mouseenter and @mouseleave events. All you need to do is listen for the appropriate events on all list-items that could have children, then perform your class addition (or removal) if the target element has the class of "has-children". Here's how I would do it:

<template>
  <nav id="secondary-menu" v-if="secondaryMenu">
    <ul>
      <li
        :class="[{ 'has-children': r.children }]"
        v-for="(r, r_key, r_index) in secondaryMenu"
        :key="`${r_key}-${r_index}`"
        @mouseenter="addClass"
        @mouseleave="removeClass"
      >
        <a :href="r.url" :title="r.title">
          {{ r.title }}
        </a>
        <ul class="children" v-if="r.children">
          <li
            v-for="(c1, c1_key, c1_index) in r.children"
            :key="`${c1_key}-${c1_index}`"
          >
            <a :href="c1.url" :title="c1.title">
              {{ c1.title }}
            </a>
          </li>
        </ul>
      </li>
    </ul>
  </nav>
</template>
<script>
export default {
  name: "HoverNav",
  props: {
    secondaryMenu: {
      type: Array,
      required: true,
    },
  },
  methods: {
    addClass: function (e) {
      if (e.target.classList.contains("has-children")) {
        e.target.classList.add("hovered");
      }
    },
    removeClass: function (e) {
      if (e.target.classList.contains("has-children")) {
        e.target.classList.remove("hovered");
      }
    },
  },
};
</script>

Here's a very unaesthetic sandbox of this in action. Let me know if this works for you :)

https://codesandbox.io/s/headless-brook-ysq97?file=/src/components/HoverNav.vue:0-1169

Sign up to request clarification or add additional context in comments.

Comments

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.