Skip to content

Instantly share code, notes, and snippets.

@henninga
Created July 20, 2010 12:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save henninga/482883 to your computer and use it in GitHub Desktop.
Save henninga/482883 to your computer and use it in GitHub Desktop.
// Common initialization
var xVal = xVal || {};
xVal.Plugins = xVal.Plugins || {};
xVal.Messages = xVal.Messages || {};
xVal.AttachValidator = function(elementPrefix, rulesConfig, options, pluginName) {
if (pluginName != null)
this.Plugins[pluginName].AttachValidator(elementPrefix, rulesConfig, options);
else
for (var key in this.Plugins) {
this.Plugins[key].AttachValidator(elementPrefix, rulesConfig, options);
return;
}
};
// xVal.jquery.validate.js
// An xVal plugin to enable support for jQuery Validate
// http://xval.codeplex.com/
// (c) 2009 Steven Sanderson
// License: Microsoft Public License (Ms-PL) (http://www.opensource.org/licenses/ms-pl.html)
(function($) {
xVal.Plugins["jquery.validate"] = {
AttachValidator: function(elementPrefix, rulesConfig, options) {
var self = this;
self._ensureCustomFunctionsRegistered();
$(function() {
self._ensureValidationSummaryContainerExistsIfRequired(options);
for (var i = 0; i < rulesConfig.Fields.length; i++) {
var fieldName = rulesConfig.Fields[i].FieldName;
var fieldRules = rulesConfig.Fields[i].FieldRules;
// Is there a matching DOM element?
var elemId = self._makeAspNetMvcHtmlHelperID((elementPrefix ? elementPrefix + "." : "") + fieldName);
var elem = document.getElementById(elemId);
if (elem) {
for (var j = 0; j < fieldRules.length; j++) {
var rule = fieldRules[j];
if (rule != null) {
var ruleName = rule.RuleName;
var ruleParams = rule.RuleParameters;
var errorText = (typeof (rule.Message) == 'undefined' ? null : rule.Message);
self._attachRuleToDOMElement(ruleName, ruleParams, errorText, $(elem), elementPrefix, options);
}
}
}
}
});
},
_makeAspNetMvcHtmlHelperID: function(fullyQualifiedModelName) {
// If you have changed HtmlHelper.IdAttributeDotReplacement from "_" to something else, then you must also change the following line to match
return fullyQualifiedModelName.replace(/\./g, "_");
},
_attachRuleToDOMElement: function(ruleName, ruleParams, errorText, element, elementPrefix, options) {
var parentForm = element.parents("form");
if (parentForm.length != 1)
alert("Error: Element " + element.attr("id") + " is not in a form");
this._ensureFormIsMarkedForValidation($(parentForm[0]), options);
this._associateNearbyValidationMessageSpanWithElement(element);
var options = {};
switch (ruleName) {
case "Required":
options.required = true;
options.messages = { required: errorText || xVal.Messages.Required };
break;
case "Range":
if (ruleParams.Type == "string") {
options.xVal_stringRange = [ruleParams.Min, ruleParams.Max];
if (errorText != null) options.messages = { xVal_stringRange: $.format(errorText) };
}
else if (ruleParams.Type == "datetime") {
var minDate, maxDate;
if (typeof (ruleParams.MinYear) != 'undefined')
minDate = new Date(ruleParams.MinYear, ruleParams.MinMonth - 1, ruleParams.MinDay, ruleParams.MinHour, ruleParams.MinMinute, ruleParams.MinSecond);
if (typeof (ruleParams.MaxYear) != 'undefined')
maxDate = new Date(ruleParams.MaxYear, ruleParams.MaxMonth - 1, ruleParams.MaxDay, ruleParams.MaxHour, ruleParams.MaxMinute, ruleParams.MaxSecond);
options.xVal_dateRange = [minDate, maxDate];
if (errorText != null) options.messages = { xVal_dateRange: $.format(errorText) };
}
else if (typeof (ruleParams.Min) == 'undefined') {
options.max = ruleParams.Max;
errorText = errorText || xVal.Messages.Range_Numeric_Max;
if (errorText != null) options.messages = { max: $.format(errorText) };
}
else if (typeof (ruleParams.Max) == 'undefined') {
options.min = ruleParams.Min;
errorText = errorText || xVal.Messages.Range_Numeric_Min;
if (errorText != null) options.messages = { min: $.format(errorText) };
}
else {
options.range = [ruleParams.Min, ruleParams.Max];
errorText = errorText || xVal.Messages.Range_Numeric_MinMax;
if (errorText != null) options.messages = { range: $.format(errorText) };
}
break;
case "StringLength":
if (typeof (ruleParams.MinLength) == 'undefined') {
options.maxlength = ruleParams.MaxLength;
errorText = errorText || xVal.Messages.StringLength_Max;
if (errorText != null) options.messages = { maxlength: $.format(errorText) };
}
else if (typeof (ruleParams.MaxLength) == 'undefined') {
options.minlength = ruleParams.MinLength;
errorText = errorText || xVal.Messages.StringLength_Min;
if (errorText != null) options.messages = { minlength: $.format(errorText) };
}
else {
options.rangelength = [ruleParams.MinLength, ruleParams.MaxLength];
errorText = errorText || xVal.Messages.StringLength_MinMax;
if (errorText != null) options.messages = { rangelength: $.format(errorText) };
}
break;
case "DataType":
switch (ruleParams.Type) {
case "EmailAddress":
options.email = true;
options.messages = { email: errorText || xVal.Messages.DataType_EmailAddress };
break;
case "Integer":
options.xVal_regex = ["^\\-?\\d+$", ""];
options.messages = { xVal_regex: errorText || xVal.Messages.DataType_Integer || "Please enter a whole number." };
break;
case "Decimal":
options.number = true;
options.messages = { number: errorText || xVal.Messages.DataType_Decimal };
break;
case "Date":
options.date = true;
options.messages = { date: errorText || xVal.Messages.DataType_Date };
break;
case "DateTime":
options.xVal_regex = ["^\\d{1,2}/\\d{1,2}/(\\d{2}|\\d{4})\\s+\\d{1,2}\\:\\d{2}(\\:\\d{2})?$", ""];
options.messages = { xVal_regex: errorText || xVal.Messages.DataType_DateTime || "Please enter a valid date and time." };
break;
case "Currency":
options.xVal_regex = ["^\\D?\\s?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(.[0-9][0-9])?$", ""];
options.messages = { xVal_regex: errorText || xVal.Messages.DataType_Currency || "Please enter a currency value." };
break;
case "CreditCardLuhn":
options.xVal_creditCardLuhn = true;
if (errorText != null) options.messages = { xVal_creditCardLuhn: errorText };
break;
}
break;
case "RegEx":
options.xVal_regex = [ruleParams.Pattern, ruleParams.Options];
if (errorText != null) options.messages = { xVal_regex: errorText };
break;
case "Comparison":
var elemToCompareId = this._makeAspNetMvcHtmlHelperID((elementPrefix ? elementPrefix + "." : "") + ruleParams.PropertyToCompare);
var elemToCompare = document.getElementById(elemToCompareId);
if (elemToCompare != null) {
options.xVal_comparison = [ruleParams.PropertyToCompare, elemToCompare, ruleParams.ComparisonOperator];
if (errorText != null) options.messages = { xVal_comparison: errorText };
}
break;
case "Remote":
var dataAccessor = {};
parentForm.find("input[name], textarea[name], select[name]").each(function() {
var input = this;
dataAccessor[input.name] = function() { return $(input).val(); };
});
options.remote = {
url: ruleParams.url,
data: dataAccessor,
type: 'post'
};
break;
case "Custom":
var ruleFunction = this._parseAsFunctionWithWarnings(ruleParams.Function);
if (ruleFunction != null) {
var customFunctionName = this._registerCustomValidationFunction(ruleFunction);
var evaluatedParams = ruleParams.Parameters == "null" ? null : eval("(" + ruleParams.Parameters + ")");
options[customFunctionName] = evaluatedParams || true;
options.messages = [];
options.messages[customFunctionName] = errorText;
}
break;
}
element.rules("add", options);
},
_parseAsFunctionWithWarnings: function(functionString) {
var result;
try { result = eval("(" + functionString + ")") }
catch (ex) {
alert("Custom rule error: Could not find or could not parse the function '" + functionString + "'");
return null;
}
if (typeof (result) != 'function') {
alert("Custom rule error: The JavaScript object '" + functionString + "' is not a function.");
return null;
}
return result;
},
_associateNearbyValidationMessageSpanWithElement: function(element) {
// If there's a <span class='field-validation-error'> soon after, it's probably supposed to display the error message
// jquery.validation goes looking for an attribute called "htmlfor" as follows
var nearbyMessages = element.nextAll("span.field-validation-error");
if (nearbyMessages.length > 0) {
$(nearbyMessages[0]).attr("generated", "true")
.attr("htmlfor", element.attr("id"));
}
},
_ensureFormIsMarkedForValidation: function(formElement, options) {
if (!formElement.data("isMarkedForValidation")) {
formElement.data("isMarkedForValidation", true);
var validationOptions = {
errorClass: "field-validation-error",
errorElement: "span",
highlight: function(element) { $(element).addClass("input-validation-error"); },
unhighlight: function(element) { $(element).removeClass("input-validation-error"); }
};
if (options.ValidationSummary) {
validationOptions.wrapper = "li";
validationOptions.errorLabelContainer = "#" + options.ValidationSummary.ElementID + " ul:first";
}
var validator = formElement.validate(validationOptions);
if (options.ValidationSummary)
this._modifyJQueryValidationElementHidingBehaviourToSupportValidationSummary(validator, options);
}
},
_registerCustomValidationFunction: function(evalFn) {
jQuery.validator.xValCustomFunctionCount = (jQuery.validator.xValCustomFunctionCount || 0) + 1;
var functionName = "xVal_customFunction_" + jQuery.validator.xValCustomFunctionCount;
jQuery.validator.addMethod(functionName, function(value, element, params) {
if (this.optional(element))
return true;
return evalFn(value, element, params);
});
return functionName;
},
_ensureCustomFunctionsRegistered: function() {
if (!jQuery.validator.xValFunctionsRegistered) {
jQuery.validator.xValFunctionsRegistered = true;
jQuery.validator.addMethod("xVal_stringRange", function(value, element, params) {
if (this.optional(element)) return true;
if (params[0] != null)
if (value < params[0]) return false;
if (params[1] != null)
if (value > params[1]) return false;
return true;
}, function(params) {
if ((params[0] != null) && (params[1] != null))
return $.format(xVal.Messages.Range_String_MinMax || "Please enter a value alphabetically between '{0}' and '{1}'.", params[0], params[1]);
else if (params[0] != null)
return $.format(xVal.Messages.Range_String_Min || "Please enter a value not alphabetically before '{0}'.", params[0]);
else
return $.format(xVal.Messages.Range_String_Max || "Please enter a value not alphabetically after '{0}'.", params[1]);
});
jQuery.validator.addMethod("xVal_dateRange", function(value, element, params) {
if (this.optional(element)) return true;
var parsedValue = Date.parse(value);
if (isNaN(parsedValue))
return false;
else
parsedValue = new Date(parsedValue);
if (params[0] != null)
if (parsedValue < params[0]) return false;
if (params[1] != null)
if (parsedValue > params[1]) return false;
return true;
}, function(params, elem) {
if (isNaN(Date.parse(elem.value)))
return xVal.Messages.DataType_Date || "Please enter a valid date in yyyy/mm/dd format.";
var formatDate = function(date) {
var result = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
if (date.getHours() + date.getMinutes() + date.getSeconds() != 0)
result += " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
return result.replace(/\b(\d)\b/g, '0$1');
};
if ((params[0] != null) && (params[1] != null))
return $.format(xVal.Messages.Range_DateTime_MinMax || "Please enter a date between {0} and {1}.", formatDate(params[0]), formatDate(params[1]));
else if (params[0] != null)
return $.format(xVal.Messages.Range_DateTime_Min || "Please enter a date no earlier than {0}.", formatDate(params[0]));
else
return $.format(xVal.Messages.Range_DateTime_Max || "Please enter a date no later than {0}.", formatDate(params[1]));
});
jQuery.validator.addMethod("xVal_regex", function(value, element, params) {
if (this.optional(element)) return true;
var pattern = params[0];
var options = params[1];
var regex = new RegExp(pattern, options);
return regex.test(value);
}, function(params) {
return xVal.Messages.Regex || "This value is invalid."; // Pity we can't be more descriptive
});
jQuery.validator.addMethod("xVal_creditCardLuhn", function(value, element, params) {
if (this.optional(element)) return true;
value = value.replace(/\D/g, "");
if (value == "") return false;
var sum = 0;
for (var i = value.length - 2; i >= 0; i -= 2)
sum += Array(0, 2, 4, 6, 8, 1, 3, 5, 7, 9)[parseInt(value.charAt(i), 10)];
for (var i = value.length - 1; i >= 0; i -= 2)
sum += parseInt(value.charAt(i), 10);
return (sum % 10) == 0;
}, function(params) {
return xVal.Messages.DataType_CreditCardLuhn || "Please enter a valid credit card number.";
});
jQuery.validator.addMethod("xVal_comparison", function(value, element, params) {
if (this.optional(element)) return true;
var elemToCompare = params[1];
var comparisonOperator = params[2];
switch (comparisonOperator) {
case "Equals": return value == elemToCompare.value;
case "DoesNotEqual": return value != elemToCompare.value;
}
return true; // Ignore unrecognized operator
}, function(params) {
var propertyToCompareName = params[0];
var comparisonOperator = params[2];
switch (comparisonOperator) {
case "Equals": return $.format(xVal.Messages.Comparison_Equals || "This value must be the same as {0}.", propertyToCompareName);
case "DoesNotEqual": return $.format(xVal.Messages.Comparison_DoesNotEqual || "This value must be different from {0}.", propertyToCompareName);
}
});
$.expr[":"].displayableValidationSummaryMessage = function(object) {
var span = $(object).find("span:first");
if (span.length == 0)
return true;
return !(span.css("display") === "none") && !span.is(":empty");
};
}
},
_ensureValidationSummaryContainerExistsIfRequired: function(options) {
// If we're using a validation summary, make sure the container contains an UL
// (If there were no server-generated errors, there won't be until we create one)
if (options.ValidationSummary) {
var validationSummaryContainer = $("#" + options.ValidationSummary.ElementID);
if (validationSummaryContainer.length == 0)
alert("Cannot find validation summary element \"" + options.ValidationSummary.ElementID + "\". Make sure you've put an element with this ID into your HTML document.");
if (!validationSummaryContainer.is(":has(ul)")) {
validationSummaryContainer.append($("<span class='validation-summary-errors' />").text(options.ValidationSummary.HeaderMessage))
.append($("<ul />"))
.hide();
}
}
},
_modifyJQueryValidationElementHidingBehaviourToSupportValidationSummary: function(validator, options) {
// Intercept the hideErrors() and showErrors() methods. Pity there isn't a proper API for this.
var originalHideErrorsMethod = validator.hideErrors;
var originalShowErrorsMethod = validator.showErrors;
validator.hideErrors = function() {
this.toHide = this.toHide.not("ul"); // Don't ever hide ULs, because these might still contain server-generated error messages
originalHideErrorsMethod.apply(this, arguments);
// If the summary container contains no displayable messages, hide the whole thing
$("#" + options.ValidationSummary.ElementID + ":not(:has(li:displayableValidationSummaryMessage))").hide();
};
validator.showErrors = function() {
originalShowErrorsMethod.apply(this, arguments);
// If the summary container contains any displayable messages, show it
$("#" + options.ValidationSummary.ElementID + ":has(li:displayableValidationSummaryMessage)").show();
};
}
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment