Vee-Validate custom date validation - vue.js

I was wondering if there is anyway you can write a custom date validation using vee-validate plugin where the end date cannot be less than the start date? I have looked high and low, and there is nowhere I can find a definite answer to this.
If there is no way to implement this, then I can make do without it, however, right now what I have implemented in my template for my start date is:
<input type="text" id="startDate" name="startDate" class="form-control" v-model="startDate" v-validate="'required|date_format:DD-MM-YYYY'" :class="{'input': true, 'is-danger': errors.has('startDate') }">
<label class="mb-0" for="startDate">Start Date</label>
<span v-show="errors.has('startdate')" class="text-danger"><center>{{ errors.first('startdate') }}</center></span>
My script looks like this:
export default {
name: 'App',
data: () => ({
task: '',
startDate: '',
startTime: '',
endDate: '',
endTime: '',
description: 'test'
}),
methods: {
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
// eslint-disable-next-line
alert('Form Submitted!');
return;
}
alert('Correct them errors!');
});
}
}
};
But there is no validation that is showing up. I think I am missing something in my script but I am not sure how to implement the date into there. Any help would be greatly appreciated.

First, it's maybe some typo error but in your template you use startDate and startdate lowercased.
Then, to answer your question, it's possible to define a custom validator with a date comparison with vee-validate.
As your chosen date format "DD-MM-YYYY" is not a valid javascript Date format, the string dates need to be rewritten into a valid format to make it work.
Vue.use(VeeValidate)
new Vue({
el: "#app",
data() {
return {
startDate: '',
endDate: '',
}
},
created() {
let self = this
this.$validator.extend('earlier', {
getMessage(field, val) {
return 'must be earlier than startDate'
},
validate(value, field) {
let startParts = self.startDate.split('-')
let endParts = value.split('-')
let start = new Date(startParts[2], startParts[1] -1, startParts[0]) // month is 0-based
let end = new Date(endParts[2], endParts[1] -1, endParts[0])
return end > start
}
})
},
methods: {
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
alert('Form Submitted!');
return;
}
alert('Correct them errors!');
});
}
}
})
.is-danger, .text-danger {
color: red;
}
<script src="https://unpkg.com/vee-validate#2.0.0-rc.19/dist/vee-validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div>
<input type="text" name="startDate" v-model="startDate"v-validate="'required|date_format:DD-MM-YYYY'" :class="{'input': true, 'is-danger': errors.has('startDate') }">
<label class="mb-0" for="startDate">Start Date</label>
<span v-show="errors.has('startDate')" class="text-danger">{{ errors.first('startDate') }}</span>
</div>
<div>
<input type="text" name="endDate" v-model="endDate" v-validate="'required|date_format:DD-MM-YYYY|earlier'" :class="{'input': true, 'is-danger': errors.has('endDate') }">
<label class="mb-0" for="endDate">End Date</label>
<span v-show="errors.has('endDate')" class="text-danger">{{ errors.first('endDate') }}</span>
</div>
<button #click="validateBeforeSubmit">Save</button>
</div>
Note: i put the custom validator inside the created hook for the example but you can put it inside any file you want in your projet. Just import it correctly as the documentation recommends.

Related

How to toggle between DIVs with bound data in Vue?

