Bit of a preface on my motive: I'm self taught and have never really worked in a collaborative environment. My only benchmark for quality is "does it work" and I've never been really subject to peer review.
With that in mind I just thought I'd dump a small jQuery validation plugin I wrote here and see if anyone can spot any rookie errors or advise on best practice. I'm very aware that because I've never had anyone looking over my shoulder while I'm coding, I could be committing all sorts of programmatic faux pas and not even realising.
Here's the plugin in its entirety followed by some example markup (sorry about the annoying comments)
// Using a global "Tiga" object. This allows me to extend the same object with all my different plugins
if (typeof Tiga == "undefined") {
Tiga = {};
}
//extend jQuery with the .tigaValidate method. This allows for easy running
$.fn.tigaValidate = function(options) {
// Plugin Settings
var s = $.extend( {
// Default Settings
form : this,
live : true,
formatting : true,
invalid : function($form){
$("body, html").animate({
scrollTop: 0
}, 500);
$form.addClass("error");
}
}, options);
//let's just call our the init() method of our core object now.
Tiga.Validate.init(s.form, s.live, s.formatting, s.invalid);
};
// here's our core object
Tiga.Validate = {
init: function(form, live, formatting, invalid){
// defaults (for when/if Tiga.Validate.init() is invoked directly)
if (typeof live == "undefined") { live = true; };
if (typeof formatting == "undefined") { formatting = true; };
// cache $(form)
var $form = $(form),
// keycodes to be excluded from the keyup binding ( left & right arrows, backspace, delete, shift and ctrl )
allowKeys = [37, 39, 8, 46, 17, 16];
// disable HTML5 validation
$form.attr("novalidate", true);
// if live validation is enabled:
if (live) {
// ..find and cache our submit button before disabling it
var $submit = $(form.selector + " .submit");
$submit.attr("disabled", true);
// ..traverse the DOM and look for required fields, bind validation to the blur event
// (all traversal accomplished with sizzle when possible - http://jsperf.com/jquery-find-vs-sizzle/3)
$(form.selector + " [required]").on("blur", function(e){
//validate that mofo
Tiga.Validate.validateSingle($(this));
//validation check on form and enable/disable the button;
if (Tiga.Validate.validateForm(form)) {
$submit.attr("disabled", false);
} else {
$submit.attr("disabled", true);
};
});
} else {
// otherwise bind validation to form submit
$form.on("submit", function(e){
//validate all forms at once
$(form.selector + " [required]").each(function(i,o){
Tiga.Validate.validateSingle($(o));
});
//valid - submit the form
if (Tiga.Validate.validateForm(form)) {
return;
//invalid - scroll to the top and show some error messages maybe.
} else {
//run invalid callback
invalid($form);
e.preventDefault();
return false;
};
});
};
// if fomatting is enabled lets also check for special, funky field types (like cards) so we can do some on-the-fly formatting.
if (formatting) {
$(form.selector + " input[pattern='card_date'], " + form.selector + " input[pattern='card_number'], " + form.selector + " input[pattern='card_cvv']").on("keyup", function(e){
var code = e.keyCode || e.which;
// check key being pressed is not an allowed key.
if(allowKeys.indexOf(code) == -1 ) {
if ($(this).is("[pattern='card_date']")){
Tiga.Validate.cardFormat(this, 7, "card_date");
}
if ($(this).is("[pattern='card_number']")){
Tiga.Validate.cardFormat(this, 19, "card_number");
}
if ($(this).is("[pattern='card_cvv']")){
// simplified version just automatically strips out letters on keyup
Tiga.Validate.cardFormat(this);
}
}
});
//postcode capitalisation
$(form.selector + " input[pattern='postcode']").on("keyup", function(e){
var code = e.keyCode || e.which;
// check key being pressed is not an allowed key.
if(allowKeys.indexOf(code) == -1 ) {
var val = $(this).val().toUpperCase();
$(this).val(val);
}
});
};
},
Rules : {
alpha : "^[A-Za-z ]+$",
alpha_numeric : "^[a-zA-Z0-9_]*$",
email : "^[^@]+@[^@]+\\.[^@]+$",
card_number : "[0-9 ]{19}",
card_cvv : "[0-9]{3}",
card_date : "(0[1-9]|1[0-2])\\s\\/\\s[0-9]{2}",
postcode : "^(GIR ?0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]([0-9ABEHMNPRV-Y])?)|[0-9][A-HJKPS-UW]) ?[0-9][ABD-HJLNP-UW-Z]{2})$"
},
validateSingle: function($field){
// check if theres a pattern specified
if ($field.attr("pattern")) {
//grab the pattern attribute
var pattern = $field.attr("pattern");
// do we have the pattern defined in our rules?
if (typeof Tiga.Validate.Rules[pattern] != "undefined") {
rule = new RegExp(Tiga.Validate.Rules[pattern]);
// nope? we'll assume it's a regex then
} else {
rule = new RegExp(pattern);
};
//test our rule
if (rule.test($field.val())) {
//pass
$field.removeAttr("data-invalid");
} else{
//fayul!
$field.attr("data-invalid", true);
};
// check if field should be equal to another field (e.g. confirm password)
} else if ($field.attr("data-equalto")) {
var compareto = $field.attr("data-equalto");
if ($field.val() == $(compareto).val()) {
$field.removeAttr("data-invalid");
} else {
$field.attr("data-invalid", true);
};
};
},
validateForm: function(form){
// innocent until proven guilty
var valid = true;
// Let's assess each forms validity one at a time with $.each *cringe*
$(form.selector + " [required]").each(function(i,o){
// checking two things: 1) is the field invalid? 2) is it empty?
if ($(o).attr("data-invalid") != undefined || $(o).val().length == 0) {
// OBJECTION YOUR HONOUR! This man can't type properly!
valid = false;
};
});
// return validity check
if (valid) {
return true;
} else {
return false;
};
},
cardFormat: function(field, maxlength, fieldtype){
// set no max value as default
if (typeof maxlength == "undefined") { maxlength = 9999; /* should be high enough :) */ }
// cache the caret pos
var cp = Tiga.Validate.getCaretPos(field);
//visa or mastercard detection classes for adding icons ( TODO: add more card types )
if (fieldtype == "card_number") {
if (field.value.charAt(0) == 4) {
$(field).removeClass("mastercard").addClass("visa");
} else if (field.value.charAt(0) == 5) {
$(field).removeClass("visa").addClass("mastercard");
} else {
$(field).removeClass("visa sprite");
};
};
//remove everything except numbers
var val = String(field.value).replace(/[^0-9.]/g, "");
if (typeof fieldtype != "undefined") {
var spacedval;
if (fieldtype == "card_number") {
//add spaces
spacedval = val.replace(/(.{4})/g,"$1 ");
//check to see if we're coming up to a space. if we are, jump the caret over it.
if ( [4,9,14].indexOf(cp) != -1 ) {
cp++;
}
}
if (fieldtype == "card_date") {
//add spaces
spacedval = val.replace(/(.{2})/g,"$1 \/ ");
//check to see if we're coming up to a space. if we are, jump the caret over it.
if ( cp == 2) {
cp = cp + 3;
}
if ( cp == 4) {
cp++;
}
}
//enforce max length (use a "maxlength" attribute as well. This just stop the script adding additional charaters)
if (spacedval.length > maxlength) {
spacedval = spacedval.substr(0, maxlength);
};
//set the field value to our nicely formatted one
field.value = spacedval;
} else {
//enforce max length (use a "maxlength" attribute as well. This just stop the script adding additional charaters)
if (val.length > maxlength) {
val = val.substr(0, maxlength);
};
//set the field value to our nicely formatted one
field.value = val;
}
Tiga.Validate.setCaretPos(field, cp);
},
getCaretPos: function(field){
// Initialize
var iCaretPos = 0;
// IE Support
if (document.selection) {
// Set focus on the element
field.focus ();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange ();
// Move selection start to 0 position
oSel.moveStart ('character', -field.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
}
// Firefox support
else if (field.selectionStart || field.selectionStart == '0')
iCaretPos = field.selectionStart;
// Return results
return (iCaretPos);
},
setCaretPos: function(field, caretPos) {
if(field != null) {
if(field.createTextRange) {
var range = field.createTextRange();
range.move('character', caretPos);
range.select();
}
else {
if(field.selectionStart) {
field.focus();
field.setSelectionRange(caretPos, caretPos);
}
else
field.focus();
}
}
}
};
Example Markup:
<form action="" id="myform">
<input type="text" required pattern="alpha" placeholder="Name">
<input type="text" required pattern="alpha_numeric" placeholder="Address">
<input type="text" maxlength="19" required pattern="card_number" placeholder="Card Number">
<input type="password" maxlength="3" required pattern="card_cvv" placeholder="CVV">
<input type="text" maxlength="7" required pattern="card_date" placeholder="Expiry Date">
<input type="text" maxlength="8" required pattern="postcode" placeholder="Postcode">
<input type="submit" class="submit">
</form>
<script>
jQuery(document).ready(function($) {
$("#myform").tigaValidate();
});
</script>