Vue.Js, binding a value to a checkbox in a component - javascript

I'm making a component which is a wrapper around a checkbox (I've done similar with inputs of type 'text' and 'number') but I cannot get my passed in value to bind correctly.
My component is:
<template>
<div class="field">
<label :for="name" class="label">
{{ label }}
</label>
<div class="control">
<input :id="name" :name="name" type="checkbox" class="control" :checked="value" v-on="listeners" />
</div>
<p v-show="this.hasErrors" class="help has-text-danger">
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
</template>
<script>
export default {
name: 'check-edit',
props: {
value: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
errors: {
type: Array,
default: () => []
}
},
mounted () {
},
computed: {
listeners () {
return {
// Pass all component listeners directly to input
...this.$listeners,
// Override input listener to work with v-model
input: event => this.$emit('input', event.target.value)
}
},
hasErrors () {
return this.errors.length > 0
}
},
}
</script>
I've imported it globally; and am invoking it in another view by doing:
<check-edit name="ShowInCalendar" v-model="model.ShowInCalendar" label="Show in calendar?" :errors="this.errors.ShowInCalendar"></check-edit>
My model is in data and the property ShowInCalendar is a boolean and in my test case is true. So when I view the page the box is checked. Using the Vue tools in firefox I can see the model.ShowInCalendar is true, and the box is checked. However, when I click it the box remains checked and the value of ShowInCalendar changes to 'on', then changes thereafter do not change the value of ShowInCalendar.
I found this example here: https://jsfiddle.net/robertkern/oovb8ym7/ and have tried to implement a local data property for it but the result is not working.
The crux of what I'm trying to do is have the initial checkstate of the checkbox be that of ShowInCalendar (or whatever property is bound via v-model on the component) and then have that property be update (to be true or false) when the checkbox is checked.
Can anyone offer me any advice please?
Thank you.

You should not $emit event.target.value, it's the value of the checkbox, it's not a Boolean value. If you want to detect the checkbox is update(to be true or false) or not, You should $emit event.target.checked just like fstep said.

If v-on is the only listener that will be used it might be easier to use v-model as in the checkbox example from the Vue input docs.
However you can use listeners based on Binding-Native-Events-to-Components docs
<template>
<div class="field">
<label :for="name" class="label">
{{ label }}
</label>
<div class="control">
<input :id="name" :name="name" type="checkbox" class="control" checked="value" v-on="listeners" />
</div>
<p v-show="this.hasErrors" class="help has-text-danger">
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
</template>
<script>
export default {
name: 'check-edit',
props: {
value: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
errors: {
type: Array,
default: () => []
}
},
mounted() {},
computed: {
listeners() {
var vm = this;
// `Object.assign` merges objects together to form a new object
return Object.assign(
{},
// We add all the listeners from the parent
this.$listeners,
// Then we can add custom listeners or override the
// behavior of some listeners.
{
// This ensures that the component works with v-model
input: function(event) {
vm.$emit('input', event.target.checked);
}
}
);
},
hasErrors() {
return this.errors.length > 0;
}
}
};
</script>

Don't change props. Your component, having a v-model, should be emitting input events on change. The parent will handle the actual changing of the value.

Related

vue radio boxes returning string instead of boolean

I have a reusable radio box that is supposed to return true or false depending on which radio box is selected. the problem is that it returns a string instead of a boolean and I have no idea why this behavior is so.
below is the parent component that calls the radio component
<radioinput name="yes" :value="true" v-model="form.time.alwaysopen">Yes</radioinput>
<radioinput name="no" :value="false" v-model="form.time.alwaysopen">No</radioinput>
form: {
time: {
alwaysopen: true,
open: null,
close: null
}
}
below is the radio component that is called
<template>
<label :for="name" class="inline-form radio">
<slot></slot>
<input type="radio" :name="name" :id="name" class="radio-input" :checked="isChecked" :value="value" #change="$emit('change', $event.target.value)">
<div class="radio-radio"></div>
</label>
</template>
export default {
model: {
prop: 'modelValue',
event: 'change'
},
props: {
name: {
type: String,
required: true
},
value: {
type: Boolean,
default: true
},
modelValue: {
type: Boolean,
},
},
computed: {
isChecked() {
return this.modelValue == this.value
}
}
}
Please what could be the issue?
When you emit the change event from your radio component, you're taking $event.target.value, which is the HTMLInputElement.value property that is always a string. In order to emit the boolean value prop itself, you should refer to it directly in the #change handler like this:
<input type="radio" :name="name" :id="name" class="radio-input" :checked="isChecked" :value="value" #change="$emit('change', value)">

