Stphane
Stphane

Reputation: 3476

Vuejs - Event delegation and v-for context reference

I'm using the following snippet to render a list:

<div @click.prevent="myhandler($event)"><!-- Delegated event handler-->
    <div class="tasks" v-for="(task, subName) in step.tasks">
        <button type="button">
            {{ task.caption }}
        </button>
        <span> {{ task.callableName }} </span>
    </div>
</div>
methods: {
    myhandler(e){
        // Event target may be a button element.
        let target = e.target;
        // …
        // Let's assume we know 'target' is a button element instance.
        // I wish I could have access to the v-for item ("task") which is associated in some ways to the button that was clicked.
        // let the_task_reference = ?;
    }…

Is there a clean way so that I could reach the v-for scope specific task related to that button?
Thank you.

Upvotes: 4

Views: 2985

Answers (2)

Bert
Bert

Reputation: 82489

An alternative would be to store the index of the task on the button.

<div class="tasks" v-for="(task, index) in step.tasks">
    <button type="button" :data-index="index">
        {{ task.caption }}
    </button>
    <span> {{ task.callableName }} </span>
</div>

And in your handler get the task using the index.

myhandler(evt){
  const task = this.step.tasks[evt.target.dataset.index]
  ...
}

Example.

If you had something stronger like an id, that would be even better.

Not Recommended

There is a hidden property, __vue__ that is added to Vue and Component root elements. Were you to iterate over a component, you would be able to do something like in this example.

I wouldn't recommend that approach because it relies heavily on Vue internals that could change from version to version, but it's there today.

Upvotes: 1

thanksd
thanksd

Reputation: 55664

The most straight-forward solution would be to put the event handler on the same div as the v-for directive and just pass in the task variable:

<div 
  class="tasks" 
  v-for="(task, subName) in step.tasks"
  @click.prevent="myhandler(task, $event)"
>
  <button type="button">{{ task.caption }}</button>
  <span>{{ task.callableName }}</span>
</div>

If you really need to use an event handler on a parent element, you could keep track of the clicked task via a component property and add an extra click handler to the div with the v-for directive:

<div @click.prevent="myhandler($event)"
  <div 
    class="tasks" 
    v-for="(task, subName) in step.tasks" 
    @click="clickedTask = task"
  >
    <button type="button">{{ task.caption }}</button>
    <span>{{ task.callableName }}</span>
  </div>
</div>

You would need to add a clickedTask property:

data() {
  return {
    clickedTask: null,
  }
}

And then in the handler you could refer to the task via this.clickedTask.

Upvotes: 0

Related Questions