So the problem I'm facing is that I have a template that fetches data from a Realtime Firebase Database and at the same time the user can import more data through an input element. I'm using Vue.js and I need the data to be bound to each other.
Here is my template:
<template>
<ul>
<li>
<input type="text" v-model="email" v-on:keyup.enter="addData()"/>
<img #click="addData()" src="#/assets/Plus.png" />
</li>
</ul>
<ul>
<li v-for="(item, key) in emails" :key="key">
<div>
<p>{{ item.data }}</p>
<img #click="deleteDataByKey(key)" src="#/assets/Delete.png" />
</div>
<div class="first">
<input type="text" v-model="comment[key]" v-on:keyup.enter="addComment(key, comment[key])"/>
<img #click="addComment(key, comment[key])" src="#/assets/Plus.png" />
</div>
<div class="second">
<p>{{ comment[key] }}</p>
<img #click="deleteCommentByKey(key)" src="#/assets/Delete.png" />
</div>
</li>
</ul>
</template>
Now what is happening is that I want to show <div class="first"> when there is no comment and when there is one, <div class="second"> should be shown while hiding the first one.
I tried using v-if="comment[key]" but it will toggle the divs straight away.
I also tried to v-model.lazy which seems to be working but then the method to update the db is not called.
I tried using pure JS in the method to change the HTML but it doesn't seem to be working as well.
These are my methods and data:
data() {
return {
emailList: [],
email: "",
comment: []
};
},
addData() {
db.ref("emailItems").push({
data: data
});
this.email = "";
this.fetchData();
},
deleteDataByKey(key) {
db.ref("emailItems"+key).remove();
this.fetchData();
},
addComment(key, comment) {
db.ref(`emailItems/${key}/comment`).set(comment);
},
deleteCommentByKey(key){
db.ref("comment/"+key).remove();
this.fetchData();
},
fetchData() {
db.ref("emailItems")
.once("value")
.then(snapshot => {
this.emailList = snapshot.val().emailItems;
});
}
And the db structure looks like this
Any help would be highly appreciated...
I think you should build more on the (arguably) biggest features of Vue, namely: reactivity & components.
Break down the logic a bit more, until you arrive at elements that do only one thing - those elements can be your components. Then build up the business logic from those atomic components.
Vue.component('NewEmail', {
data() {
return {
email: null,
}
},
methods: {
handleKeyup() {
this.$emit("add-email", {
email: this.email,
comment: null
})
this.email = null
}
},
template: `
<div>
<label>
NEW EMAIL: <input
type="email"
placeholder="Type in an email"
v-model="email"
#keyup.enter="handleKeyup"
/>
</label>
</div>
`
})
Vue.component('SingleEmailRow', {
props: {
email: {
type: Object,
default: null,
}
},
methods: {
handleDeleteClick() {
this.$emit('remove-email', this.email)
},
},
template: `
<li
class="d-flex"
>
<div>
{{ email.email }}
</div>
<div>
<button
#click="handleDeleteClick"
>
X
</button>
</div>
<component
:is="email.comment ? 'HasEmailComment' : 'NoEmailComment'"
:email="email"
v-on="$listeners"
></component>
</li>
`
})
Vue.component('HasEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: null
})
}
},
template: `
<div
class="d-flex"
>
<div>
{{ email.comment }}
</div>
<button
title="Remove comment"
#click="handleUpdateComment"
>
-
</button>
</div>
`
})
Vue.component('NoEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
data() {
return {
comment: null,
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: this.comment
})
}
},
template: `
<div
class="d-flex"
>
<div>
<input
v-model="comment"
type="text"
placeholder="Write a comment"
/>
</div>
<button
title="Add comment"
#click="handleUpdateComment"
>
+
</button>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
emailList: [],
}
},
methods: {
handleAddEmail(newEmail) {
if (!this.emailList.find(({
email
}) => email === newEmail.email)) {
this.emailList.push(newEmail)
}
},
handleRemoveEmail(toRemove) {
this.emailList = this.emailList.filter(({
email
}) => {
return email !== toRemove.email
})
},
handleUpdateComment(newEmail) {
this.emailList = this.emailList.map(email => {
if (email.email === newEmail.email) {
return newEmail
} else {
return email
}
})
}
}
})
.d-flex {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<new-email #add-email="handleAddEmail"></new-email>
<ul v-for="(email, i) in emailList" :key="i">
<single-email-row :email="email" #remove-email="handleRemoveEmail" #update:comment="handleUpdateComment"></single-email-row>
</ul>
</div>
OK, the comment handling in SingleEmailRow could be put to its separate component (based on my thoughts above).
The main points in the snippet above are:
there's only one original data source (emailList in the root component) that is passed down as props where needed, and all the other components & functions just manipulate THAT list via events (reactivity is great!)
because all the components are based on one central data source, they just have to work with the item they get as props. (Ok, they have some internal state, but hey - this is only a snippet! :) )
the two components have only one responsibility:
NewEmail: to add an item to the central emailList
SingleEmailRow: to manage the data in ONE email item
I hope this helps you a bit to reach a solution for your problem.
EDIT: UPDATING SNIPPET
I had to update the snippet, because I wasn't satisfied with it.
Modifications:
adding two new components: HasEmailComment, NoEmailComment
updated SingleEmailRow with the two new components
Now, all the components have only one task to do.

v-model input working after pressing enter

I am working in Vue
My input search bar is filtering after every letter that I type. I want it to filter after I pressed the enter key.
Can somebody help me please?
<template>
<div id="show-blogs">
<h1>All Blog Articles</h1>
<input type="text" v-model="search" placeholder="Find Car" />
<div v-for="blog in filteredBlogs" :key="blog.id" class="single-blog">
<h2>{{blog.title | to-uppercase}}</h2>
<article>{{blog.body}}</article>
</div>
</div>
</template>
<script>
export default {
data() {
return {
blogs: "",
search: ""
};
},
methods: {},
created() {
this.$http
.get("https://jsonplaceholder.typicode.com/posts")
.then(function(data) {
// eslint-disable-next-line
console.log(data);
this.blogs = data.body.slice(0, 10);
});
},
computed: {
filteredBlogs: function() {
return this.blogs.filter(blog => {
return blog.title.match(this.search);
});
}
}
};
</script>
There are a few ways you could accomplish this. Probably the most accessible would be to wrap the input in a form and then user the submit event to track the value you want to search for. Here's an example:
<template>
<div id="show-blogs">
<h1>All Blog Articles</h1>
<form #submit.prevent="onSubmit">
<input v-model="search" type="text" placeholder="Find Car" />
</form>
</div>
</template>
export default {
data() {
return {
search: '',
blogSearch: '',
};
},
computed: {
filteredBlogs() {
return this.blogs.filter(blog => {
return blog.title.match(this.blogSearch);
});
},
},
methods: {
onSubmit() {
this.blogSearch = this.search;
},
},
};
Notice that blogSearch will only be set once the form has been submitted (e.g. enter pressed inside the input).
Other notes:
You'll probably want to trim your search value
You should add a label to your input.
You could skip using v-model and instead add a keyup event handler with the .enter modifier that sets the search data property
<input type="text" :value="search" placeholder="Find Car"
#keyup.enter="search = $event.target.value" />
Demo...
new Vue({
el: '#app',
data: () => ({ search: '' })
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<input type="text" :value="search" placeholder="Find Car"
#keyup.enter="search = $event.target.value" />
<pre>search = {{ search }}</pre>
</div>

Retrieve data attribute value of clicked element with v-for

I've made a datalist which is filled dynamically and it works correctly.
Now, I need listen the click event on the options to retrieve the data-id value and put it as value in the input hidden.
I already tried with v-on:click.native and #click but there is no response in the console.
Any idea? I'm just starting at Vue, hope you can help me.
Edit:
Looks like it doesn't even fire the function. I've tried v-on:click="console.log('Clicked')" but nothing happens.
<input type="hidden" name="id_discipline" id="id_discipline">
<input list="disciplines" id="disciplines-list">
<datalist id="disciplines">
<option
v-for="discipline in disciplines"
:key="discipline.id_discipline"
:data-id="discipline.id_discipline"
v-on:click="updateDisciplineId($event)"
>{{discipline.name}}</option>
</datalist>
methods: {
updateDisciplineId(event) {
console.log('clicked!);
}
},
Using datalist is not suited for what you want to acheive, however there's a workaround with a limitation.
Template:
<template>
<div>
<input
type="text"
name="id_discipline"
v-model="selectedID"
placeholder="Data id value of clicked"
/>
<input
#input="onChange"
list="disciplines"
id="disciplines-list"
class="form-control"
placeholder="Seleccionar disciplina"
/>
<datalist id="disciplines">
<option
v-for="discipline in disciplines"
:key="discipline.id_discipline"
:data-value="discipline.id_discipline"
>{{ discipline.name }}</option
>
</datalist>
</div>
</template>
Script Part:
<script>
export default {
data() {
return {
selectedID: "",
id_discipline: "",
disciplines: [
{
id_discipline: 1,
name: "Yoga"
},
{
id_discipline: 2,
name: "Functional"
}
]
};
},
methods: {
onChange(e) {
this.getID(e.target.value).then(
resposnse => (this.selectedID = resposnse)
);
},
async getID(value) {
let promise = new Promise((resolve, reject) => {
this.disciplines.forEach(item => {
if (item.name === value) resolve(item.id_discipline);
});
});
return await promise;
}
}
};
</script>
Here's a working Sandbox demo.
**Limitation: Discipline name (Yoga, functional) should be unique.

Vue.js - v-model not tracking changes with dynamic data?

I have the following form where the input fields are dynamically generated however when I update the fields the two way binding isn't happening - nothing is changing when i view the results in dev tools?
<template v-for="field in formFields" :key="field.name">
<div class="form-group" v-if="field.type == text'">
<label class="h4" :for="field.label" v-text="field.label"></label>
<span class="required-asterisk" v-if="field.required"> *</span>
<input :class="field.className"
:id="field.name"
:name="field.name"
type="text"
:maxlength="!!field.maxLength ? field.maxLength : false"
v-validate="{ required: field.required}"
:data-vv-as="field.label"
v-model="form[field.name]"/>
<span class="field-validation-error" v-show="errors.has(field.name)" v-text="errors.first(field.name)"></span>
</div>
</template>
And the following vue instance:
export default {
props: ['formFields'],
data: function () {
return {
form: {},
}
},
created: function() {
this.resetForm();
},
methods: {
resetForm: function() {
this.form = {
'loading': false
}
_.each(this.formFields, (field) => {
this.form[field.name] = field.value;
});
$('#editModal').modal('hide');
this.errors.clear();
}
}
}
When I hard code the values in the form it seems to work:
this.form = {
'loading': false,
'Subject': 'Test',
'Author': 'Roald Dahl'
}
So it seems like something to with the following which it doesn't like:
_.each(this.formFields, (field) => {
this.form[field.name] = field.value;
});
Could it be something to do with the arrow function. Any ideas chaps?
You're running into a limitation of Vue's reactivity, which is spelled out in the documentation
Instead of
this.form[field.name] = field.value;
use
this.$set(this.form, field.name, field.value);
Trying changing the following:
<div class="form-group" v-if="field.type 'text'"> ->
<div class="form-group" v-if="field.type == 'text'">
and the model data object like this
data: {
form: {},
},
https://jsfiddle.net/Jubels/eywraw8t/373132/ Example here. For testing purposes I removed the validation
Instead of
this.form[field.name] = field.value;
use
this.$set(this.form, field.name, field.value);
this.form.splice(field.name, 1, field.value)
or
Vue.set(this.form, field.name, field.value);
this.form.splice(field.name, 1, field.value)
More information in : https://v2.vuejs.org/v2/guide/list.html#Caveats

VueJS Date Range

I have to select two dates to set input date range. I have used this library but not date range input, have used single datepicker for each dates (Start Date and End Date) as requirement is to display only one calendar at a time. I have used vuejs in my project. So I have to bind those input values to the model. I'm new in vuejs so don't know very much about vuejs. But I come to know that I have to use custom vuejs directive to bind those values to model. Here are requirements of date range inputs.
One datepicker at a time.
Dynamic min max date to fullfill some validations like start<=end
Bind selected value to the modal (twoWay)
Different date format for modal and display value (if possible)
I have already spent 25 hrs on this and got too much frustrated. So If anyone knows the answer, it will be appreciated.
Here is my code.
HTML
<div id="app">
<div class="dropdown-menu">
<div class="input-group date">
<label>from:</label>
<input size="16" type="text" v-date v-model="queries.start_date" class="form_datetime" readonly="">
<span class="input-group-addon">
<span class="calendar-icon"></span>
</span>
</div>
<div class="input-group date form_datetime">
<label>to:</label>
<input size="16" type="text" v-date v-model="queries.end_date" class="form_datetime" readonly>
<span class="input-group-addon">
<span class="calendar-icon"></span>
</span>
</div>
</div>
</div>
js
Vue.directive('date', {
twoWay: true,
bind: function (el) {
$(el).datetimepicker({
format: "mm/dd/yyyy",
autoclose: true,
minView: 2,
daysShort: true
});
}
});
var vm = new Vue({
el: '#app',
data: {
queries: {
start_date: "",
end_date: "",
}
},
methods: {
testVal: function () {
console.log([this.queries.start_date, this.queries.end_date]);
}
}
});
Here is link for content.
EDIT
I have linked wrong library. I have updated library which I have used. Please check updated link.
I got solution. I have to use component. I have read somewhere that you can not get instance of custom directive in VueJS2. I don't know it is correct or not. But got my solution from this reference.
HTML
<div class="dropdown-menu" id="app">
<div class="input-group date">
<label>from:</label>
<datepicker v-model="queries.start_date" :enddate="queries.end_date"></datepicker>
<span class="input-group-addon">
<span class="calendar-icon"></span>
</span>
</div>
<div class="input-group date form_datetime">
<label>to:</label>
<datepicker v-model="queries.end_date" :startdate="queries.start_date"></datepicker>
<span class="input-group-addon">
<span class="calendar-icon"></span>
</span>
</div>
</div>
<script type="text/x-template" id="datepicker-template">
<input size="16" type="text" class="form_datetime" readonly="">
</script>
JS
Vue.component('datepicker', {
props: ['value', 'startdate', 'enddate'],
template: '#datepicker-template',
mounted: function () {
var vm = this;
$(this.$el)
.val(this.value)
.datetimepicker({
format: "mm/dd/yyyy",
autoclose: true,
minView: 2,
daysShort: true,
startDate: this.startdate,
endDate: this.enddate
})
.on('change', function () {
vm.$emit('input', this.value);
});
},
watch: {
value: function (value) {
$(this.$el).val(value);
},
startdate: function (value) {
$(this.$el).datetimepicker('setStartDate', value);
},
enddate: function (value) {
$(this.$el).datetimepicker('setEndDate', value);
}
}
});
var vm = new Vue({
el: '#app',
data: {
queries: {
start_date: "",
end_date: "",
}
},
});