0

We are using single file vue components and in a mousemove event handler we'd like to be able to detect if the target element is clickable.

In our Vue templates we are using v-on directives: v-on:click="someCallback".

Unfortunately there doesn't seem to be an easy way to tell for a given element if an event listener was registered for it (i.e. via v-on directive).

For this we'd like to add a custom attribute to those elements with a v-on:click directve - i.e. "clickable". But this should happen automatically.

So we'd have to either wrap Vue's own "on"-directive into a custom one or somehow hook into Vue's rendering cycle - but this seems not very straight-forward: Couldn't find the directive on the Vue instance or the Vue component object.

What we have tried:

  • retrieving the information about registered listeners from the target element provided by the event object passed to the event handler. But apparently browsers don't provide this information.
  • searching the Vue component object for some object that stores information about which event listener has been registered for which element with which handler. We were not able to find this information - but it should be somewhere, right?

Hope anyone has a nice idea on how to accomplish adding a custom attribute to elements with v-on:click directive automatically.

Thanks!

EDIT:
So we have i.e. <div id="x" @click="someMethod" /> in our template. But we want to add a custom attribute automatically (we dont want to add it manually for all the trillion cases): <div id="x" clickable @click="someMethod" /> Then in the event handler for addEventListener('mousemove', handler) we would check for this attribute: if (e.target.hasAttribute('clickable')) But any other way of accomplishing this (so being able to tell inside the handler for mousemove if the element is clickable) would be fine too.

2
  • Can you show a use case of what you are trying to achieve? Because it could depend on what you have, the data you are using, etc. Just thinking about it, using a custom directive and refs should do the trick. But as I mentioned please post some example code. Commented Aug 8, 2020 at 16:02
  • added more details at the bottom (EDIT) Commented Aug 8, 2020 at 16:12

2 Answers 2

1

You could create a container component and import it into all your other vue components, ensuring it's the first component in your template, like:

<template>
  <v-container>
    // your template here
  </v-container>
</template>

<script>
// Obviously replace the path and point to your location of the component
import ComponentContainer from './common/ComponentContainer.vue'
export default {
  name: 'MyClientComponent',
  components: {
    'v-container': ComponentContainer
  }
}
</script>

And this is the container component that looks for click events and adds the clickable class:

<template>
  <div class="component-container">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'ComponentContainer',
  mounted() {
    this.$slots.default.forEach(vn => {
      this.addClickableClassNames(vn);
    });
  },
  methods: {
    addClickableClassNames(vnode) {
      if (vnode) {
        let data = vnode.data;
        if (data && data.on) {
          // Check for click events and add a
          // clickable class if one exists
          if (data.on.click && vnode.elm && vnode.elm.classList) {
              vnode.elm.classList.add('clickable');
          }
        }
        // Now recursively check children
        if (vnode.children) {
          vnode.children.forEach(vn => {
            this.addClickableClassNames(vn);
          });
        }
      }
    }
  }
}
</script>

This works, but I wouldn't like to comment on the performance of a large dom. And you, and other devs, need to remember to import into all components, which isn't ideal. But, it's a solution that might give you other ideas on improving and making more scalable.

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

2 Comments

Hey Mark, thanks for this solution! Unfortunately we cant take this approach, The array this.$slots.default only includes the wrapper element of the parent component. And also we cannot change our code base like that. But it's interesting anyway, since I never realized the $slots.default is holding nodes of the virtual DOM, not the actual DOM:)
No probs, and you're welcome! Hope you solve your problem.
0

I can't think of a way to "automatically" add this clickable attribute. I think unfortunately you will still need to "tag" your clickable elements one by one.

I would have a directive which you can add to any element in your templates.

Directive

Vue.directive('myDirective', {
  inserted(el, bindings) {
    el.addEventListener('mouseover', () => {
        alert(bindings.value);
    })
  }
});

Usage

<span v-my-directive="true">Element 1</span>
<span v-my-directive="false">Element 2</span>

You will notice that in the template when using the directive a value is being passed to it. This is then read via bindings.value. Of course based on this value you can do whatever functionality you need.

1 Comment

Manually I could also do just like this: <span clickable @click="..."> ;)

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.