Live example.
HTML
<script src="https://unpkg.com/vue"></script>
<div id="app">
<radio v-model="eventTypeInput" id="et-internal" name="event_type"
:selectedValue="0">internal</radio>
<radio v-model="eventTypeInput" id="et-external" name="event_type"
:selectedValue="1">external</radio>
{{eventTypeInput}}
</div>
JavaScript
new Vue({
el: '#app',
components: {
radio: {
template: `<div class="control-item">
<input type="radio" autocomplete="off" ref="input"
v-model="model"
:value="selectedValue"
:id="id"
:name="name"
:disabled="disabled">
<label :for="id"><slot></slot></label>
</div>`,
name: 'input-radio',
props: {
disabled: {type: Boolean, default: false},
name: {type: String, default: null},
selectedValue: {default: true}, // this radio value
value: {default: false}, // v-model variable
id: {required: true}
},
computed: {
model: {
get () {
return this.value
},
set (val) {
this.$emit('input', val)
}
}
}
}
},
data () {
return {
eventTypeInput: 'initial value'
}
}
});
I made a radio input component that works with v-model.
When I click the radio element, the expression bound via v-model is not changing.
What am I doing wrong? Is this a Vuejs bug?
Use selected-value when you pass the selectedValue property.
<radio v-model="eventTypeInput" id="et-internal" name="event_type" :selected-value="0">internal</radio>
When properties are defined in camelCase, they need to be passed in kebab-case. This is described here in the documentation.
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents
Updated fiddle.
The problem is that HTML properties are case insensitive, if you change selectedValue with selected_value (for example) in every instance, it works.
Related
I´m using a Vue base component which wraps a simple check box. In my template Im doing a 2-way bind using a v-model to a Boolean variable in my data. Nothing too fancy but there´s a problem with my implementation where instead of the target variable receiving a true/false value when the control state is turned on/off (check/unchecked), it receives an event object. I don't know what I'm doing wrong, any ideas?
I see this exception in the console when I click the control:
[Vue warn]: Invalid prop: type check failed for prop "value". Expected
Boolean, got Event
found in
<BaseSwitch> at src/components/BaseSwitch.vue
<Card> at src/components/Card.vue
<SlideYUpTransition>
<Modal> at src/components/Modal.vue
<RFQSales> at src/views/rfq/RFQSales.vue
<FadeTransition>
<App> at src/App.vue
<Root>
base-switch component:
<template>
<label class="custom-toggle">
<input type="checkbox"
v-model="model"
v-bind="$attrs"
v-on="$listeners">
<span class="custom-toggle-slider rounded-circle"></span>
</label>
</template>
<script>
export default {
name: "base-switch",
inheritAttrs: false,
props: {
value: {
type: Boolean,
default: false,
description: "Switch value"
}
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit("base-switch", value);
}
}
}
};
</script>
<style>
</style>
Template:
<base-switch class="pull-right" v-model="modals.modalNewRFQ.data.borrowButton"
You haven't stated the version of Vue you use. Is it Vue2 or Vue3?
Your tag is also not fully copy/pasted. I have changed it to
<base-switch class="pull-right" v-model="borrowButton" #base-switch="process($event)"></base-switch>
Now your code works perfectly with Vue3.
Here is the link to the working playground:
https://stackblitz.com/edit/vue-f3nqz1?file=src/App.vue
For Vue3 you must define the emitted events, to make it work well.
emits: ['base-switch'],
Also, pay attention to passing value:
this.$emit("base-switch", value);
and
#base-switch="process($event)">
If your function gets an Even Object, then you can try JSON.stringify() it to check the properties. Possibly, it is related to your $listeners solution. I couldn't guess it to reproduce the problem.
Here is the code:
App.vue
<template>
<div id="app">
<base-switch class="pull-right" v-model="borrowButton" #base-switch="process($event)"></base-switch> <br />
Value: {{value}}
</div>
</template>
<script>
import BaseSwitch from './components/BaseSwitch.vue'
export default {
name: 'App',
components: {
BaseSwitch
},
data() {
return {
borrowButton: false,
value: null
}
},
methods: {
process(value) {
this.value = value;
}
}
}
</script>
BaseSwitch.vue
<template>
<label class="custom-toggle">
<input type="checkbox"
v-model="model"
v-bind="$attrs"
>
<span class="custom-toggle-slider rounded-circle"></span>
</label>
</template>
<script>
export default {
name: "base-switch",
inheritAttrs: false,
emits: ['base-switch'],
props: {
value: {
type: Boolean,
default: false,
description: "Switch value"
}
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit("base-switch", value);
}
}
}
};
</script>
<style>
</style>
I'm new to Vue.js (using Nuxt.js) and what I'm trying to achieve is to have a Select component that I can reuse everywhere and is W3C compliant.
With the help of #Jasmonate answers, I managed to create this component, it's working. But the value attribute is still visible in the source code and so isn't W3C compliant. Maybe the problem is coming from somewhere else in the project ?!
Parent component
<custom-select
:options="options"
v-model="selectedOption"
></custom-select>
<span>Selected : {{ selectedOption }}</span>
<script>
data() {
return {
selectedOption: "A",
options: [
{ label: "One", value: "A" },
{ label: "Two", value: "B" },
{ label: "Three", value: "C" }
],
};
}
</script>
custom-select.vue
<template>
<select :value="value" #input="clicked">
<option
v-for="option in options"
:key="option.label"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
value: {
required: true
},
options: {
type: Array,
required: true
}
},
methods: {
clicked($event) {
this.$emit("input", $event.target.value);
}
}
};
</script>
I read those documentation pages:
Form Input Bindings
Components Basics
And also looked around the web to find example of v-model in a custom component, but it's always about the input tag. The only example I found about a custom select with v-model isn't actually a select tag, like the Vue Select plugin or this thread on StackOverflow.
v-model is syntax sugar. By default, the value is a prop that has the name value, and it changes (two-way-binding) whenever the event input is emitted.
Also, v-model is bound on the select element, not option.
Your code can be modified as such:
<template>
<select :value="value" #input="clicked">
<option
v-for="option in options"
:key="option.label"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
value: {
required: true
},
options: {
type: Array,
required: true
}
},
methods: {
clicked($event) {
this.$emit('input', $event.target.value);
}
}
};
</script>
Documentation here: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
You can also change the prop name and event name that v-model uses, see: https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model
Vue has a good example of using multiple checkboxes with the same name attribute bound to the same array using v-model
However, I can't use v-model for some reason so I must use #input to emit checked while keeping the value unchanged.
Its not working for me though, all the checkboxes are checked/unchecked at the same time or I have to change the value which I don't want.
Is there a workaround?
Code: https://codesandbox.io/s/71pm2wllp1?fontsize=14
Vue generates special code when compiling a template containing checkboxes bound in this way. Since you're not using v-model, you'll have to handle this functionality yourself.
Try something like this:
new Vue({
el: '#app',
data: {
checkboxes: [
{ name: 'jack', value: 'Jack' },
{ name: 'bob', value: 'Bob' },
{ name: 'alice', value: 'Alice' },
],
model: [],
},
computed: {
modelJson() {
return JSON.stringify(this.model);
},
},
methods: {
handleChange(value, checked) {
if (checked) {
this.model = this.model.concat(value);
} else {
this.model = this.model.filter(x => x !== value);
}
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="checkbox of checkboxes" :key="checkbox.name">
<input
type="checkbox"
:id="checkbox.name"
:name="checkbox.name"
:value="checkbox.value"
:checked="model.includes(checkbox.value)"
#change="handleChange(checkbox.value, $event.target.checked)"
/>
<label :for="checkbox.name">{{ checkbox.value }}</label>
</div>
<pre>{{ modelJson }}</pre>
</div>
Can the v-model syntax be used from inside a Vue component template?
The following works as expected when included directly in an .html
<input type="text" v-model="selected_service_shortname">
Putting the following stuff into a component template does not work.
var service_details = {
template: `
...
<input type="text" v-model="selected_service_shortname">
...
`
};
vm = new Vue({
el: "#app",
components: {
'service-details': service_details
},
Results in vue.min.js:6 ReferenceError: selected_service_shortname is not defined
Changing the template syntax to
<input type="text" v-model="this.$parent.selected_service_shortname">
Seems to halfway work -- changes applied externally to selected_service_shortname appear in the input box as expected. But making changes to the input box directly results in Uncaught TypeError: Cannot convert undefined or null to object
Is what I'm trying to do a supported use case? If so, are there working examples somewhere?
You can implement support for v-model in your component. This is covered in the documentation here.
Here is an example.
var service_details = {
props: ["value"],
template: `
<input type="text" v-model="internalValue">
`,
computed: {
internalValue: {
get() {
return this.value
},
set(v) {
this.$emit("input", v)
}
}
}
};
Basically, v-model, by default, is simply sugar for passing a value property and listening for the input event. So all you need to do is add a value property to your component, and emit an input event. This can also be customized as described in the documentation.
console.clear()
var service_details = {
props: ["value"],
template: `
<input type="text" v-model="internalValue">
`,
computed: {
internalValue: {
get() {
return this.value
},
set(v) {
this.$emit("input", v)
}
}
}
};
new Vue({
el: "#app",
data: {
selected_service_shortname: "some service name"
},
components: {
'service-details': service_details
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<service-details v-model="selected_service_shortname"></service-details>
<hr>
Selected Service Shortname: {{selected_service_shortname}}
</div>
Used in the parent like this:
<service-details v-model="selected_service_shortname"></service-details>
I'm trying to figure out how to detect change of the value on the textarea from within the component.
For input we can simply use
<input
:value="value"
#input="update($event.target.value)"
>
However on textarea this won't work.
What I'm working with is the CKEditor component, which should update wysiwyg's content when model value of the parent component (attached to this child component) is updated.
My Editor component currently looks like this:
<template>
<div class="editor" :class="groupCss">
<textarea :id="id" v-model="input"></textarea>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
},
id: {
type: String,
required: false,
default: 'editor'
}
},
data() {
return {
input: this.$slots.default ? this.$slots.default[0].text : '',
config: {
...
}
}
},
watch: {
input(value) {
this.update(value);
}
},
methods: {
update(value) {
CKEDITOR.instances[this.id].setData(value);
},
fire(value) {
this.$emit('input', value);
}
},
mounted () {
CKEDITOR.replace(this.id, this.config);
CKEDITOR.instances[this.id].setData(this.input);
this.fire(this.input);
CKEDITOR.instances[this.id].on('change', () => {
this.fire(CKEDITOR.instances[this.id].getData());
});
},
destroyed () {
if (CKEDITOR.instances[this.id]) {
CKEDITOR.instances[this.id].destroy()
}
}
}
</script>
and I include it within the parent component
<html-editor
v-model="fields.body"
id="body"
></html-editor>
however, whenever parent component's model value changes - it does not trigger the watcher - effectively not updating the editor's window.
I only need update() method to be called when parent component's model fields.body is updated.
Any pointer as to how could I approach it?
That's a fair bit of code to decipher, but what I would do is break down the text area and the WYSIWYG HTML window into two distinct components and then have the parent sync the values, so:
TextArea Component:
<template id="editor">
<textarea :value="value" #input="$emit('input', $event.target.value)" rows="10" cols="50"></textarea>
</template>
/**
* Editor TextArea
*/
Vue.component('editor', {
template: '#editor',
props: {
value: {
default: '',
type: String
}
}
});
All I'm doing here is emitting the input back to the parent when it changes, I'm using input as the event name and value as the prop so I can use v-model on the editor. Now I just need a wysiwyg window to show the code:
WYSIWYG Window:
/**
* WYSIWYG window
*/
Vue.component('wysiwyg', {
template: `<div v-html="html"></div>`,
props: {
html: {
default: '',
type: String
}
}
});
Nothing much going on there, it simply renders the HTML that is passed as a prop.
Finally I just need to sync the values between the components:
<div id="app">
<wysiwyg :html="value"></wysiwyg>
<editor v-model="value"></editor>
</div>
new Vue({
el: '#app',
data: {
value: '<b>Hello World</b>'
}
})
Now, when the editor changes it emits the event back to the parent, which updates value and in turn fires that change in the wysiwyg window. Here's the entire thing in action: https://jsfiddle.net/Lnpmbpcr/