UNPKG

can

Version:

MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.

183 lines (181 loc) 5.99 kB
steal('can/util', 'can/map', function (can) { //validations object is by property. You can have validations that //span properties, but this way we know which ones to run. // proc should return true if there's an error or the error message var validate = function (attrNames, options, proc) { // normalize argumetns if (!proc) { proc = options; options = {}; } options = options || {}; attrNames = typeof attrNames === 'string' ? [attrNames] : can.makeArray(attrNames); // run testIf if it exists if (options.testIf && !options.testIf.call(this)) { return; } var self = this; can.each(attrNames, function (attrName) { // Add a test function for each attribute if (!self.validations[attrName]) { self.validations[attrName] = []; } self.validations[attrName].push(function (newVal) { // if options has a message return that, otherwise, return the error var res = proc.call(this, newVal, attrName); return res === undefined ? undefined : options.message || res; }); }); }; var old = can.Map.prototype.__set; can.Map.prototype.__set = function (prop, value, current, success, error) { var self = this, validations = self.constructor.validations, errorCallback = function (errors) { var stub = error && error.call(self, errors); // if 'setter' is on the page it will trigger // the error itself and we dont want to trigger // the event twice. :) if (stub !== false) { can.trigger(self, 'error', [ prop, errors ], true); } return false; }; old.call(self, prop, value, current, success, errorCallback); if (validations && validations[prop]) { var errors = self.errors(prop); if (errors) { errorCallback(errors); } } return this; }; can.each([ can.Map, can.Model ], function (clss) { // in some cases model might not be defined quite yet. if (clss === undefined) { return; } var oldSetup = clss.setup; /** * @static */ can.extend(clss, { setup: function (superClass) { oldSetup.apply(this, arguments); if (!this.validations || superClass.validations === this.validations) { this.validations = {}; } }, validate: validate, validationMessages: { format: 'is invalid', inclusion: 'is not a valid option (perhaps out of range)', lengthShort: 'is too short', lengthLong: 'is too long', presence: 'can\'t be empty', range: 'is out of range', numericality: 'must be a number' }, validateFormatOf: function (attrNames, regexp, options) { validate.call(this, attrNames, options, function (value) { if (typeof value !== 'undefined' && value !== null && value !== '' && String(value) .match(regexp) === null) { return this.constructor.validationMessages.format; } }); }, validateInclusionOf: function (attrNames, inArray, options) { validate.call(this, attrNames, options, function (value) { if (typeof value === 'undefined') { return; } for (var i = 0; i < inArray.length; i++) { if (inArray[i] === value) { return; } } return this.constructor.validationMessages.inclusion; }); }, validateLengthOf: function (attrNames, min, max, options) { validate.call(this, attrNames, options, function (value) { if ((typeof value === 'undefined' || value === null) && min > 0 || typeof value !== 'undefined' && value !== null && value.length < min) { return this.constructor.validationMessages.lengthShort + ' (min=' + min + ')'; } else if (typeof value !== 'undefined' && value !== null && value.length > max) { return this.constructor.validationMessages.lengthLong + ' (max=' + max + ')'; } }); }, validatePresenceOf: function (attrNames, options) { validate.call(this, attrNames, options, function (value) { if (typeof value === 'undefined' || value === '' || value === null) { return this.constructor.validationMessages.presence; } }); }, validateRangeOf: function (attrNames, low, hi, options) { validate.call(this, attrNames, options, function (value) { if ((typeof value === 'undefined' || value === null) && low > 0 || typeof value !== 'undefined' && value !== null && (value < low || value > hi)) { return this.constructor.validationMessages.range + ' [' + low + ',' + hi + ']'; } }); }, validatesNumericalityOf: function (attrNames) { validate.call(this, attrNames, function (value) { var res = !isNaN(parseFloat(value)) && isFinite(value); if (!res) { return this.constructor.validationMessages.numericality; } }); } }); }); /** * @prototype */ can.extend(can.Map.prototype, { errors: function (attrs, newVal) { // convert attrs to an array if (attrs) { attrs = can.isArray(attrs) ? attrs : [attrs]; } var errors = {}, self = this, // helper function that adds error messages to errors object // attr - the name of the attribute // funcs - the validation functions addErrors = function (attr, funcs) { can.each(funcs, function (func) { var res = func.call(self, isTest ? newVal : self.attr(attr)); if (res) { if (!errors[attr]) { errors[attr] = []; } errors[attr].push(res); } }); }, validations = this.constructor.validations || {}, isTest = attrs && attrs.length === 1 && arguments.length === 2; // go through each attribute or validation and // add any errors can.each(attrs || validations, function (funcs, attr) { // if we are iterating through an array, use funcs // as the attr name if (typeof attr === 'number') { attr = funcs; funcs = validations[attr]; } // add errors to the addErrors(attr, funcs || []); }); // return errors as long as we have one return can.isEmptyObject(errors) ? null : isTest ? errors[attrs[0]] : errors; } }); return can.Map; });