I want to create a formRow component that have a slot for the input field and vee-validate for validation
this is my markup
//form
<vFormRow validate="required" v-slot="{ onInput, errors }">
<vTextfield #input="onInput" :errors="errors"/>
</vFormRow>
I tried to emit the value to the parent and get the value with #input="onInput" on the slot, but this doesn't work
//formrow
<template>
<div class="mb-4">
<v-validation-provider
v-slot="{ errors }"
:rules="validate"
>
<label>{{ label }}</label>
<slot
:onInput="onInput"
:errors="errors"
/>
<vFormError
:message="errors[0]"
/>
</v-validation-provider>
</div>
</template>
<script>
import { ValidationProvider as vValidationProvider } from 'vee-validate'
import vRadioButton from '#primitives/textfield'
import vFormError from '#primitives/formError'
export default {
components: {
vTextfield,
vFormError,
vValidationProvider
},
props: {
value: {
type: String,
default: '',
},
validate: {
type: String,
required: true,
}
},
methods: {
onInput(value) {
console.log(value)
}
}
}
</script>
//textfield
<template>
<div>
<input :value="value" #input="onInput"/>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
errors: {
type: Array,
default: () => {}
}
},
data: {
return {
// local_value: ''
}
}
methods: {
onInput(e) {
// this.local_value = e.target.value
this.$emit('input', e.target.value)
}
}
}
</script>
What I'm doing wrong?
You're not actually passing the slot props to <vTextfield>. I think you might be assuming that data bindings and event handlers on the <slot> are automatically applied to the slot's children, but that's not true.
To use the value slot prop, destructure it from v-slot, and bind it to the <vTextfield> child:
<vFormRow validate="required" v-slot="{ value }">
<vTextfield :value="value" />
</vFormRow>
You could also pass the onInput method as a slot prop:
// vFormRow.vue
<slot :onInput="onInput" />
Then bind the event handler to <vTextfield>:
<vFormRow validate="required" v-slot="{ onInput }">
<vTextfield #input="onInput" />
</vFormRow>
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>
So I have encountered a weird issue with my code, that I hope to get some help with.
I have a custom "Input" component where I have a normal HTML input with some styling. I have then called this component with a value and a function to call upon changes. No v-model is used as I have to do some validation on the field. However, it doesn't work. I can see that the value variable in the "Input" component changes correctly, but it does not impact the HTML input element at all, if you enter multiple values into the input field. How can this be?
InputComponent
<template>
<label class="block text-sm flex justify-end lg:justify-start w-28 h-10">
<span class="text-gray-800">{{ label }}</span>
<input
class="block text-black placeholder:text-black placeholder:opacity-40 w-14 lg:w-full rounded-lg text-center"
:placeholder="placeholder"
:type="type"
:value="value"
#input="handleInput($event.target.value)"
/>
</label>
</template>
<script>
export default {
props: {
label: String,
placeholder: String,
type: String,
value: String,
size: String,
},
methods: {
handleInput(value) {
this.$emit('input', value);
}
},
}
</script>
Page component calling Input
<template>
<Input
type="number"
placeholder="0"
size="sm"
:value="test"
#input="changePlannedVacationDay($event)"
/>
</template>
<script>
export default {
data() {
return {
test: ""
};
},
methods: {
changePlannedVacationDay(value) {
let localValue = value;
const maxValue = 5;
if (parseInt(localValue) < 0) {
localValue = "0";
} else if (parseInt(localValue) > maxValue) {
localValue = maxValue.toString();
}
this.test = localValue;
}
},
</script>
You should use a computed property with getter and setter:
<template>
<input v-model="localModel">
</template>
<script>
export default
{
name: 'CustomInputComponent',
props:
{
value:
{
type: String,
default: null,
},
},
computed:
{
localModel:
{
get()
{
return this.value;
},
set(val)
{
this.$emit('input', val);
}
},
},
}
</script>
In the parent component you should use a watcher to detect value changes and act upon them.
Vue 3
I am trying to update the value of the data variable from the Axios response. If I print the value in the parent component it's getting printed and updates on the response but the variable's value is not updating in the child component.
What I am able to figure out is my child component is not receiving the updated values. But I don't know why is this happening.
input-field is a global component.
Vue 3
Parent Component
<template>
<input-field title="First Name" :validation="true" v-model="firstName.value" :validationMessage="firstName.validationMessage"></input-field>
</template>
<script>
export default {
data() {
return {
id: 0,
firstName: {
value: '',
validationMessage: '',
},
}
},
created() {
this.id = this.$route.params.id;
this.$http.get('/users/' + this.id).then(response => {
this.firstName.value = response.data.data.firstName;
}).catch(error => {
console.log(error);
});
},
}
</script>
Child Component
<template>
<div class="form-group">
<label :for="identifier">{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input :id="identifier" :type="type" class="form-control" :class="validationMessageClass" :placeholder="title" v-model="inputValue">
<div class="invalid-feedback" v-if="validationMessage">{{ validationMessage }}</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
validation: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: 'text',
},
validationMessage: {
type: String,
required: false,
default: '',
},
modelValue: {
required: false,
default: '',
}
},
emits: [
'update:modelValue'
],
data() {
return {
inputValue: this.modelValue,
}
},
computed: {
identifier() {
return this.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
},
validationMessageClass() {
if (this.validationMessage) {
return 'is-invalid';
}
return false;
}
},
watch: {
inputValue() {
this.$emit('update:modelValue', this.inputValue);
},
},
}
</script>
The reason your child will never receive an update from your parent is because even if you change the firstName.value your child-component will not re-mount and realize that change.
It's bound to a property that it internally creates (inputValue) and keeps watching that and not the modelValue that's been passed from the parent.
Here's an example using your code and it does exactly what it's supposed to and how you would expect it to work.
It receives a value once (firstName.value), creates another property (inputValue) and emits that value when there's a change.
No matter how many times the parent changes the firstName.value property, the child doesn't care, it's not the property that the input v-model of the child looks at.
You can do this instead
<template>
<div class="form-group">
<label :for="identifier"
>{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input
:id="identifier"
:type="type"
class="form-control"
:class="validationMessageClass"
:placeholder="title"
v-model="localValue" // here we bind localValue as v-model to the input
/>
<div class="invalid-feedback" v-if="validationMessage">
{{ validationMessage }}
</div>
</div>
</template>
<script>
export default {
... // your code
computed: {
localValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
We remove the watchers and instead utilize a computed property which will return the modelValue in it's getter (so whenever the parent passes a new value we actually use that and not the localValue) and a setter that emits the update event to the parent.
Here's another codesandbox example illustrating the above solution.
I want to add a v-model on a component but I got this warning:
[Vue warn]: Component emitted event "input" but it is neither declared in the emits option nor as an "onInput" prop.
Here is my code:
// Parent.vue
<template>
<h2>V-Model Parent</h2>
<Child v-model="name" label="Name" />
<p>{{ name }}</p>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const name = ref('')
</script>
// Child.vue
<template>
<input
class="input"
type="text"
:placeholder="props.label"
:value="props.value"
v-on:input="updateValue($event.target.value)"
/>
</template>
<script setup>
import { defineProps, defineEmit } from 'vue'
const props = defineProps({
label: String,
value: String
})
const emit = defineEmit('input')
function updateValue(value) {
emit('input', value)
}
</script>
I was trying to reproduce this tutorial but I'am stuck and got no idea what I am missing.
I want to display {{ name }} in the Parent.vue component. Do you got an idea how to solve this?
In vue 3 value prop has been changed to modelValue and the emitted event input to update:modelValue:
// Child.vue
<template>
<input
class="input"
type="text"
:placeholder="props.label"
:value="props.modelValue"
v-on:input="updateValue($event.target.value)"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue'])
function updateValue(value) {
emit('update:modelValue', value)
}
</script>
I like to use with computed as well
<template>
<div>
<input v-model="model">
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const model = computed({
get () {
return props.modelValue
},
set (value) {
return emit('update:modelValue', value)
}
})
</script>
I have the similar issues and finally I got it work. Here are one solution for one or multiple checkbox for Vue 3 and TypeScript.
ref: https://v2.vuejs.org/v2/guide/forms.html?redirect=true#Checkbox
solution : for one or multiple checkbox
CheckBox Component:
<template>
<input
type="checkbox"
:value="inputValue"
:disabled="isDisabled"
v-model="model"
:class="[defaultClass, inputClass, checkboxClass]"
/>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
export default defineComponent({
components: {},
props: {
inputValue: {
type: String,
required: false,
default: '',
},
modelValue: {
type: [Object, Boolean] as PropType<String[] | Boolean>,
required: false,
default: (() => ({})) || false,
},
isDisabled: {
type: Boolean,
required: false,
default: false,
},
checkboxClass: {
type: String,
required: false,
default: '',
},
},
data() {
return {
defaultClass: 'h-4 w-4 rounded text-primary shadow-sm',
};
},
emits: ['update:modelValue'],
computed: {
model: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
},
},
inputClass() {
if (this.isDisabled) {
return 'bg-dark-17 border-dark-13';
}
return 'bg-dark-23 border-dark-10 hover:bg-dark-25 focus:border-primary';
},
},
});
</script>
import CheckBox and use it
import CheckBox in other components;
<div>
<div v-for="(option, index) in options" :key="index">
<div
class="flex items-center justify-between p-6 py-4 border-b border-b-dark-13"
>
<div class="w-10">
<Checkbox :inputValue="option.name" v-model="selectedOptions" />
</div>
</div>
</div>
</div>
data() {
return {
selectedOptions: [],
};
},
I am able to build a simple textbox component from <input /> and setup v-model binding correctly.
I'm trying to do same with a custom component: vs-input from vuesax.
Following the pattern below does not work as expected:
<template>
<div>
<vs-input type="text" v-model="value" #input="text_changed($event)" />
<!-- <input type="text" :value="value" #input="$emit('input', $event.target.value)" /> -->
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
methods: {
text_changed(val) {
console.log(val)
// this.$emit('input', val)
}
}
}
</script>
In building custom components from other custom components is there anything particular we should look out for to get v-model binding working properly?
Following code might help you.(Sample code try it in codepen)
updating props inside a child component
//html
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<input type="text" :value="test" #change="abc">
{{ test }}
</div>
//VUE CODE
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
},
props:{
test:{
type:String,
default:''
}
},
methods:{
abc:function(event){
//console.log("abc");
console.log(event.target.value);
this.test=event.target.value;
}
}
})
I prefer to interface props with computed:
<template>
<div>
<vs-input type="text" v-model="cValue" />
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
computed: {
cValue: {
get: function(){
return this.value;
},
set: function(val){
// do w/e
this.$emit('input', val)
}
}
}
}
</script>
Computed Setter