Vue.js doesnt detect that i swap 2 array elements in my data object:
data: {
list: [
'Foo',
'Bar',
'Test'
]
}
Method to swap entries:
swapIndex: function(from, to) {
var first = this.list[from];
this.list[from] = this.list[to];
this.list[to] = first;
}
JsFiddle https://jsfiddle.net/aaroniker/r11hxce8/
I want re-render the v-for loop if i swap the indexies.
Thanks!
here is the solution I came with. I created a copy of your list to modify it, and I invoked the this.$set() method on the list:
new Vue({
el: '#app',
data: {
list: [
'Foo',
'Bar',
'Test'
]
},
methods: {
swapIndex: function(from, to) {
var copy = JSON.parse(JSON.stringify(this.list))
var first = copy[from];
copy[from] = copy[to];
copy[to] = first;
this.$set(this,'list',copy)
console.log(this.list);
}
}
})
Changing value in an array with the [] operator won't let vue detect the change, replacing the array is one way to solve this, or you can use the slightly better solution of arr.splice(index, 1, newVal) suggested by the guide.
Related
I am trying to make a simple to do app using vue.js, I want to try and save my to-dos that are set in the array so that when I reset the site, they still remain. Looking through some of the documentation I arrived at this:
data() {
return {
array: [
{id: 1, label: 'learn vuejs'},
]
}
},
methods: {
persist() {
localStorage.array = this.array;
alert('items saved')
}
},
mounted() {
if (localStorage.array && localStorage.array.id) {
this.array = localStorage.array;
this.array[id] = localStorage.array.id;
}
},
while this does save my array to localStorage, IT DOES NOT THE OBJECTS WITHIN. When I check localStorage in the console it shows :
array: "[object Object]"
anyone knows how to save the items within the array? if you do please explain it to me.
You need to store them as string. So localStorage.array = JSON.stringify(this.array), and when fetching from localStorage this.array = JSON.parse(localStorage.array);
I have basically this structure for my data (this.terms):
{
name: 'First Category',
posts: [
{
name: 'Jim James',
tags: [
'nice', 'friendly'
]
},
{
name: 'Bob Ross',
tags: [
'nice', 'talkative'
]
}
]
},
{
name: 'Second Category',
posts: [
{
name: 'Snake Pliskin',
tags: [
'mean', 'hungry'
]
},
{
name: 'Hugo Weaving',
tags: [
'mean', 'angry'
]
}
]
}
I then output computed results so people can filter this.terms by tags.
computed: {
filteredTerms: function() {
let self = this;
let terms = this.terms; // copy original data to new var
if(this.search.tags) {
return terms.filter((term) => {
let updated_term = {}; // copy term to new empty object: This doesn't actually help or fix the problem, but I left it here to show what I've tried.
updated_term = term;
let updated_posts = term.posts.filter((post) => {
if (post.tags.includes(self.search.tags)) {
return post;
}
});
if (updated_posts.length) {
updated_term.posts = updated_posts; // now this.terms is changed even though I'm filtering a copy of it
return updated_term;
}
});
} else {
return this.terms; // should return the original, unmanipulated data
}
}
},
filteredTerms() returns categories with only the matching posts inside it. So a search for "angry" returns just "Second Category" with just "Hugo Weaving" listed.
The problem is, running the computed function changes Second Category in this.terms instead of just in the copy of it (terms) in that function. It no longer contains Snake Pliskin. I've narrowed it down to updated_term.posts = updated_posts. That line seems to also change this.terms. The only thing that I can do is reset the entire data object and start over. This is less than ideal, because it would be loading stuff all the time. I need this.terms to load initially, and remain untouched so I can revert to it after someone clears their search criterea.
I've tried using lodash versions of filter and includes (though I didn't really expect that to make a difference). I've tried using a more complicated way with for loops and .push() instead of filters.
What am I missing? Thanks for taking the time to look at this.
Try to clone the object not to reference it, you should do something like :
let terms = [];
Object.assign(terms,this.terms);
let terms = this.terms;
This does not copy an array, it just holds a reference to this.terms. The reason is because JS objects and arrays are reference types. This is a helpful video: https://www.youtube.com/watch?v=9ooYYRLdg_g
Anyways, copy the array using this.terms.slice(). If it's an object, you can use {...this.terms}.
I updated my compute function with this:
let terms = [];
for (let i = 0; i < this.terms.length; i++) {
const term = this.copyObj(this.terms[i]);
terms.push(term);
}
and made a method (this.copyObj()) so I can use it elsewhere. It looks like this:
copyObj: function (src) {
return Object.assign({}, src);
}
In Vue.js, I have a data object with dynamically added/edited properties that are themselves arrays. For example, the property starts out as follows:
data: function () {
return {
vals: {}
};
}
And over time, through various button clicks, etc., vals may look like the following (with the actual property names and values being 100% dynamic based on a number of factors):
vals: {
set1: [
{
prop1: 123,
prop2: 'hello'
},
{
prop1: 456,
prop2: 'bye'
}
],
set2: [
{
prop3: 'Why?!',
prop4: false
}
]
}
As the array properties (i.e., set1 and set2) are changed, I want to be able to react to those changes.
For example, I may do something like the following in my code:
var prop = 'set1';
this.vals[prop].push({
{
prop1: 789,
prop2: 'hmmm...'
}
});
However, when I do that, the component is not updating (I presume because I am pushing an object onto the end of a subarray of an object; and Vue.js doesn't seem to track those changes).
I have been able to force the component to be reactive by doing this.$forceUpdate(); after the above push, but I imagine there has to be a more eloquent way of getting Vue.js to be reactive when it comes to objects being pushed onto the end of object subarrays.
Does anyone know of a better way to try to do what I am trying to achieve? Thank you.
Any time you're adding a new property to an object or changing a value within an array, you need to use Vue.set().
Vue.set(this.vals, prop, [ /* ... */ ])
Ideally, you should define all your properties up front so Vue doesn't have to invalidate computed properties depending on your data model's shape. Even if you have them set to null you should try to map out all the properties you expect your component to need.
Also, in your first code block, you have a colon after your return: which would evaluate to a label, meaning your data function isn't returning anything.
You could try something like this using lodash and a deep watcher..
More on deep watcher here
new Vue({
el: "#app",
methods: {
setValue() {
this.oldPeople = _.cloneDeep(this.people);
}
},
mounted() {
this.setValue();
},
el: "#app",
data: {
changed: null,
people: [
{ id: 0, name: "Bob", age: 27 },
{ id: 1, name: "Frank", age: 32 },
{ id: 2, name: "Joe", age: 38 }
],
oldPeople: []
},
watch: {
people: {
deep: true,
handler(after, before) {
// Return the object that changed
let vm = this;
let changed = after.filter(function(p, idx) {
return Object.keys(p).some(function(prop) {
return p[prop] !== vm.oldPeople[idx][prop];
});
});
vm.setValue();
this.changed = changed;
},
}
}
});
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js"></script>
<div id="app">
<div>
<input type="text" v-for="(person, index) in people" v-model="people[index].age" />
<div v-if="changed !== null">
You changed:<br/>{{ changed }}
</div>
</div>
</div>
I got a vue object like this:
var vm = new Vue({
el: '#app',
data: {
items: [],
index: 0
},
});
Inside items array i will push items like:
item1 = {
a: 1,
b: 'type',
c: '3.556'
}
...
itemN = {
a: n,
b: 'type',
c: '5.226'
}
then i will update one of the item's "c" property and i would like to set up a watcher that warn me as soon as one of this property changes.
EDIT: i also want to know witch item has changed
You can use deep watch, but... it does not provide ease way to determine which item has changed.
...
watch: {
items: {
handler: function (val, oldVal) {
},
deep: true
}
}
...
One of possible workarounds is mentioned in this answer,
idea behind this solution is to wrap each item in component and listen to event from component.
You can also store cloned items array and update that clone in watch handler, you can use that clone to filter item that has changed.
Have to avoid any mutations in state object. Can you advice, how to change row text by rowId using Immutable.Map()?
setIn function doesn't seems to be working with Map in this case?
var state = {
pages: [
{
tiles: [
{
rows: [
{rowId: 'row1', text: 'text1'},
{rowId: 'row2', text: 'text2'},
{rowId: 'row3', text: 'text3'}
//...
]
},
{
//...
}
//...
]
}
//...
]
};
function onRowUpdateAction(state, rowId, rowText) {
var map = Immutable.Map(state);
//TODO: Update row text by row id ?
return map.toObject();
}
var newState = onRowUpdateAction(state, 'row2', 'updatedText');
Thanks in advance!
setIn plays nicely with both Maps and Lists. From what you wrote the problem seems to be, that you are not actually dealing with deeply immutable structure. Note, that creating immutable.Map such as
immutable.Map({a: {b: 'c'}})
creates immutable Map with key 'a' and value {b: 'c'} (as a plain JS object)! No wonder, that immutable's getIn does not work with it.
To make structure deeply immutable, use immutable.fromJS (this is probably what you intended to use).