Vue not updating same type of component on v-if argument change

I have a custom Toggle component, made from scratch in Vue.
<template>
<div :class="{Switch: true, disabled: disabled}" #click="toggle" :title="disabled ? disabledtitle : ''">
<div :class="{Background: true, active: isToggled}"></div>
<div :class="{Toggle: true, active: isToggled}"></div>
</div>
</template>
<script>
export default {
name: 'Toggle',
props: {
toggled: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
disabledtitle: {
type: String,
default: ''
}
},
data () {
return {
isToggled: false
}
},
methods: {
toggle () {
if(this.disabled)
return
this.isToggled = !this.isToggled
this.$emit('input', this.isToggled)
}
},
mounted () {
this.isToggled = this.toggled
}
}
</script>
...
It has multiple instances in an another component, like this:
<template>
<div v-if="category === 'Basic'" class="Basic">
<Toggle :toggled="config.isToggledThisOption" #input="updateConfig($event, 'isToggledThisOption')" />
</div>
<div v-else-if="category === 'Other'" class="Other">
<Toggle :toggled="config.isToggledOtherOption" #input="updateConfig($event, 'isToggledOtherOption')" />
</div>
</template>
...
There is also a menu, that switches between the categories (changes the category variable)
This works well, but the problem is, that the toggled prop does not update when switching between the categories.
I managed to track it down, and the problem is the following:
Whenever the value of the category changes, the <Toggle /> component does not update. It always stays the same, even in inspect element, no updates happen. mounted() also doesn't get executed again, only when the page is reloaded (and even then, not all <Toggle />s' mounted() function get executed).
Do I need to add some kind of id/identificator to the component, so it gets re-mounted/re-rendered/re-initiated?

Clear input VUE component data from parent

I'm a newbie of Vue, and I'm trying to simply clear the data of input component once I've submitted, but it seems I'm missing something, because since it's parent data is cleared, I still see the filled value of the input component.
Here is a living example.
I've set to the input child component v-model="title" from it's parent wrapper. Once I submit the data to the parent, I call addItem and in the end, I supposed to clear the data model just by clear it this.title = '', but I probably do something wrong on how to bind data from parent to child.
And above the code, starting from the parent component:
<template>
<form #submit="addItem" class="todo-insert">
<input-item
icon="create"
name="title"
placeholder="Add a ToVue item..."
v-model="title"
/>
<button-item tone="confirm" class="todo-insert__action">
Aggiungi
</button-item>
</form>
</template>
<script>
import ButtonItem from '#vue/Form/ButtonItem/ButtonItem.vue'
import InputItem from '#vue/Form/InputItem/InputItem.vue'
import uuid from 'uuid'
export default {
name: 'TodoInsert',
components: {
ButtonItem,
InputItem
},
data () {
return {
title: ''
}
},
methods: {
addItem (e) {
e.preventDefault()
const todo = {
id: uuid.v4(),
isComplete: false,
title: this.title
}
this.$emit('add-todo', todo)
this.title = ''
}
}
}
</script>
<style lang="scss" scoped src="./TodoList.scss"></style>
This is the child input component:
<template lang="html">
<label class="input">
<div v-if="label" class="input__label text-sans text-sans--label">
{{ label }}
</div>
<div class="input__row">
<input
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
<div v-if="placeholderLabel" class="input__placeholder text-sans text-sans--placeholder">
{{ placeholderLabel }}
</div>
<div v-if="icon" class="input__icon-area">
<icon-item
:name="icon"
/>
</div>
</div>
</label>
</template>
<script>
import IconItem from '../../IconItem/IconItem.vue'
export default {
name: 'InputItem',
props: {
autocomplete: {
type: String,
default: 'off'
},
icon: String,
label: String,
name: {
type: String,
default: 'input-text'
},
placeholder: String,
placeholderLabel: String
},
computed: {
hasPlaceholderLabel () {
return this.placeholderLabel ? 'input__field--placeholder-label' : ''
},
isDirty () {
// return this.$attrs.value ? 'input__field--dirty' : ''
return 'input__field--dirty'
}
},
methods: {
updateValue: function (value) {
this.$emit('input', value)
}
},
components: {
IconItem
}
}
</script>
<style lang="scss" src="./InputItem.scss"></style>
What am I missing?
Your child component is bound unidirectionally. It means that it can change the value, but does not receive any update from the parent component. To receive updates, you need to receive the property value in your child:
props: {
value: String
}
Then, you need to pass the value received to the input :
<input
:value="value"
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
Now the input should update when the parent component changes the value

