Vuejs2 issue passing data changes between children - vue.js

Another issue with a project I am working on.
I have the following vuejs2 parent-child structure
Parent app
child product-list
product
product-image
colour-select
Within the product template I initiate the sibling components
The colourSelect component takes a comma delimited string and turns it into a drop down list. Whenever the selected option changes it emits the colour back to the product component which has a data variable "colour"
This appears to work fine.
The product-image component takes the product colour as a prop.
Whenever the colour changes I want the product-image component to detect it and trigger it to go get the relevant image. But its not detecting the change in colour.
Vue.component('product', {
props: ['productID', 'images', 'product'],
data: function () {
return {
colour: 'Navy',
}
},
computed: {
returnColour: function (colour) {
// this.colour = colour
//return colour
}
},
template: '<transition name="list"><li class="moving-item" id="productID">' +
'<product-image :productID="productID" :images="getImage(product.productID)" :colour="colour"></product-image>' +
'<colourSelect :colours="product.colour" :productID="product.productID" v-on:set-colour="setColour(colour)"></colourSelect>' +
'</li></transition>',
methods: {
getImage: function (listItemId) {
var images = this.images.filter(function (item) {
return returnCleanedData(item.Products_x003a_ID) === listItemId
})
},
setColour: function (colour) {
console.log('in main colour emit')
this.colour = colour
console.log(this.colour)
}
}
});
Vue.component('colourSelect', {
props: ['productID', 'colours', 'set-colour'],
template: '<select v-bind:id="getID()" class="form-control input-xs" :disabled=" isActive" v-bind:class="{disabledSelect: isActive}" v-on:click="setColour(productID)">' +
'<colourOption v-for="colourItem in colourArray">{{ colourItem.colour }}</colourOption>' +
'</select>',
data: function() {
return {
isActive: false
}
},
computed: {
colourArray: function () {
//splits data and applies it to the select
}
},
methods: {
getID: function () {
return 'colourSelect' + this.productID;
},
**setColour: function (productID) {**
//yeah used jquery here
var colour = $('#colourSelect' + productID).val()
this.$emit('set-colour', colour)
}
}
});
Vue.component('product-image', {
props: ['productID', 'images', 'colour'],
template: '<p><slot></slot><img :src="getImage(productID, images, colourSelected)" class="productImagePosition img-responsive img-rounded"></img></p>',
data: function () {
return {
isActive: false,
selectedColour: this.colour
}
},
computed: {
colourSelected: function () {
console.log('colour change detected')
return this.colour
}
},
methods: {
getID: function (test) {
return 'colourSelect' + this.productID;
},
getImage: function (listItemId, images, colour) {
console.log('selected colour')
console.log(colour)
//filter images to the specific colour and return link
},
}
});
The issue appears to be related to this line in the product template
v-on:set-colour="setColour(colour)"
When the child component emits the set-colour data back, the product is correctly running this method. But the product-image doesn't detect the change to its prop.
If i change the line to v-on:set-colour="setColour()" it will actually detect the change in the product-image but will error due to no data being passed.
Within the product-image component I have tried referencing a computed value (colourSelected) instead of the prop within the template which has no effect.
Any ideas?
Thanks

On product-image add a watcher to the colour prop:
watch: {
colour(value) {
// make changes here
},
},

Related

How to use/evoke to set in computed through methods?

