You've already forked Sonarr
							
							
				mirror of
				https://github.com/Sonarr/Sonarr.git
				synced 2025-10-31 00:07:55 +02:00 
			
		
		
		
	can display server-side errors on the UI.
This commit is contained in:
		| @@ -21,6 +21,8 @@ module.exports = function (grunt) { | ||||
|             'UI/JsLibraries/backbone.backgrid.paginator.js'  : 'http://raw.github.com/wyuenho/backgrid/master/lib/extensions/paginator/backgrid-paginator.js', | ||||
|             'UI/JsLibraries/backbone.backgrid.filter.js'     : 'http://raw.github.com/wyuenho/backgrid/master/lib/extensions/filter/backgrid-filter.js', | ||||
|  | ||||
|             'UI/JsLibraries/backbone.validation.js'          : 'https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation.js', | ||||
|  | ||||
|             'UI/JsLibraries/handlebars.runtime.js'           : 'http://raw.github.com/wycats/handlebars.js/master/dist/handlebars.runtime.js', | ||||
|             'UI/JsLibraries/handlebars.helpers.js'           : 'http://raw.github.com/danharper/Handlebars-Helpers/master/helpers.js', | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ using NzbDrone.Api.Mapping; | ||||
| using NzbDrone.Api.REST; | ||||
| using NzbDrone.Core.Indexers; | ||||
| using Omu.ValueInjecter; | ||||
| using FluentValidation; | ||||
|  | ||||
| namespace NzbDrone.Api.Indexers | ||||
| { | ||||
| @@ -20,6 +21,12 @@ namespace NzbDrone.Api.Indexers | ||||
|             CreateResource = CreateIndexer; | ||||
|             UpdateResource = UpdateIndexer; | ||||
|             DeleteResource = DeleteIndexer; | ||||
|  | ||||
|  | ||||
|             SharedValidator.RuleFor(c => c.Name).NotEmpty(); | ||||
|             SharedValidator.RuleFor(c => c.Implementation).NotEmpty(); | ||||
|  | ||||
|             PostValidator.RuleFor(c => c.Fields).NotEmpty(); | ||||
|         } | ||||
|  | ||||
|         private List<IndexerResource> GetAll() | ||||
|   | ||||
							
								
								
									
										606
									
								
								UI/JsLibraries/backbone.validation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										606
									
								
								UI/JsLibraries/backbone.validation.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,606 @@ | ||||
| // Backbone.Validation v0.8.1 | ||||
| // | ||||
| // 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(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 ? 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 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 passes validation | ||||
|         // without updating the model | ||||
|         preValidate: function(attr, value) { | ||||
|           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.1', | ||||
|  | ||||
|       // 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) { | ||||
|         var model = view.model, | ||||
|             collection = view.collection; | ||||
|  | ||||
|         options = _.extend({}, defaultOptions, defaultCallbacks, options); | ||||
|  | ||||
|         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) { | ||||
|         var model = view.model, | ||||
|             collection = view.collection; | ||||
|  | ||||
|         if(model) { | ||||
|           unbindModel(view.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; | ||||
| }(_)); | ||||
							
								
								
									
										79
									
								
								UI/Mixins/AsValidatedView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								UI/Mixins/AsValidatedView.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| define( | ||||
|     [ | ||||
|         'backbone.validation', | ||||
|         'underscore', | ||||
|         'jQuery/Validation' | ||||
|     ], function (Validation, _) { | ||||
|         'use strict'; | ||||
|  | ||||
|         return function () { | ||||
|  | ||||
|             var originalOnRender = this.prototype.onRender; | ||||
|             var originalOnClose = this.prototype.onClose; | ||||
|             var originalBeforeClose = this.prototype.onBeforeClose; | ||||
|  | ||||
|  | ||||
|             this.prototype.onRender = function () { | ||||
|  | ||||
|                 Validation.bind(this); | ||||
|  | ||||
|  | ||||
|                 if (!this.originalSync && this.model) { | ||||
|  | ||||
|                     var self = this; | ||||
|                     this.originalSync = this.model.sync; | ||||
|  | ||||
|  | ||||
|                     var boundHandler = errorHandler.bind(this); | ||||
|  | ||||
|                     this.model.sync = function () { | ||||
|                         self.$el.removeBootstrapError(); | ||||
|                         return self.originalSync.apply(this, arguments).fail(boundHandler); | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 if (this.model) { | ||||
|                     if (originalOnRender) { | ||||
|                         originalOnRender.call(this); | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             this.prototype.onBeforeClose = function () { | ||||
|  | ||||
|                 if (this.model) { | ||||
|                     Validation.unbind(this); | ||||
|                 } | ||||
|  | ||||
|                 if (originalBeforeClose) { | ||||
|                     originalBeforeClose.call(this); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             this.prototype.onClose = function () { | ||||
|  | ||||
|                 if (this.model && this.model.isNew()) { | ||||
|                     this.model.destroy(); | ||||
|                 } | ||||
|  | ||||
|                 if (originalOnClose) { | ||||
|                     originalBeforeClose.call(this); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|  | ||||
|             var errorHandler = function (response) { | ||||
|  | ||||
|                 if (response.status === 400) { | ||||
|  | ||||
|                     var view = this; | ||||
|  | ||||
|                     var validationErrors = JSON.parse(response.responseText); | ||||
|  | ||||
|                     _.each(validationErrors, function (error) { | ||||
|                         view.$el.addBootstrapError(error); | ||||
|                     }); | ||||
|                 } | ||||
|             }; | ||||
|         }; | ||||
|     }); | ||||
| @@ -4,8 +4,9 @@ define( | ||||
|     [ | ||||
|         'app', | ||||
|         'marionette', | ||||
|         'Mixins/AsModelBoundView' | ||||
|     ], function (App, Marionette, AsModelBoundView) { | ||||
|         'Mixins/AsModelBoundView', | ||||
|         'Mixins/AsValidatedView' | ||||
|     ], function (App, Marionette, AsModelBoundView, AsValidatedView) { | ||||
|  | ||||
|         var view = Marionette.ItemView.extend({ | ||||
|             template: 'Settings/Indexers/EditTemplate', | ||||
| @@ -53,5 +54,8 @@ define( | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         return AsModelBoundView.call(view); | ||||
|         AsModelBoundView.call(view); | ||||
|         AsValidatedView.call(view); | ||||
|  | ||||
|         return view; | ||||
|     }); | ||||
|   | ||||
| @@ -12,6 +12,7 @@ require.config({ | ||||
|         'bootstrap'           : 'JsLibraries/bootstrap', | ||||
|         'backbone.deepmodel'  : 'JsLibraries/backbone.deep.model', | ||||
|         'backbone.pageable'   : 'JsLibraries/backbone.pageable', | ||||
|         'backbone.validation' : 'JsLibraries/backbone.validation', | ||||
|         'backbone.modelbinder': 'JsLibraries/backbone.modelbinder', | ||||
|         'backgrid'            : 'JsLibraries/backbone.backgrid', | ||||
|         'backgrid.paginator'  : 'JsLibraries/backbone.backgrid.paginator', | ||||
| @@ -92,6 +93,14 @@ require.config({ | ||||
|                 ] | ||||
|         }, | ||||
|  | ||||
|         'backbone.validation': { | ||||
|             deps   : | ||||
|                 [ | ||||
|                     'backbone' | ||||
|                 ], | ||||
|             exports: 'Backbone.Validation' | ||||
|         }, | ||||
|  | ||||
|         marionette: { | ||||
|             deps: | ||||
|                 [ | ||||
|   | ||||
							
								
								
									
										30
									
								
								UI/jQuery/Validation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								UI/jQuery/Validation.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| define( | ||||
|     [ | ||||
|         'jquery' | ||||
|     ], function ($) { | ||||
|         'use strict'; | ||||
|  | ||||
|         $.fn.addBootstrapError = function (error) { | ||||
|             var input = this.find('[name]').filter(function () { | ||||
|                 return this.name.toLowerCase() === error.propertyName.toLowerCase(); | ||||
|             }); | ||||
|  | ||||
|             var controlGroup = input.parents('.control-group'); | ||||
|             if (controlGroup.find('.help-inline').length === 0) { | ||||
|                 controlGroup.find('.controls').append('<span class="help-inline error-message">' + error.errorMessage + '</span>'); | ||||
|             } | ||||
|  | ||||
|             controlGroup.addClass('error'); | ||||
|  | ||||
|             return controlGroup.find('.help-inline').text(); | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         $.fn.removeBootstrapError = function () { | ||||
|  | ||||
|             this.removeClass('error'); | ||||
|  | ||||
|             return this.parents('.control-group').find('.help-inline.error-message').remove(); | ||||
|         }; | ||||
|  | ||||
|     }); | ||||
		Reference in New Issue
	
	Block a user