Jump To …

can.observe.validations.js

/*
* CanJS - 1.1.3 (2012-12-11)
* http://canjs.us/
* Copyright (c) 2012 Bitovi
* Licensed MIT
*/
(function (can, window, undefined) {

can/observe/validations/validations.js

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 = 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.Observe.prototype.__set;
  can.Observe.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);
      errors && errorCallback(errors)
    }

    return this;
  }

  can.each([can.Observe, can.Model], function (clss) {

in some cases model might not be defined quite yet.

    if (clss === undefined) {
      return;
    }
    var oldSetup = clss.setup;

    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"
      },


      validateFormatOf: function (attrNames, regexp, options) {
        validate.call(this, attrNames, options, function (value) {
          if ((typeof value != 'undefined' && 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;
          }

          if (can.grep(inArray, function (elm) {
            return (elm == value);
          }).length == 0) {
            return this.constructor.validationMessages.inclusion;
          }
        });
      },


      validateLengthOf: function (attrNames, min, max, options) {
        validate.call(this, attrNames, options, function (value) {
          if ((typeof value == 'undefined' && min > 0) || value.length < min) {
            return this.constructor.validationMessages.lengthShort + " (min=" + min + ")";
          } else if (typeof value != 'undefined' && 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 < low || value > hi) {
            return this.constructor.validationMessages.range + " [" + low + "," + hi + "]";
          }
        });
      }
    });
  });

  can.extend(can.Observe.prototype, {


    errors: function (attrs, newVal) {

convert attrs to an array

      if (attrs) {
        attrs = can.isArray(attrs) ? attrs : [attrs];
      }

      var errors = {},
        self = this,
        attr,

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 ? (self.__convert ? self.__convert(attr, newVal) : newVal) : self[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;
    }
  });

})(can, this);