Vue2 watch Set Not Working - javascript

I have a simple Vue app that is supposed to add a number to a Set when you click the "Add to Set" button --
https://codepen.io/jerryji/pen/mKqNvm?editors=1011
<div id="app">
<input type="number" placeholder="enter a number" v-model="n">
<button type="button" #click.prevent="addToSet()">Add to Set</button>
</div>
new Vue({
el: "#app",
data: {
n: null,
nSet: new Set()
},
methods: {
addToSet: function() {
this.nSet.add(this.n);
console.log(this.n + ' added to Set');
}
},
watch: {
nSet: function (newVal, oldVal) {
console.log(newVal);
}
}
});
Why is nothing logged in the console by the watch?

Saving and re Setting the Set using the .values() method on Set worked for me and i didn't have to use $forceUpdate
Using $forceUpdate might be the more sensible way to go though. In some use cases in the past i have found forcing components to update to be problematic.
new Vue({
el: "#app",
data: {
n: null,
nSet: new Set()
},
methods: {
addToSet: function() {
let set = this.nSet;
let newSet = set.add(this.n)
this.nSet = new Set(newSet.values())
}
},
watch: {
nSet: function (newVal, oldVal) {
console.log('newVal', ...newVal);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<input type="number" placeholder="enter a number" v-model="n">
<button type="button" #click.prevent="addToSet()">Add to Set</button>
<p>n = {{ n }}</p>
</div>

Vue adds special handling for Arrays, but not for Sets. As a result, Vue doesn't automatically detect changes to Set membership. You can force an update, though
this.nSet.add(this.n);
this.$forceUpdate();

It's because Vue doesn't support Set, Map, WeakSet and WeakMap. And it's because browsers didn't support these structures well. Especially WeakMap. But... They decided to support these structures. Maybe in version 3 - when they decide to drop support for older browsers. So, for now use an object, add properties with Vue.$set() and watch for changes with deep: true.

Related

[Vue warn]: Property or method "hp" is not defined on the instance but referenced during render

I have the following code:
var example1;
var hp = ["p"];
document.addEventListener("DOMContentLoaded", function(event) {
hp = ["x"];
example1 = new Vue({
el: '#example-1', //yes i have an element with #example-1 (not relevant here)
data: {
iLoveMyself: window.hp
},
watch: {
iLoveMyself: {
deep: true,
immediate: true,
handler (val, oldVal) {
console.log("yeeeh")
}
}
}
})
});
I tried many things (that's why my code ^ is so damn ugly) but i keep this console.error:
vue.js:634 [Vue warn]: Property or method "hp" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
If i look up the value in the chrome plugin for VueJs the the data set is as followed:
iLoveMyself:Array[1]
0:"x"
All good so far but when trying to update hp like:
hp.push("y");
hp.pop();
hp = "ZEBRA";
I get no response what so ever.
What is that i don't understand ?
Gratitude in advance!
Edit:
So after all i start to gather to pieces, my html is important after all :/
<div id="example-1">
<div v-for="ev in hp" :key="ev.beavus">
{{ ev.beavus }}
</div>
</div>
Here is a more idiomatically Vue implementation of what you posted in the question:
new Vue({
el: '#example-1',
data: {
iLoveMyself: [{beavus: "x"}]
},
watch: {
iLoveMyself: {
deep: true,
immediate: true,
handler (val, oldVal) {
console.log("yeeeh")
}
}
},
methods: {
add () {
this.iLoveMyself.push({beavus: Math.random()})
}
}
});
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="example-1">
<div v-for="ev in iLoveMyself" :key="ev.beavus">
{{ ev.beavus }}
</div>
<button #click="add">Add</button>
</div>
I've got rid of the global variables, there's now just an instance property called iLoveMyself that holds the array. Pushing extra data to the array triggers both the watch and updates the DOM with the new data.
You should do :
<div id="example-1">
<div v-for="ev in iLoveMyself" :key="ev.beavus">
{{ ev.beavus }}
</div>
</div>

Storing edited form fields for submittal with Vue

Objective: I have a form interface that is being loaded with an object's current data for editing. The user opens this modal with the form that is loaded with the current info so they an either edit it or leave it
Currently working: The form loads with the data from my three objects (details, editSubEvents, instructions) and shows properly without issue
My problem: When I edit the fields and hit submit, I'm only currently dumping the submitted data object to make sure I have what I need. I get the eventID fine becasue it won't change and I get it from the original object. However, I need to store the new title, instruction, and subEvents (as an array) in order to submit them because they're obviously different from the origin ones
How can I properly store the new info from these input fields, including storing the new subEvent title and instructions as an array?
<div class="modal-body">
<div class="form-group row" v-for="detail in details">
<p class="modal-title">Title</p>
<input v-model="detail.title" type="text" class="form-control" id="EventTitle" name="EventTitle">
</div>
<div class="form-group row" v-for="subEvent in editSubEvents">
<p class="modal-title">SubEvent Title</p>
<input v-model="subEvent.title" type="text" class="form-control" id="newSubTitle" name="newSubTitle">
<p class="modal-title">SubEvent Instructions</p>
<textarea v-model="subEvent.instructions" type="text" class="form-control" id="newSubInstructions" name="newSubInstructions"></textarea>
</div>
</div>
data() {
return {
details: [],
instructions:[],
editSubEvents:[],
}
},
methods: {
updateEvent() {
let data = {
EventID: this.details[0].event_id,
title:
origin:
instructions:
subEvents: //needs to be an array
};
console.dir(data);
}
}
All of the properties of your data object can be bound to the UI elements (and most of them are, going by your template example code). The properties of the data object are accessible through the Vue component's this.
new Vue({
el: '#vueApp',
data() {
return {
details: [],
instructions:[],
editSubEvents:[],
}
},
methods: {
updateEvent() {
const data = {
EventID: this.details[0].event_id,
title: this.details[0].title,
origin: this.details[0].origin,
instructions: this.instructions,
subEvents: this.subEvents,
};
console.dir(data);
}
}
}

How to temporize the analysis of an <input> field?

I would like to analyze the content of an <input> field when there is no user activity.
I will take below a simple example (counting the number of characters) but the actual analysis if very expensive so I would like to do it in batches, when there is some inactivity of the user instead of doing it at every change of the bound variable.
The code for the straightforward analysis could be
var app = new Vue({
el: '#root',
data: {
message: ''
},
computed: {
// a computed getter
len: function() {
// `this` points to the vm instance
return this.message.length
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<div id="root">
<input v-model="message">Length: <span>{{len}}</span>
</div>
My problem is that function() is called at each change of message. Is there a built-in mechanism to throttle the query, or a typical approach to such a problem in JS?
That works the way it is supposed to. As it is said in the docs:
It will update any bindings that depend on computed property when the original data changes
But there's a way to do it:
var app = new Vue({
el: '#root',
data: {
message: '',
messageLength: 0
},
methods: {
len: _.debounce(
function() {
this.messageLength = this.message.length
},
300 // time
)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message" v-on:keyup="len">Length: <span>{{ messageLength }}</span>
</div>
Full example: https://v2.vuejs.org/v2/guide/computed.html#Watchers
p.s. A comment about computed being sync from the vue's author: https://forum-archive.vuejs.org/topic/118/throttle-computed-properties/3
p.p.s Classics article about difference between debounce and throttle.

How to get an input field value in Vue

I'm new to Vue and I would like some help getting a value from an input field:
In my form I have:
<input type="hidden" id="groupId" value="1">
If I was using jQuery I would do:
var group_id = $('#groupId').val();
However, in Vue I don't know how to bind the hidden field:
<div id="app">
<input type="text" v-model="groupId"> //Where do I put the value?
</div>
new Vue({
el: '#app',
data: {
groupId: //What do I put here to get the field's value?
}
How can I achieve this?
Update to the update: See this answer. Previously updated answer was wrong.
Original answer:
In Vue, you don't get things from the view and put things into the view. Vue does that. You do all manipulations in the viewmodel, and make bindings in the view so that Vue knows how to synchronize it. So you'd bind the input to your model data item:
<input type="hidden" id="groupId" v-model="groupId">
and set its value in your viewmodel:
data: {
groupId: 1
}
I had the same question. I'm working with Vue + Laravel.
For me, the solution was simple after searching and not finding a concrete solution in the Vue documentation.
Simply:
document.getElementById('MyId').value;
Details in → https://www.w3schools.com/jsref/prop_text_value.asp
It is not the most efficient solution, but it works for now!
Greetings.
Working sample of getting value from input field in this case it is hidden type:
<input type="hidden" name="test">
<script>
new Vue ({
created () {
const field = document.querySelector("input[name=test]").value
console.log(field)
}
})
</script>
this code helped me
i hope that this work with you
define the input
<div class="root">
<input type="hidden" ref="groupId" value="1">
<button type="button" v-on:click="get_id()">test</button>
</div>
define the method
new Vue({
el: ".root",
data: {
id: null,
}
methods: {
get_id() {
this.id = this.$refs.groupId.value;
}
}
});
// if you want it displayed on your page, use {{ groupId }}
/* you can get the value by using #change.enter=".." #keypress.enter="getInputValue",
or #input="getInputValue" or #click="getInputValue" using button,
or if it is with a form element, #submit.prevent="getInputValue" */
/* #keypress.enter tracks input but only calls the function when the Enter key
is pressed, #input track changes as it's being entered */
// it is important to use event.preventDefault() when using #change or #keypress
<div id="app">
<input type="text" v-model="groupId">
<p> {{ groupId }} </p>
<button #click="getInputValue">Get Input</button>
</div>
new Vue({
el: '#app',
data: {
groupId: //What do I put here to get the field's value?
// for what to put there, you can use an empty string or null
groupId: "",
},
// to get the value from input field
methods: {
getInputValue: function() {
if(this.groupId !== "") {
console.log(this.groupId);
}
},
}
})
look at this I did it in laravel, vuejs, vuetable2 and children's row, and don't use the v-model:
this.$refs['est_'+id_det].localValue
en VUE:
<div class="col-md-3">
<b-form-select class="form-control selectpicker" :ref="'est_'+props.row.id_detalle_oc"
:value="props.row.id_est_ven" v-on:change="save_estado(props.row.id_detalle_oc)">
<option value="0">Sin estado</option>
<option value="1">Pendiente</option>
<option value="2">Impresa</option>
<option value="3">Lista</option>
</b-form-select>
in methods
methods: {
save_estado:function (id_det){
var url= 'ordenes-compra/guardar_est_ven'
var id_estado = this.$refs['est_'+id_det].localValue
axios.post(url,{
id_det: id_det,
id_est_ven: id_estado,
est_ven: est_ve
}).then(function (response) {
var respuesta= response.data;
if(respuesta == "OK"){
swal({
type: 'success',
title: '¡Éxito!',
text: 'Estado modificado',
confirmButtonText: 'Entendido',
})
}
})
.catch(function (error) {
console.log(error);
});
},
I hope it helps, I've been hanging around for a while.
Regards
Hi you can also try the following:
const input = this.$el.firstElementChild;
in case you are using TypeScript, declare input as:
: HTMLInputElement
Then, you can simply get the value if you do:
input.value
Hope it helps!
Ok, this does the job: document.querySelector('#groupId').getAttribute('value');

Output data from one Vue.js instance into another

I have a pretty big page with lots of stuff going on. So i have 2 Vue instances for 2 parts of the page. How can i bind data from one Vue instance into another?
This example should show what i am trying to do. (it's not working that way)
<div class="app1">...</div>
...
<div class="app2">{{app1.$data.msg}}</div>
var app1 = new Vue({
el: '.app1',
data: {msg: "test"}
});
var app2 = new Vue({
el: '.app2'
});
In advance, I know this isn't the question you are asking, but I don't know why you need two instances of Vue. Why not just bind Vue to the body and treat both the Vue instances as components. It might be difficult to do what you are trying to do, because it was not intended. Maybe it was, I don't know. I have set Vue up on the body and I haven't seen a performance hit. Here is a JSBin.
HTML
<div class="container">
<div id="app1">
<h1>{{ msg }}</h1>
<input type="text" v-model="msg" class="form-control"/>
</div>
<div id="app2">
<h1>{{ msg }}</h1>
<input type="text" v-model="msg" class="form-control"/>
</div>
</div>
JavaScript
var VueComponent1 = Vue.extend({
template: '#app1',
data: function(){
return {
msg: ""
}
}
});
var VueComponent2 = Vue.extend({
template: '#app2',
data: function(){
return {
msg: ""
}
}
});
var app1 = Vue.component('app1', VueComponent1);
var app2 = Vue.component('app2', VueComponent2);
var app = new Vue({
el: 'body',
data: { msg: 'Everybody loves Vue.' }
});
If you are looking for a better way to separate you code, you might want to check out this Vue with Browserify example.
Laracasts has a free series on Vue which has been pretty informative as well.
I am still looking for the best solution. The following feels a bit hacky but it works.
You can use the watch option to listen for the change of an expression and trigger a function. In this function you can update the desired data of another Vue instance. In you example we would do this:
var app1 = new Vue({
el: '.app1',
data: {msg: 'test'},
watch: {
'msg': function() { app2.msg = this.msg; }
}
});
var app2 = new Vue({
el: '.app2',
data: { msg: app1.$data.msg },
watch: {
'msg': function() { app1.msg = this.msg; }
}
});
You can see this at work in this jsbin.
Moreover, I am wondering if you even need to do this. If this was a real-life-situation there could be better ways to handle this avoiding this hacky solution.

Categories