Vue - add delay before enabling a disabled button - javascript

I have a submit button that is initially disabled (through v-bind:disabled) and will only be enabled until all form inputs are non-empty and errors are resolved. For each of my input, I have a spinner that will become a check or an x after verifying the validity of the input. This takes a few seconds and I was hoping that the same delay be applied to the enabling of the submit button. At the moment, this is what the form looks like:
This is the form (in pug):
.form-group
label Email Address
.input-group
input.form-control(type="email" name="emailAddress" value=profile.email
v-model="email"
v-validate
data-vv-delay="1000"
data-vv-rules="required|email"
data-vv-as="email"
:class="{ 'input': true, 'is-danger': errors.has('emailAddress') }"
placeholder="eg. andres#gmail.com")
.input-group-append
span.input-group-text
i.fal.fa-pulse.fa-spinner(v-if="email && emailBusy")
i.fal.fa-check.text-green(v-if="email && !emailBusy && !isEmailTaken && !errors.has('emailAddress')")
i.fal.fa-times.text-red(v-if="email && !emailBusy && (isEmailTaken || errors.has('emailAddress'))")
span.text-danger.text-error(v-show="errors.has('emailAddress')") {{ errors.first('emailAddress') }}
span.text-danger.text-error(v-if="email && email.length > 0 && isEmailTaken") Email address has already been taken
.form-group
label Username
.input-group
input.form-control(type="name" name="username"
v-model="username"
v-validate
data-vv-delay="1000"
data-vv-rules="required|verify_username"
:class="{ 'input': true, 'is-danger': errors.has('username') }"
placeholder="eg. andres45")
.input-group-append
span.input-group-text
i.fal.fa-pulse.fa-spinner(v-if="username && usernameBusy")
i.fal.fa-check.text-green(v-if="username && !usernameBusy && !isUsernameTaken && !errors.has('username')")
i.fal.fa-times.text-red(v-if="username && !usernameBusy && (isUsernameTaken || errors.has('username'))")
span.text-danger.text-error(v-show="errors.has('username')") {{ errors.first('username') }}
span.text-danger.text-error(v-if="username && username.length > 0 && isUsernameTaken") Username has already been taken
.form-group
button.btn.btn-blue(:disabled="errors.any() || isEmailTaken || isUsernameTaken || !username || !email" type="submit")
i.fal.fa-sign-in.mr-2
span Complete Sign Up
The vue instance:
var register = new Vue({
el: '#register',
data: {
email: email,
username: null,
isUsernameTaken: false,
usernameTimer: null,
usernameBusy: false,
isEmailTaken: false,
emailTimer: null,
emailBusy: false
},
methods: {
validateEmail: function(email) {
var self = this;
var url = '/api/users?email=' + email;
self.$http.get(url)
.then(function(res){
self.isEmailTaken = true;
self.emailBusy = false;
}, function(err){
self.isEmailTaken = false;
self.emailBusy = false;
});
},
validateUsername: function(username) {
var self = this;
var url = '/api/users/' + username;
self.$http.get(url)
.then(function(res){
self.isUsernameTaken = true;
self.usernameBusy = false;
}, function(err){
self.isUsernameTaken = false;
self.usernameBusy = false;
});
}
},
watch: {
username: function(val) {
var self = this;
clearTimeout(self.usernameTimer);
self.usernameBusy = true;
self.usernameTimer = setTimeout(function() {
self.validateUsername(val);
}, 1600);
},
email: function(val) {
var self = this;
clearTimeout(self.emailTimer);
self.emailBusy = true;
self.emailTimer = setTimeout(function() {
self.validateEmail(val);
}, 1600);
}
}
});

I’m on mobile so apologies about formatting and lack of code. For me I would probably set up a variable to track the disabled state, say var isFormComplete = false; I would use the vue disabled prop to control the button/form state. :disabled=“!isFormComplete”.
Then I would set up a vue watch or even computed method in the JS which basically will check if each form control is empty or whatever arbitrary value checking you want to do, since it’s dynamic behind the scenes with variables it should be pretty simple to check through each form control and when all conditions are satisfied, set the isFormComplete or whatever you want to call it to true and your control will be enabled.
For extra fun and bonus points, you could set up some generic validation code to be reusable and abstract it out as a vue mixin and have yourself a nifty custom form validation you can reuse. I know this isn’t a traditional answer but since I’m mobile I felt this was more indepth than a comment even though there is a lack of code. You can add a timer as well in the validation method to when all conditions are satisfied delay by however long you want and then set the disabled variable to false.

Related

Parsley.js Dynamic messages in custom validator

