Using vuelidate on element inside an object array - vue.js

I am trying to validate a form with vuelidate where it it shows the same amount of field depending on how many users i want to add, so if i want to add 3 users than 3 Name and age fields will show up.
However when i try to use #blur to validate the fields i always get an "undefined" error even thouygh i already set up may validations corectly. What exacly am i doing wrong?
<div v-for="(user, index) in users" :key="index" >
<div>
<Input
label="name"
v-model="user.name"
#blur="$v.user.name.$touch()"
/>
</div>
<div >
<Input
label="Date"
mask="##/##/####"
v-model="user.birth"
#blur="$v.user.birth.$touch()"
/>
</div>
</div>
data() {
return {
users: [
{
name: '',
birth: '',
}
],
}
validations: {
benefs: {
$each: {
name: {
required
},
birth: {
required
}
}
},

Did you import the library on your page?
You can try to use
#blur="$v.user.name.required"

Related

How to register an array of objects with v-model? Vue 2

The first time that I dive into making this type of form with Vue, the issue is that I can't think of how to save the data inside the foreach that I generate with axios.
Where I would like to save the ID and the option selected with the input select as an object in order to make faster the match in the backend logic.
<template>
<div class="row" v-else>
<div class=" col-sm-6 col-md-6 col-lg-6" v-for="(project, index) in projects" :key="index">
<fieldset class="border p-2 col-11">
<legend class="w-auto col-12">Proyecto: {{project.name}}</legend>
<b-form-group
id="user_id"
label="Reemplazante"
>
<b-form-select
v-model="formProject[index].us"
:options="project.users"
value-field="replacement_user_id"
text-field="replacement_user_name"
#change="addReemplacemet($event,project.id)"
>
<template v-slot:first>
<b-form-select-option value="All">Seleccione</b-form-select-option>
</template>
</b-form-select>
<input type="hidden" name="project" v-model="formProject[index].proj">
</b-form-group>
</fieldset>
</div>
</div>
</template>
<script>
import skeleton from './skeleton/ProjectUserSkeleton.vue'
export default {
name: 'ProjectsUser',
components: { skeleton },
props:{
user: { type: String },
},
data() {
return {
user_id: null,
showProject: false,
projects: [],
loadingProjects: true,
formProject: [
{
us: 'All',
proj: null
}
]
}
},
watch: {
user: function() {
this.viewProjects(this.user)
}
},
methods: {
async getProjects(salesman){
this.loadingProjects = true
await axios.get(route('users.getProjects'),{
params: {
filter_user: salesman
}
})
.then((res)=>{
this.projects = res.data.data
setTimeout(() => {
this.loadingProjects = false
}, 800);
})
},
This is the form:
This is the message error:
At first glance, this looks like an issue with data. Your error suggests that you're trying to read an undefined variable.
It's often undesirable to use an index from iterating one array on another. In your Vue code, the index is projects, but you use it to access the variable formProject. This is usually undesirable because you can no longer expect that index to reference a defined variable (unless you're referencing the array you are currently iterating).
The easiest solution, for now, is to make sure the arrays are of the same length. Then utilizing v-if or other methods to not render the snippet if the variable is not defined.
A more complicated but better solution is restructuring your data such that formProject exists in projects

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>

Vuelidate check box validation

I am using Vue.js and I am new on it. I am currently working on validation. I had used vuelidate as my validation library. I had successfully done form validation, but trouble came when I had to check validation for check box.
How can I check validation for check box? Also, I had used bootstrapvue to display check box.
<b-col lg="6" md="6" sm="6">
<label>Bus Route</label>
<b-form-group>
<b-form-checkbox v-for="route in busRouteList"
v-model.trim="selectedRoute"
v-bind:value="route.value"
v-bind:unchecked-value="$v.selectedRoute.$touch()">
{{route.text}}</b-form-checkbox>
</b-form-group>
<div class="form-group__message" v-if="$v.selectedRoute.error && !$v.selectedRoute.required">
Field is required
</div>
</b-col>
validations: {
selectedRoute: {
required
},
}
As false is also valid value so, you should try to use sameAs
import { sameAs } from 'vuelidate/lib/validators'
terms: {
sameAs: sameAs( () => true )
}
You should bind #change methods:
<b-form-checkbox v-for="route in busRouteList"
v-model.trim="selectedRoute"
v-bind:value="route.value"
#change="$v.selectedRoute.$touch()">
and you might want to use custom function:
selectedRoute: {
checked (val) {
return val
}
},
This worked for me.
Basically, you need to make its value 'sameAs' a boolean 'true', which means the checkbox is checked.
So, i.e:
privacyCheck: {
sameAs: sameAs(true)
},
I hope this little example will help you to understand how to validate a checkbox.
You have to check when the input is changing. I recommend you to use #change.
In template
<div class="input">
<label for="country">Country</label>
<select id="country" v-model="country">
<option value="usa">USA</option>
<option value="india">India</option>
<option value="uk">UK</option>
<option value="germany">Germany</option>
</select>
</div>
<div class="input inline" :class="{invalid: $v.terms.$invalid}">
<input type="checkbox" id="terms" v-model="terms" #change="$v.terms.$touch()">
<label for="terms">Accept Terms of Use</label>
</div>
So the terms will be valid if selected country will be germany.
validations: {
terms: {
checked(val) {
return this.country === "germany" ? true : val;
}
}
}
of course country, terms are defined in data():
country:'',
terms: false
`
validations: {
terms: {
checked: (val) => {return val;}
}
}
`
With vuelidate-next (for both Vue 2 and Vue 3 support) it's so simple as using sameAs built-in validator with true as a direct parameter. For example, when using inside a setup method:
const termsAccepted = ref(false)
const v$ = useVuelidate(
{ termsAccepted: { sameAs: sameAs(true) } },
{ termsAccepted }
)
return { v$, termsAccepted }

How To Create a Reusable Form Vue Component

Let's say I want to create a contact form. In this contact form, a user can have multiple addresses. I would think that this is a perfect opportunity to use a Vue Component so that I don't have to create redundant address form fields. I would then be able to use this component in different areas of a website, say on edit, create, etc....
How would I go about creating a form component that a parent can use and have values from that form get added to an addresses array? Also, I would like to be able to populate this form with existing values if it's being edited.
I've tried different things but nothing I've tried seems to work. Any ideas? I've searched Stack and Google but haven't been able to find an answer.
Here is some starting code of what I'm trying to accomplish (crude example). Of course, I would allow a user to dynamically add multiple addresses on creation/edit but I would also on edit of the form loop through the addresses data to display each address component but this is just a quick non-working setup to get my point across.
AddressComponent.vue
<template>
<div>
<h4>Address</h4>
<label>Address</label>
<input type="text" v-model="address">
<label>City</label>
<input type="text" v-model="city">
<label>State</label>
<input type="text" v-model="state">
</div>
</template>
<script>
export default {
data() {
return {
address: null,
city: null,
state: null
}
}
}
</script>
ContactForm.vue
<template>
<h1>My Contact Form</h1>
<form>
<AddressComponent></AddressComponent> // Addresses[0]
<AddressComponent></AddressComponent> // Addresses[1]
</form>
</template>
<script>
import AddressComponent from '../components/AddressComponent'
export default {
components: {AddressComponent},
data() {
return {
addresses: [],
}
}
}
</script>
Perhaps something like this, pass in the data and then emit the change back to the parent.
Vue.component('address-component', {
template: '#address',
props: ['data', 'index'],
data() {
return {
item: {
address: this.data.address,
city: this.data.city,
state: this.data.state
}
}
},
methods: {
inputOccurred() {
this.$emit('on-change', this.item, this.index)
}
}
});
//
var vm = new Vue({
el: '#app',
data() {
return {
addresses: [{
address: '1 Stackoverflow Way',
city: 'San Fran',
state: 'California'
},
{
address: '2 Stackoverflow Way',
city: 'San Fran',
state: 'California'
}
]
}
},
methods: {
setAddress(value, index) {
this.$set(this.addresses, index, value);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
<div id="app">
<h1>My Contact Form</h1>
<address-component
v-for="(address, index) in addresses"
:data="address"
:index="index"
:key="index"
#on-change="setAddress"
></address-component>
<pre>{{ addresses }}</pre>
</div>
<template id="address">
<div>
<h4>Address</h4>
<label>Address</label>
<input type="text" v-model="item.address" #input="inputOccurred"/>
<label>City</label>
<input type="text" v-model="item.city" #input="inputOccurred"/>
<label>State</label>
<input type="text" v-model="item.state" #input="inputOccurred"/>
</div>
</template>

Watch date input value and use it with v-model

I tried for 4 days to get a stupid date from the input, that the user selected. Looks probably simple but believe me that I ask here because I run out of solutions.
I have a page/component AddNewEvent and inside this input where the user adds the date and time. I have to get it in my v-model so I can send it back to database.
I use bootstrap 4 input type="datetime-local" to get the date and time. I tried to use some vue plugins for date but all are based on bootstrap 3 and in the project is bootstrap 4.
Inside template:
<div class="form-group">
<label class="eda-form-label" for="event-name">Event Name <span>(Max characters number is 60)</span></label>
<input type="text" class="eda-form-input" v-model="newEvent.eventName">
</div>
<div class="form-group">
<label class="eda-form-label" for="exampleTextarea">Event Description <span>(Max characters number is 2000)</span></label>
<textarea class="eda-form-input" rows="3" v-model="newEvent.description"></textarea>
</div>
<div class="form-group">
<div class="row">
<div class="col-6 ">
<label class="eda-form-label" for="start-time">START TIME</label>
<input class="form-control" type="datetime-local" v-model="dateNow">
</div>{{createdDate(value)}}
<div class="col-6">
<label class="eda-form-label" for="end-time">END TIME</label>
<input class="form-control" type="datetime-local" v-model="dateEnd">
</div>
</div>
</div>
In the script:
data() {
return {
dateNow: '',
value: '',
oldValue: ''
}
},
watch: {
dateNow(val, oldVal) {
this.value = val;
this.oldValue = oldVal;
}
},
methods: {
createEvent(){
axios.post("/event", this.newEvent,
{'headers':{'X-AUTH-TOKEN': localStorage.token}},
{'headers':{'Content-Type': 'application/json'}})
.then((response) => {
alertify.success("Success! You added a new the user");
this.$router.push('/events');
})
.catch((response) => {
alertify.error();
})
},
}
If I use in the input, how is right now, v-model="dateNow" works. I can see when I select date, time am/pm shows me the seleted date. But I have to use it like this
v-model="newEvent.dateNow"
v-model="newEvent.dateEnd"
so I can add it to newEvent and send the whole obj back to database.
createdDate is a function that transforms the date in a real date. It's only for testing, because I have to send the date in ms back to database.
Someone please show me what I do wrong, because I'm not so very advance in vuejs.
First you need to initialize dateNow varable in datetime-locale:
data() {
return {
dateNow: new Date().toLocaleString(),
value: '',
oldValue: ''
}
},
It support this format: "2017-09-18T08:30".
I think this will solve your problem.
And also you need to check Browser compatibility, as it is not supported in IE, Firefox and Safari
or you can try:
<input type="datetime-local" :value="dateValue" #input="updateValue($event.target.value)" >
in script:
data() {
return {
dateValue: new Date().toLocaleString()
};
},
methods: {
updateValue: function(value) {
this.dateValue= value;
this.$emit('input', value);
}
}
Try this