1 rio.Form = {
  2 	
  3 	build: function(options) {
  4 		var attributes = options.attributes || {};
  5 		var attr = rio.Attr.create({ 
  6 			attrAccessors: Object.keys(attributes),
  7 			methods: {
  8 				reset: function() {
  9 					Object.keys(attributes).each(function(attribute) {
 10 						var initialValue = attributes[attribute].initialValue || "";
 11 						initialValue = initialValue.constructor == rio.Binding ? initialValue.value() : initialValue;
 12 						this[attribute].update(initialValue || "");
 13 						this.errors[attribute].update("");
 14 					}.bind(this));
 15 				},
 16 				
 17 				values: function() {
 18 					var values = {};
 19 					Object.keys(attributes).each(function(attribute) {
 20 						values[attribute] = this[attribute].value();
 21 					}.bind(this));
 22 					return values;
 23 				},
 24 				
 25 				commit: function() {
 26 					if (this.valid()) {
 27 						(options.onCommit || Prototype.emptyFunction)(this.values());
 28 					}
 29 				},
 30 				
 31 				errorsFor: function(field) {
 32 					return this.errors[field];
 33 				},
 34 				
 35 				valid: function() {
 36 					return this.validate();
 37 				},
 38 				
 39 				validate: function() {
 40 					var valid = true;
 41 					Object.keys(attributes).each(function(attribute) {
 42 						var errorMessage = "";
 43 						(attributes[attribute].validates || []).uniq().each(function(validate) {
 44 							var newErrorMessage = "";
 45 							if (Object.isString(validate)) {
 46 								newErrorMessage = rio.Validators[validate](this[attribute].value());
 47 							} else if (Object.isArray(validate)) {
 48 								newErrorMessage = rio.Validators[validate[0]](this[attribute].value(), validate[1]);
 49 							} else {
 50 								newErrorMessage = validate(this.values());
 51 							}
 52 
 53 							if (!(newErrorMessage || "").blank()) {
 54 								valid = false;
 55 								errorMessage = newErrorMessage;
 56 							}
 57 						}.bind(this));
 58 
 59 						this.errors[attribute].update(errorMessage);
 60 					}.bind(this));
 61 					return valid;
 62 				}
 63 			}
 64 		});
 65 		var form = new attr();
 66 		
 67 		
 68 		var errorAttr = rio.Attr.create({
 69 			attrAccessors: Object.keys(attributes).map(function(attribute) { return [attribute, ""]; })
 70 		});
 71 		form.errors = new errorAttr();
 72 		form.reset();
 73 
 74 		Object.keys(attributes).each(function(attribute) {
 75 			form[attribute].bind(form.validate.bind(form), true);
 76 		});
 77 		
 78 		
 79 		return form;
 80 	}
 81 	
 82 	
 83 };
 84 rio.Form.toString = function() { return "Form"; };
 85 
 86 rio.Validators = {
 87 	email: function(value) {
 88 		return value.validEmail() ? "" : "not a valid email address";
 89 	},
 90 	
 91 	presence: function(value) {
 92 		return value && !value.blank() ? "" : "cannot be blank";
 93 	},
 94 	
 95 	length: function(value, options) {
 96 		var blank = value == undefined || value.blank();
 97 
 98 		if (options.allowBlank && blank) { return ""; }
 99 
100 		if (blank) { return this.presence(value); }
101 
102 		if (options.minimum && value.length < options.minimum) {
103 			return "must be at least " + options.minimum + " characters long";
104 		}
105 		if (options.maximum && value.length > options.maximum) {
106 			return "can't be more than " + options.maximum + " characters long";
107 		}
108 		
109 		return "";
110 	},
111 	
112 	toString: function() { return "Validators"; }
113 };