Programmatically add v-on directives to DOM elements

<span @click="showModal = $event.target.innerHtml>Tag 1</span>
<span @click="showModal = $event.target.innerHtml>Tag 2</span>
<span @click="showModal = $event.target.innerHtml>Tag 3</span>

Clicking in any of the 3 spans will make this.showModal to have the value of each of the span content elements. But this code looks repetitive and unnecessary. I know I can create a component with v-for and have the data for the span contents somewhere else, but I want to know how to do this for very specific reasons. I'd like to have this:

<span>Tag 1</span>
<span>Tag 2</span>
<span>Tag 3</span>

And a function, e.g. in the hook mounted() of the component, that adds the v-on directive for click to each one of them.

Can you help me?

Thanks.

Upvotes: 0

Views: 2883

Answers (5)

Sphinx
Sphinx

Reputation: 10729

If direct-process Dom elements, custom directive will be one option.

Vue.config.productionTip = false

let vMyDirective = {}
vMyDirective.install = function install (_Vue) {
  _Vue.directive('my-directive', {
    inserted: function (el, binding, vnode) {
      el.addEventListener('click', () => {
        _Vue.set(vnode.context, binding.value.model, el.innerHTML)
      }, false)
    }
  })
}

Vue.use(vMyDirective)

new Vue({
  el: '#app',
  data() {
    return {
      testValues: ['label a', 'label b'],
      showModal: 'nothing!!!'
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <h2>showModal: {{showModal}}</h2>
  <div>
    <p v-for="(item, index) in testValues" v-my-directive="{'model': 'showModal'}">Test:<span>{{item}}</span></p>
  </div>
</div>

Upvotes: 0

I've finally added the listeners manually with vanilla js, in order to save code:

mounted: function() {
    let spans = document.querySelectorAll('span');

    spans.forEach(el => {
      el.addEventListener('click', this.clickTag);
    })
}
methods: {
    clickTag(event) { this.showModal = event.target.innerHTML }
}

It's important not using an arrow function for mounted because otherwise it won't bind the vue instance for this.

Thanks for your answers.

Upvotes: 0

Erik Withak
Erik Withak

Reputation: 45

You could set up a method to call when the tag is clicked and pass the id of the tag that was clicked through to handle appropriately.

Assuming that you have an array of the tag text:

data: function() {
  return {
    tagTotal: ['Tag 1', 'Tag 2', 'Tag 3'];
  }
}

Then in the HTML section:

<span v-for="tag in tagTotal" @click="methodToCall(tag)">
  {{ tag }}
</span>

Then in your mounted, methods, or created section you could add:

mounted: {
  methodToCall: function(tag) {
    showModal = tag;
    // or 'this.showModal = tag' if showModal is a part of the componenet.
  }
}

Upvotes: 0

dr_barto
dr_barto

Reputation: 6075

You can add a method which is called on clicks that reads the element's HTML content.

The template:

<span @click="doStuff">Tag 1</span>
<span @click="doStuff">Tag 2</span>
<span @click="doStuff">Tag 3</span>

The method:

doStuff(e) {
  this.showModal = e.target.innerHTML
}

Upvotes: 1

mtflud
mtflud

Reputation: 866

You could try something like this:

<template>
    <span v-for="tag in tags" @click="showModal(tag)" v-text="tag"></span>
</template>

<script>
    export default {
        data() {
            return {
                tags: ['Tag 1', 'Tag 2', 'Tag 3']
            }
        },
        methods: {
            showModal(tag) {
                console.log("Showing modal for tag:", tag)
            }
        }
    }
</script>

Hope this helps!

Upvotes: 1

Related Questions