I am trying to update data property through computed property and found that it is impossible to set the value but I can use get/set to assign value in data property. Please see my example first.
data () {
return {
title: '',
color: null
}
},
computed: {
isTitle: {
get () {
return this.title
},
set () {
console.log('how can I come to this line?')
this.title = 'update title example'
}
},
isTitle() {
this.color = 'red'
return 'update title example'
}
},
mounted () {
this.getAccessToTitle()
},
methods: {
getAccessToTitle () {
if (isTitle) {
this.color = 'red'
}
},
example looks little bit weird but what I wanted to ask is..
when getAccessToTitle() is called through mounted, I assume, isTitle's set() should update the title in data property isn't it? I am not sure how can I use set in computed property when I call isTitle through methods but not template(I saw many examples that use template to call computed like https://vuejs.org/guide/essentials/computed.html#writable-computed but it is not what I am looking for!)
Thank you
this is what I wanted to do originally. update color in data and return title in isTitle. Tt works but was told that it is bad way to use computed so I added get/set
data () {
return {
title: '',
color: null
}
},
computed: {
isTitle() {
this.color = 'red' <---
return 'update title example' <---
}
},
mounted () {
this.getAccessToTitle()
},
methods: {
getAccessToTitle () {
if (isTitle) {
isColor(this.color)
}
},
isColor(val) {
// do something...
}

Prevent Vue Multiple Select to Store an Empty Array

I want this select multiple to pre-select one option, and not be able to deselect all options.
Whenever the last selected option is deselected it should be reselected. In other words when the user tries to deselect the last selected option it should visually not be deselected.
<template>
<b-select
if="Object.keys(doc).length !== 0 /* wait until firebase has loaded */"
:options="computedOptions"
v-model="model"
multiple
#input="onChange"
/>
</template>
<script>
//import Vue from 'vue'
import { fb } from "../fbconf";
export default {
name: "MyMultiSelect",
props: {
doc: Object, // firestore document
},
data() {
return {
options: []
};
},
firestore() {
var options = fb.db.collection("options");
return {
options: options
};
},
computed: {
computedOptions: function() {
return this.options.map(function(option) {
return {
text: option.name,
value: option.id
};
});
},
// to make sure mySelectedOptions is an array, before this.doc is loaded
// I use the following custom model
// because not using 'get' below causes a warning:
// [Vue warn]: <select multiple v-model="localValue"> expects an Array value for its binding, but got Undefined
model: {
get: function() {
if (!this.doc.hasOwnProperty('mySelectedOptions')) return []; // empty array before this.doc is loaded
else return this.doc['mySelectedOptions'];
},
set: function(newValue) {
// here I can prevent the empty array from being stored
// but visually the user can deselect all options, which is bad UX
//if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
}
},
},
methods: {
onChange: function(newValue){
// I can manually store the array as I want here
// but I cannot in any way prevent the user from deselecting all options
if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
else {
// none of these reselects the last selected option
var oldValue = this.doc['mySelectedOptions'];
this.doc['mySelectedOptions'] = this.doc['mySelectedOptions'];
//this.$forceUpdate();
//this.$emit("change", newValue);
//Vue.set(this.doc, 'mySelectedOptions', this.doc['mySelectedOptions']);
}
}
}
};
</script>
You could add watcher and when length becomes 0 just add previous value.
watch: {
model(val, oldVal) {
if(val.length == 0 && oldVal.length > 0) {
// take only one item in case there's clear button or etc.
this.model = [oldval[0]];
}
}
}

Vuejs: listen to props changes and use it

I am developing a project using Vue JS and I need to watch the props changes and call it inside a <span>.
I have used watch() and it shows that the props values are assigned.
But when I call it inside the <span> the value is not showing.
props: ['verifyText', 'verifyValue', 'profileId', 'logged', 'verifyType', 'status'],
watch: {
verifyText: function () { // watch it
this.verify_text = this.verifyText;
},
verifyValue: function () {
this.verify_value = this.verifyValue;
},
verifyType: function () {
this.verify_type = this.verifyType;
}
},
data() {
return {
verify_type: this.verifyType,
verify_text: this.verifyText,
verify_value: this.verifyValue,
}
},
//using inside span
<span>{{verify_text}}</span>
Receive and insert new data that changes from 'watch'
Try this.
props: ['verifyText', 'verifyValue', 'profileId', 'logged', 'verifyType', 'status'],
watch: {
verifyText: function (new_value) {
this.verify_text = new_value;
}
},
data() {
return {
verify_text: this.verifyText,
}
},
//using inside span
<span>{{verify_text}}</span>
I solved this issue by watching the verify_text in the parent component.
'verify_text': function (value) {
this.verify_text = value;
},
Same for the verify_type and verify_value
Thank you all for replying.

Vuejs2 - computed property in components

I have a component to display names. I need to calculate number of letters for each name.
I added nameLength as computed property but vuejs doesn't determine this property in loop.
var listing = Vue.extend({
template: '#users-template',
data: function () {
return {
query: '',
list: [],
user: '',
}
},
computed: {
computedList: function () {
var vm = this;
return this.list.filter(function (item) {
return item.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
})
},
nameLength: function () {
return this.length; //calculate length of current item
}
},
created: function () {
this.loadItems();
},
methods: {
loadItems: function () {
this.list = ['mike','arnold','tony']
},
}
});
http://jsfiddle.net/apokjqxx/22/
So result expected
mike-4
arnold-6
tony-4
it seems there is some misunderstanding about computed property.
I have created fork from you fiddle, it will work as you needed.
http://jsfiddle.net/6vhjq11v/5/
nameLength: function () {
return this.length; //calculate length of current item
}
in comment it shows that "calculate length of current item"
but js cant get the concept of current item
this.length
this will execute length on Vue component it self not on that value.
computed property work on other property of instance and return value.
but here you are not specifying anything to it and used this so it wont able to use any property.
if you need any more info please comment.

vue.js $watch array of objects

mounted: function() {
this.$watch('things', function(){console.log('a thing changed')}, true);
}
things is an array of objects [{foo:1}, {foo:2}]
$watch detects when an object is added or removed, but not when values on an object are changed. How can I do that?
You should pass an object instead of boolean as options, so:
mounted: function () {
this.$watch('things', function () {
console.log('a thing changed')
}, {deep:true})
}
Or you could set the watcher into the vue instance like this:
new Vue({
...
watch: {
things: {
handler: function (val, oldVal) {
console.log('a thing changed')
},
deep: true
}
},
...
})
[demo]
There is a more simple way to watch an Array's items without having deep-watch: using computed values
{
el: "#app",
data () {
return {
list: [{a: 0}],
calls: 0,
changes: 0,
}
},
computed: {
copy () { return this.list.slice() },
},
watch: {
copy (a, b) {
this.calls ++
if (a.length !== b.length) return this.onChange()
for (let i=0; i<a.length; i++) {
if (a[i] !== b[i]) return this.onChange()
}
}
},
methods: {
onChange () {
console.log('change')
this.changes ++
},
addItem () { this.list.push({a: 0}) },
incrItem (i) { this.list[i].a ++ },
removeItem(i) { this.list.splice(i, 1) }
}
}
https://jsfiddle.net/aurelienlt89/x2kca57e/15/
The idea is to build a computed value copy that has exactly what we want to check. Computed values are magic and only put watchers on the properties that were actually read (here, the items of list read in list.slice()). The checks in the copy watcher are actually almost useless (except weird corner cases maybe) because computed values are already extremely precise.
If someone needs to get an item that was changed inside the array, please, check it:
JSFiddle Example
The post example code:
new Vue({
...
watch: {
things: {
handler: function (val, oldVal) {
var vm = this;
val.filter( function( p, idx ) {
return Object.keys(p).some( function( prop ) {
var diff = p[prop] !== vm.clonethings[idx][prop];
if(diff) {
p.changed = true;
}
})
});
},
deep: true
}
},
...
})
You can watch each element in an array or dictionary for change independently with $watch('arr.0', () => {}) or $watch('dict.keyName', () => {})
from https://v2.vuejs.org/v2/api/#vm-watch:
Note: when mutating (rather than replacing) an Object or an Array, the
old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
However, you can iterate the dict/array and $watch each item independently. ie. $watch('foo.bar') - this watches changes in the property 'bar' of the object 'foo'.
In this example, we watch all items in arr_of_numbers, also 'foo' properties of all items in arr_of_objects:
mounted() {
this.arr_of_numbers.forEach( (index, val) => {
this.$watch(['arr_of_numbers', index].join('.'), (newVal, oldVal) => {
console.info("arr_of_numbers", newVal, oldVal);
});
});
for (let index in this.arr_of_objects) {
this.$watch(['arr_of_objects', index, 'foo'].join('.'), (newVal, oldVal) => {
console.info("arr_of_objects", this.arr_of_objects[index], newVal, oldVal);
});
}
},
data() {
return {
arr_of_numbers: [0, 1, 2, 3],
arr_of_objects: [{foo: 'foo'}, {foo:'bar'}]
}
}
If your intention is to render and array and watch for changes on rendered items, you can do this:
Create new Component:
const template = `<div hidden></div>`
export default {
template,
props: ['onChangeOf'],
emits: ['do'],
watch: {
onChangeOf: {
handler(changedItem) {
console.log('works')
this.$emit('do', changedItem)
},
deep: true
}
},
}
Register that component:
Vue.component('watcher', watcher)
Use it inside of your foreach rendering:
<tr v-for="food in $store.foods" :key="food.id">
<watcher :onChangeOf="food" #do="(change) => food.name = 'It works!!!'"></watcher>
</tr>