How to watch only one object in an array? - javascript

I have an array:
basicForm.schema = [
{},
{} // I want to watch only this
]
I tried doing this:
‘basicForm.schema[1].value’: {
handler (schema) {
const plan = schema.find(field => {
return field.name === ‘plan’
})
},
deep: true
},
But I got this error:
vue.js?3de6:573 [Vue warn]: Failed watching path:
“basicForm.schema[1]” Watcher only accepts simple dot-delimited paths.
For full control, use a function instead.
What's the correct way of doing this?

You can watch a computed property instead:
new Vue({
el: '#app',
data: {
basicForm: {
schema: [
{a: 1},{b: 2} // I want to watch only this
]
}
},
computed: {
bToWatch: function() {
return this.basicForm.schema[1].b
}
},
methods: {
incB: function() {
this.basicForm.schema[1].b++
}
},
watch: {
bToWatch: function(newVal, oldVal) {
console.log(newVal)
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<button #click="incB()">Inc</button>
</div>

You should use a function as the warning message suggests. You need to do so via vm.$watch.
new Vue({
el: '#app',
data: {
items: [
{ name: 'bob' },
{ name: 'fred' },
{ name: 'sue' },
],
},
created() {
this.$watch(() => this.items[1].name, this.onNameChanged);
},
methods: {
changeValue() {
this.items[1].name = 'rose';
},
onNameChanged(name) {
alert('name changed to ' + name);
},
},
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<button #click="changeValue">Click me</button>
</div>
You should probably check that this.items[1] exists before accessing it inside the watch function otherwise you'll get an error.

Related

Why Vue can't watch the data change?

i want the function:createComparation work when tableRowName change,but acturely when tableRowName change , it didnt works,vue follows;
createComparation is another function which didnt define by vue but only by javascript
const selectedRight =Vue.createApp({
data(){
return {
tableRow:0,
tableRowName:[],
stockName:[],
rectWidth:40,
rectHeight:5,
}
},
watch: {
tableRowName(newtable,oldtable){
console.log(1)
createComparation()
},
immediate:true,
stockName(){
changeTip()
},
},
methods:{
}
}).mount('#selectedRight')
in case of tableRowName contain objects then you have to use
deep:true
watch: {
tableRowName(newtable,oldtable){
console.log(1)
createComparation()
},
immediate:true,
deep: true,
stockName(){
changeTip()
},
},
but i think you are updating the array without reactive manner, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g.
vm.items.length = newLength
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // is NOT reactive
vm.items.length = 2 // is NOT reactive
I guess whatching array might be the issue. You can try this:
computed: {
rowNames() {
return this.tableRowName;
// if the line above doesn't work:
return this.tableRowName.join();
}
},
watch: {
rowNames(newtable,oldtable){
createComparation()
},
I think this is what you're looking for. You need to define the handler as an object for the property you're trying to watch and set immediate: true.
Vue.config.productionTip = false
Vue.config.devtools = false
new Vue({
el: "#app",
data() {
return {
tableRow: 0,
tableRowName: [],
stockName: [],
rectWidth: 40,
rectHeight: 5,
}
},
watch: {
tableRowName: {
handler(newtable) {
console.log('Calling createComparation function');
console.info(newtable);
},
immediate: true
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="tableRowName.push(Math.random())">Trigger change manually again</button>
</div>
The watch method definition is wrong. When you need to use immediate, you have to put you function body into handler property.
For example,
watch: {
tableRowName: {
handler: function() {
},
immediate: true
}
},

Vue data variable to methods and then to provide

I have issue to get some values from methods and want to parse to provide.
How I can solve the problem?
methods: {
onClickCategory: (value) => {
return (this.catId = value);
},
},
provide() {
return {
categoryId: this.value,
};
},
I get always categoryId:undefined
I found solution:
methods: {
onClickCategory(value) {
this.categoryId.value = value;
},
},
provide() {
this.catID = this.categoryId;
return {
catId: this.catID,
};
},
As Vue Guide highlights,
Note: the provide and inject bindings are NOT reactive. This is
intentional. However, if you pass down an observed object, properties
on that object do remain reactive.
So one solution is wrap your value into one observed object, like test2.value in below example:
Vue.config.productionTip = false
Vue.component('v-parent', {template: `
<div>
<h4>Example</h4>
<p>Not Working: <input v-model="test1"></p>
<p>Working: <input v-model="test2.value"></p>
<v-child></v-child>
</div>
`,
data () {
return {
test1: 'blabla1',
test2: {value: 'blabla2'}
}
},
provide () {
return {parent1: this.test1, parent2: this.test2}
}
}),
Vue.component('v-child', {
template: `<div><pre>{{parent1}}</pre><pre>{{parent2.value}}</pre></div>`,
inject: ['parent1', 'parent2']
})
new Vue({
el: '#app',
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<v-parent/>
</div>
</div>

Data object inside the event function doesn't work and resulted to undefined

I'm having an undefined value when I put my object data inside the event.
Here are my codes:
data(){
return {
eventSources: [],
myId: 1
}
},
methods:{
myMethod(){
this.eventSources = [{
events(start,end,timezone,callback){
alert(this.myId);
axios.get(`/Something?id=${this.myId}`).then(response=>{
callback(response.data);
}).catch(error=>{console.log(error);});
}
}]
}
}
My alert is resulted to undefined but when I put my alert above the this.eventSources = [{...]] the alert has a value of 1
I hope somebody helps me.
The problem is this inside events() is not actually your Vue instance. You can fix the context by declaring events as an arrow-function:
this.eventSources = [{
events: (start,end,timezone,callback) => {
alert(this.myId);
}
}]
new Vue({
el: '#app',
data() {
return {
eventSources: [],
myId: 1
};
},
methods: {
myMethod() {
this.eventSources = [{
events: (start,end,timezone,callback) => {
alert(this.myId);
}
}]
this.eventSources[0].events(0, 1, 'UTC', data => console.log(data))
}
}
})
<script src="https://unpkg.com/vue#2.5.16"></script>
<div id="app">
<button #click="myMethod">Click</button>
</div>

Vue.js .$set is not a function

I'm trying to modify the data that stored in vuejs by using $set function. But I got this error: TypeError: app.messageBox.$set is not a function.
Here is the code about how I define app and messageBox:
app = new Vue({
el: '#app',
data: {
messageBox: [{
id: 1,
message: 'first'
}, {
id: 2,
message: 'second'
}]
}
});
and in another js file, I try to modify the data in messageBox:
function test() {
app.messageBox.$set(0, {message: 'aaa'});
}
The correct syntax is Vue.set(target, index, value).
When used inside component code or single-file-components, you could use the equivalent alias: this.$set(target, index, value):
Vue.set(app.messageBox, 0, { message: 'outside component' });
// or when you don't access to `Vue`:
app.$set(app.messageBox, 0, { message: 'outside component' });
// or when `this` is the Vue instance:
this.$set(this.messageBox, 0, { message: 'inside component' })
const app = new Vue({
el: '#app',
data() {
return {
messageBox: [{
id: 1,
message: 'first'
}, {
id: 2,
message: 'second'
}]
};
},
mounted() {
setTimeout(() => {
this.$set(this.messageBox, 0, { message: 'inside component' })
}, 1000)
}
});
setTimeout(() => {
Vue.set(app.messageBox, 0, { message: 'outside component' });
}, 2000);
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app">
<p v-for="msgBox of messageBox">{{msgBox.message}}</p>
</div>
This example is for update the qty of product in the array car:
const indice = this.car.findIndex((pr) => pr.id === product.id);
if(indice===-1){
product.qty = 1
this.car.push(product)
}else{
//Vue no detectara cambios en el array si se actualiza por indice https://stackoverflow.com/a/59289650/16988223
//this.car[indice].qty++
const productUpdated = product
productUpdated.qty++
this.$set(this.car, indice, productUpdated)
}

How to get a dynamically generated VueJS component to render

I am trying to dynamically add components to my vue template. Things have not gone as expected.
I have this component:
Vue.component('form-x-text', {
delimiters: ["[[", "]]"],
template: `
<div v-if="_.indexOf(['text','email','tel','number','url','color'], fType) > 0" class="form-group" :class="{'has-error': errors.has(fName)}">
<label :for="fId">[[fLabel]]</label>
<input v-validate="{ rules: parsedRule }" class="form-control" :type="fType" :name="fName" :id="fId" data-vv-delay="700">
<span class="field-error" v-show="errors.has(fName)"> [[ errors.first(fName) ]]</span>
</div>
`,
props: {
fType: {
type: String,
validation: function(value){
return _.indexOf(['text','email','tel','number','url','color'], value) > 0
},
required: true
},
fName: {
type: String,
required: true
},
fId: null,
fLabel: null,
fRule: null
},
computed: {
parsedRule: function(){
console.log(this.fRule);
return JSON.parse(this.fRule);
}
}
})
class FormX{
constructor(){
}
static newForm(){
return JSON.parse(`
{
"node": "root",
"child": []
}
`)
}
static textInput(ftype, name, label, id, required = false){
let emailR = false;
if (ftype == 'email'){
emailR = true;
}
return JSON.parse(
`
{
"node": "element",
"tag": "form-x-text",
"attr": {
"f-type": "${ftype}",
"f-name": "${name}",
"f-label": "${label}",
"f-id": "${id}"
}
}
`
)
}
}
var builder = new Vue({
el: '#builder',
delimiters: ["[[", "]]"],
data: {
blankTemplate: {
node: 'root',
child: [
{
node: 'element',
tag: 'div',
attr: { id: '1', class: 'foo'},
child: []
},
workingTemplate: {
content: {}
},
primaryButton: {},
secondaryButton: {},
bForm: null,
},
methods: {
openFormDesigner: function(){
$('#formDesignerModal').modal('show');
this.primaryButton = {'show':false, 'text':'Save', 'spanstyle':'fa fa-floppy-o'};
this.secondaryButton = {'show':true, 'text': 'Close'};
},
addToFormCanvas: function(ftype){
if (!ftype){
console.log("You need to provide the type of the Form element");
}else if (ftype == 'email'){
this.bForm.child.push(FormX.textInput('email', 'mail1','mail1','mail1'));
}else if (ftype == 'text'){
this.bForm.child.push(FormX.textInput('text', 'mail1','mail1','mail1'));
}else if (ftype == 'number'){
this.bForm.child.push(FormX.textInput('number', 'mail1','mail1','mail1'));
}
},
jsonToHtml: function(jsono){
return json2html(jsono);
},
getAttr: function(aobj){
return aobj
}
},
mounted: function(){
$('#vwr-panel > iframe').contents().find('body').css({"margin":"0 auto", "background-color":"#fff"}).html(
json2html(this.blankTemplate));
},
created: function(){
this.bForm = FormX.newForm();
},
computed: {
bFormHtml: function(){
return json2html(this.bForm)
}
},
filters: {
capitalize: function(v){
return _.toUpper(v);
}
}
})
When a click happens, I basically append to a data object in my vue instance.
I then use json2html to convert this data object to "html" which is really a component.
I expect that the component will be rendered, but although I can see it in inspector, it doesn't render.
I am inserting the component with <div v-html="bFormHtml"></div>
Have you tried using a mutable <component> tag?
html:
...
<component :is="componentId"></component>
...
javascript:
...
data: {
componentId: 'blank'
},
components: {
'blank': {
'template': '<div></div>'
}
},
methods: {
'updateComponent': function() {
this.componentId = 'form-x-text'
}
}
...
JsFiddle an Example of multiple components.
I'm not sure if it will work exactly with what you have but it might do better than trying to inject the HTML.

Categories