I am new to VUE and for one project I am trying to update an array passing the object. I have two button which is called BUTTON 1 and BUTTON 2. If you click on BUTTON 1 then it will set an object to list[] using this.$set. But when BUTTON 2 is pressed it should update with the new value and should display as
{
a:1,
b:2,
c:3
}
Right now I am using this.$set for button 2 as well and it removes the previous value then adds the new one as {c:3} only. Is there a way to add on value using VUE to display { a:1,b:2,c:3} when BUTTON 2 is clicked.
View
<div id="app">
<button #click="button1()">Button 1</button>
<button #click="button2()">Button 2</button>
</div>
Method
new Vue({
el: "#app",
data: {
list:[]
},
methods: {
button1(){
var b = '0';
this.$set(this.list, b, {
1:{
a: '1',
b: '2'
}
})
console.log(this.list);
},
button2(){
var b = '0';
this.$set(this.list, b,{
1:{
c: '3'
}
})
console.log(this.list);
},
}
})
Below there is a jsfiddle link for my code
https://jsfiddle.net/ujjumaki/enj3dwo6/19/
Hope this works.
new Vue({
el: "#app",
data: {
list:[]
},
methods: {
button1(){
console.log('button 1');
const b = '0';
const restObj=this.list[b] && this.list[b][1]
this.$set(this.list, b, {
1:{
...restObj,
a: '1',
b: '2'
}
})
console.log(this.list);
},
button2(){
const b = '0';
const restObj=this.list[b] && this.list[b][1]
this.$set(this.list, b,{
1:{
...restObj,
c: '3'
}
})
console.log(this.list);
},
}
})
var vue_app = new Vue({
el: '#id1',
data: {
v1:[],
},
methods:{
pushUnique: function() {
this.v1.push({'id':1,'name':'josh'});
this.v1.push({'id':1,'name':'josh'}); //this should not work.
},
},
});
In above code the second push should not execute. I would like to keep id unique. How can this be done in Vue.
THanks
I would move to storing data in an object (keyed by id) and use a computed property to produce your v1 array. For example
data: {
v1obj: {}
},
computed: {
v1 () {
return Object.keys(this.v1obj).map(id => ({ id, name: this.v1obj[id] }))
}
}
Then you can use methods like Object.prototype.hasOwnProperty() to check for existing keys...
methods: {
pushUnique () {
let id = 1
let name = 'josh'
if (!this.v1obj.hasOwnProperty(id)) {
this.v1obj[id] = name
}
}
}
I have two objects in my root - obj and newObj. I watch changes on my obj object with deep: true, and on changes I update newObj accordingly.
In my vue debugger, the newObj seems updated as expected, however the component doesn't perform the for loop count. Or if I try to {{ newObj }}, it logs only the first update.
I tried to re-create the issue on this Fiddle.
my html:
<div id="app">
<button #click="appendTo">Append</button>
<my-comp v-bind:new-obj="newObj"></my-comp>
</div>
and vue:
new Vue({
el: '#app',
data: {
obj: {},
newObj: {}
},
methods: {
appendTo() {
if (typeof this.obj[1] === 'undefined') {
this.$set(this.obj, 1, {})
}
var randLetter = String.fromCharCode(Math.floor(Math.random() * (122 - 97)) + 97);
this.$set(this.obj[1], randLetter, [ [] ])
}
},
watch: {
obj: {
handler(obj) {
var oldKeys = Object.keys(obj)
var newKeys = Object.keys(this.newObj);
var removedIndex = newKeys.filter(x => oldKeys.indexOf(x) < 0 );
for (var i = 0, len = removedIndex.length; i < len; i++) {
delete this.newObj[removedIndex[i]]
}
oldKeys.map((key) => {
if (this.newObj.hasOwnProperty(key)) {
var newInnerKeys = Object.keys(this.newObj[key]);
var oldInnerKeys = Object.keys(obj[key]);
var additions = oldInnerKeys.filter(x => newInnerKeys.indexOf(x) < 0);
for (var i = 0, len = additions.length; i < len; i++) {
// here
this.$set(this.newObj[key], additions[i], [ [] ]);
}
var deletions = newInnerKeys.filter(x => oldInnerKeys.indexOf(x) < 0);
for (var i = 0, len = deletions.length; i < len; i++) {
delete this.newObj[key][deletions[i]]
}
} else {
this.newObj[key] = {}
for (var innerKey in obj[key]) {
this.$set(this.newObj, key, {
[innerKey]: [ [] ]
});
}
}
console.log(obj);
console.log(this.newObj)
});
},
deep: true
}
}
})
Vue.component('my-comp', {
props: ['newObj'],
template: `
<div>
<div v-for="item in newObj">
test
</div>
</div>
`
})
Your data newObj has a getter and setter defined by vue. When the setter is called, the UI is re-rendered. The setter is triggered when you change the reference of newObj, not when you change its value. I mean:
this.newObj = {} // triggered
this.newObj['key'] = 'value' // not triggered
You can add a deep watcher on the property this.newObj. Or change its reference with a trick:
this.newObj = Object.assign({}, this.newObjec);
which create a copy of the object newObject.
Here is the fiddle updated.
https://jsfiddle.net/749nc5d2/
I'm using a Vue.js computed property but am running into an issue: The computed method IS being called at the correct times, but the value returned by the computed method is being ignored!
My method
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
}
The values printed out by the console.log statement are correct, but when I use filteredClasses in template, it just uses the first cached value and never updates the template. This is confirmed by Vue chrome devtools (filteredClasses never changes after the initial caching).
Could anyone give me some info as to why this is happening?
Project.vue
<template>
<div>
<div class="card light-blue white-text">
<div class="card-content row">
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.name" id="filter-name">
<label for="filter-name">Name</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.status" id="filter-status">
<label for="filter-status">Status (PASS or FAIL)</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.apkVersion" id="filter-apkVersion">
<label for="filter-apkVersion">APK Version</label>
</div>
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.executionStatus" id="filter-executionStatus">
<label for="filter-executionStatus">Execution Status (RUNNING, QUEUED, or IDLE)</label>
</div>
</div>
</div>
<div v-for="(klass, classIndex) in filteredClasses">
<ClassView :klass-raw="klass"/>
</div>
</div>
</template>
<script>
import ClassView from "./ClassView.vue"
export default {
name: "ProjectView",
props: {
projectId: {
type: String,
default() {
return this.$route.params.id
}
}
},
data() {
return {
project: {},
filter: {
name: "",
status: "",
apkVersion: "",
executionStatus: ""
}
}
},
async created() {
// Get initial data
const res = await this.$lokka.query(`{
project(id: "${this.projectId}") {
name
classes {
name
methods {
id
name
reports
executionStatus
}
}
}
}`)
// Augment this data with latestReport and expanded
const reportPromises = []
const reportMeta = []
for(let i = 0; i < res.project.classes.length; ++i) {
const klass = res.project.classes[i];
for(let j = 0; j < klass.methods.length; ++j) {
res.project.classes[i].methods[j].expanded = false
const meth = klass.methods[j]
if(meth.reports && meth.reports.length) {
reportPromises.push(
this.$lokka.query(`{
report(id: "${meth.reports[meth.reports.length-1]}") {
id
status
apkVersion
steps {
status platform message time
}
}
}`)
.then(res => res.report)
)
reportMeta.push({
classIndex: i,
methodIndex: j
})
}
}
}
// Send all report requests in parallel
const reports = await Promise.all(reportPromises)
for(let i = 0; i < reports.length; ++i) {
const {classIndex, methodIndex} = reportMeta[i]
res.project.classes[classIndex]
.methods[methodIndex]
.latestReport = reports[i]
}
this.project = res.project
// Establish WebSocket connection and set up event handlers
this.registerExecutorSocket()
},
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
},
methods: {
isFiltered(method, klass) {
const nameFilter = this.testFilter(
this.filter.name,
klass.name + "." + method.name
)
const statusFilter = this.testFilter(
this.filter.status,
method.latestReport && method.latestReport.status
)
const apkVersionFilter = this.testFilter(
this.filter.apkVersion,
method.latestReport && method.latestReport.apkVersion
)
const executionStatusFilter = this.testFilter(
this.filter.executionStatus,
method.executionStatus
)
return nameFilter && statusFilter && apkVersionFilter && executionStatusFilter
},
testFilter(filter, item) {
item = item || ""
let outerRet = !filter ||
// Split on '&' operator
filter.toLowerCase().split("&").map(x => x.trim()).map(seg =>
// Split on '|' operator
seg.split("|").map(x => x.trim()).map(segment => {
let quoted = false, postOp = x => x
// Check for negation
if(segment.indexOf("!") === 0) {
if(segment.length > 1) {
segment = segment.slice(1, segment.length)
postOp = x => !x
}
}
// Check for quoted
if(segment.indexOf("'") === 0 || segment.indexOf("\"") === 0) {
if(segment[segment.length-1] === segment[0]) {
segment = segment.slice(1, segment.length-1)
quoted = true
}
}
if(!quoted || segment !== "") {
//console.log(`Item: ${item}, Segment: ${segment}`)
//console.log(`Result: ${item.toLowerCase().includes(segment)}`)
//console.log(`Result': ${postOp(item.toLowerCase().includes(segment))}`)
}
let innerRet = quoted && segment === "" ?
postOp(!item) :
postOp(item.toLowerCase().includes(segment))
//console.log(`InnerRet(${filter}, ${item}): ${innerRet}`)
return innerRet
}).reduce((x, y) => x || y, false)
).reduce((x, y) => x && y, true)
//console.log(`OuterRet(${filter}, ${item}): ${outerRet}`)
return outerRet
},
execute(methID, klassI, methI) {
this.project.classes[klassI].methods[methI].executionStatus = "QUEUED"
// Make HTTP request to execute method
this.$http.post("/api/Method/" + methID + "/Execute")
.then(response => {
}, error =>
console.log("Couldn't execute Test: " + JSON.stringify(error))
)
},
registerExecutorSocket() {
const socket = new WebSocket("ws://localhost:4567/api/Executor/")
socket.onmessage = msg => {
const {methodID, report, executionStatus} = JSON.parse(msg.data)
for(let i = 0; i < this.project.classes.length; ++i) {
const klass = this.project.classes[i]
for(let j = 0; j < klass.methods.length; ++j) {
const meth = klass.methods[j]
if(meth.id === methodID) {
if(report)
this.project.classes[i].methods[j].latestReport = report
if(executionStatus)
this.project.classes[i].methods[j].executionStatus = executionStatus
return
}
}
}
}
},
prettyName: function(name) {
const split = name.split(".")
return split[split.length-1]
}
},
components: {
"ClassView": ClassView
}
}
</script>
<style scoped>
</style>
If your intention is for the computed property to update when project.classes.someSubProperty changes, that sub-property has to exist when the computed property is defined. Vue cannot detect property addition or deletion, only changes to existing properties.
This has bitten me when using a Vuex store with en empty state object. My subsequent changes to the state would not result in computed properties that depend on it being re-evaluated. Adding explicit keys with null values to the Veux state solved that problem.
I'm not sure whether explicit keys are feasible in your case but it might help explain why the computed property goes stale.
Vue reactiviy docs, for more info:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
I've ran into similar issue before and solved it by using a regular method instead of computed property. Just move everything into a method and return your ret.
Official docs.
I had this issue when the value was undefined, then computed cannot detect it changing. I fixed it by giving it an empty initial value.
according to the Vue documentation
I have a workaround for this kind of situations I don't know if you like it. I place an integer property under data() (let's call it trigger) and every time the object that I used in computed property changes, it gets incremented by 1. So, this way, computed property updates every time the object changes.
Example:
export default {
data() {
return {
trigger: 0, // this will increment by 1 every time obj changes
obj: { x: 1 }, // the object used in computed property
};
},
computed: {
objComputed() {
// do anything with this.trigger. I'll log it to the console, just to be using it
console.log(this.trigger);
// thanks to this.trigger being used above, this line will work
return this.obj.y;
},
},
methods: {
updateObj() {
this.trigger += 1;
this.obj.y = true;
},
},
};
here's working a link
If you add console.log before returning, you may be able to see computed value in filteredClasses.
But DOM will not updated for some reason.
Then you need to force to re-render DOM.
The best way to re-render is just adding key as computed value like below.
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
Caution:
Don’t use non-primitive values like objects and arrays as keys. Use string or numeric values instead.
That is why I converted array filteredClasses to string. (There can be other array->string convert methods)
And I also want to say that "It is recommended to provide a key attribute with v-for whenever possible".
You need to assign a unique key value to the list items in the v-for. Like so..
<ClassView :klass-raw="klass" :key="klass.id"/>
Otherwise, Vue doesn't know which items to udpate. Explanation here https://v2.vuejs.org/v2/guide/list.html#key
I have the same problem because the object is not reactivity cause I change the array by this way: arrayA[0] = value. The arrayA changed but the computed value that calculate from arrayA not trigger. Instead of assign value to the arrayA[0], you need to use $set for example.
You can dive deeper by reading the link below
https://v2.vuejs.org/v2/guide/reactivity.html
I also use some trick like adding a cache = false in computed
compouted: {
data1: {
get: () => {
return data.arrayA[0]
},
cache: false
}
}
For anybody else being stuck with this on Vue3, I just resolved it and was able to get rid of all the this.$forceUpdate()-s that I had needed before by wrapping the values I returned from the setup() function [and needed to be reactive] in a reference using the provided ref() function like this:
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'CardDisplay',
props: {
items: {
type: Array,
default: () => []
},
itemComponent: Object,
maxItemWidth: { type: Number, default: 200 },
itemRatio: { type: Number, default: 1.25 },
gapSize: { type: Number, default: 50 },
maxYCount: { type: Number, default: Infinity }
},
setup () {
return {
containerSize: ref({ width: 0, height: 0 }),
count: ref({ x: 0, y: 0 }),
scale: ref(0),
prevScrollTimestamp: 0,
scroll: ref(0),
isTouched: ref(false),
touchStartX: ref(0),
touchCurrentX: ref(0)
}
},
computed: {
touchDeltaX (): number {
return this.touchCurrentX - this.touchStartX
}
},
...
}
After doing this every change to a wrapped value is reflected immediately!
If you are adding properties to your returned object after vue has registered the object for reactivity then it won't know to listen to those new properties when they change. Here's a similar problem:
let classes = [
{
my_prop: 'hello'
},
{
my_prop: 'hello again'
},
]
If I load up this array into my vue instance, vue will add those properties to its reactivity system and be able to listen to them for changes. However, if I add new properties from within my computed function:
computed: {
computed_classes: {
classes.map( entry => entry.new_prop = some_value )
}
}
Any changes to new_prop won't cause vue to recompute the property, as we never actually added classes.new_prop to vues reactivity system.
To answer your question, you'll need to construct your objects with all reactive properties present before passing them to vue - even if they are simply null. Anyone struggling with vues reactivity system really should read this link: https://v2.vuejs.org/v2/guide/reactivity.html
I have an list like this one
<div class="list-group entity-list">
<a v-for="entity in master.data"
:class="['entity list-group-item', entity.selected ? 'selected' : '']"
#click="itemselect(entity)"
>
#{{entity.name}}
</a>
</div>
The list is displayed as expected. The Bound entity is added as an prop to an outer compontent like this.
<my-compontent :entityname="customers" :master="{{json_encode($customers->toArray())}}" inline-template>
As you can see it should be a list of customers. To indicate if a customer is selected i want to set an "selected" attribute on click. However it does not seem to work if the default bound object does not have a select property.
methods: {
itemselect: function(item) {
this.master.data.forEach(function(el) {
el.selected = false;
});
item.selected = true;
}
}
The shown method does not seem to work / trigger a change
If you want to modify data passed in by a prop then you need to copy it, vue will throw out a warning if you try to directly modify a prop. I would probably handle this by doing something like:
new Vue({
el: '#app',
created() {
// Assume this is passed in by a prop
var myData = {master: {
data: [{
name: 'foo'
}, {
name: 'bar'
}, {
name: 'baz'
}]
}};
// Copy data from prop
myData.master.data.forEach(el => {
var obj = {
name: el.name,
selected: false
};
this.master.data.push(obj);
});
},
methods: {
itemselect(item) {
this.master.data.forEach(el => {
// if this element is selected set to true
el['selected'] = (el.name == item.name);
});
}
},
data() {
return {
master: {
data: []
}
}
}
});
I just mocked the actual data coming in, but obviously this would add this to a prop and pass it to your component as usual. Here's the jsFiddle: https://jsfiddle.net/xswtfkaf/
Instead of altering your master data, track selection in model, using $index assuming you don't reorder your master.data
<div class="list-group entity-list">
<a v-for="entity in master.data"
:class="eClass[$index]"
#click="itemselect($index)"
>
#{{entity.name}}
</a>
</div>
methods: {
itemselect: function(idx) {
if ( 0 > this.selected.indexOf(idx) ) {
this.selected.push(idx)
}
}
}
computed : {
eClass : function () {
var r=[];
for (var i= 0;c=this.master.data[i];++i) {
r[i] = {'selected': -1 < this.selected.indexOf(i),'entity list-group-item':true};
}
return r;
}
}
if entity has am unique id
<div class="list-group entity-list">
<a v-for="entity in master.data"
:class="eClass[entity.id]"
#click="itemselect(entity.id)"
>
#{{entity.name}}
</a>
</div>
methods: {
itemselect: function(idx) {
if ( 0 > this.selected.indexOf(idx) ) {
this.selected.push(idx)
}
}
}
computed : {
eClass : function () {
var r={};
for (var i= 0;c=this.master.data[i];++i) {
r[c.id] = {'selected': -1 < this.selected.indexOf(c.id),'entity list-group-item':true};
}
return r;
}
}