vue.js method can't access variable from data object with multiple rows - vuejs2

I am currently learning vue.js and having trouble accessing data in the methods.
data is loaded and set as a global variable (for now, this will probably change but not part of the problem now i think)
through ajax call this data is received:
data":[{"itemId":"58646f066803fa62388b4573","color":"#ffb878","name":"test1","startDate":"04/24/2017","work":"9.25"},{"itemId":"58646f066803fa62388b4572","color":"#ffb878","name":"test2","startDate":"04/24/2017","work":"4.25"},{"itemId":"58646f066803fa62388b4571","color":"#a4bdfc","name":"test3","startDate":"05/01/2017","work":"24.00"}]
which is set as a global (variable data is set outside of the functions) with:
...success: function (jsonObj)
{
data['item'] = jsonObj.data
....
now for the vue part:
var app = new Vue({
el:'#canvas',
data: {
items: data['item']
},
methods: {
moveItem: function(){
console.log("new date: "+this.startDate);
}
}
})
the html:
<div v-for="row in items" class="entirerow" v-bind:id="'row'+row.itemId">
<div class="itemrow">{{ row.name }}</div>
<div class="itemrow"><input type="text" v-model="row.startDate" #change="moveItem"></div>
<div class="itemrowlast">{{ row.work }}</div>
</div>
this nicely shows 3 rows with the correct data in each row. So far so good. But now if I change something in the input value the method moveItem is triggered but states "new date: undefined" in the console.log
I've tried console.log("new date: "+this.items.startDate) as well but no cigar and then it would seem the method wouldn't know which row is handled.
How can I access the correct data in the method, so from a certain row in the loop?
Thanks!

You refer to data object in method (this.startDate) not to item
moveItem: function(){
console.log("new date: "+this.startDate);
}
You can change your code like this
template
#change="moveItem(row)"
script
moveItem: function(row){
console.log("new date: " + row.startDate);
}

Related

VueJS: computed property using another computed property isn't working correctly

this is not these: post1, post2
i'm building a small SPA and so i started with this basic principle that i tested and built up into the code farther below that i'm having problems with. it works ONLY if i call the secondary property in the DOM (why??) and i've included it to illustrate where i started from, hoping that the principles would translate as i built up.
JS:
var app = new Vue({
el: '#app',
computed: {
today: function() {
return new Date().getTime();
},
tomorrow: function() {
return this.today + (1*24*60*60*1000);
},
});
DOM:
<div id='app'>
<p>{{ today }}</p>
<p>{{ tomorrow }}</p>
</div>
so. that works. again, ONLY if app.tomorrow is called in the DOM. (wish i knew how NOT to need that...) BUT my problem is this next part, which i based on this starter code. it DOESN'T work:
JS:
var app = new Vue({
el: '#app',
data: {
dueNow: 0,
reports: { 90: [], 'DN': [] },
},
computed: {
today() {
return new Date().getTime();
},
tMinus30() {
return new Date().getTime() - (30 * 24 * 60 * 60 * 1000);
},
dueNow() {
var count = 0;
$.each(this.rows, function(index, obj) {
var workingDate = new Date(obj.dateSubmitted).getTime();
console.log('workingDate: '+workingDate);
console.log('today: '+this.today); <-- console reports 'undefined'!?
console.log('today: '+this.tMinus30); <-- console reports 'undefined'!?
if(workingDate < this.today && workingDate > this.tMinus30){
console.log('knock, knock: due NOW'); <-- therefore this never fires
count++;
console.log('count: '+count);
// build reports array - doesn't work, but that's for another day...
this.reports['DN']['count'] = push(count);
this.reports['DN'][index]['reportID'] = push(obj.id);
this.reports['DN'][index]['assetID'] = push(obj.assetid);
}
});
return count;
},
}
});
DOM:
<div id='app'>
<div class="col-lg-2">
<h1 class="text-center pt-2">NOW</h1>
<p>{{ today.getDay() }}</p>
<p class="img-text mb-1" #click="expandInfo('DN')">{{ dueNow }}</p>
</div>
</div>
so not only am i not getting the count of rows that are dueNow, i'm not getting today.getDay() calculated either, which, based on this from VueJS, should work. i suspect this is a flying nightmare, but i don't see it... it DOES iterate over the rows properly. it DOES calculate the working date for each row. but nothing else works. i thought i understood VueJS well enough to attempt this. and i even built it up from the first example... but i'm at a loss as to what's broken.
what principle am i overlooking to make this gel? it feels like some concept has eluded me.
EDIT: i also perused this article but don't know that it applies as there's nothing that isn't declared in the data already...
EDIT: OK! :) i need to clarify. when i say "it doesn't work", what i mean is the computed property tomorrow does not render and reports the other computed property in the console as "undefined", despite it (today) rendering just fine (without the additional method). my expected/desired result is that i will see a number indicating how many rows passed muster and are dueNow and i want to see the today rendered as "Monday" and tomorrow as "Tuesday" (for example). this last should be interchangeable with other date functions. if possible, i'd also like to have the array reports update properly... but that's out of scope right now.

For each results in v-for loop how can I nest another v-for loop using a parameter from the results of the first loop

Using a v-for loop in Vue js. I am looping through the readingTasks data object which correctly produces two results from the data below.
readingTasks:Array[2]
0:Object
enabled:true
newunit:-1
task:"The part 3 guide"
unit:-1
unit_task_id:27
url:"#"
1:Object
enabled:true
newunit:-1
task:"The part 3 training units"
unit:-1
unit_task_id:28
url:"#"
The bit I am unsure about is how for each result, how do I run another Axios database call that shows if the reading Task is complete or not. For example for the first record, the complete status should be true (unit_task_id:27) and the second record should be false.
userTasks:Array[1]
0:Object
complete:true
enabled:true
newunit:-1
task:"The part 3 guide"
unit:-1
unit_task_id:27
unit_task_user_id:21
<ul>
<li v-for="task in readingTasks">
{{task.task}}
//trying to call a function that does an Axios call passing in parameters from readingTasks
{{getUserTaskByUnit(task.unit, task.unit_task_id)}}
<template v-for="usertask in userTasks">
{{usertask.complete}}
</template>
</li>
</ul>
//javascript if its useful
data: {
readingTasks: [],
userTasks: []
},
mounted() {
this.lastUnit();
},
methods: {
//functons
lastUnit: function() {
this.tasks();
},
tasks: function() {
var self = this;
var unit = this.unit;
axios.get("/WebService/units.asmx/GetTasks?unit=" + unit).then(function(response) {
self.readingTasks = response.data;
})
.catch(function(error) {
console.log(error);
})
.then(function() {
});
},
getUserTaskByUnit: function(unit, unitTaskId) {
var self = this;
axios.get("/WebService/units.asmx/GetUserTasks?unit=" + unit + "&unitTaskId=" + unitTaskId).then(function(response) {
self.userTasks = response.data;
})
.catch(function(error) {
console.log(error);
})
.then(function() {});
}
This code seems close to doing the correct thing, however {{usertask.complete}} flickers between true and false for both sets of results. Like it is stuck in a loop.
I would expect the first result to show True here and the second result to show False.
The part 3 guide - true
The part 3 training units - false
There are a few problems here.
The template has a dependency on userTasks, so every time userTasks changes it will cause the component to re-render, running the template again.
Every time the template runs it calls getUserTaskByUnit for both tasks. That will, asynchronously, update userTasks. When userTasks is updated it will trigger a re-render, which will call getUserTaskByUnit again, going round and round in an infinite loop.
Worse than just being an infinite loop, each time it renders it will trigger two requests, each of which will trigger another re-rendering. The number of requests will balloon exponentially.
When those requests do return you're then storing them in userTasks. But both responses are being stored in exactly the same place, so you'll only ever see the results of one request in the UI.
The first thing you'll need is a better data structure for storing the responses in getUserTaskByUnit. The simplest place to store them would be on the tasks in readingTask. That might look something like this:
// Note the whole task is now being passed to getUserTaskByUnit
getUserTaskByUnit: function(task) {
var self = this;
axios.get("/WebService/units.asmx/GetUserTasks?unit=" + task.unit + "&unitTaskId=" + task.unit_task_id).then(function(response) {
task.userTasks = response.data;
})
...
}
The call to getUserTaskByUnit needs moving out of the template. Moving it into the tasks method seems as good a place as any. There are also a few changes required to get it to work with the new version of getUserTaskByUnit:
tasks: function() {
var self = this;
var unit = this.unit;
axios.get("/WebService/units.asmx/GetTasks?unit=" + unit).then(function(response) {
var readingTasks = response.data;
// Pre-populate userTasks so it will be reactive
readingTasks.forEach(function(task) {
task.userTasks = [];
});
// This must come after userTasks is pre-populated
self.readingTasks = readingTasks;
readingTasks.forEach(function(task) {
// Passing task to getUserTaskByUnit, not unit and unit_task_id
self.getUserTaskByUnit(task);
});
})
...
Then within the template we'd need to loop over task.userTasks:
<ul>
<li v-for="task in readingTasks">
{{task.task}}
<template v-for="usertask in task.userTasks">
{{usertask.complete}}
</template>
</li>
</ul>
There are alternative data structures we could use depending on what other requirements you have. For example, you could retain a separate userTasks object to hold the userTasks but for that to work it would need to be a nested data structure rather than just an array. You'd need to key it by unit and then unitTaskId. The result in the template would be something like this:
<ul>
<li v-for="task in readingTasks">
{{task.task}}
<template v-for="usertask in userTasks[task.unit][task.unit_task_id]">
{{usertask.complete}}
</template>
</li>
</ul>
Much like with the earlier solution you would need to pre-populate the userTasks with empty values when readingTasks first loads to ensure the values are reactive and also to avoid the template blowing up at the undefined entries. Alternatively you could use $set and suitable v-if checks respectively.
This is all quite fiddly. It may be that you can simplify it a little based on your knowledge of the system. For example, it may be possible to form compound string keys for userTasks rather than using two levels of nesting. Or it might be that unit is a prop that can be considered constant and doesn't need including in that data structure.
Your userTasks is a view property and gets overwritten upon every call to getUserTaskByUnit (i.e. for each item in readingTasks). What you instead want is a nested structure. You should call getUserTaskByUnit in a loop as soon as readingTasks got loaded, i.e. after the line self.readingTasks = response.data;, and store the response as a property for every readingTask object.

set value of input programatically doesn't refresh component DOM

I have an input which is bidden to property message. I have a set button , which I use for changing of input value programmatically. When the button is pressed, value of input is correctly changed to 'xxxx'. when I press clean button after that , the input is correctly cleaned but when I repeat pressing set and clean again the input does not get cleared anymore.
Working example is here:
https://codepen.io/dan-ouek/pen/rNBjxRO
<div class="container">
<div id='root' class="box">
<input ref="templateFilter" type='text' id='input' v-model='message'>
{{ message }}
<button #click="set">set</button>
<button #click="clean">clean</button>
</div>
</div>
new Vue({
el: '#root',
data: {
message: 'Hello Vue'
},
methods: {
set: function () {
let element = this.$refs.templateFilter
Vue.set(element, 'value', 'xxxx')
}, clean: function () {
this.message = ""
}
}})
Questions:
1) Is it possible to change input programmatically without working with bidden property value? Something like direct DOM manipulation:
let element = document.getElementsByClassName("templateFilter")[0]
element.value = 'xxxxx'
but with proper component refresh?
2) Why is the {{ message }} not refreshed after calling set method?
Why is the message not refreshed after calling set method?
When you type something on your input the message data gets updated via an event handler BUT when you set it programmatically the event handler is NOT triggered and that's why message was not getting updated / refreshed....
Solution :
a short solution to your issue would be just to mutate the message value this.message = "xxxx" but if you insist on making that programmatically you have to trigger the input event :
set: function() {
let element = this.$refs.templateFilter
Vue.set(element, 'value', 'xxxx')
element.dispatchEvent(new Event('input'))
}
Here is a Demo : codepen

Vuejs2 - How to update the whole object from the server not losing reactivity?

I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});

