0

I was trying to figure out how to access both a v-for member and the original event data to a bound function on an event in vue.js, but couldn't for the life of me find it in the documentation.

What I want:

<div class="card pickup" v-for="pickup in pickups">
        <select v-on:change="locationChanged">

with locationChanged calling the following method in my vue object:

vuePickup = new Vue({
    el: '#pickups',
    data: {
        pickups: pickups,
    },
    methods: {
        locationChanged: (event, pickup) => {
            pickup.newLocation = event.target.value == -1;
        }
    },
});

which requires access to both the element in question and the object that was bound to that portion of the v-model.

By default, using v-on:change="locationChanged" calls locationChanged with the event, and the element can be reached via event.target but I couldn't find a way to access the pickup object I was binding the particular element to.

Using v-on:change="locationChanged(pickup)" instead causes locationChanged to be called with the model I need, but I then lose the reference to the HTML element in question.

What I ended up doing was setting up a local function in the model itself to forward the values:

v-on:change="e => locationChanged(e, pickup)"

which provides me with the information I need.

However, I feel that this is not the correct approach as a) I couldn't find it in the documentation, and b) it's not the most user friendly.

@Lawless pointed me to an existing question by another user who was wondering more generally how to access the event (as compared to my goal of accessing the event target, i.e. the element that triggered the event), but my question about the idiomatic approach here remains as this is a very basic question (callbacks for enumerated elements) and yet both solutions (mine and the linked question) are not very obvious and not covered in the vue.js manual.

Is there a more idiomatic approach prefered by the vue.js community?

Mahmoud Al-Qudsi
  • 28,357
  • 12
  • 85
  • 125
  • 2
    Have you tried ` – jaredrethman Feb 17 '19 at 20:58
  • Possible duplicate of [Passing event and argument to v-on in Vue.js](https://stackoverflow.com/questions/40956671/passing-event-and-argument-to-v-on-in-vue-js) – tony19 Feb 18 '19 at 01:13

1 Answers1

0

Another question on StackOverflow (pointed out to me by @Lawless) was asking the same question I started off with (namely, how to access the event data in a binding for an enumerated element rather than one associated with a top-level vue.js object), providing another, more direct (but imho even more arcane) approach to accomplishing the same.

To recap, starting with an HTML element generated by vue.js bound to a vuejs object obtained via enumeration

<div class="card pickup" v-for="pickup in pickups">
        <select v-on:change="locationChanged">
            ...
        </select>
</div>

How do you access both the backing object (here, pickup) and a reference to the select element so you can query its new value and act accordingly.

My approach was to take advantage of the fact that vuejs passes in the event by default and work around the fact that vuejs suppresses the event parameter if you explicitly provide a local object as the parameter in the callback by using a lambda:

<div class="card pickup" v-for="pickup in pickups">
        <select v-on:change="e => locationChanged(e, pickup)">
            ...
        </select>
</div>

Explicitly adding the element I'm interested in to the callback, at the cost of an extra function call/layer of indirection.

The previous answer provided a solution that takes advantage of the explicitly defined vuejs $event variable, which is always translated to the native browser event, which can be directly used in addition to whatever other variables you wish to capture, making the solution look like this:

<div class="card pickup" v-for="pickup in pickups">
        <select v-on:change="locationChanged($event, pickup)">
            ...
        </select>
</div>

Which requires more intimate knowledge of vuejs, but avoids the closure.

However, I kept searching because it did not feel like this solution was in line with the vuejs ethos (and all I ever heard was everyone raving about how awesome the vuejs docs are, but this answer wasn't explicitly there but rather had to be pieced together).

It seems that I was grappling with a common case of knowing what I want but not how to get there.. and my previous domain knowledge that precluded vuejs led me to the non-idiomatic approach in my question rather than how I would have solved it if I started off learning frontend development with vue.js in the first place.

In the end, I found the obvious answer I was looking for, an answer that felt like it was the correct "vuejs way" of doing it and the proper idiomatic solution to my original question:

The problem is that I was intermingling standard JS/DOM introspection with the vuejs model, and the friction arose in the transition between the two (leaky abstractions and all). I wanted a native handle to the select element along with a vuejs object (pickup), when what I should have been doing was using vuejs exclusively to accomplish what I needed, ultimately by binding the select element to a vuejs object/property, and referencing that directly in my bound callback:

<div class="card pickup" v-for="pickup in pickups">
        <select v-model:pickup.location v-on:change="locationChanged(pickup)">
            ...
        </select>
</div>

With the select element now bound to the location property of my vuejs model, I can directly query its state/value in my locationChanged handler and pass in my pickup object without needing to use either $event or a closure:

let vuePickup = new Vue({
    el: '#pickups',
    data: {
        pickups: pickups,
    },
    methods: {
        locationChanged: (pickup) => {
            pickup.newLocation = pickup.location == -1;
        }
    },
});
Mahmoud Al-Qudsi
  • 28,357
  • 12
  • 85
  • 125