diff --git a/ajax/libs/backbone.validation/0.8.2/backbone-validation-amd-min.js b/ajax/libs/backbone.validation/0.8.2/backbone-validation-amd-min.js new file mode 100644 index 000000000..13507a0eb --- /dev/null +++ b/ajax/libs/backbone.validation/0.8.2/backbone-validation-amd-min.js @@ -0,0 +1,8 @@ +// Backbone.Validation v0.8.2 +// +// Copyright (c) 2011-2013 Thomas Pedersen +// Distributed under MIT License +// +// Documentation and full license available at: +// http://thedersen.com/projects/backbone-validation +!function(a){"object"==typeof exports?module.exports=a(require("backbone"),require("underscore")):"function"==typeof define&&define.amd&&define(["backbone","underscore"],a)}(function(a,b){return a.Validation=function(b){"use strict";var c={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},d={formatLabel:function(a,b){return j[c.labelFormatter](a,b)},format:function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return"undefined"!=typeof a[c]?a[c]:b})}},e=function(c,d,f){return d=d||{},f=f||"",b.each(c,function(b,g){c.hasOwnProperty(g)&&(!b||"object"!=typeof b||b instanceof Array||b instanceof Date||b instanceof RegExp||b instanceof a.Model||b instanceof a.Collection?d[f+g]=b:e(b,d,f+g+"."))}),d},f=function(){var a=function(a){return b.reduce(b.keys(b.result(a,"validation")||{}),function(a,b){return a[b]=void 0,a},{})},f=function(a,c){var d=a.validation?b.result(a,"validation")[c]||{}:{};return(b.isFunction(d)||b.isString(d))&&(d={fn:d}),b.isArray(d)||(d=[d]),b.reduce(d,function(a,c){return b.each(b.without(b.keys(c),"msg"),function(b){a.push({fn:k[b],val:c[b],msg:c.msg})}),a},[])},h=function(a,c,e,g){return b.reduce(f(a,c),function(f,h){var i=b.extend({},d,k),j=h.fn.call(i,e,c,h.val,a,g);return j===!1||f===!1?!1:j&&!f?b.result(h,"msg")||j:f},"")},i=function(a,c){var d,f={},g=!0,i=b.clone(c),j=e(c);return b.each(j,function(b,c){d=h(a,c,b,i),d&&(f[c]=d,g=!1)}),{invalidAttrs:f,isValid:g}},j=function(c,d){return{preValidate:function(a,c){var d,e=this,f={};return b.isObject(a)?(b.each(a,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),b.isEmpty(f)?void 0:f):h(this,a,c,b.extend({},this.attributes))},isValid:function(a){var c=e(this.attributes);return b.isString(a)?!h(this,a,c[a],b.extend({},this.attributes)):b.isArray(a)?b.reduce(a,function(a,d){return a&&!h(this,d,c[d],b.extend({},this.attributes))},!0,this):(a===!0&&this.validate(),this.validation?this._isValid:!0)},validate:function(f,g){var h=this,j=!f,k=b.extend({},d,g),l=a(h),m=b.extend({},l,h.attributes,f),n=e(f||m),o=i(h,m);return h._isValid=o.isValid,b.each(l,function(a,b){var d=o.invalidAttrs.hasOwnProperty(b);d||k.valid(c,b,k.selector)}),b.each(l,function(a,b){var d=o.invalidAttrs.hasOwnProperty(b),e=n.hasOwnProperty(b);d&&(e||j)&&k.invalid(c,b,o.invalidAttrs[b],k.selector)}),b.defer(function(){h.trigger("validated",h._isValid,h,o.invalidAttrs),h.trigger("validated:"+(h._isValid?"valid":"invalid"),h,o.invalidAttrs)}),!k.forceUpdate&&b.intersection(b.keys(o.invalidAttrs),b.keys(n)).length>0?o.invalidAttrs:void 0}}},l=function(a,c,d){b.extend(c,j(a,d))},m=function(a){delete a.validate,delete a.preValidate,delete a.isValid},n=function(a){l(this.view,a,this.options)},o=function(a){m(a)};return{version:"0.8.2",configure:function(a){b.extend(c,a)},bind:function(a,d){d=b.extend({},c,g,d);var e=d.model||a.model,f=d.collection||a.collection;if("undefined"==typeof e&&"undefined"==typeof f)throw"Before you execute the binding your view must have a model or a collection.\nSee http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.";e?l(a,e,d):f&&(f.each(function(b){l(a,b,d)}),f.bind("add",n,{view:a,options:d}),f.bind("remove",o))},unbind:function(a,c){c=b.extend({},c);var d=c.model||a.model,e=c.collection||a.collection;d&&m(d),e&&(e.each(function(a){m(a)}),e.unbind("add",n),e.unbind("remove",o))},mixin:j(null,c)}}(),g=f.callbacks={valid:function(a,b,c){a.$("["+c+'~="'+b+'"]').removeClass("invalid").removeAttr("data-error")},invalid:function(a,b,c,d){a.$("["+d+'~="'+b+'"]').addClass("invalid").attr("data-error",c)}},h=f.patterns={digits:/^\d+$/,number:/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,email:/^((([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,url:/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i},i=f.messages={required:"{0} is required",acceptance:"{0} must be accepted",min:"{0} must be greater than or equal to {1}",max:"{0} must be less than or equal to {1}",range:"{0} must be between {1} and {2}",length:"{0} must be {1} characters",minLength:"{0} must be at least {1} characters",maxLength:"{0} must be at most {1} characters",rangeLength:"{0} must be between {1} and {2} characters",oneOf:"{0} must be one of: {1}",equalTo:"{0} must be the same as {1}",pattern:"{0} must be a valid {1}"},j=f.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return 0===b?a.toUpperCase():" "+a.toLowerCase()}).replace(/_/g," ")},label:function(a,b){return b.labels&&b.labels[a]||j.sentenceCase(a,b)}},k=f.validators=function(){var a=String.prototype.trim?function(a){return null===a?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return null===a?"":a.toString().replace(b,"").replace(c,"")},c=function(a){return b.isNumber(a)||b.isString(a)&&a.match(h.number)},d=function(c){return!(b.isNull(c)||b.isUndefined(c)||b.isString(c)&&""===a(c)||b.isArray(c)&&b.isEmpty(c))};return{fn:function(a,c,d,e,f){return b.isString(d)&&(d=e[d]),d.call(e,a,c,f)},required:function(a,c,e,f,g){var h=b.isFunction(e)?e.call(f,a,c,g):e;return h||d(a)?h&&!d(a)?this.format(i.required,this.formatLabel(c,f)):void 0:!1},acceptance:function(a,c,d,e){return"true"===a||b.isBoolean(a)&&a!==!1?void 0:this.format(i.acceptance,this.formatLabel(c,e))},min:function(a,b,d,e){return!c(a)||d>a?this.format(i.min,this.formatLabel(b,e),d):void 0},max:function(a,b,d,e){return!c(a)||a>d?this.format(i.max,this.formatLabel(b,e),d):void 0},range:function(a,b,d,e){return!c(a)||ad[1]?this.format(i.range,this.formatLabel(b,e),d[0],d[1]):void 0},length:function(b,c,e,f){return d(b)&&a(b).length===e?void 0:this.format(i.length,this.formatLabel(c,f),e)},minLength:function(b,c,e,f){return!d(b)||a(b).lengthe?this.format(i.maxLength,this.formatLabel(c,f),e):void 0},rangeLength:function(b,c,e,f){return!d(b)||a(b).lengthe[1]?this.format(i.rangeLength,this.formatLabel(c,f),e[0],e[1]):void 0},oneOf:function(a,c,d,e){return b.include(d,a)?void 0:this.format(i.oneOf,this.formatLabel(c,e),d.join(", "))},equalTo:function(a,b,c,d,e){return a!==e[c]?this.format(i.equalTo,this.formatLabel(b,d),this.formatLabel(c,d)):void 0},pattern:function(a,b,c,e){return d(a)&&a.toString().match(h[c]||c)?void 0:this.format(i.pattern,this.formatLabel(b,e),c)}}}();return f}(b),a.Validation}); \ No newline at end of file diff --git a/ajax/libs/backbone.validation/0.8.2/backbone-validation-amd.js b/ajax/libs/backbone.validation/0.8.2/backbone-validation-amd.js new file mode 100644 index 000000000..1919c88ad --- /dev/null +++ b/ajax/libs/backbone.validation/0.8.2/backbone-validation-amd.js @@ -0,0 +1,632 @@ +// Backbone.Validation v0.8.2 +// +// Copyright (c) 2011-2013 Thomas Pedersen +// Distributed under MIT License +// +// Documentation and full license available at: +// http://thedersen.com/projects/backbone-validation +(function (factory) { + if (typeof exports === 'object') { + module.exports = factory(require('backbone'), require('underscore')); + } else if (typeof define === 'function' && define.amd) { + define(['backbone', 'underscore'], factory); + } +}(function (Backbone, _) { + Backbone.Validation = (function(_){ + 'use strict'; + + // Default options + // --------------- + + var defaultOptions = { + forceUpdate: false, + selector: 'name', + labelFormatter: 'sentenceCase', + valid: Function.prototype, + invalid: Function.prototype + }; + + + // Helper functions + // ---------------- + + // Formatting functions used for formatting error messages + var formatFunctions = { + // Uses the configured label formatter to format the attribute name + // to make it more readable for the user + formatLabel: function(attrName, model) { + return defaultLabelFormatters[defaultOptions.labelFormatter](attrName, model); + }, + + // Replaces nummeric placeholders like {0} in a string with arguments + // passed to the function + format: function() { + var args = Array.prototype.slice.call(arguments), + text = args.shift(); + return text.replace(/\{(\d+)\}/g, function(match, number) { + return typeof args[number] !== 'undefined' ? args[number] : match; + }); + } + }; + + // Flattens an object + // eg: + // + // var o = { + // address: { + // street: 'Street', + // zip: 1234 + // } + // }; + // + // becomes: + // + // var o = { + // 'address.street': 'Street', + // 'address.zip': 1234 + // }; + var flatten = function (obj, into, prefix) { + into = into || {}; + prefix = prefix || ''; + + _.each(obj, function(val, key) { + if(obj.hasOwnProperty(key)) { + if (val && typeof val === 'object' && !( + val instanceof Array || + val instanceof Date || + val instanceof RegExp || + val instanceof Backbone.Model || + val instanceof Backbone.Collection) + ) { + flatten(val, into, prefix + key + '.'); + } + else { + into[prefix + key] = val; + } + } + }); + + return into; + }; + + // Validation + // ---------- + + var Validation = (function(){ + + // Returns an object with undefined properties for all + // attributes on the model that has defined one or more + // validation rules. + var getValidatedAttrs = function(model) { + return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) { + memo[key] = void 0; + return memo; + }, {}); + }; + + // Looks on the model for validations for a specified + // attribute. Returns an array of any validators defined, + // or an empty array if none is defined. + var getValidators = function(model, attr) { + var attrValidationSet = model.validation ? _.result(model, 'validation')[attr] || {} : {}; + + // If the validator is a function or a string, wrap it in a function validator + if (_.isFunction(attrValidationSet) || _.isString(attrValidationSet)) { + attrValidationSet = { + fn: attrValidationSet + }; + } + + // Stick the validator object into an array + if(!_.isArray(attrValidationSet)) { + attrValidationSet = [attrValidationSet]; + } + + // Reduces the array of validators into a new array with objects + // with a validation method to call, the value to validate against + // and the specified error message, if any + return _.reduce(attrValidationSet, function(memo, attrValidation) { + _.each(_.without(_.keys(attrValidation), 'msg'), function(validator) { + memo.push({ + fn: defaultValidators[validator], + val: attrValidation[validator], + msg: attrValidation.msg + }); + }); + return memo; + }, []); + }; + + // Validates an attribute against all validators defined + // for that attribute. If one or more errors are found, + // the first error message is returned. + // If the attribute is valid, an empty string is returned. + var validateAttr = function(model, attr, value, computed) { + // Reduces the array of validators to an error message by + // applying all the validators and returning the first error + // message, if any. + return _.reduce(getValidators(model, attr), function(memo, validator){ + // Pass the format functions plus the default + // validators as the context to the validator + var ctx = _.extend({}, formatFunctions, defaultValidators), + result = validator.fn.call(ctx, value, attr, validator.val, model, computed); + + if(result === false || memo === false) { + return false; + } + if (result && !memo) { + return _.result(validator, 'msg') || result; + } + return memo; + }, ''); + }; + + // Loops through the model's attributes and validates them all. + // Returns and object containing names of invalid attributes + // as well as error messages. + var validateModel = function(model, attrs) { + var error, + invalidAttrs = {}, + isValid = true, + computed = _.clone(attrs), + flattened = flatten(attrs); + + _.each(flattened, function(val, attr) { + error = validateAttr(model, attr, val, computed); + if (error) { + invalidAttrs[attr] = error; + isValid = false; + } + }); + + return { + invalidAttrs: invalidAttrs, + isValid: isValid + }; + }; + + // Contains the methods that are mixed in on the model when binding + var mixin = function(view, options) { + return { + + // Check whether or not a value, or a hash of values + // passes validation without updating the model + preValidate: function(attr, value) { + var self = this, + result = {}, + error; + + if(_.isObject(attr)){ + _.each(attr, function(value, key) { + error = self.preValidate(key, value); + if(error){ + result[key] = error; + } + }); + + return _.isEmpty(result) ? undefined : result; + } + else { + return validateAttr(this, attr, value, _.extend({}, this.attributes)); + } + }, + + // Check to see if an attribute, an array of attributes or the + // entire model is valid. Passing true will force a validation + // of the model. + isValid: function(option) { + var flattened = flatten(this.attributes); + + if(_.isString(option)){ + return !validateAttr(this, option, flattened[option], _.extend({}, this.attributes)); + } + if(_.isArray(option)){ + return _.reduce(option, function(memo, attr) { + return memo && !validateAttr(this, attr, flattened[attr], _.extend({}, this.attributes)); + }, true, this); + } + if(option === true) { + this.validate(); + } + return this.validation ? this._isValid : true; + }, + + // This is called by Backbone when it needs to perform validation. + // You can call it manually without any parameters to validate the + // entire model. + validate: function(attrs, setOptions){ + var model = this, + validateAll = !attrs, + opt = _.extend({}, options, setOptions), + validatedAttrs = getValidatedAttrs(model), + allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), + changedAttrs = flatten(attrs || allAttrs), + + result = validateModel(model, allAttrs); + + model._isValid = result.isValid; + + // After validation is performed, loop through all changed attributes + // and call the valid callbacks so the view is updated. + _.each(validatedAttrs, function(val, attr){ + var invalid = result.invalidAttrs.hasOwnProperty(attr); + if(!invalid){ + opt.valid(view, attr, opt.selector); + } + }); + + // After validation is performed, loop through all changed attributes + // and call the invalid callback so the view is updated. + _.each(validatedAttrs, function(val, attr){ + var invalid = result.invalidAttrs.hasOwnProperty(attr), + changed = changedAttrs.hasOwnProperty(attr); + + if(invalid && (changed || validateAll)){ + opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); + } + }); + + // Trigger validated events. + // Need to defer this so the model is actually updated before + // the event is triggered. + _.defer(function() { + model.trigger('validated', model._isValid, model, result.invalidAttrs); + model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs); + }); + + // Return any error messages to Backbone, unless the forceUpdate flag is set. + // Then we do not return anything and fools Backbone to believe the validation was + // a success. That way Backbone will update the model regardless. + if (!opt.forceUpdate && _.intersection(_.keys(result.invalidAttrs), _.keys(changedAttrs)).length > 0) { + return result.invalidAttrs; + } + } + }; + }; + + // Helper to mix in validation on a model + var bindModel = function(view, model, options) { + _.extend(model, mixin(view, options)); + }; + + // Removes the methods added to a model + var unbindModel = function(model) { + delete model.validate; + delete model.preValidate; + delete model.isValid; + }; + + // Mix in validation on a model whenever a model is + // added to a collection + var collectionAdd = function(model) { + bindModel(this.view, model, this.options); + }; + + // Remove validation from a model whenever a model is + // removed from a collection + var collectionRemove = function(model) { + unbindModel(model); + }; + + // Returns the public methods on Backbone.Validation + return { + + // Current version of the library + version: '0.8.2', + + // Called to configure the default options + configure: function(options) { + _.extend(defaultOptions, options); + }, + + // Hooks up validation on a view with a model + // or collection + bind: function(view, options) { + options = _.extend({}, defaultOptions, defaultCallbacks, options); + + var model = options.model || view.model, + collection = options.collection || view.collection; + + if(typeof model === 'undefined' && typeof collection === 'undefined'){ + throw 'Before you execute the binding your view must have a model or a collection.\n' + + 'See http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.'; + } + + if(model) { + bindModel(view, model, options); + } + else if(collection) { + collection.each(function(model){ + bindModel(view, model, options); + }); + collection.bind('add', collectionAdd, {view: view, options: options}); + collection.bind('remove', collectionRemove); + } + }, + + // Removes validation from a view with a model + // or collection + unbind: function(view, options) { + options = _.extend({}, options); + var model = options.model || view.model, + collection = options.collection || view.collection; + + if(model) { + unbindModel(model); + } + if(collection) { + collection.each(function(model){ + unbindModel(model); + }); + collection.unbind('add', collectionAdd); + collection.unbind('remove', collectionRemove); + } + }, + + // Used to extend the Backbone.Model.prototype + // with validation + mixin: mixin(null, defaultOptions) + }; + }()); + + + // Callbacks + // --------- + + var defaultCallbacks = Validation.callbacks = { + + // Gets called when a previously invalid field in the + // view becomes valid. Removes any error message. + // Should be overridden with custom functionality. + valid: function(view, attr, selector) { + view.$('[' + selector + '~="' + attr + '"]') + .removeClass('invalid') + .removeAttr('data-error'); + }, + + // Gets called when a field in the view becomes invalid. + // Adds a error message. + // Should be overridden with custom functionality. + invalid: function(view, attr, error, selector) { + view.$('[' + selector + '~="' + attr + '"]') + .addClass('invalid') + .attr('data-error', error); + } + }; + + + // Patterns + // -------- + + var defaultPatterns = Validation.patterns = { + // Matches any digit(s) (i.e. 0-9) + digits: /^\d+$/, + + // Matched any number (e.g. 100.000) + number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/, + + // Matches a valid email address (e.g. mail@example.com) + email: /^((([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, + + // Mathes any valid url (e.g. http://www.xample.com) + url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i + }; + + + // Error messages + // -------------- + + // Error message for the build in validators. + // {x} gets swapped out with arguments form the validator. + var defaultMessages = Validation.messages = { + required: '{0} is required', + acceptance: '{0} must be accepted', + min: '{0} must be greater than or equal to {1}', + max: '{0} must be less than or equal to {1}', + range: '{0} must be between {1} and {2}', + length: '{0} must be {1} characters', + minLength: '{0} must be at least {1} characters', + maxLength: '{0} must be at most {1} characters', + rangeLength: '{0} must be between {1} and {2} characters', + oneOf: '{0} must be one of: {1}', + equalTo: '{0} must be the same as {1}', + pattern: '{0} must be a valid {1}' + }; + + // Label formatters + // ---------------- + + // Label formatters are used to convert the attribute name + // to a more human friendly label when using the built in + // error messages. + // Configure which one to use with a call to + // + // Backbone.Validation.configure({ + // labelFormatter: 'label' + // }); + var defaultLabelFormatters = Validation.labelFormatters = { + + // Returns the attribute name with applying any formatting + none: function(attrName) { + return attrName; + }, + + // Converts attributeName or attribute_name to Attribute name + sentenceCase: function(attrName) { + return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) { + return index === 0 ? match.toUpperCase() : ' ' + match.toLowerCase(); + }).replace(/_/g, ' '); + }, + + // Looks for a label configured on the model and returns it + // + // var Model = Backbone.Model.extend({ + // validation: { + // someAttribute: { + // required: true + // } + // }, + // + // labels: { + // someAttribute: 'Custom label' + // } + // }); + label: function(attrName, model) { + return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model); + } + }; + + + // Built in validators + // ------------------- + + var defaultValidators = Validation.validators = (function(){ + // Use native trim when defined + var trim = String.prototype.trim ? + function(text) { + return text === null ? '' : String.prototype.trim.call(text); + } : + function(text) { + var trimLeft = /^\s+/, + trimRight = /\s+$/; + + return text === null ? '' : text.toString().replace(trimLeft, '').replace(trimRight, ''); + }; + + // Determines whether or not a value is a number + var isNumber = function(value){ + return _.isNumber(value) || (_.isString(value) && value.match(defaultPatterns.number)); + }; + + // Determines whether or not a value is empty + var hasValue = function(value) { + return !(_.isNull(value) || _.isUndefined(value) || (_.isString(value) && trim(value) === '') || (_.isArray(value) && _.isEmpty(value))); + }; + + return { + // Function validator + // Lets you implement a custom function used for validation + fn: function(value, attr, fn, model, computed) { + if(_.isString(fn)){ + fn = model[fn]; + } + return fn.call(model, value, attr, computed); + }, + + // Required validator + // Validates if the attribute is required or not + required: function(value, attr, required, model, computed) { + var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required; + if(!isRequired && !hasValue(value)) { + return false; // overrides all other validators + } + if (isRequired && !hasValue(value)) { + return this.format(defaultMessages.required, this.formatLabel(attr, model)); + } + }, + + // Acceptance validator + // Validates that something has to be accepted, e.g. terms of use + // `true` or 'true' are valid + acceptance: function(value, attr, accept, model) { + if(value !== 'true' && (!_.isBoolean(value) || value === false)) { + return this.format(defaultMessages.acceptance, this.formatLabel(attr, model)); + } + }, + + // Min validator + // Validates that the value has to be a number and equal to or greater than + // the min value specified + min: function(value, attr, minValue, model) { + if (!isNumber(value) || value < minValue) { + return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue); + } + }, + + // Max validator + // Validates that the value has to be a number and equal to or less than + // the max value specified + max: function(value, attr, maxValue, model) { + if (!isNumber(value) || value > maxValue) { + return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue); + } + }, + + // Range validator + // Validates that the value has to be a number and equal to or between + // the two numbers specified + range: function(value, attr, range, model) { + if(!isNumber(value) || value < range[0] || value > range[1]) { + return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]); + } + }, + + // Length validator + // Validates that the value has to be a string with length equal to + // the length value specified + length: function(value, attr, length, model) { + if (!hasValue(value) || trim(value).length !== length) { + return this.format(defaultMessages.length, this.formatLabel(attr, model), length); + } + }, + + // Min length validator + // Validates that the value has to be a string with length equal to or greater than + // the min length value specified + minLength: function(value, attr, minLength, model) { + if (!hasValue(value) || trim(value).length < minLength) { + return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength); + } + }, + + // Max length validator + // Validates that the value has to be a string with length equal to or less than + // the max length value specified + maxLength: function(value, attr, maxLength, model) { + if (!hasValue(value) || trim(value).length > maxLength) { + return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength); + } + }, + + // Range length validator + // Validates that the value has to be a string and equal to or between + // the two numbers specified + rangeLength: function(value, attr, range, model) { + if(!hasValue(value) || trim(value).length < range[0] || trim(value).length > range[1]) { + return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]); + } + }, + + // One of validator + // Validates that the value has to be equal to one of the elements in + // the specified array. Case sensitive matching + oneOf: function(value, attr, values, model) { + if(!_.include(values, value)){ + return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', ')); + } + }, + + // Equal to validator + // Validates that the value has to be equal to the value of the attribute + // with the name specified + equalTo: function(value, attr, equalTo, model, computed) { + if(value !== computed[equalTo]) { + return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model)); + } + }, + + // Pattern validator + // Validates that the value has to match the pattern specified. + // Can be a regular expression or the name of one of the built in patterns + pattern: function(value, attr, pattern, model) { + if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { + return this.format(defaultMessages.pattern, this.formatLabel(attr, model), pattern); + } + } + }; + }()); + + return Validation; + }(_)); + return Backbone.Validation; +})); \ No newline at end of file diff --git a/ajax/libs/backbone.validation/0.8.2/backbone-validation-min.js b/ajax/libs/backbone.validation/0.8.2/backbone-validation-min.js new file mode 100644 index 000000000..e555b40bd --- /dev/null +++ b/ajax/libs/backbone.validation/0.8.2/backbone-validation-min.js @@ -0,0 +1,8 @@ +// Backbone.Validation v0.8.2 +// +// Copyright (c) 2011-2013 Thomas Pedersen +// Distributed under MIT License +// +// Documentation and full license available at: +// http://thedersen.com/projects/backbone-validation +Backbone.Validation=function(a){"use strict";var b={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},c={formatLabel:function(a,c){return i[b.labelFormatter](a,c)},format:function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return"undefined"!=typeof a[c]?a[c]:b})}},d=function(b,c,e){return c=c||{},e=e||"",a.each(b,function(a,f){b.hasOwnProperty(f)&&(!a||"object"!=typeof a||a instanceof Array||a instanceof Date||a instanceof RegExp||a instanceof Backbone.Model||a instanceof Backbone.Collection?c[e+f]=a:d(a,c,e+f+"."))}),c},e=function(){var e=function(b){return a.reduce(a.keys(a.result(b,"validation")||{}),function(a,b){return a[b]=void 0,a},{})},g=function(b,c){var d=b.validation?a.result(b,"validation")[c]||{}:{};return(a.isFunction(d)||a.isString(d))&&(d={fn:d}),a.isArray(d)||(d=[d]),a.reduce(d,function(b,c){return a.each(a.without(a.keys(c),"msg"),function(a){b.push({fn:j[a],val:c[a],msg:c.msg})}),b},[])},h=function(b,d,e,f){return a.reduce(g(b,d),function(g,h){var i=a.extend({},c,j),k=h.fn.call(i,e,d,h.val,b,f);return k===!1||g===!1?!1:k&&!g?a.result(h,"msg")||k:g},"")},i=function(b,c){var e,f={},g=!0,i=a.clone(c),j=d(c);return a.each(j,function(a,c){e=h(b,c,a,i),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},k=function(b,c){return{preValidate:function(b,c){var d,e=this,f={};return a.isObject(b)?(a.each(b,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),a.isEmpty(f)?void 0:f):h(this,b,c,a.extend({},this.attributes))},isValid:function(b){var c=d(this.attributes);return a.isString(b)?!h(this,b,c[b],a.extend({},this.attributes)):a.isArray(b)?a.reduce(b,function(b,d){return b&&!h(this,d,c[d],a.extend({},this.attributes))},!0,this):(b===!0&&this.validate(),this.validation?this._isValid:!0)},validate:function(f,g){var h=this,j=!f,k=a.extend({},c,g),l=e(h),m=a.extend({},l,h.attributes,f),n=d(f||m),o=i(h,m);return h._isValid=o.isValid,a.each(l,function(a,c){var d=o.invalidAttrs.hasOwnProperty(c);d||k.valid(b,c,k.selector)}),a.each(l,function(a,c){var d=o.invalidAttrs.hasOwnProperty(c),e=n.hasOwnProperty(c);d&&(e||j)&&k.invalid(b,c,o.invalidAttrs[c],k.selector)}),a.defer(function(){h.trigger("validated",h._isValid,h,o.invalidAttrs),h.trigger("validated:"+(h._isValid?"valid":"invalid"),h,o.invalidAttrs)}),!k.forceUpdate&&a.intersection(a.keys(o.invalidAttrs),a.keys(n)).length>0?o.invalidAttrs:void 0}}},l=function(b,c,d){a.extend(c,k(b,d))},m=function(a){delete a.validate,delete a.preValidate,delete a.isValid},n=function(a){l(this.view,a,this.options)},o=function(a){m(a)};return{version:"0.8.2",configure:function(c){a.extend(b,c)},bind:function(c,d){d=a.extend({},b,f,d);var e=d.model||c.model,g=d.collection||c.collection;if("undefined"==typeof e&&"undefined"==typeof g)throw"Before you execute the binding your view must have a model or a collection.\nSee http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.";e?l(c,e,d):g&&(g.each(function(a){l(c,a,d)}),g.bind("add",n,{view:c,options:d}),g.bind("remove",o))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d&&m(d),e&&(e.each(function(a){m(a)}),e.unbind("add",n),e.unbind("remove",o))},mixin:k(null,b)}}(),f=e.callbacks={valid:function(a,b,c){a.$("["+c+'~="'+b+'"]').removeClass("invalid").removeAttr("data-error")},invalid:function(a,b,c,d){a.$("["+d+'~="'+b+'"]').addClass("invalid").attr("data-error",c)}},g=e.patterns={digits:/^\d+$/,number:/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,email:/^((([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,url:/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i},h=e.messages={required:"{0} is required",acceptance:"{0} must be accepted",min:"{0} must be greater than or equal to {1}",max:"{0} must be less than or equal to {1}",range:"{0} must be between {1} and {2}",length:"{0} must be {1} characters",minLength:"{0} must be at least {1} characters",maxLength:"{0} must be at most {1} characters",rangeLength:"{0} must be between {1} and {2} characters",oneOf:"{0} must be one of: {1}",equalTo:"{0} must be the same as {1}",pattern:"{0} must be a valid {1}"},i=e.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return 0===b?a.toUpperCase():" "+a.toLowerCase()}).replace(/_/g," ")},label:function(a,b){return b.labels&&b.labels[a]||i.sentenceCase(a,b)}},j=e.validators=function(){var b=String.prototype.trim?function(a){return null===a?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return null===a?"":a.toString().replace(b,"").replace(c,"")},c=function(b){return a.isNumber(b)||a.isString(b)&&b.match(g.number)},d=function(c){return!(a.isNull(c)||a.isUndefined(c)||a.isString(c)&&""===b(c)||a.isArray(c)&&a.isEmpty(c))};return{fn:function(b,c,d,e,f){return a.isString(d)&&(d=e[d]),d.call(e,b,c,f)},required:function(b,c,e,f,g){var i=a.isFunction(e)?e.call(f,b,c,g):e;return i||d(b)?i&&!d(b)?this.format(h.required,this.formatLabel(c,f)):void 0:!1},acceptance:function(b,c,d,e){return"true"===b||a.isBoolean(b)&&b!==!1?void 0:this.format(h.acceptance,this.formatLabel(c,e))},min:function(a,b,d,e){return!c(a)||d>a?this.format(h.min,this.formatLabel(b,e),d):void 0},max:function(a,b,d,e){return!c(a)||a>d?this.format(h.max,this.formatLabel(b,e),d):void 0},range:function(a,b,d,e){return!c(a)||ad[1]?this.format(h.range,this.formatLabel(b,e),d[0],d[1]):void 0},length:function(a,c,e,f){return d(a)&&b(a).length===e?void 0:this.format(h.length,this.formatLabel(c,f),e)},minLength:function(a,c,e,f){return!d(a)||b(a).lengthe?this.format(h.maxLength,this.formatLabel(c,f),e):void 0},rangeLength:function(a,c,e,f){return!d(a)||b(a).lengthe[1]?this.format(h.rangeLength,this.formatLabel(c,f),e[0],e[1]):void 0},oneOf:function(b,c,d,e){return a.include(d,b)?void 0:this.format(h.oneOf,this.formatLabel(c,e),d.join(", "))},equalTo:function(a,b,c,d,e){return a!==e[c]?this.format(h.equalTo,this.formatLabel(b,d),this.formatLabel(c,d)):void 0},pattern:function(a,b,c,e){return d(a)&&a.toString().match(g[c]||c)?void 0:this.format(h.pattern,this.formatLabel(b,e),c)}}}();return e}(_); \ No newline at end of file diff --git a/ajax/libs/backbone.validation/0.8.2/backbone-validation.js b/ajax/libs/backbone.validation/0.8.2/backbone-validation.js new file mode 100644 index 000000000..7d1bddfd6 --- /dev/null +++ b/ajax/libs/backbone.validation/0.8.2/backbone-validation.js @@ -0,0 +1,623 @@ +// Backbone.Validation v0.8.2 +// +// Copyright (c) 2011-2013 Thomas Pedersen +// Distributed under MIT License +// +// Documentation and full license available at: +// http://thedersen.com/projects/backbone-validation +Backbone.Validation = (function(_){ + 'use strict'; + + // Default options + // --------------- + + var defaultOptions = { + forceUpdate: false, + selector: 'name', + labelFormatter: 'sentenceCase', + valid: Function.prototype, + invalid: Function.prototype + }; + + + // Helper functions + // ---------------- + + // Formatting functions used for formatting error messages + var formatFunctions = { + // Uses the configured label formatter to format the attribute name + // to make it more readable for the user + formatLabel: function(attrName, model) { + return defaultLabelFormatters[defaultOptions.labelFormatter](attrName, model); + }, + + // Replaces nummeric placeholders like {0} in a string with arguments + // passed to the function + format: function() { + var args = Array.prototype.slice.call(arguments), + text = args.shift(); + return text.replace(/\{(\d+)\}/g, function(match, number) { + return typeof args[number] !== 'undefined' ? args[number] : match; + }); + } + }; + + // Flattens an object + // eg: + // + // var o = { + // address: { + // street: 'Street', + // zip: 1234 + // } + // }; + // + // becomes: + // + // var o = { + // 'address.street': 'Street', + // 'address.zip': 1234 + // }; + var flatten = function (obj, into, prefix) { + into = into || {}; + prefix = prefix || ''; + + _.each(obj, function(val, key) { + if(obj.hasOwnProperty(key)) { + if (val && typeof val === 'object' && !( + val instanceof Array || + val instanceof Date || + val instanceof RegExp || + val instanceof Backbone.Model || + val instanceof Backbone.Collection) + ) { + flatten(val, into, prefix + key + '.'); + } + else { + into[prefix + key] = val; + } + } + }); + + return into; + }; + + // Validation + // ---------- + + var Validation = (function(){ + + // Returns an object with undefined properties for all + // attributes on the model that has defined one or more + // validation rules. + var getValidatedAttrs = function(model) { + return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) { + memo[key] = void 0; + return memo; + }, {}); + }; + + // Looks on the model for validations for a specified + // attribute. Returns an array of any validators defined, + // or an empty array if none is defined. + var getValidators = function(model, attr) { + var attrValidationSet = model.validation ? _.result(model, 'validation')[attr] || {} : {}; + + // If the validator is a function or a string, wrap it in a function validator + if (_.isFunction(attrValidationSet) || _.isString(attrValidationSet)) { + attrValidationSet = { + fn: attrValidationSet + }; + } + + // Stick the validator object into an array + if(!_.isArray(attrValidationSet)) { + attrValidationSet = [attrValidationSet]; + } + + // Reduces the array of validators into a new array with objects + // with a validation method to call, the value to validate against + // and the specified error message, if any + return _.reduce(attrValidationSet, function(memo, attrValidation) { + _.each(_.without(_.keys(attrValidation), 'msg'), function(validator) { + memo.push({ + fn: defaultValidators[validator], + val: attrValidation[validator], + msg: attrValidation.msg + }); + }); + return memo; + }, []); + }; + + // Validates an attribute against all validators defined + // for that attribute. If one or more errors are found, + // the first error message is returned. + // If the attribute is valid, an empty string is returned. + var validateAttr = function(model, attr, value, computed) { + // Reduces the array of validators to an error message by + // applying all the validators and returning the first error + // message, if any. + return _.reduce(getValidators(model, attr), function(memo, validator){ + // Pass the format functions plus the default + // validators as the context to the validator + var ctx = _.extend({}, formatFunctions, defaultValidators), + result = validator.fn.call(ctx, value, attr, validator.val, model, computed); + + if(result === false || memo === false) { + return false; + } + if (result && !memo) { + return _.result(validator, 'msg') || result; + } + return memo; + }, ''); + }; + + // Loops through the model's attributes and validates them all. + // Returns and object containing names of invalid attributes + // as well as error messages. + var validateModel = function(model, attrs) { + var error, + invalidAttrs = {}, + isValid = true, + computed = _.clone(attrs), + flattened = flatten(attrs); + + _.each(flattened, function(val, attr) { + error = validateAttr(model, attr, val, computed); + if (error) { + invalidAttrs[attr] = error; + isValid = false; + } + }); + + return { + invalidAttrs: invalidAttrs, + isValid: isValid + }; + }; + + // Contains the methods that are mixed in on the model when binding + var mixin = function(view, options) { + return { + + // Check whether or not a value, or a hash of values + // passes validation without updating the model + preValidate: function(attr, value) { + var self = this, + result = {}, + error; + + if(_.isObject(attr)){ + _.each(attr, function(value, key) { + error = self.preValidate(key, value); + if(error){ + result[key] = error; + } + }); + + return _.isEmpty(result) ? undefined : result; + } + else { + return validateAttr(this, attr, value, _.extend({}, this.attributes)); + } + }, + + // Check to see if an attribute, an array of attributes or the + // entire model is valid. Passing true will force a validation + // of the model. + isValid: function(option) { + var flattened = flatten(this.attributes); + + if(_.isString(option)){ + return !validateAttr(this, option, flattened[option], _.extend({}, this.attributes)); + } + if(_.isArray(option)){ + return _.reduce(option, function(memo, attr) { + return memo && !validateAttr(this, attr, flattened[attr], _.extend({}, this.attributes)); + }, true, this); + } + if(option === true) { + this.validate(); + } + return this.validation ? this._isValid : true; + }, + + // This is called by Backbone when it needs to perform validation. + // You can call it manually without any parameters to validate the + // entire model. + validate: function(attrs, setOptions){ + var model = this, + validateAll = !attrs, + opt = _.extend({}, options, setOptions), + validatedAttrs = getValidatedAttrs(model), + allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), + changedAttrs = flatten(attrs || allAttrs), + + result = validateModel(model, allAttrs); + + model._isValid = result.isValid; + + // After validation is performed, loop through all changed attributes + // and call the valid callbacks so the view is updated. + _.each(validatedAttrs, function(val, attr){ + var invalid = result.invalidAttrs.hasOwnProperty(attr); + if(!invalid){ + opt.valid(view, attr, opt.selector); + } + }); + + // After validation is performed, loop through all changed attributes + // and call the invalid callback so the view is updated. + _.each(validatedAttrs, function(val, attr){ + var invalid = result.invalidAttrs.hasOwnProperty(attr), + changed = changedAttrs.hasOwnProperty(attr); + + if(invalid && (changed || validateAll)){ + opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); + } + }); + + // Trigger validated events. + // Need to defer this so the model is actually updated before + // the event is triggered. + _.defer(function() { + model.trigger('validated', model._isValid, model, result.invalidAttrs); + model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs); + }); + + // Return any error messages to Backbone, unless the forceUpdate flag is set. + // Then we do not return anything and fools Backbone to believe the validation was + // a success. That way Backbone will update the model regardless. + if (!opt.forceUpdate && _.intersection(_.keys(result.invalidAttrs), _.keys(changedAttrs)).length > 0) { + return result.invalidAttrs; + } + } + }; + }; + + // Helper to mix in validation on a model + var bindModel = function(view, model, options) { + _.extend(model, mixin(view, options)); + }; + + // Removes the methods added to a model + var unbindModel = function(model) { + delete model.validate; + delete model.preValidate; + delete model.isValid; + }; + + // Mix in validation on a model whenever a model is + // added to a collection + var collectionAdd = function(model) { + bindModel(this.view, model, this.options); + }; + + // Remove validation from a model whenever a model is + // removed from a collection + var collectionRemove = function(model) { + unbindModel(model); + }; + + // Returns the public methods on Backbone.Validation + return { + + // Current version of the library + version: '0.8.2', + + // Called to configure the default options + configure: function(options) { + _.extend(defaultOptions, options); + }, + + // Hooks up validation on a view with a model + // or collection + bind: function(view, options) { + options = _.extend({}, defaultOptions, defaultCallbacks, options); + + var model = options.model || view.model, + collection = options.collection || view.collection; + + if(typeof model === 'undefined' && typeof collection === 'undefined'){ + throw 'Before you execute the binding your view must have a model or a collection.\n' + + 'See http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.'; + } + + if(model) { + bindModel(view, model, options); + } + else if(collection) { + collection.each(function(model){ + bindModel(view, model, options); + }); + collection.bind('add', collectionAdd, {view: view, options: options}); + collection.bind('remove', collectionRemove); + } + }, + + // Removes validation from a view with a model + // or collection + unbind: function(view, options) { + options = _.extend({}, options); + var model = options.model || view.model, + collection = options.collection || view.collection; + + if(model) { + unbindModel(model); + } + if(collection) { + collection.each(function(model){ + unbindModel(model); + }); + collection.unbind('add', collectionAdd); + collection.unbind('remove', collectionRemove); + } + }, + + // Used to extend the Backbone.Model.prototype + // with validation + mixin: mixin(null, defaultOptions) + }; + }()); + + + // Callbacks + // --------- + + var defaultCallbacks = Validation.callbacks = { + + // Gets called when a previously invalid field in the + // view becomes valid. Removes any error message. + // Should be overridden with custom functionality. + valid: function(view, attr, selector) { + view.$('[' + selector + '~="' + attr + '"]') + .removeClass('invalid') + .removeAttr('data-error'); + }, + + // Gets called when a field in the view becomes invalid. + // Adds a error message. + // Should be overridden with custom functionality. + invalid: function(view, attr, error, selector) { + view.$('[' + selector + '~="' + attr + '"]') + .addClass('invalid') + .attr('data-error', error); + } + }; + + + // Patterns + // -------- + + var defaultPatterns = Validation.patterns = { + // Matches any digit(s) (i.e. 0-9) + digits: /^\d+$/, + + // Matched any number (e.g. 100.000) + number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/, + + // Matches a valid email address (e.g. mail@example.com) + email: /^((([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, + + // Mathes any valid url (e.g. http://www.xample.com) + url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i + }; + + + // Error messages + // -------------- + + // Error message for the build in validators. + // {x} gets swapped out with arguments form the validator. + var defaultMessages = Validation.messages = { + required: '{0} is required', + acceptance: '{0} must be accepted', + min: '{0} must be greater than or equal to {1}', + max: '{0} must be less than or equal to {1}', + range: '{0} must be between {1} and {2}', + length: '{0} must be {1} characters', + minLength: '{0} must be at least {1} characters', + maxLength: '{0} must be at most {1} characters', + rangeLength: '{0} must be between {1} and {2} characters', + oneOf: '{0} must be one of: {1}', + equalTo: '{0} must be the same as {1}', + pattern: '{0} must be a valid {1}' + }; + + // Label formatters + // ---------------- + + // Label formatters are used to convert the attribute name + // to a more human friendly label when using the built in + // error messages. + // Configure which one to use with a call to + // + // Backbone.Validation.configure({ + // labelFormatter: 'label' + // }); + var defaultLabelFormatters = Validation.labelFormatters = { + + // Returns the attribute name with applying any formatting + none: function(attrName) { + return attrName; + }, + + // Converts attributeName or attribute_name to Attribute name + sentenceCase: function(attrName) { + return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) { + return index === 0 ? match.toUpperCase() : ' ' + match.toLowerCase(); + }).replace(/_/g, ' '); + }, + + // Looks for a label configured on the model and returns it + // + // var Model = Backbone.Model.extend({ + // validation: { + // someAttribute: { + // required: true + // } + // }, + // + // labels: { + // someAttribute: 'Custom label' + // } + // }); + label: function(attrName, model) { + return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model); + } + }; + + + // Built in validators + // ------------------- + + var defaultValidators = Validation.validators = (function(){ + // Use native trim when defined + var trim = String.prototype.trim ? + function(text) { + return text === null ? '' : String.prototype.trim.call(text); + } : + function(text) { + var trimLeft = /^\s+/, + trimRight = /\s+$/; + + return text === null ? '' : text.toString().replace(trimLeft, '').replace(trimRight, ''); + }; + + // Determines whether or not a value is a number + var isNumber = function(value){ + return _.isNumber(value) || (_.isString(value) && value.match(defaultPatterns.number)); + }; + + // Determines whether or not a value is empty + var hasValue = function(value) { + return !(_.isNull(value) || _.isUndefined(value) || (_.isString(value) && trim(value) === '') || (_.isArray(value) && _.isEmpty(value))); + }; + + return { + // Function validator + // Lets you implement a custom function used for validation + fn: function(value, attr, fn, model, computed) { + if(_.isString(fn)){ + fn = model[fn]; + } + return fn.call(model, value, attr, computed); + }, + + // Required validator + // Validates if the attribute is required or not + required: function(value, attr, required, model, computed) { + var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required; + if(!isRequired && !hasValue(value)) { + return false; // overrides all other validators + } + if (isRequired && !hasValue(value)) { + return this.format(defaultMessages.required, this.formatLabel(attr, model)); + } + }, + + // Acceptance validator + // Validates that something has to be accepted, e.g. terms of use + // `true` or 'true' are valid + acceptance: function(value, attr, accept, model) { + if(value !== 'true' && (!_.isBoolean(value) || value === false)) { + return this.format(defaultMessages.acceptance, this.formatLabel(attr, model)); + } + }, + + // Min validator + // Validates that the value has to be a number and equal to or greater than + // the min value specified + min: function(value, attr, minValue, model) { + if (!isNumber(value) || value < minValue) { + return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue); + } + }, + + // Max validator + // Validates that the value has to be a number and equal to or less than + // the max value specified + max: function(value, attr, maxValue, model) { + if (!isNumber(value) || value > maxValue) { + return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue); + } + }, + + // Range validator + // Validates that the value has to be a number and equal to or between + // the two numbers specified + range: function(value, attr, range, model) { + if(!isNumber(value) || value < range[0] || value > range[1]) { + return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]); + } + }, + + // Length validator + // Validates that the value has to be a string with length equal to + // the length value specified + length: function(value, attr, length, model) { + if (!hasValue(value) || trim(value).length !== length) { + return this.format(defaultMessages.length, this.formatLabel(attr, model), length); + } + }, + + // Min length validator + // Validates that the value has to be a string with length equal to or greater than + // the min length value specified + minLength: function(value, attr, minLength, model) { + if (!hasValue(value) || trim(value).length < minLength) { + return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength); + } + }, + + // Max length validator + // Validates that the value has to be a string with length equal to or less than + // the max length value specified + maxLength: function(value, attr, maxLength, model) { + if (!hasValue(value) || trim(value).length > maxLength) { + return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength); + } + }, + + // Range length validator + // Validates that the value has to be a string and equal to or between + // the two numbers specified + rangeLength: function(value, attr, range, model) { + if(!hasValue(value) || trim(value).length < range[0] || trim(value).length > range[1]) { + return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]); + } + }, + + // One of validator + // Validates that the value has to be equal to one of the elements in + // the specified array. Case sensitive matching + oneOf: function(value, attr, values, model) { + if(!_.include(values, value)){ + return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', ')); + } + }, + + // Equal to validator + // Validates that the value has to be equal to the value of the attribute + // with the name specified + equalTo: function(value, attr, equalTo, model, computed) { + if(value !== computed[equalTo]) { + return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model)); + } + }, + + // Pattern validator + // Validates that the value has to match the pattern specified. + // Can be a regular expression or the name of one of the built in patterns + pattern: function(value, attr, pattern, model) { + if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { + return this.format(defaultMessages.pattern, this.formatLabel(attr, model), pattern); + } + } + }; + }()); + + return Validation; +}(_)); \ No newline at end of file diff --git a/ajax/libs/backbone.validation/package.json b/ajax/libs/backbone.validation/package.json index 75507ae97..55fae79ca 100644 --- a/ajax/libs/backbone.validation/package.json +++ b/ajax/libs/backbone.validation/package.json @@ -1,7 +1,7 @@ { "name": "backbone.validation", "filename": "backbone-validation-min.js", - "version": "0.7.1", + "version": "0.8.2", "description": "A validation plugin for Backbone.js that validates both your model as well as form input.", "homepage": "http://thedersen.com/projects/backbone-validation", "keywords": [