vuelicious
vuelicious

Reputation: 61

Vue template: How to automatically add custom attribute to elements that have a v-on:click directive

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:

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.

Upvotes: 0

Views: 2001

Answers (2)

Daniel Grima
Daniel Grima

Reputation: 2825

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.

Upvotes: 0

Mark Graham
Mark Graham

Reputation: 171

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.

Upvotes: 1

Related Questions