Appropriate two way binding for checkboxes in Vue JS - javascript

I have the data from MySQL database in the form of "1" and "0" representing the boolean true and false.These values are set in the vue component in the following manner :
data(){
return {
form : {
attribute_1 : "1", //attribute 1 is true
attribute_2 : "0", //attribute 2 is false
attribute_3 : "1", //attribute 3 is true
}
}
}
To maintain the two-way binding I am currently using the computed properties as follows :
attribute1: {
get(){
return this.form.attribute_1 == "1" ? true : false ;
},
set(newValue){
this.form.attribute_1 = newValue ? "1" : "0";
}
},
attribute2: {
get(){
return this.form.attribute_2 == "1" ? true : false ;
},
set(newValue){
this.form.attribute_2 = newValue ? "1" : "0";
}
}, ...
These computed properties are wired on HTML code in following manner.
<input type="checkbox" checked v-model="attribute1">
<input type="checkbox" checked v-model="attribute2">
This works quite good for the two way binding in VUE. But there is a severe repetition in the code.
There is another way I have in mind using the #change event to track the changes in the checkbox :checked property and change the data attributes according but It seems to be one way binding and in the Vue console values are only updated when the I refresh the VUE panel.
Is there is a better way to achieve two way binding in this particular scenario?

You can achieve this by simply updating your template like:
<input type="checkbox" v-model="form.attribute1" :true-value="1" :false-value="0">
<input type="checkbox" v-model="form.attribute2" :true-value="1" :false-value="0">
and that's it. You will not need any computed properties anymore. You will get this.form.attribute1 value as "1" when checkbox will be checked or "0" when unchecked. Also, if you set form.attribute1 value as "1" then the checkbox will be checked by default as shown in the demo below.
DEMO:
new Vue({
el: '#app',
data(){
return {
form: {
attribute1: "1", //attribute 1 is true
attribute2: "0" //attribute 2 is false
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<input type="checkbox" v-model="form.attribute1" :true-value="1" :false-value="0">
<label for="checkbox">{{ form.attribute1 }}</label><br/><br/>
<input type="checkbox" v-model="form.attribute2" :true-value="1" :false-value="0">
<label for="checkbox">{{ form.attribute2 }}</label><br/><br/>
</div>

My favorite solution is to create component to achieve that:
My Checkbox.vue component:
<template>
<input type="checkbox" :checked="isChecked" #change="change" />
</template>
<script>
export default {
props: {
value: {}
},
computed: {
isChecked() {
return this.value === "1" || this.value === true;
}
},
methods: {
change(e) {
this.$emit("input", e.target.checked ? "1" : "0");
}
}
};
</script>
and use it in other components:
<template>
<div>
<Checkbox v-model="isChecked" />
</div>
</template>
<script>
import Checkbox from "./Checkbox";
export default {
components: {
Checkbox
},
data: () => ({
isChecked: "1"
})
};
</script>

Related

Vue JS prop error for value on radio button with v-model and v-bind="$attrs"

I am getting some strange behaviour that I cannot wrap my head around.
I have a simple radio button component that's used as a "wrapper" for an actual radio button.
On this component, I have inheritAttrs: false and use v-bind="$attrs" on the element itself so I can use v-model and value etc.
However, upon selecting a radio button, an error is thrown that the prop value is invalid (because it's an event and not a string) and interestingly I noticed that on initial render the value prop is blank in Vue Devtools.
I'm simply trying to get these radio buttons updating the parent's data object value for location with a string value of the radio button selected.
I can't figure out where I'm going wrong here exactly. Any help greatly appreciated.
Example project of the problem:
https://codesandbox.io/embed/m40y6y10mx
FormMain.vue
<template>
<div>
<p>Location: {{ location }}</p>
<form-radio
id="location-chicago"
v-model="location"
value="Chicago"
name="location"
label="Chicago"
#change="changed"
/>
<form-radio
id="location-london"
v-model="location"
value="London"
name="location"
label="London"
#change="changed"
/>
</div>
</template>
<script>
import FormRadio from "./FormRadio.vue";
export default {
name: "FormMain",
components: {
FormRadio
},
data() {
return {
location: ""
};
},
methods: {
changed(e) {
console.log("Change handler says...");
console.log(e);
}
}
};
</script>
FormRadio.vue
<template>
<div>
<label :for="id">
{{ label }}
<input
:id="id"
type="radio"
:value="value"
v-on="listeners"
v-bind="$attrs"
>
</label>
</div>
</template>
<script>
export default {
name: "FormRadio",
inheritAttrs: false,
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
},
value: {
type: String,
required: true
}
},
computed: {
listeners() {
return {
...this.$listeners,
change: event => {
console.log("Change event says...");
console.log(event.target.value);
this.$emit("change", event.target.value);
}
};
}
}
};
</script>
Edit
Found this neat article which describes the model property of a component. Basically it allows you to customise how v-model works. Using this, FormMain.vue would not have to change. Simply remove the value prop from FormRadio and add the model property with your own definition
See updated codepen:
FormRadio Script
<script>
export default {
name: "FormRadio",
inheritAttrs: false,
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
// customize the event/prop pair accepted by v-model
model: {
prop: "radioModel",
event: "radio-select"
},
computed: {
listeners() {
return {
...this.$listeners,
change: event => {
console.log("Change event says...");
console.log(event.target.value);
// emit the custom event to update the v-model value
this.$emit("radio-select", event.target.value);
// the change event that the parent was listening for
this.$emit("change", event.target.value);
}
};
}
}
};
</script>
Before Edit:
Vue seems to ignore the value binding attribute if v-model is present. I got around this by using a custom attribute for the value like radio-value.
FormMain.vue
<form-radio
id="location-chicago"
v-model="location"
radio-value="Chicago"
name="location"
label="Chicago"
#change="changed"
/>
<form-radio
id="location-london"
v-model="location"
radio-value="London"
name="location"
label="London"
#change="changed"
/>
The input event handler will update the v-model.
FormRadio.vue
<template>
<div>
<label :for="(id) ? `field-${id}` : false">
{{ label }}
<input
:id="`field-${id}`"
type="radio"
:value="radioValue"
v-on="listeners"
v-bind="$attrs"
>
</label>
</div>
</template>
<script>
export default {
name: "FormRadio",
inheritAttrs: false,
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
},
radioValue: {
type: String,
required: true
}
},
computed: {
listeners() {
return {
...this.$listeners,
input: event => {
console.log("input event says...");
console.log(event.target.value);
this.$emit("input", event.target.value);
},
change: event => {
console.log("Change event says...");
console.log(event.target.value);
this.$emit("change", event.target.value);
}
};
}
}
};
</script>
See forked codepen
I removed the v-model entirely from the child component call (this was conflicting);
<form-radio
id="location-chicago"
value="Chicago"
name="location"
label="Chicago"
#change="changed"
/>
<form-radio
id="location-london"
value="London"
name="location"
label="London"
#change="changed"
/>
I then updated your changed method to include to set the location variable
methods: {
changed(e) {
console.log("Change handler says...");
console.log(e);
this.location = e;
}
}
Updated: Link to updated CodeSandbox

v-model in icheck-box component not working in vue

I'm trying to create a checkbox component but I am unable to do it. How can I create three checkboxs (using ichecks and inputs) and get all the selected ones using v-model in an array? The problem is that I can get the value of the clicked checkbox separately but I can not get all the values in array as it is suppose happen with vue's v-model. It seems that, they are getting lost somewhere. here my code
<html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iCheck/1.0.2/icheck.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/iCheck/1.0.2/skins/all.css" rel="stylesheet">
<div id='app'>
<icheck :value="'Jack'" id="jack" v-model="checkedNames" > </icheck>
<icheck :value="'John'" id="john" v-model="checkedNames" > </icheck>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
</html>
<script>
Vue.component('icheck', {
props: {
value:{ required: false }
},
template: `
<input type="checkbox" >
`,
mounted: function () {
var vm = this
$(this.$el).iCheck({
//checkboxClass: 'icheckbox_minimal',
checkboxClass: 'icheckbox_square-red',
//radioClass: 'iradio_minimal',
radioClass: 'iradio_square',
increaseArea: '20%' // optional
})
.val(this.value)
.trigger('ifChanged')
// emit event on change. here the problem
.on('ifChanged', (event)=>{
let isChecked = event.target.checked
let val = event.target.value
// here the problem need an array
vm.$emit('input',isChecked ? [val]:[])
});
},
watch: {
value: function (value) {
// update value
$(this.$el).val(value)
}
}
})
new Vue({
el: '#app',
data: {
checkedNames: []
}
})
</script>
Here is how I would do it.
<icheck :val="'Jack'" id="jack" v-model="checkedNames" > </icheck>
<icheck :val="'John'" id="john" v-model="checkedNames" > </icheck>
props: {
val:{ required: true },
value: { required: false }
},
....
// Check if the value is preset from the props and toggle check
if (this.value.includes(this.val)) {
$(this.$el).iCheck('check');
}
$(this.$el).iCheck({
//checkboxClass: 'icheckbox_minimal',
checkboxClass: 'icheckbox_square-red',
//radioClass: 'iradio_minimal',
radioClass: 'iradio_square',
increaseArea: '20%' // optional
})
.val(this.val)
.trigger('ifChanged')
.on('ifChanged', (event)=>{
// create a local copy so as not to modify the prop
let value = [].concat(this.value)
// check if the array includes the value already
if(value.includes(this.val)){
// if it does remove it
value.splice(value.indexOf(this.val), 1)
} else {
// if it doesn't add it
value.push(this.val)
}
// emit the entire array
vm.$emit('input', value)
});
I see what you try to do, but this only works with native checkboxes. if the v-model is an array and the element is a checkbox, it will change the v-model to something like v-model="data[index]". This does not work with custom elements, as you try to do.

Vue JS - injecting default value to custom radio group

So i tried to inject a default value to custom radio component that i wrote
Here's the code:
<template>
<div class="custom-radio-button">
{{value}}
<div v-for= "item in localValue">
<input type="radio" :value="item.value" name=item.name #click=onSelected(item.value) >
<span>{{item.label}}</span>
</input>
</div>
</div>
<script>
import Vue from 'vue'
const name = 'CustomRadioButton'
export default {
name,
componentName: name,
props: [ 'name', 'value', 'isDefault', 'label'],
data() {
return {
localName: this.name,
localValue: this.value
}
},
methods: {
onSelected (value) {
this.$emit('clicked', value)
}
}
}
</script>
And here's how i called it:
<CustomRadioButton :value=RadioFieldData #clicked="isRadioButtonSelection" isDefault='yellow'></CustomRadioButton>
And here's the Json Data that goes with it
RadioFieldData:[
{label:'Fire', value:'red', name:'colour' },
{label:'Sun', value:'yellow', name:'colour',isDefault:'yellow'},
{label:'Water', value:'blue', name:'colour'}
]
My question is what is the best way to pass the value "yellow" to the radio buttons group?
Your issue is that props need to be represented in their kebab-case format when used in your template. To set isDefault to "yellow", you need to use
is-default="yellow"
See https://v2.vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
Once you're able to read that property correctly, you can use
:checked="item.value == isDefault"
Here's an example.
Vue.component('custom-radio-button', {
template: `<div class="custom-radio-button">
Default: {{isDefault}}
<div v-for="item in value">
<input type="radio" :value="item.value" name="item.name" #click="onSelected(item.value)" :checked="item.value == isDefault" />
<span>{{item.label}}</span>
</div></div>`,
props: ['value', 'isDefault'],
methods: {
onSelected(value) {
this.$emit('clicked', value)
}
}
})
new Vue({
el: '#app',
methods: {
isRadioButtonSelection (val) {
console.log('isRadioButtonSelection', val)
}
},
data: {
RadioFieldData: [{"label":"Fire","value":"red","name":"colour"},{"label":"Sun","value":"yellow","name":"colour","isDefault":"yellow"},{"label":"Water","value":"blue","name":"colour"}]
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<custom-radio-button :value="RadioFieldData"
#clicked="isRadioButtonSelection"
is-default="yellow">
</custom-radio-button>
</div>

How to add multiple attribute - vue.js

This works:
<input v-model="project.name" :readonly="isReadOnly" :disabled="isReadOnly">
Is there a way to make below code work?
<input v-model="project.name" {{ readOnlyAttr }} >
<input v-model="project.name" {{ isReadOnly : 'readonly disabled' ? ''}}>
Script as follows:
<script>
export default {
props: {
isReadOnly: {
default: ture,
type: Boolean
}
},
data () {
return {
readOnlyAttr: 'readonly disabled'
}
}
}
</script>
v-bind is your friend here.
Because you want the attributes to be computed, I created a computed method to build the object everytime there is a change to isReadOnly value.
When you want to bind statically group of attributes, you can use the data method.
<template>
<div>
<input v-model="project.name" v-bind="readOnlyAttributes">
</div>
</template>
<script>
export default {
name: 'example',
computed: {
readOnlyAttributes() {
return {
readonly: this.isReadOnly,
disabled: this.isReadOnly ? 'readonly' : ''
}
},
isReadOnly() {
// returning always true for the example
return true;
}
}
}

Fire different functions depending on checkbox state in Vue2

I'd like to know is there any way to fire custom functions - one function when checkbox changes to true value and another when it changes to false value (without using $watch).
For example:
I have input wrapped in root div
<div id="root">
<input type="checkbox" v-model="editModeOn">
</div>
and a vue instance with disableEditMode (on checkbox unchecked) and enableEditMode (on checkbox checked)
new Vue({
el: '#root',
props: {
editModeOn: {
type: Boolean,
default: false
}
},
methods: {
disableEditMode() {
// some code
},
enableEditMode() {
// some code
}
},
});
How can I achieve this functionality? Thanks!
Handle the change event.
#change="editModeOn ? enableEditMode() : disableEditMode()"
new Vue({
el: '#root',
data:{
editModeOn: false
},
methods: {
disableEditMode() {
console.log("disable")
},
enableEditMode() {
console.log("enable")
}
},
});
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="root">
<input type="checkbox" v-model="editModeOn" #change="editModeOn ? enableEditMode() : disableEditMode()">
</div>

Categories

Resources