Vue JS, checkboxes and computed properties - javascript

I am having some problems using vue, checkboxes and computed properties.
I made a very small example showing my problem: https://jsfiddle.net/El_Matella/s2u8syb3/1/
Here is the HTML code:
<div id="general">
Variable:
<input type="checkbox" v-model="variable">
Computed:
<input type="checkbox" v-model="computed()">
</div>
And the Vue code:
new Vue({
el: '#general',
data: {
variable: true
},
compute: {
computed: function() {
return true;
}
}
})
The problem is, I can't make the v-model="computed" work, it seems that Vue doesn't allow such things.
So my question is, how could I use the benefits of the computed data and apply it to checkboxes?
Here is an other jsfiddle showing the same problem, but with more code, I was trying to use computed properties to build a "selected" products array variable: https://jsfiddle.net/El_Matella/s2u8syb3/
Thank you for your answers and have a nice day!

Computed properties are basically JavaScript getters and setters, they are used like regular properties.
You can use a computed setter to set the value (currently, you only have a getter). You will need to have a data or props property in which you can save the changes of the model though, because getters and setters don't have an inherent state.
new Vue({
el: '#general',
data: {
variable: true,
cmpVariable: true,
},
computed: { // "computed" instead of "compute"
cmp: {
get: function() {
return this.$data.cmpVariable;
},
set: function(val) {
this.$data.cmpVariable = val;
},
}
}
});
Also, you don't need to call the computed with brackets (as it behaves like a regular property):
<div id="general">
Variable:
<input type="checkbox" v-model="variable">
Computed:
<input type="checkbox" v-model="cmp">
</div>

You miss-spelled computed. Here Computed Properties
I guess you want to check an item in Product list,
So it can be displayed in the selected list.
And you also want to check it off from both the lists.
Thus you don't need a computed property.
For check boxes, you can easily change the selected set by referring to it with v-model and set value for what you want to put in the set.
In your case, that's the product.id.
You may want to save the object itself in the selectedProducts list,
but I highly recommend you not to do that.
In some case, it will cause unexpected results as objects are mutable.
So it will work if it is written this way.
new Vue({
el: '#general',
data: {
products: [{
id: 1
}, {
id: 2
}],
selectedProducts: []
}
})
<script src="//cdn.bootcss.com/vue/1.0.13/vue.min.js"></script>
<h1>Hello</h1>
<div id="general">
<h2>Products</h2>
<ul>
<li v-for="product in products">
<input v-model="selectedProducts" value="{{product.id}}" type="checkbox">{{ product.id }}
</li>
</ul>
<h2>Selected Products</h2>
<ul>
<li v-for="p in selectedProducts">
<input v-model="selectedProducts" value="{{p}}" type="checkbox">{{ p }}
</li>
</ul>
</div>

Related

How to set default value on dynamic input fields in Vue.js?

