Get reference to element in method in Vue.js - vue.js

How can I get reference to the element that fired the method in Vue.js?
I have HTML like this:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" />
And in my Vue.js viewmodel I have a method:
dataFieldClass: function () {
// Here I need the element and get its ID
// Pseudo code
var elementId = $element.id;
}
I know that it's possible to get the element from event (v-on:click), but this is not an event, it's a simple method returning CSS class for the element according to few conditions of the viewmodel. It should be computable as well, but the problem is the same.

You can get the reference to your element in three ways
1. with Method Event Handlers (doc)
template:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" />
script:
dataFieldClass: function (e) {
const element = e.target;
}
2. with Inline Handlers (doc)
template:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass($event, otherArgument)" />
script:
dataFieldClass: function (e, otherArgument) {
const element = e.target;
}
3. with Refs (doc)
template:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" ref="el"/>
script:
dataFieldClass: function () {
const element = this.$refs.el;
}

Maybe you could use ref?
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" ref="el" />
And use it like this:
dataFieldClass: function () {
var elementId = this.$refs.el;
}
See documentation here: https://v2.vuejs.org/v2/api/#ref

What about using the ref pattern. Put ref="someName" in your DOM element, and access it in your method with this.$refs["someName"] (you can pass 'someName' as parameter to your method).
Note that's not a very good pattern except if for some reason you really need the DOM element. Otherwise just pass a relevant parameter to your method.
It's not a good method mainly because it has a major drawback: there is no $refs the first time the vue is rendered (because the element is not present yet). So you should force the vue to render twice.
If you have multiple elements inside a v-for loop, then this.$refs["someName"] becomes an array. You can get it to work with some adaptation, here is an example:
new Vue({
el: '#app',
data() {
return {
fields: [{
name: 'field1',
value: 'value1'
},
{
name: 'field2',
value: 'value2'
}
]
};
},
methods: {
dataFieldClass(index) {
if (!this.$refs.fields) {
// First render, the element is not there yet
return '';
} else {
// Here is the element
console.log(this.$refs.fields[index]);
}
}
},
mounted() {
// Force the instance to render a second time
this.$forceUpdate();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<label v-for="(field, index) in fields">
{{ field.name }}:
<input ref="fields" :value="field.value" v-bind:class="dataFieldClass(index)">
</label>
</div>

You can get the reference from DOM event object. "event.currentTarget" is the property that references the element where the event listener(vuejs method) assigned.
This is standard DOM specification, but you can also use this property in Vuejs.
dataFieldClass: function (event) {
var elementId = event.currentTarget.id;
}

A straightforward solution is to pass a reference to the element in the method to be called.
Here's what worked for me (a pretty basic example to help understand):
new Vue({
el: '#app',
data: {
msg: '',
},
methods: {
// in order to access the HTML element,
// add an argument (namely 'event') in the method definition,
// and access the element's current value by `event.target.value`
updateValue: function(event) {
this.msg = event.target.value;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input :value="msg" #input="updateValue" autofocus>
<br/>
<h2>
>>> {{ msg }}
</h2>
</div>

This seem to work for me, using ref (if element is nested another element)
<div ref="element">
vm.$refs.element
or $el if targeted element is the outermost
<template><div class="targeted-element">
this.$el

You can use refs as mentioned in other answers here.
Remember, refs cannot apply to computed objects. So be careful when using refs

Related

Vue v-on:click change it to load or mouse over event

I am trying to get v-bind:value on to function reservationBy() as load event instead of v_on:clickevent. Right now it passes the value when I click on it only.
Is there a way to make it load automatically or use mouse over event? I even try to use v-on:load and v-on:focus event but it did not work.
View
<div id="app">
<input v-bind:value="2" v-on:click="reservationBy"/>
</div>
Script
new Vue({
el: "#app",
data: {
},
methods: {
reservationBy: function(e) {
var peopleBookedId = e.target.value;
console.log(peopleBookedId);
}
}
})
Here is example on JSFIDDLE
https://jsfiddle.net/ujjumaki/yz0p1vqL/4/
#mouseover and #mouseleave will do the job.
<input v-bind:value="2" #mouseover="reservationBy"/>
or
<input v-bind:value="2" #mouseleave="reservationBy"/>
If using v-model is not an option (that would be the easiest way), and you want to execute the function when component is rendered, you can use ref and access value in mounted hook:
<input ref="myInputRef" :value="2" />
Script:
mounted: function() {
console.log(this.$refs.myInputRef.value);
}

Attach v-model to a dynamic element added with .appendChild in Vue.js

I'm working with a library that doesn't have a Vue.js wrapper.
The library appends elements in the DOM in a dynamic way.
I want to be able to bind the v-model attribute to those elements with Vue and once appended work with them in my model.
I've done this in the past with other reactive frameworks such as Knockout.js, but I can't find a way to do it with vue.js.
Any pay of this doing?
It should be something among these lines I assume:
var div = document.createElement('div');
div.setAttribute('v-model', '{{demo}}');
[VUE CALL] //tell vue.js I want to use this element in my model.
document.body.appendChild(div);
You could create a wrapper component for your library and then setup custom v-model on it to get a result on the lines of what you're looking for. Since your library is in charge of manipulating the DOM, you'd have to hook into the events provided by your library to ensure your model is kept up-to-date. You can have v-model support for your component by ensuring two things:
It accepts a value prop
It emits an input event
Here's an example of doing something similar: https://codesandbox.io/s/listjs-jquery-wrapper-sdli1 and a snipper of the wrapper component I implemented:
<template>
<div>
<div ref="listEl">
<ul ref="listUlEl" class="list"></ul>
</div>
<div class="form">
<div v-for="variable in variables" :key="variable">
{{ variable }}
<input v-model="form[variable]" placeholder="Enter a value">
</div>
<button #click="add()">Add</button>
</div>
</div>
</template>
<script>
export default {
props: ["value", "variables", "template"],
data() {
return {
form: {}
};
},
mounted() {
this.list = new List(
this.$refs.listEl,
{
valueNames: this.variables,
item: this.template
},
this.value
);
this.createFormModels();
},
methods: {
createFormModels() {
for (const variable of this.variables) {
this.$set(this.form, variable, "");
}
},
add() {
this.$emit("input", [
...this.value,
{
id: this.value.slice(-1)[0].id + 1,
...this.form
}
]);
}
},
watch: {
value: {
deep: true,
handler() {
this.$refs.listUlEl.innerHTML = "";
this.list = new List(
this.$refs.listEl,
{
valueNames: this.variables,
item: this.template
},
this.value
);
}
}
},
beforeDestroy() {
// Do cleanup, eg:
// this.list.destroy();
}
};
</script>
Key points:
Do your initialization of the custom library on mounted() in order to create the DOM. If it needs an element to work with, provide one via <template> and put a ref on it. This is also the place to setup event listeners on your library so that you can trigger model updates via $emit('value', newListOfStuff).
watch for changes to the value prop so that you can reinitialize the library or if it provides a way to update its collection, use that instead. Make sure to cleanup the previous instance if the library provides support for it as well as unbind event handlers.
Call any cleanup operations, event handler removals inside beforeDestroy.
For further reference:
https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components

Vue js passing data owned by component to methods owned by component

Trying to pass different arrays that are within "data" of a vue component to methods within the same vue component. I am trying to do this to have different variables be affected by the same method without having to have an individual method for each variable.
Tried having the variable be passed as a parameter to the method and it did not respond as expected. It seemed as though the variable being passed was a copy of the data variable rather than the actual value. I want to do this to allow the data to be modified in a method before being displayed on the page
Example data:
data () {
return {
a: [],
b: []
}
}
How I'd like watchers to work:
a {
this.method(this.a)
}
b {
this.method(this.b)
}
Methods:
method(value) {
value.add(1)
}
Result currently would be a and b not be changed
UPDATE:
Based on your comments it looks like what you are trying to do is reuse a method in your component to mutate two different arrays in your viewmodel.
You can create a method that gets a collection, the value and an action that you want to run.
NOTE: This might end up making your html bindings a bit messy, as you would need to have something like:
<button type="button" #click="this.mutate(a, 'a', (c, v) => c.push(v))">Add to a</button>
Here's a sample snippet where a shared method is used to mutate and filter the array. I added wrapper methods to keep the html bindings clean (which ends up with the same number of methods but less repetition).
Vue.component('some-component', {
template: `
<div>
<div>a: {{a}}</div>
<div>b: {{b}}</div>
<button type="button" #click="onAdd(a, 'a')">Add to a</button>
<button type="button" #click="onSplice(a, 1)">Splice a</button>
<br>
<button type="button" #click="onAdd(b, 'b')">Add to b</button>
<button type="button" #click="onSplice(b, 1)">Splice b</button>
</div>
`,
data:() => ({ a: [], b: [] }),
methods: {
mutate(collection, value, callback){
callback(collection, value);
},
onAdd(collection, value) {
this.mutate(collection, value, (c, v) => c.push(v));
},
onSplice(collection, value) {
this.mutate(collection, value, (c, v) => c.splice(0, value));
}
}
})
new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<some-component></some-component>
</div>
Watchers must be functions, you can do:
watch: {
a: function(aNewValue) {
this.method(aNewValue)
}
}
Anyway, if you intend that every time a variable changes, a change in another variable is triggered, you should use a computed property.

How am I suppose to get the value of a checkbox?

So I have the following element:
<input v-on:click="$emit('addPartId', $event)" v-bind:value="13209" name="add_13209" type="checkbox">
Which then calls the following method:
methods: {
....
addPartId(evnt) {
console.log(evnt);
}
},
In the parent container and is passed to the child:
<table-body
v-bind:items="items"
v-bind:columns="columns"
v-bind:sort-column="sortColumn"
v-bind:direction="direction"
#sort="sort"
#addPartId="addPartId"
>
</table-body>
The question I have, that I can't find on stack, is how do I register a click event so that when the checkbox is clicked I get the event object (I want the value, from v-bind:value, of the checkbox.
You should use event name which is the kebab-cased version, check Vue Guide: Custom Event,
As the guide says:
Unlike components and props, event names will never be used as
variable or property names in JavaScript, so there’s no reason to use
camelCase or PascalCase. Additionally, v-on event listeners inside DOM
templates will be automatically transformed to lowercase (due to
HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent
– making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event
names.
Vue.component('my-checkbox', {
template: `
<input v-on:click="$emit('add-part-id', {'whole': $event, 'value':13209})" v-bind:value="13209" name="add_13209" type="checkbox">
`
})
Vue.component('my-another-checkbox', {
template: `
<input v-on:click="$emit('add-part-id', $event)" v-bind:value="13209" name="add_13209" type="checkbox">
`
})
new Vue({
el: '#emit-example-simple',
methods: {
getChecked1: function (ev) {
console.log('checkbox1', ev.value)
console.log('checkbox1', ev.whole.target.value)
},
getChecked2: function (ev) {
console.log('checkbox2', ev.target.value)
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="emit-example-simple">
First Example: <my-checkbox #add-part-id="getChecked1"></my-checkbox>
Second Example: <my-another-checkbox #add-part-id="getChecked2"></my-another-checkbox>
</div>

Vue v-once equivalent

Is there a way to tell Vue to call a method only once when used as an expression?
Here's my code:
<div v-for="i in a.b.c.items">
<div :id="foo(i.value)"></div>
</div>
The way it is now, the foo() method will be executed any time anything on the model changes, not only items. Is there something in Vue that I can tell to evaluate this only once?
like this: <div :id.only-once="foo(i.value)"
Unfortunately that's only possible for certain events, e.g. in this question here. What you may want to consider instead is a computed property where you compute all of these values and return the array. This resulting array will be cached by Vue and will not be reevaluated until your items array is modified (and modified in such a way that Vue will detect the change).
An example:
Vue Setup
<script>
new Vue({
el: '. . .',
data: {
a: {b: {c: {items: [. . .]}}}
},
methods: {
foo: function(val) {
. . .
}
},
computed: {
itemsAfterFoo: function() {
var this_vue_instance = this;
var computed_items = [];
this_vue_instance.items.forEach(function(item) {
computed_items.push(this_vue_instance.foo(item.value));
});
return computed_items;
}
}
});
</script>
Template
<div v-for="(i, index) in a.b.c.items">
<div :id="itemsAfterFoo[index]"></div>
</div>
Or something to that effect.
More information on computed properties here: https://v2.vuejs.org/v2/guide/computed.html