var BFNValidator = new function () { var me = this; this.ValidationStateTypes = { Valid: 0, NonRequired: 1, Warning: 2, Error: 3 }; var validationList = function (options) { this.ControlName = ""; this.Required = false; this.Warning = false; this.Conditional = null; this.RequiredMessage = " "; this.RequiredBypass = new Array(); this.Validators = new Array(); $.extend(this, options); }; var validator = function (options) { this.Method = null; this.Conditional = null; this.Warning = false; this.Message = " "; this.Params = new Array(); $.extend(this, options); }; this.ValidatorType = function (options) { this.MethodName = null; this.ParamNames = new Array(); /* Method of formatting params to desired state. ie Split, parse, order etc*/ this.FormatParams = null; this.IsRequiredBypass = false; $.extend(this, options); }; this.ValidationResult = function (options) { this.ControlName = ""; this.ValidationState = me.ValidationStateTypes.Valid; this.Message = ""; $.extend(this, options); }; this.Settings = { AllSelector: "input[data-val='true'], textarea[data-val='true'], select[data-val='true']", TextSelector: "input[type='text'][data-val='true'], textarea[data-val='true'], input[type='password'][data-val='true'], input[type='hidden'][data-val='true']", CheckableSelector: "input[type='radio'][data-val='true'], input[type='checkbox'][data-val='true']", ListSelector: "select[data-val='true']", FormImmitationClass: ".form", DisplayCss: { Valid: { Input: "valid", Field: "field-validation-valid", Summary: "" }, NonRequired: { Input: "input-validation-nonrequired", Field: "field-validation-nonrequired", Summary: "" }, Warning: { Input: "input-validation-warning", Field: "field-validation-warning", Summary: "summary-field-validation-warning" }, Error: { Input: "input-validation-error", Field: "field-validation-error", Summary: "summary-field-validation-error" } }, /*Dynamically set items*/ AllValidationClasses: "" }; var data = new Array(); var availableValidators = new Array( new this.ValidatorType({ MethodName: "reqgroup", ParamNames: new Array("val"), IsRequiredBypass: true, FormatParams: function (element$) { var form$ = formISubmitTo(element$); var others$ = form$.find("[data-val-reqgroup-val=" + this.Params[0] + "]").not(element$); this.Params[0] = others$; others$.on("change.BFNValidator", function () { validateSingleElement(element$); }); } }), new this.ValidatorType({ MethodName: "regex", ParamNames: new Array("pattern") }), new this.ValidatorType({ MethodName: "minlength", ParamNames: new Array("value"), FormatParams: function () { this.Params[0] = parseInt(this.Params[0]); } }), new this.ValidatorType({ MethodName: "maxlength", ParamNames: new Array("value"), FormatParams: function () { this.Params[0] = parseInt(this.Params[0]); } }), new this.ValidatorType({ MethodName: "rangelength", ParamNames: new Array("minlength", "maxlength"), FormatParams: function () { this.Params[0] = parseInt(this.Params[0]); this.Params[1] = parseInt(this.Params[1]); } }), new this.ValidatorType({ MethodName: "min", ParamNames: new Array("value"), FormatParams: function () { this.Params[0] = parseFloat(this.Params[0]); } }), new this.ValidatorType({ MethodName: "max", ParamNames: new Array("value"), FormatParams: function () { this.Params[0] = parseFloat(this.Params[0]); } }), new this.ValidatorType({ MethodName: "range", ParamNames: new Array("min", "max"), FormatParams: function () { this.Params[0] = parseFloat(this.Params[0]); this.Params[1] = parseFloat(this.Params[1]); } }), new this.ValidatorType({ MethodName: "selectedindex", ParamNames: new Array("min"), FormatParams: function () { this.Params[0] = parseInt(this.Params[0], 10); } }), new this.ValidatorType({ MethodName: "email" }), new this.ValidatorType({ MethodName: "date" }), new this.ValidatorType({ MethodName: "datepickerdate" }), new this.ValidatorType({ MethodName: "mindatepickerdate", ParamNames: new Array("value"), FormatParams: function () { this.Params[0] = new Date(this.Params[0].replace(/-/gi, " ")); } }), new this.ValidatorType({ MethodName: "maxdatepickerdate", ParamNames: new Array("value"), FormatParams: function () { this.Params[0] = new Date(this.Params[0].replace(/-/gi, " ")); } }), new this.ValidatorType({ MethodName: "rangedatepickerdate", ParamNames: new Array("min", "max"), FormatParams: function () { this.Params[0] = new Date(this.Params[0].replace(/-/gi, " ")); this.Params[1] = new Date(this.Params[1].replace(/-/gi, " ")); } }), new this.ValidatorType({ MethodName: "time" }), new this.ValidatorType({ MethodName: "popup", ParamNames: new Array("type") }), new this.ValidatorType({ MethodName: "creditcard" }), new this.ValidatorType({ MethodName: "equalTo", ParamNames: new Array("other"), FormatParams: function (element$) { var form$ = formISubmitTo(element$); /*Try and find the item as a name*/ myComparitor = form$.find("input[name='" + escapeAttributeValue(this.Params[0]) + "'], textarea[name='" + escapeAttributeValue(this.Params[0]) + "'], select[name='" + escapeAttributeValue(this.Params[0]) + "']"); if (myComparitor.length > 0) { this.Params[0] = myComparitor; return; } /*Try and find the item as an ID*/ myComparitor = form$.find("#" + this.Params[0]); if (myComparitor.length > 0) { this.Params[0] = myComparitor; return; } /*Try and find the item using jQuery selector*/ var myComparitor = form$.find(this.Params[0]); if (myComparitor.length > 0) { this.Params[0] = myComparitor; return; } tryLogError("Other element of equalTo validator could not be found. Element: " + element$[0].name | element.id + " Other syntax: " + this.Params[0]); } }) ); var methods = { reqgroup: function (element$, others$) { var val = element$.val(); if (val.length == 0) { if (others$.filter(function () { return $(this).val().length > 0; }).length > 0) { return false; } } return true; }, required: function (element$) { var element = element$[0]; switch (element.nodeName.toLowerCase()) { case 'select': // could be an array for select-multiple or a string, both are fine this way var val = element$.val(); if (val && val.length > 0) { if (isNaN(val)) return true; return parseInt(val) !== -1; } return false; case 'input': if (/radio|checkbox/i.test(element.type)) { return $("input[name='" + element.name + "']").filter(":checked").length > 0; } default: return $.trim(element$.val()).length > 0; } }, minlength: function (element$, length) { return $.trim(element$.val()).length >= length; }, maxlength: function (element$, length) { return $.trim(element$.val()).length <= length; }, rangelength: function (element$, params) { var length = $.trim(element$.val()).length; return (length >= params[0] && length <= params[1]); }, min: function (element$, min) { var elemValue = parseFloat(element$.val()); return elemValue >= min; }, max: function (element$, max) { var elemValue = parseFloat(element$.val()); return elemValue <= max; }, range: function (element$, params) { var value = parseFloat(element$.val()); return (value >= params[0] && value <= params[1]); }, selectedindex: function (element$, min) { return (element$[0].selectedIndex >= min); }, email: function (element$) { // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(element$.val()); }, creditcard: function (element$) { var value = element$.val(); // accept only spaces, digits and dashes if (/[^0-9 -]+/.test(value)) return false; var nCheck = 0, nDigit = 0, bEven = false; value = value.replace(/\D/g, ""); for (var n = value.length - 1; n >= 0; n--) { var cDigit = value.charAt(n); var nDigit = parseInt(cDigit, 10); if (bEven) { if ((nDigit *= 2) > 9) nDigit -= 9; } nCheck += nDigit; bEven = !bEven; } return (nCheck % 10) == 0; }, // http://docs.jquery.com/Plugins/Validation/Methods/equalTo equalTo: function (element$, other$) { if (!other$.attr("data-val-equalTo-target") === true) { other$.on("change.BFNValidator", function () { validateSingleElement(element$); }); other$.attr("data-val-equalTo-target", "true"); } return other$.val() == element$.val(); }, regex: function (element$, pattern) { var match; var value = element$.val(); match = new RegExp(pattern).exec(value); return (match && (match.index === 0) && (match[0].length === value.length)); }, date: function (element$) { var isValid = false; try { // NOTE: .replace(/-/gi," ") is a fix for the '2013-10-10' date format not parsing correctly var input = element$.val(), date = new Date(input.replace(/-/gi, " ")); if (input.trim() !== "" && !isNaN(date.getYear())) { date = new Date(date.getFullYear(), date.getMonth(), date.getDate()); //correct / account for missing year if (input.indexOf(date.getFullYear().toString()) < 0) date.setFullYear(new Date().getFullYear()); element$.val(date.toUsefulDateString()); isValid = true; } } catch (ex) { element$.val(""); tryLogError(ex.stack); } return isValid; }, mindatepickerdate: function (element$, min) { return methods.datepickerdate(element$, min); }, maxdatepickerdate: function (element$, max) { return methods.datepickerdate(element$, max); }, rangedatepickerdate: function (element$, params) { return methods.datepickerdate(element$, params[0], params[1]); }, datepickerdate: function (element$, min, max) { var result = false; var date = null; try { if (typeof (DateOverrides) != "undefined") { var value = element$.val(); var match = new RegExp(DateOverrides.ValidationRegex).exec(value); if (match == undefined || (match.index !== 0) || (match[0].length !== value.length)) return false; if (min != null) { date = DateOverrides.ParseDate(value); if (date == null || date.getTime() < min.getTime()) { return false; } } if (max != null) { if (date == null) date = DateOverrides.ParseDate(value); if (date == null || date.getTime() > max.getTime()) { return false; } } return true; } else { tryLogError("Date Overrides MUST be implemented to use this validator"); } } catch (ex) { element$.val(""); tryLogError(ex.stack); } return false; } }; var elementValueLength = function (element$) { var element = element$[0]; switch (element.nodeName.toLowerCase()) { case 'select': // could be an array for select-multiple or a string, both are fine this way var val = element$.val(); return val && val.length; case 'input': if (/radio|checkbox/i.test(element.type)) { return $("input[name='" + element.name + "']").filter(":checked").length; } default: return $.trim(element$.val()).length; } }; /*Add a custom validator for this instance*/ var addValidator = function (validatorType, callback) { availableValidators.push(validatorType); methods[validatorType.MethodName] = callback; }; /*Find parent form or fake parent form*/ var formISubmitTo = function (element$) { var fakeForm = element$.closest(me.Settings.FormImmitationClass); if (fakeForm.length > 0) return $(fakeForm); return element$.closest("form"); }; /*if a summary element exists, show summary of all failed elements*/ var showErrorSummary = function (form$, errors) { var container = form$.find("[data-valmsg-summary='true']"), list = container.find("ul"); // hide any other validation summaires (such as the server error message) form$.find(".validation-summary-errors.show").removeClass("show"); if (list && list.length) { list.empty(); if (errors.length > 0) container.addClass("show"); else container.removeClass("show"); $.each(errors, function () { if (this.Message.length <= 1) return; var sumError$ = $("
  • ").html(this.Message); if (this.ValidationState == me.ValidationStateTypes.Error) { sumError$.addClass(me.Settings.DisplayCss.Error.Summary); } else if (this.ValidationState == me.ValidationStateTypes.Warning) { sumError$.addClass(me.Settings.DisplayCss.Warning.Summary); } sumError$.appendTo(list); }); } }; /*Element to have its display reset, Validation Result*/ var resetState = function (element$, validationResult) { resetElement(element$, "Input", validationResult.ValidationState); var container = $(formISubmitTo(element$)).find("[data-valmsg-for='" + escapeAttributeValue(element$[0].name) + "']"); if (container) { container.each(function () { var current$ = $(this); //var replace = $.parseJSON(current$.attr("data-valmsg-replace")) === true; // causes js error "Uncaught SyntaxError: Unexpected token u" var replace = current$.attr("data-valmsg-replace") === "true"; resetElement(current$, "Field", validationResult.ValidationState); current$.attr('title', validationResult.Message); if (replace) { current$.html(validationResult.Message); } }); } }; /*Deliberately made private.*/ /*element to update, valtype (Error css to reference Field|Input|Summary), validationstate of current element */ var resetElement = function (element$, valType, validationState) { element$.removeClass(allValidationClasses()); if (validationState == me.ValidationStateTypes.Error) { element$.addClass(me.Settings.DisplayCss.Error[valType]); } else if (validationState == me.ValidationStateTypes.Warning) { element$.addClass(me.Settings.DisplayCss.Warning[valType]); } else if (validationState == me.ValidationStateTypes.NonRequired) { element$.addClass(me.Settings.DisplayCss.NonRequired[valType]); } else if (validationState == me.ValidationStateTypes.Valid) { element$.addClass(me.Settings.DisplayCss.Valid[valType]); } }; var escapeAttributeValue = function (value) { // As mentioned on http://api.jquery.com/category/selectors/ return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); }; /*Validate a single element*/ var validateSingleElement = function (element$) { if (!element$.attr("data-val-parsed")) { element$.on("keyup", function () { validateSingleElement($(this)); }); } var validate = isValidElement(element$); resetState(element$, validate); return validate; }; /*Validate entire "form" that encapsulates element*/ var validateMyForm = function (element$) { var form$ = $(formISubmitTo(element$)); return validateForm(form$); }; /*Validate entire "form"*/ var validateForm = function (form$) { var validationState = me.ValidationStateTypes.Valid; var errors = new Array(); var targets = form$.find(me.Settings.AllSelector); targets.each(function () { var validate = validateSingleElement($(this)); if (validate.ValidationState == me.ValidationStateTypes.Error || validate.ValidationState == me.ValidationStateTypes.Warning) { errors.push(validate); } if (validate.ValidationState > validationState) validationState = validate.ValidationState; }); showErrorSummary(form$, errors); return validationState; }; /*Check validity of element against its validators*/ var isValidElement = function (element$) { var elemData = elementData(element$); if (elemData.Conditional != null) { if (!callNamedFunction(elemData.Conditional, element$)) { return new me.ValidationResult({ ValidationState: me.ValidationStateTypes.NonRequired, Message: elemData.RequiredMessage, ControlName: elemData.ControlName }); } } var result = new me.ValidationResult({ ValidationState: me.ValidationStateTypes.Valid, Message: elemData.RequiredMessage, ControlName: elemData.ControlName }); if (elemData.Required) { if (!methods.required(element$, "")) { if (elemData.Warning) { result.ValidationState = me.ValidationStateTypes.Warning; } else { result.ValidationState = me.ValidationStateTypes.Error; } return result; } } else if (elementValueLength(element$) <= 0) { if (elemData.RequiredBypass.length > 0) { var bypass = loopValidators(element$, elemData.RequiredBypass, result); if (bypass.ValidationState == me.ValidationStateTypes.Error || bypass.ValidationState == me.ValidationStateTypes.Warning) return bypass; } result.ValidationState = me.ValidationStateTypes.NonRequired; return result; } return loopValidators(element$, elemData.Validators, result); }; var loopValidators = function (element$, validators, result) { for (var i = 0; i < validators.length; i++) { var passed = true; try { if (validators[i].Conditional != null && !callNamedFunction(elemData.Conditional, element$)) { result.ValidationState = me.ValidationStateTypes.NonRequired; continue; } var params = validators[i].Params; if (params.length == 0) { passed = methods[validators[i].Method](element$); } else if (params.length == 1) { passed = methods[validators[i].Method](element$, params[0]); } else { passed = methods[validators[i].Method](element$, params); } } catch (e) { tryLogError(e.stack); } if (!passed) { result.Message = validators[i].Message; if (!validators[i].Warning) { result.ValidationState = me.ValidationStateTypes.Error; return result; } else { result.ValidationState = me.ValidationStateTypes.Warning; } } } return result; }; /*Expected return value true/false*/ var callNamedFunction = function (fn, element$) { if (typeof fn !== "function") fn = window[fn]; try { return fn.call(element$, element$); } catch (e) { tryLogError(e.stack); } /*Failsafe*/ return true; }; /*Parse element and store data in Validator array*/ var parseElement = function (element$) { var elemData = new validationList({ ControlName: element$[0].name }); var msg = element$.attr("data-val-required"); if (msg) { elemData.Required = true; elemData.RequiredMessage = msg; if (element$.attr("data-val-required-warning")) { elemData.Warning = true; } } var cond = element$.attr("data-val-conditional"); if (cond) { elemData.Conditional = cond; } /*Get all validators assigned to this element*/ for (var val = 0; val < availableValidators.length; val++) { msg = element$.attr("data-val-" + availableValidators[val].MethodName); if (msg) { var newValidator = new validator({ Method: availableValidators[val].MethodName, Message: msg }); if (element$.attr("data-val-" + availableValidators[val].MethodName + "-warning")) { newValidator.Warning = true; } if (element$.attr("data-val-" + availableValidators[val].MethodName + "-conditional")) { newValidator.Conditional = element$.attr("data-val-" + availableValidators[val].MethodName + "-conditional"); } for (var param = 0; param < availableValidators[val].ParamNames.length; param++) { var paramValue = element$.attr("data-val-" + availableValidators[val].MethodName + "-" + availableValidators[val].ParamNames[param]); newValidator.Params.push(paramValue); } /*Format param values as required, ie convert to ints/floats etc once rather than every validation instance*/ if (availableValidators[val].FormatParams != null) { availableValidators[val].FormatParams.apply(newValidator, [element$]); } if (availableValidators[val].IsRequiredBypass) elemData.RequiredBypass.push(newValidator); else elemData.Validators.push(newValidator); } } return elemData; }; /*Check if element has already been parsed, if so erturn data from array, otherwise re-parse element*/ var elementData = function (element$) { var valList = null; if (element$.attr("data-val-parsed")) { for (var i = 0; i < data.length; i++) { if (element$[0].name == data[i].ControlName) valList = data[i]; } } else { valList = parseElement(element$); data.push(valList); element$.attr("data-val-parsed", true); } return valList; }; /*Clear all form validation CSS and reset values to blank.*/ var clearForm = function (form$) { var txtEls$ = form$.find("input[type='text'],input[type='hidden'],input[type='password'],textarea"); var chkEls$ = form$.find("input[type='checkbox'],input[type='radio']"); var selEls$ = form$.find("select"); txtEls$.val(""); chkEls$.prop("checked", false); selEls$.prop("selectedIndex", 0); clearValidation(form$); }; /*Clear validation CSS from elements */ var clearValidation = function (form$) { var classes = allClasses(); var els$ = form$.find("[data-val='true']"); for (var i = 0; i < els$.length; i++) { var el$ = $(els$[i]); var val$ = form$.find("[data-valmsg-for='" + (el$.attr("id") || el$.attr("name")) + "']"); el$.removeClass(classes); val$.removeClass(classes); } }; var allClasses = function () { return "dirty " + allValidationClasses(); }; var allValidationClasses = function () { if (me.Settings.AllValidationClasses.length > 0) return me.Settings.AllValidationClasses; var classes = new Array(); for (var prop in me.Settings.DisplayCss) { for (var k in me.Settings.DisplayCss[prop]) { classes.push(me.Settings.DisplayCss[prop][k]); } } me.Settings.AllValidationClasses = classes.join(" "); return me.Settings.AllValidationClasses; }; /*Delete element data for individual element*/ var clearElementData = function (element$) { if (element$.attr("data-val-parsed")) { var form$ = formISubmitTo(element$); var classes = allValidationClasses(); var val$ = form$.find("[data-valmsg-for='" + element$.attr("id") + "']"); element$.removeClass(classes); val$.removeClass(classes); element$.removeAttr("data-val-parsed"); element$.off(".BFNValidator"); for (var i = 0; i < data.length; i++) { if (element$[0].name == data[i].ControlName) { data.splice(i, 1); return; } } } }; /*Remove validation from all elements*/ var smash = function () { $("[data-val-triggerpopup-for]").off("click.BFNValidator"); $(me.Settings.AllSelector).off(".BFNValidator"); $(me.Settings.AllSelector).each(function () { $(this).removeAttr("data-val-parsed"); }); clearValidation($("body")); data = new Array(); }; /*Bind validation to all children of parent$ */ var attachToSection = function (parent$) { parent$.find(me.Settings.AllSelector).on("change.BFNValidator", function () { validateSingleElement($(this)); }); parent$.find(me.Settings.CheckableSelector).on("click.BFNValidator", function () { validateSingleElement($(this)); }); parent$.find(".SubmitLink, a[data-submitlink], input[data-submit='true']").on('click.BFNValidator', function (event) { var validationState = validateMyForm($(this)); if (validationState == me.ValidationStateTypes.Error) { event.preventDefault(); event.stopPropagation(); } }); parent$.find(".EnterKeySubmits, a[data-enterkeysubmits]").each(function () { var toSubmit$ = $(this); var form = formISubmitTo(toSubmit$); $(form).on("keydown.BFNValidator", me.Settings.AllSelector, function (event) { if (!event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode)) { event.which = event.charCode || event.keyCode; } if (event.which == 13) { toSubmit$.trigger('click'); if (toSubmit$.attr("href") && /^javascript:__doPostBack/.test(toSubmit$.attr("href"))) { eval(toSubmit$.attr("href")); } event.preventDefault(); event.stopPropagation(); return false; } }); }); }; /*Re-initialize all validation*/ var reInitialize = function () { smash(); attachToSection($("body")); }; var tryLogError = function (msg) { if (typeof (console) != "undefined") { console.log(msg); return; } alert(msg); }; var init = function () { $(document).ready(function () { attachToSection($("body")); }); }; init(); /*Public Functions*/ this.Smash = function () { smash(); }; this.ReInitialize = function () { reInitialize(); }; this.AttachToSection = function (parent$) { attachToSection(parent$); }; /*Delete element data for individual element*/ this.ClearElementData = function (element$) { clearElementData(element$); }; this.ValidateMyForm = function (element$) { var validationResult = validateMyForm(element$); var isValid = validationResult == BFNValidator.ValidationStateTypes.Valid || validationResult == BFNValidator.ValidationStateTypes.NonRequired; return isValid; }; this.ValidateForm = function (form$) { var validationResult = validateForm(form$); var isValid = validationResult == BFNValidator.ValidationStateTypes.Valid || validationResult == BFNValidator.ValidationStateTypes.NonRequired; return isValid; }; this.ClearForm = function (form$) { clearForm(form$); }; this.ClearValidation = function (form$) { clearValidation(form$); }; this.ValidateSingleElement = function (element$) { return validateSingleElement(element$); }; this.AddValidator = function (validatorType, callback) { addValidator(validatorType, callback); }; };