I have created a dynamic input fields but the problem is that I don't know how to set default values on that dynamic fields for example number "1". Can anyone help me with that? Thanks
Here is my example
new Vue({
el: '#app',
data: {
result: [],
array: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(item, index) in array" :key="item.id">
<input type="number" :id="item.id" v-model="result[index]">
<br>
</div>
<span>Result: {{ result }}</span>
</div>
As Vue js is reactive you can simply set initial value in result
new Vue({
el: '#app',
data: {
result: ["1","1","1"],
array: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
}
})
According to the official documentation,
v-model will ignore the initial value, checked or selected attributes
found on any form elements. It will always treat the Vue instance data
as the source of truth. You should declare the initial value on the
JavaScript side, inside the data option of your component.
I think it is a design decision of Vue to use the benefit of Vue instance’s reactive system rather than listening to DOM updates when such attributes are updated.
So you can put directly your default values into the result array, here's the updated working jsfiddle.

Array change detection for an array of complex objects in Vue JS 2

Update
Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
Problem:
I have a vue component that looks like this:
sub-comp.vue
<template>
<div>
<input type="text" class="form-control" v-model="textA">
<input type="text" class="form-control" v-model="textB">
<input type="text" class="form-control" v-model="textC">
</div>
</template>
<script>
export default {
props: {
textA: {
type: Number,
required: false
},
textB: {
type: Number,
required: false
},
textC: {
type: Number,
required: false
}
}
}
</script>
I have a parent component that looks like this:
layout-comp.vue
<template>
<div>
<button #click="addItem">Add</button>
<ul>
<li v-for="listItem in listItems"
:key="listItem.id">
<sub-comp
:textA="listItem.item.textA"
:textB="listItem.item.textB"
:textC="listItem.item.textC"
/>
</li>
</ul>
</div>
</template>
import subComp from '../sub-comp.vue'
export default {
components: {
subComp
},
data() {
return {
listItems: []
}
},
methods: {
addItem: function () {
var item = {
textA: 5,
textB: 100,
textC: 200
}
if (!item) {
return
}
this.length += 1;
this.listItems.push({
id: length++,
item: item
});
}
}
</script>
The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as
{
textA: 5,
textB: 100,
textC: 200
}
Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:
https://v2.vuejs.org/v2/guide/list.html#Caveats
Due to limitations in JavaScript, Vue cannot detect the following changes to an array
Question:
I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the #blur event. I'd like to see what ways this can be done.
I read these materials:
https://codingexplained.com/coding/front-end/vue-js/array-change-detection
https://v2.vuejs.org/v2/guide/list.html
But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.
Update 4/12/2018
Found out that in my addItem() that I had:
item = this.conditionItems[this.conditionItems.length - 1].item);
to
item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));
I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:
https://github.com/vuejs/Discussion/issues/292
The problem is that props flow in one direction, from parent to child.
Setting the value using v-model in child won't affect parent's data.
Vue has a shortcut to update parent's data more easily. It's called .sync modifier.
Here's how.
In sub-comp.vue
<template>
<div>
<input type="text" class="form-control" :value="textA" #input="$emit('update:textA', $event.target.value)" >
<input type="text" class="form-control" :value="textB" #input="$emit('update:textB', $event.target.value)">
<input type="text" class="form-control" :value="textC" #input="$emit('update:textC', $event.target.value)">
</div>
</template>
<script>
export default {
// remains the same
}
</script>
add .sync when you add the props
<sub-comp
:textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
:textB.sync="listItem.item.textB"
:textC.sync="listItem.item.textC"
/>
update:
if you have reactivity problem, don't use .sync, add a custom event and use $set
<sub-comp
:textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
/>

vue js disable input if value hasn't changed

I have an input with an initial value:
<input type="text" v-model="name" ref="input" />
<button type="submit" :disabled="$refs.input.defaultValue == $refs.input.value">Submit</button>
However the disabled binding gives an error: Cannot read property defaultValue of undefined.
Best way to do this without spamming vm.data too much?
The error:
Cannot read property defaultValue of undefined
Is because the ref is not available so soon:
An important note about the ref registration timing: because the refs
themselves are created as a result of the render function, you cannot
access them on the initial render - they don’t exist yet! $refs is
also non-reactive, therefore you should not attempt to use it in
templates for data-binding.
And when you add it to the button's template, it tries to use it too soon.
The workaround would be to add a simple conditional:
<button type="submit" :disabled="!$refs.input || $refs.input.defaultValue == $refs.input.value">Submit</button>
But don't be happy just yet.
The defaultValue won't have the value you think
When using v-model, defaultValue will actually always be empty string ("") because Vue initially renders the <input> with an empty value.
To use a variable in the disabled button like you want, my suggestion is: use a mouted() logic to "save" the initial value and, in the button template, compare to it instead of defaultValue.
Demo below.
new Vue({
el: '#app',
data: {
name: 'Hello Vue.js!'
},
mounted() {
this.$refs.input.dataset.defVal = this.$refs.input.value;
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ name }}</p>
<input type="text" v-model="name" ref="input" />
<button type="submit" :disabled="!$refs.input || $refs.input.dataset.defVal == $refs.input.value">Submit</button>
</div>
Alternative: Going Vue all the way
Of course, if it's a possibilty, you should take advantage of Vue's data-driven reactive nature, as tackling with the DOM is always tricky.
The solution would be to just create another variable and populate it on mounted():
new Vue({
el: '#app',
data: {
name: 'Hello Vue.js!',
defaultName: null
},
mounted() {
this.defaultName = this.name;
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ name }}</p>
<input type="text" v-model="name"/>
<button type="submit" :disabled="name == defaultName">Submit</button>
</div>
Or, if you can set both name and defaultName to the same initial value, the mounted() logic above wouldn't even be necessary.
My solution would probably be more verbose than using a disabled flag. But I would use:
#click.prevent="submit"
Then make a submit method to handle the check, return false if input has not changed.

Input-fields as components with updating data on parent

I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>

VueJS checkbox update

I'm a little wired with VueJS states.
This is my simple app :
new Vue({
el: '#media-app',
data: {
activeTypes: [],
activeCategories: [],
medias: []
},
methods: {
getFilteredData: function () {
// some computing needed
// refresh vue
Vue.set(me, "medias", []);
},
filterMedia: function () {
console.debug(this.activeCategories);
console.debug(this.activeTypes);
this.getFilteredData();
}
}
});
And some HTML stuff:
<input type="checkbox" id="i1" value="i1" class="filter categories" v-model="activeCategories" v-on:click="filterMedia()">
<label for='i1'>My cat 1</label>
</div>
#{{ activeCategories }}
When I check the checkbox, the template displays #{{ activeCategories }} correctly with "i1". But the console.debug(this.activeCategories) displays an empty array. I have to put that debug into an updated method to get the correct value. But if I do that, I cannot call a method which change the data or I'll get into an infinity loop…
So, where should I call my filterMedia function to be able to access updated values from activeCategories ?
Thanks for your help.
Try the onchange event:
<input type="checkbox" id="i1" value="i1" class="filter categories"
v-model="activeCategories" v-on:change="filterMedia()">

Categories

Resources