I have created a custom rule that uses Ajax to check if the same title already exists (title must be unique). The problem is: when this validation fails - the form is not prevented from being submitted.
I have defined the following custom rule in _resources/assets/js/_app.js:
require('./bootstrap');
import VeeValidate from 'vee-validate';
Vue.use(VeeValidate);
import { Validator } from 'vee-validate';
const checkUnique = {
getMessage(field) {
return `Such ${field} already exists.`
},
validate(value) {
if(value) {
return Vue.http.get('api/todos/exists', {params: { title: value }}).then(function (response) {
return {
valid: response.data.valid
}
})
} else {
return false;
}
}
};
Validator.extend('check-unique', checkUnique);
import store from './store'
Vue.component('todos-list', require('./components/todos/TodoList.vue'));
Vue.component('todos-add', require('./components/todos/TodoAdd.vue'));
const app = new Vue({
el: '#app',
store
});
As you can see, this rule uses Ajax (vue-resource) to check if the same title already exists in the database (title must be unique).
In my component's template (_resources/assets/js/components/todos/_TodoAdd.vue) I have applied this rule:
<template id="todos-add-template">
<div>
<form @submit.prevent="addTodo" autocomplete="off">
<div class="form-group">
<input name="title" v-validate data-vv-rules="required|check-unique" :class="{'input': true, 'is-danger': errors.has('title') }" v-model="newTodo.title" class="form-control" placeholder="Add a new Todo">
<span v-show="errors.has('title')" class="help is-danger">{{ errors.first('title') }}</span>
</div>
<div class="form-group">
<button class="btn btn-success">Add Todo</button>
</div>
</form>
</div>
</template>
<script>
export default {
template: '#todos-add-template',
data() {
return {
newTodo: { id: null, title: '', completed: false }
}
},
methods: {
addTodo(e) {
this.$validator.validateAll();
if (this.errors.any()) {
e.preventDefault();
} else {
this.$store.dispatch('addTodo', this.newTodo.title);
this.newTodo = { id: null, title: '', completed: false }
}
}
}
}
</script>
<style>
.input.is-danger, .textarea.is-danger {
border-color: #ff3860;
}
.help.is-danger {
color: #ff3860;
}
</style>
As you can see - I prevent form submission:
<form @submit.prevent="addTodo" autocomplete="off">
...
methods: {
addTodo(e) {
this.$validator.validateAll();
if (this.errors.any()) {
e.preventDefault();
} else {
this.$store.dispatch('addTodo', this.newTodo.title);
this.newTodo = { id: null, title: '', completed: false }
}
}
}
And if works fine with rules that are not Async (custom), but when this rule (check-unique) fails - it only prevents form from being submitted for the first click on the form's submit button... BUT if I keep clicking on the submit button - at one moment the form will be submitted.


Haven't checked in detail, but are you sure that the unique validator is not always teturnibg false, even when you test with a unique value?
You probably need to make your ajax synchronous for this to work...
@denjaland Just added console.log(value); and console.log(response.data);:
const checkUnique = {
getMessage(field) {
return `Such ${field} already exists.`
},
validate(value) {
console.log(value);
if(value) {
return Vue.http.get('api/todos/exists', {params: { title: value }}).then(function (response) {
console.log(response.data);
return {
valid: response.data.valid
}
})
} else {
return false;
}
}
};
And, as I see - it always returning false:

I noticed a weird thing - when I add a new '_todo_' - the input is cleared and then the '_check-unique_' rule fails:

Note that this input is binded to newTodo.title defined in data:
...
<input name="title" v-validate data-vv-rules="required|min:3|check-unique" :class="{'input': true, 'is-danger': errors.has('title') }" v-model="newTodo.title" class="form-control" placeholder="Add a new Todo">
...
...
data() {
return {
newTodo: { id: null, title: '', completed: false }
}
},
methods: {
addTodo(e) {
this.$validator.validateAll();
if (this.errors.any()) {
e.preventDefault();
} else {
this.$store.dispatch('addTodo', this.newTodo.title);
this.newTodo = { id: null, title: '', completed: false }
}
}
}
...
I think the issue is here: this.$validator.validateAll(), this evaluates immediately and allows the errors check to fail since it has not yet received the result. and it might conflict with next calls depending on when the promise actually finishes.
validateAll returns a promise so you can successfully evaluate the result:
To take advantage of this you need to do this:
this.$validator.validateAll().then(result => {
// result is true or false depending on the validation result.
if (result) {
// Do successful stuff.
} else {
// Something went wrong.
}
});
btw one of your screenshots has 500 error, so something might be wrong on your server.
@logaretm
Changed from:
addTodo(e) {
this.$validator.validateAll();
if (this.errors.any()) {
e.preventDefault();
} else {
this.$store.dispatch('addTodo', this.newTodo.title);
this.newTodo = { id: null, title: '', completed: false }
}
}
to:
addTodo(e) {
this.$validator.validateAll().then(result => {
if (result) {
this.$store.dispatch('addTodo', this.newTodo.title);
this.newTodo = { id: null, title: '', completed: false }
} else {
e.preventDefault();
}
})
}
... and now it works fine. Thanks. I'm not sure if this was mentioned in the documentation, if not - it would be good to have this.
btw one of your screenshots has 500 error, so something might be wrong on your server.
Yeah, I noticed that, I am getting the _500 error_ always when there are a lot of Ajax requests in a short amount of time, something might be wrong on my server (XAMPP) but I have no idea what...
Anyway, thanks!
I guess its not properly documented yet, as I'm planning to reject the promise if the result is false to make it more natural and consistent with other libraries and APIs, so you would put the success handler in then and the fail handler in catch.
Most helpful comment
I think the issue is here:
this.$validator.validateAll(), this evaluates immediately and allows the errors check to fail since it has not yet received the result. and it might conflict with next calls depending on when the promise actually finishes.validateAllreturns a promise so you can successfully evaluate the result:To take advantage of this you need to do this:
btw one of your screenshots has 500 error, so something might be wrong on your server.