Why is the Vue.js input value not updating?

I have a Vue.js text-input component like the following:
<template>
<input
type="text"
:id="name"
:name="name"
v-model="inputValue"
>
</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: this.value
};
},
watch: {
inputValue: function () {
eventBus.$emit('inputChanged', {
type: 'text',
name: this.name,
value: this.inputValue
});
}
}
};
</script>
And I am using that text-input in another component as follows:
<ul>
<li v-for="row in rows" :key="row.id">
<text-input :name="row.name" :value="row.value">
</text-input>
</li>
</ul>
Then, within the JS of the component using text-input, I have code like the following for removing li rows:
this.rows = this.rows.filter((row, i) => i !== idx);
The filter method is properly removing the row that has an index of idx from the rows array, and in the parent component, I can confirm that the row is indeed gone, however, if I have, for example, two rows, the first with a value of 1 and the second with a value of 2, and then I delete the first row, even though the remaining row has a value of 2, I am still seeing 1 in the text input.
Why? I don't understand why Vue.js is not updating the value of the text input, even though the value of value is clearly changing from 1 to 2, and I can confirm that in the parent component.
Maybe I'm just not understanding how Vue.js and v-model work, but it seems like the value of the text input should update. Any advice/explanation would be greatly appreciated. Thank you.
You cannot mutate values between components like that.
Here is a sample snippet on how to properly pass values back and forth. You will need to use computed setter/getter. Added a button to change the value and reflect it back to the instance. It works for both directions.
<template>
<div>
<input type="text" :id="name" v-model="inputValue" />
<button #click="inputValue='value2'">click</button>
</div>
</template>
<script>
export default {
props: ['name', 'value'],
computed: {
inputValue: {
get() {
return this.value;
},
set(val) {
this.$emit('updated', val);
}
}
}
}
</script>
Notice that the "#updated" event updates back the local variable with the updated value:
<text-input :name="row.name" :value="row.value" #updated="item=>row.value=item"></text-input>
From your code you are trying to listen to changes.. in v-model data..
// Your Vue components
<template>
<input
type="text"
:id="name"
:name="name"
v-model="inputValue"
>
</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: ""
};
},
};
</script>
If You really want to listen for changes..
<ul>
<li v-for="row in rows" :key="row.id">
<text-input #keyup="_keyUp" :name="row.name" :value="row.value">
</text-input>
</li>
</ul>
in your component file
<template>...</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: ""
};
},
methods : {
_keyUp : () => {// handle events here}
};
</script>
check here for events on input here
To bind value from props..
get the props value, then assign it to 'inputValue' variable
it will reflect in tthe input element

Prop value lost after running a method Vuejs

I made a material design input field as a Vue component. I listen for the focus event and run a function to check the value every time the user focuses out. Here's the code:
<template>
<span class="h-input-container">
<input :type="type" :name="name" v-validate="validation"
#focusout="classHandle" :id="id" :value="value">
<p :class="focusClass"><i :class="icon"></i> {{placeholder}}</p>
</span>
</template>
<script>
export default {
mounted: function(){
if (this.value != '') {
this.focusClass = 'focused'
}
},
props: {
placeholder: {
default: ''
},
name: {
default: 'no-name'
},
type: {
default: 'text'
},
validation: {
default: ''
},
icon: {
default: ''
},
id: {
default: ''
},
value: {
default: ''
}
},
data: function(){
return {
focusClass: '',
}
},
methods: {
classHandle: function(event){
if (event.target.value != '') {
this.focusClass = 'focused'
} else {
this.focusClass = ''
}
}
}
};
</script>
I pass the value as a prop called value and I've used that value for the input field using :value="value". The thing is, every time the method classHandle runs, the input field's value disappears. I can't figure out why.
To clarify the accepted answer, Vue does not "refresh" the value when the focusout handler fires. The property, value, never changed, the input element's value changed.
Changing the focusClass forces Vue to re-render the component to the DOM. Because you've told Vue to use the property, value, as the value of the input via :value="value", it uses the current state of the property, which has never changed as stated above, to render the component and whatever you typed in the input disappears.
The accepted answer goes on to state that you should fix this by updating this.value. In a component, Vue will allow you to do that but it will throw a warning in the console.
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "value"
Properties of components in Vue are just like function arguments in javascript. Inside the component, you can change them, but that change is limited to the scope of the component. If the parent component ever has to re-render, then the property "value" of your input will be redrawn using the parent's version of the value, and you will lose your changes.
Component communication in Vue "props down, events up". In order to change the value outside your component, you have to $emit it. Here is a version of your component that works, without throwing any warnings, and properly emits your changes.
{
props: {
placeholder: {
default: ''
},
name: {
default: 'no-name'
},
type: {
default: 'text'
},
validation: {
default: ''
},
icon: {
default: ''
},
id: {
default: ''
},
value: {
default: ''
}
},
template:`
<div class="h-input-container" style="background-color: lightgray">
<input :type="type" :name="name"
#focusout="classHandle" :id="id" :value="value" #input="$emit('input', $event.target.value)" />
<p :class="focusClass"><i :class="icon"></i> {{placeholder}}</p>
</div>
`,
data: function() {
return {
focusClass: '',
}
},
methods: {
classHandle: function(event) {
if (event.target.value != '') {
this.focusClass = 'focused'
} else {
this.focusClass = ''
}
}
}
}
I've put together an example to demonstrate the differences in the two approaches.
Typically, I would not use :value="value" #input="$emit('input', $event.target.value)" and use v-model instead, but you are using :type="type" as well, and Vue will throw a warning about using v-model with a dynamic type.
Your current component do not seems to refresh this.value when you make change in the input. Vue cause a component refresh when you focusOut, and because your value is not updated, it shown empy. To resolve your problem, you need to update this.value on event input
new Vue(
{
el: '#app',
props: {
placeholder: {
default: ''
},
name: {
default: 'no-name'
},
type: {
default: 'text'
},
validation: {
default: ''
},
icon: {
default: ''
},
id: {
default: ''
},
value: {
default: ''
}
},
data: function() {
return {
focusClass: '',
}
},
methods: {
updateValue(event) {
this.value = event.target.value
},
classHandle: function(event) {
if (event.target.value != '') {
this.focusClass = 'focused'
} else {
this.focusClass = ''
}
}
}
});
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<span class="h-input-container">
<input :type="type" :name="name" v-validate="validation"
#focusout="classHandle" :id="id" :value="value" #input="updateValue" />
<p :class="focusClass"><i :class="icon"></i> {{placeholder}}</p>
</span>
</div>
So you should put your attention on:
#input="updateValue"
And
updateValue(event) {
this.value = event.target.value
}

Categories