dynamic change doesn't work on vuejs input

i'm working on a vue file and have a form :
<div class="input-group">
<span class="input-group-addon">Montant</span>
<input type="number" class="form-control" v-model="amount" v-bind:value="pattern['value']"]>
</div>
my tab pattern is loaded like that :
var request = $.ajax({
url: '{{ path ('home') }}promos/pattern/'+value,
})
request.success(function(data){
if(data['pattern']==='yes'){
this.pattern=data[0];
alert(this.pattern['value']);
}
})
and my instance :
var instance = new Vue({
el: "#General",
data: {
[...]
pattern: []
}
and the request is made evertyime i do 'action a'. I have the right alert with the value i want everytime i do 'action a' but the input stays at 0 and won't dynamically change.
Something is wrong with your code. Firstly, let's look at your ajax request:
request.success(function(data){
if(data['pattern']==='yes'){
this.pattern=data[0];
alert(this.pattern['value']);
}
})
What is the form of your data response? Because you are checking something with data['pattern'], and then you are trying to associate to this.pattern something that you call data[0]
Then, as stated in #thanksd answer, you are referencing a wrong this in your ajax callback, you need to create a self variable:
var self = this
var request = $.ajax({
url: '{{ path ('home') }}promos/pattern/'+value,
})
request.success(function(data){
if(data['pattern']==='yes'){
self.pattern=data[0];
alert(this.pattern['value']);
}
})
Finally, you write:
<input type="number" class="form-control" v-model="amount" v-bind:value="pattern['value']"]>
So there are a few mistakes here. Firstly, you have a ] at the end of the line that has nothing to do here.
Secondly, you are using v-bind:value, this is not something that is going to be responsive. If you want this input to be responsive, you should use v-model and set the value of amount when you want to change the input value.
Hope this helps
Three things:
The this in your success handler is not referencing the Vue instance. You need to set a reference outside the scope of the handler and use that instead.
You can't chain a success callback to jQuery's ajax method in the first place. It's defined as a property in the parameter object passed to the call. (Maybe you copied code over wrong?)
You need to get rid of v-model="amount" if you want the input's value to reflect the value bound by v-bind:value="pattern"
Your code should look like this:
let self = this; // set a reference to the Vue instance outside the callback scope
var request = $.ajax({
url: '{{ path ('home') }}promos/pattern/'+value,
success: function(data) { // success handler should go in the parameter object
if (data['pattern']==='yes') {
self.pattern=data[0];
alert(this.pattern['value']);
}
}
})