I have a problem with my generic input fields.
In other words I've made a generic input field which should cover regular input fields, checkboxes and radiobuttons. But when I try to pass a string value as a value of the radio input field, the prop is empty.
<TextInput
v-model="name"
description="Name & LastName"
name="Name & Surname"
rules="required"
/>
<TextInput
v-model="age"
type="number"
description="Age"
name="Age"
rules="required|digits:2"
/>
<div id="gender-fields">
<legend>Please specify your gender:</legend>
<TextInput
v-model="gender"
type="radio"
description="Male"
name="Gender"
rules="required"
/>
<TextInput
v-model="gender"
type="radio"
description="Female"
name="Gender"
rules="required"
/>
<TextInput
v-model="gender"
type="radio"
description="Unspecified"
name="Gender"
rules="required"
/>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
This is how I'm declaring my input fields in my form.
Here's the definiton of the Input Field.
<template>
<ValidationProvider
tag="div"
:rules="rules"
:name="name"
:vid="vid"
v-slot="{ errors }"
:mode="mode"
>
<label>
{{ description }}
<input :type="type" v-model="currentValue" :value="value" />
</label>
<span>{{ errors[0] }}</span>
</ValidationProvider>
</template>
<script>
import { ValidationProvider } from "vee-validate";
export default {
name: "TextInput",
components: {
ValidationProvider
},
props: {
description: {
type: String,
default: ""
},
value: {
required: true
},
rules: {
type: [String, Object],
default: ""
},
name: {
type: String,
default: ""
},
vid: {
type: String,
default: undefined
},
type: {
type: String,
default: "text"
},
mode: {
type: String,
default: "aggressive"
}
},
data: () => ({
currentValue: ""
}),
watch: {
currentValue(val) {
// allows us to use v-model on our input.
this.$emit("input", val);
}
}
};
</script>
<style></style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
THe only input fields I have a problem with are those radio buttons. Is there something I'm missing?
The easiest way to fix this is to skip putting :value="value" on the input and change your watch like this:
watch: {
//watch for value to change and assign it to our currentValue
value: {
handler(val) {
this.currentValue = val;
},
//this makes it run the handler function on mount in addition to whenever the value changes
immediate:true
},
currentValue(val) {
// allows us to use v-model on our input.
this.$emit("input", val);
}
}
Related
I want to validate using vue vee-validate and baseinputs. But I cannot use v-model where I call the component.
my TextInput Component.
<template>
<div
class="TextInput"
:class="{ 'has-error': !!errorMessage, success: meta.valid }"
>
<label :for="name">{{ label }}</label>
<input
:name="name"
:id="name"
:type="type"
:value="inputValue"
:placeholder="placeholder"
#input="handleChange"
#blur="handleBlur"
/>
<p class="help-message" v-show="errorMessage || meta.valid">
{{ errorMessage || successMessage }}
</p>
</div>
</template>
<script>
import { useField } from "vee-validate";
export default {
props: {
type: {
type: String,
default: "text",
},
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
successMessage: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "",
},
},
setup(props) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
return {
handleChange,
handleBlur,
errorMessage,
inputValue,
meta,
};
},
};
App.vue file where I call the component
<template>
<div>
{{ test }}
<Form
#submit="onSubmit"
:validation-schema="schema"
#invalid-submit="onInvalidSubmit"
>
<TextInput
name="name"
type="text"
label="Full Name"
placeholder="Your Name"
success-message="Nice to meet you!"
v-model="test"
/>
<button class="submit-btn" type="submit">Submit</button>
</Form>
</div>
</template>
<script>
import { ref } from "vue";
import { Form } from "vee-validate";
import * as Yup from "yup";
import TextInput from "./components/TextInput.vue";
export default {
name: "App",
components: {
TextInput,
Form,
},
setup() {
const test = ref("asda");
function onSubmit(values) {
alert(JSON.stringify(values, null, 2));
}
function onInvalidSubmit() {
const submitBtn = document.querySelector(".submit-btn");
submitBtn.classList.add("invalid");
setTimeout(() => {
submitBtn.classList.remove("invalid");
}, 1000);
}
const schema = Yup.object().shape({
name: Yup.string().required(),
email: Yup.string().email().required(),
password: Yup.string().min(6).required(),
confirm_password: Yup.string()
.required()
.oneOf([Yup.ref("password")], "Passwords do not match"),
});
return {
onSubmit,
schema,
onInvalidSubmit,
test,
};
},
};
</script>
where I wrote in v-model ="test" doesn't work. I tried to send it with emit but I couldn't.
So, in summary, let the component I validate perform the validation process, but I can use the v-model where I call the component.
I'm writing a unit test with vue 3 using vee-validate 4 and Jest. But I'm new to this and I'm stuck at one place.
I have a TextInput component that I use validations, and when I call this component, I do validation when submit is done.
There is a separate component where I write the form where I use these textInputs.
First let me show you the code in my TkTextInput component.
<template>
<div
class="tk-Input"
:class="{ 'has-error': !!errorMessage, success: meta.valid }"
>
<label class="tk-label" :for="name">{{ label }}</label>
<input
id="demos"
class="col-12"
v-model="inputValue"
:name="name"
:type="type"
:value="inputValue"
:placeholder="placeholder"
#input="handleChange"
#blur="handleBlur"
v-bind="$attrs"
/>
<p class="help-message" v-show="errorMessage || meta.valid">
{{ errorMessage }}
</p>
</div>
</template>
<script>
import {useField} from "vee-validate";
import {watch} from "vue";
export default {
props: {
type: {
type: String,
default: "text",
},
modelValue: String,
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
},
placeholder: {
type: String,
default: "",
},
},
emits: ['update:modelValue'],
setup(props, {emit}) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
watch(inputValue, (val) => {
emit('update:modelValue', val);
});
watch(() => props.modelValue, (val) => {
if (val !== inputValue.value) {
inputValue.value = val;
}
})
return {
handleChange,
handleBlur,
errorMessage,
meta,
inputValue,
};
},
};
</script>
Then in my form component where I call these textInputs is as follows.
<Form
#submit="onSubmit"
:validation-schema="schema">
<div class="grid ">
<div class="col-12 lg:col-6 lg:mb-0">
<tk-text-input v-model.trim="vehicleInfo.Plate" label="Plaka*" name="Plate" type="text"/>
</div>
<div class="grid ">
<div class="col-12 lg:col-6 lg:mb-0">
<tk-text-input v-model.trim="vehicleInfo.PhoneNumber" label="Cep Telefonu*" name="PhoneNumber"/>
</div>
<div class="col-12 lg:col-6 lg:mb-0">
</div>
</div>
<Button #click="clicked" class=" p-button-success" type="submit">{{buttonLabel}}</Button>
</Form>
Now I want to test the validation process in the TkTextInput component when the button clicks when I click the button in the component where I wrote the form with gesture. But I couldn't do it.
The test I wrote in the .spec file is as follows.
describe('TkTextInput.vue', () => {
it('when validation is done', async() => {
const wrapperVehicle = mount(VehicleInfoDialog, {
global:{
plugins: [PrimeVue]
},
})
const button = wrapperVehicle.find("button")
button.trigger('submit')
await button.trigger("click")
expect(wrapperVehicle.html()).toContain("Boş Geçilemez.")
});
})
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)">
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
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.