I'm using a custom validator to check for date of birth, so far it has almost nothing, but I'm trying to add a dynamic message depending on the error and it's not working for me it displays the container with a blank message, any ideas?
Here's the piece of code the custom validator:
window.Parsley.addValidator('age', {
validate: function(value, id){
switch(id){
case 'main':
var day = $('#birthdate_day').val();
var month = $('#birthdate_month').val();
var year = $('#birthdate_year').val();
if(!day || !month || !year){
window.Parsley.addMessage('en', 'age','Error1 ');
return false;
} else {
window.Parsley.addMessage('en', 'age','Error 2');
}
break;
}
return true;
},
messages: {
en: 'Default error',
}
});
Another thing I've tried is setting the data-parsley-age-message="error" during the execution of the validation, but it only shows the error the second time the validation is ran.
Thanks in advance.
EDIT1:
window.Parsley.addValidator('age', {
validate: function(value, id){
$('.birthdate_container').find('ul').remove();
switch(id){
case 'main':
var day = $('#birthdate_day').val();
var month = $('#birthdate_month').val();
var year = $('#birthdate_year').val();
if(!day || !month || !year){
return $.Deferred().reject("One of them is blank");
} else if(day > 2 || month > 2 || year < 2016){
return $.Deferred().reject("Else test of another message");
} else {
return true;
}
break;
}
return true;
},
});
A little cleaner solution (don't mind the else, it's there just for testing) but can't still make it work becasue I don't know how I can update the classes of the 3 elements on returning true.
EDIT 2:
Just using jQuery to handle the classes work, however, since I need to remove the ul (otherwise the messages will stack and I don't want that), whenever there's an error triggered AFTER another error is in there, it simply erases it.
window.Parsley.addValidator('age', {
validate: function(value, id){
$('.birthdate_container').find('ul').remove();
switch(id){
case 'main':
var day = $('#birthdate_day').val();
var month = $('#birthdate_month').val();
var year = $('#birthdate_year').val();
if(!day || !month || !year){
$('.birthdate_container').find('.parsley-success').removeClass('parsley-success').addClass('parsley-error');
return $.Deferred().reject("Un campo es blanco");
} else if(day > 2 || month > 2 || year < 2016){
$('.birthdate_container').find('.parsley-success').removeClass('parsley-success').addClass('parsley-error');
return $.Deferred().reject("dia > 2 o mes > 2 o años < 2016");
} else {
$('.birthdate_container').find('.parsley-error').removeClass('parsley-error').addClass('parsley-success');
return true;
}
break;
}
return true;
},
});
It's not well documented, but you can return an error message from your validator by returning a rejected promise. Check this example.
After too much tinkering with it, I think I got it, I have to reset all the previous parsley so it could rewrite the message if needed, even if it's the same one
window.Parsley.addValidator('age', {
validate: function(value, id){
switch(id){
case 'main':
var container = $('.birthdate_container');
container.find('ul').remove();
var day = $('#birthdate_day');
day.parsley().reset();
var month = $('#birthdate_month');
month.parsley().reset();
var year = $('#birthdate_year');
year.parsley().reset();
if(day.val() === '' || month.val() === '' || year.val() === ''){
container.find('.dropdown').removeClass('parsley-success').addClass('parsley-error');
return $.Deferred().reject("Un campo es blanco");
} else if(day.val() > 2 || month.val() > 2 || year.val() < 2016){
container.find('.dropdown').removeClass('parsley-success').addClass('parsley-error');
return $.Deferred().reject("dia > 2 o mes > 2 o años < 2016");
} else {
container.find('.dropdown').removeClass('parsley-error').addClass('parsley-success');
return true;
}
break;
}
return true;
}
});
PD: Again, the second else is just there to test that you can throw a different message; the validation itself is irrelevant.
My final work:
window.Parsley.addValidator('age', {
validate: function(value, id, instance){
var container = instance.$element.closest('.form-item');
container.find('ul').remove();
var msg = '';
var day = container.find('select').filter(function() {return this.id.match(/\w*birthdate_day/);});
var month = container.find('select').filter(function() {return this.id.match(/\w*birthdate_month/);});
var year = container.find('select').filter(function() {return this.id.match(/\w*birthdate_year/);});
day.parsley().reset();
month.parsley().reset();
year.parsley().reset();
var helpContainer = '<ul class="parsley-errors-list filled"><li class="parsley-age"></li></ul>';
if(value === ''){
container.find('select').parent().addClass('parsley-error');
msg = "This field is required";
}
/* Take several notes here
1) I wrap my select in a div, for stylying purposes, so I check the default placeholder, when something is selected
this placeholder is changed with the value of the select (this is not shown here, it's done elsewhere)
2) I use DD - MM - YYYY as placeholders, if any of these place holders are already showing in the div,
then I don't validate them because they are still 'clean'
3) If the user clicks the submit button though, I set a js variable with this action and then validate the value anyways
because I don't allow blank dates, however, I can't use the parsley-required feature as it messes up my validation
*/
else if(obj.action !== 'submit' && (day.parent().attr('data-placeholder') === 'DD' ||
month.parent().attr('data-placeholder') === 'MM' ||
year.parent().attr('data-placeholder') === 'YYYY'))
{
container.find('select').parent().removeClass('parsley-error')
container.find('select').filter(function(){ return this.value}).parent().addClass('parsley-success');
return true;
}
else if(day.val() === '' || month.val() === '' || year.val() === '') {
container.find('select').parent().addClass('parsley-error');
msg = "This field is required";
}
/*
I have another validation process past this point that uses a different error, but I'll point out the very basic
which is just make some other validation and fill the message you want to display.
Let's assume you want the person not to be underage and you control that in your form somehow
*/
else if (!underageAllowed){
var bdate = String("00" + day.val()).slice(-2) + "/" + String("00" + month.val()).slice(-2) + "/" + year.val();
var tdate = moment(); // Today
bdate = moment(bdate,'DD/MM/YYYY');
var age = tdate.diff(bdate, 'years');
if(age < 18){
container.find('select').parent().addClass('parsley-error');
msg = "Only people with over 18 years old are allower";
}
}
if(msg !== ''){
if(obj.action === 'submit'){
container.append(helpContainer);
container.find('.parsley-age').html(msg)
}
return $.Deferred().reject(msg);
} else {
container.find('select').filter(function(){ return this.value}).parent().addClass('parsley-success');
return true;
}
},
});
I then assign the validator to every field that has "birthdate_" as an id (birthdate_day, birthdate_month and birthdate_year)
$("[id^='birthdate_']").attr('data-parsley-age','true')
.attr('data-parsley-trigger', "change")
.attr('data-parsley-validate-if-empty', "true");
Proper errorContainer for each of the fields
$('#main_form').parsley({
errorsContainer: function(el){
if(el.$element.is('[data-parsley-age]'))
return el.$element.closest('.form-item');
},
And finally how I have my html layout
<div class="form-item">
<label>Date of birth</label>
<div class="dropdown" data-placeholder="DD">
<select id="birthdate_day" name="birthdate_day">
<option value="">-</option> //If user selects this, the validation throws error for all the fields
//Options 1 through 31
</select>
</div>
<div class="dropdown" data-placeholder="MM">
<select id="birthdate_month" name="birthdate_month">
<option value="">-</option> //If user selects this, the validation throws error for all the fields
//Options 1 through 12
</select>
</div>
<div class="dropdown" data-placeholder="YYY">
<select id="birthdate_year" name="birthdate_year">
<option value="">-</option> //If user selects this, the validation throws error for all the fields
//Options 1 through 12
</select>
</div>
Hope this helps out anyone, it took me some time to come up with all this.
If you ONLY want to deal with dynamic messages, do not include a message text in your messages object, then in your validation method:
instance.addError('en', {message: 'Hello World'});
It's as simple as that. Just read the annotated source code and it will unveil a whole host of simple features.
EDIT: actually I just noticed you do need something or it throws it's own error message, so you can fix that with something like:
messages: {
en: '...'
}
I just tested and that works fine

Email validation doesn't capture with required fileds

I have a javascript validation function.I need to check if required fileds are empty or wrong mail address.Required fileds empty is working But when i type mail like abc#abc or something wrong then it doent catch the error in my code.
When i type all required fileds but wrong email address ( abc#abc or abc.com like doesn't capture.)
My Code
function newsValidation() {
var status = true;
if (($.trim($('#txtNewsname').val()) == '') || ($.trim($('#txtnewsarea').val()) == '') ||
($.trim($('#txtemail').val()) == '')) {
$("#reqfield").removeClass("hidden");
if (!ValidateEmail($("#txtemail").val())) {
$("#emailval").removeClass("hidden");
}
status = false;
}
Email Validate Function
function ValidateEmail(email) {
var expr = /^([\w-\.]+)##((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
return expr.test(email);
}
Your test for a valid email is inside the if block which test if the value is not null, so when you enter any value in the text box (whether its valid or not) the if (!ValidateEmail($("#txtemail").val())) { will never be called. Change your script to
function newsValidation() {
var status = true;
if (($.trim($('#txtNewsname').val()) == '') || ($.trim($('#txtnewsarea').val()) == '') || ($.trim($('#txtemail').val()) == '')) {
$("#reqfield").removeClass("hidden");
status = false;
} else if (!ValidateEmail($("#txtemail").val())) {
$("#emailval").removeClass("hidden");
status = false;
}
}
Side note: All this functionality is provide out of the box in MVC by simply adding the [Required] and [EmailAddress] attribute to your property and including the relevant scripts (jquery.validate.js and jquery.validate.unobtrusive.js) and #Html.ValidationMessageFor() helpers which means you get both client and server side validation (and it's all done correctly!)

JavaScript Model Form Validation

I am using BackboneJS MVC pattern and have a form with 2 fields which the user can select.
I call changeAction whenever there is any change in selection by the user (to call validation);
changeAction: function (event) {
var self = this;
var target = event.target;
target.value = $.trim(target.value);
change[target.name] = target.value;
this.model.set(change);
var check = self.model.validateItem(target.id);
if (check.isValid === false) {
self.addValidationError(target.id, check.message);
} else {
self.removeValidationError(target.id);
}
}
My validation is defined in the Model as below;
this.validators.myDropDownField = function(value) {
return !_.isUndefined(value) && !_.isNull(value) && $.trim(value).length > 0 ? {
isValid: true
} : {
isValid: false,
message: "Select dropdown field"
};
};
validateItem: function(key) {
var result = (this.validators[key]) ? this.validators[key](this.get(key)) : {
isValid: true
};
return result;
}
Now my question is I do not want to do anything to the Model in changeAction.
But if I remove the line this.model.set(change) in changeAction()
the validation does not work correctly. I do not get the value in the function
this.validators.myDropDownField
How do I handle this ?

Unhighlighting input group

I have an input form that I'm performing client-sided validation on with the jQuery validator plugin. Basic usage is working great, except for a specific scenario:
The form splits up address input fields, allowing separate fields for street number, name, city, state, and zip. The address itself is an optional input to the form (a user may opt to enter no address), but I want to ensure that if any one of these fields are used, the user is prompted to enter all the fields.
This works, except in the case when someone enters in an address and hits submit, and then decides to enter in no address. The ideal behavior in this case would be that, as soon as the text in the inputs they've entered is removed, for the address group to be unhighlighted.
Here is the current scenario:
User enters information into only one input field, e.g., street name.
The submit button is clicked.
The validator plugin highlights the other address inputs with an error message prompting for the full address.
User decides to enter no address, and removes the prior input, e.g. erases street name
Ideally: All the other highlighted address inputs are unhighlighted and the error message is removed. Actually: The highlighted address inputs and message remain until form submission.
Here is the javascript that demonstrates the problem and the corresponding JSFiddle.
$("form").validate({
errorClass: 'error',
errorElement: 'label',
submitHandler: function() {
alert("Form submitted");
return false;
},
groups: {
address: "streetNumber streetName city state zipcode"
},
rules: {
streetNumber: {
required: {
depends: function(){
return $("#streetName").val() != '' || $("#city").val() != '' || $("#state").val() != '' || $("#zipcode").val() != '';
}
}
},
streetName: {
required: {
depends: function(){
return $("#streetNumber").val() != '' || $("#city").val() != '' || $("#state").val() != '' || $("#zipcode").val() != '';
}
}
},
city: {
required: {
depends: function(){
return $("#streetNumber").val() != '' || $("#streetName").val() != '' || $("#state").val() != '' || $("#zipcode").val() != '';
}
}
},
state: {
required: {
depends: function(){
return $("#streetNumber").val() != '' || $("#streetName").val() != '' || $("#city").val() != '' || $("#zipcode").val() != '';
}
}
},
zipcode: {
required: {
depends: function(){
return $("#streetNumber").val() != '' || $("#streetName").val() != '' || $("#city").val() != '' || $("#state").val() != '';
}
}
}
},
messages: {
streetNumber: {required: "Must provide full address"},
streetName: {required: "Must provide full address"},
city: {required: "Must provide full address"},
state: {required: "Must provide full address"},
zipcode: {required: "Must provide full address"}
},
highlight: function(element, errorClass, validClass) {
$(element).addClass(errorClass).removeClass(validClass);
},
unhighlight: function(element, errorClass, validClass) {
$(element).removeClass(errorClass);
},
errorPlacement: function(error, element) {
var n = element.attr("name");
if (n == "streetNumber" || n == "streetName" || n == "city" || n == "state" || n == "zipCode")
error.insertAfter("#zipcode");
}
});
Besides trying to get the desired functionality of the highlight, I'm also wondering if there is a smarter way to accomplish the "all or nothing" input groups that doesn't involve the mess of conditional statements. Perhaps I can use a form input group?
You need to add a function on the focus event, then when the user leaves the field, the form fields will update.
It's difficult because you are using a plugin, so all the calls are happening inside that but I think something like this will work:
var inputs = $('form').find('input');
inputs.focus(function () {
inputs.each(function () {
$(this).removeClass('error');
});
});
Just stick this in your code outside of the validate initialiser.
It would be even better if you defined your errorCode variable outside of the validator and then used that var in both functions, like this:
var errorClass = 'error';
$('form').validate({
errorClass: errorClass,
...
...
});
var inputs = $('form').find('input');
inputs.focus(function () {
inputs.each(function () {
$(this).removeClass(errorClass);
});
});
you can try to add a method in the
unhighlight:
something like
$('.error').removeClass(errorClass);
or define related inputs and do
$('.relatedInputs').removeClass(errorClass);
you could also add an onChange function like
function(el){
if(el.val() == ''){
$('.relatedInputs').removeClass(errorClass);
}
}
I was able to get desired functionality by using this:
onfocusout: function(element) {
var inputs = $(element).closest('form').find('input, select');
inputs.each(function() {
if ($(this).valid())
$(this).removeClass('error');
});
}
Which was inspired from another post and DoubleA's answer. I haven't tested it thoroughly to see if it regresses anything, but so far it seems to work.

Alerts conditional on model validation

I have an Picture model with various validations:
validates :title, presence: true
validates :caption, presence: true
validates :image, presence: true
validates :price, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 1000 }
validates_size_of :tag_list, :minimum => 3, :message => "please add at least three tags"
The tag list has to be submitted in a specific format: at least three tags, separated by a comma and a space: eg foo, bar, cats
I want to have an alert that tells the user to "please wait, we're uploading your image" - but only AFTER the model has passed ALL of the validations ( before the .save in the controller)
Is there a way of doing this in the controller, which I'd prefer, or do I have to use some javascript like:
$("form#new_picture").on("submit", function () {
if LOTS OF HORRIBLE REGEX ON FORM FIELDS {
MESSAGE HERE
return true;
} else {
return false;
}
});
OR Is there a way of doing this in the model, as part of an after_validation callback?
Any suggestions much appreciated. Thanks in advance.
I would build a JS function to extract the fields that I want to be validated.
Then create a custom AJAX controller action, which:
instantiates a new object with given params
call valid? on it without saving it
Then:
On failure, update the form with error messages
On success, I would return a custom ajax response to display the alert and start POSTing the real object.
I've realised that this isn't really possible through through the model or controller, and resorted to a combination of three validation processes:
Validations in the model
The simpleform client side validations gem - this is v good, it tests validity the moment a form field loses focus - "real time" validation.
And some additional javascript to alert with popups and errors, pasted below.
Hopefully this makes the form virtually un-submittable without the user knowing what's missing.
THE JS SOLUTION
FORM
<form id="new_pic" novalidate>
<p><input type="file" name="file" required></p>
<p><input type="string" name="name" placeholder="Name" required></p>
<p><input type="string" name="tags" placeholder="Tags" data-validation="validateTags"></textarea></p>
<p><textarea name="description" data-validation="validateDescription"></textarea></p>
<p><button type="submit">Submit</button>
</form>
JS
var Validator = function(form) {
this.form = $(form);
}
$.extend(Validator.prototype, {
valid: function() {
var self = this;
this.errors = {};
this.form.find('[required]').each(function() {
self.validateRequired($(this));
});
this.form.find('[data-validation]').each(function() {
var el = $(this),
method = el.data('validation');
self[method].call(self, el);
});
return $.isEmptyObject(this.errors);
},
validateRequired: function(input) {
if (input.val() === '') {
this.addError(input, 'is required');
}
},
validateDescription: function(input) {
if (input.val().length < 64) {
this.addError(input, 'must be at least 64 characters');
}
},
validateTags: function(input) {
var tags = input.val().split(/, ?/);
if (tags.length < 3) {
this.addError(input, 'must have at least 3 tags');
}
},
addError: function(input, error) {
var name = input.attr('name');
this.errors[name] = this.errors[name] || [];
this.errors[name].push(error);
input.after('<span class="error">' + error + '</span>');
}
});
$('form#new_pic').on('submit', function(event) {
event.preventDefault();
var form = $(this),
validator = new Validator(form);
form.find('.error').remove();
if (validator.valid()) {
// continue with upload
alert('Go!');
return true;
} else {
// complain
alert('Stop!');
return false;
}
});

Categories