validatious-on-rails 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/Rakefile +52 -0
  2. data/generators/validatious/templates/v2.standalone.full.min.js +7 -0
  3. data/generators/validatious/templates/validatious.config.js +83 -0
  4. data/generators/validatious/validatious_generator.rb +12 -0
  5. data/lib/validatious-on-rails/helpers.rb +20 -0
  6. data/lib/validatious-on-rails/model_validations.rb +312 -0
  7. data/lib/validatious-on-rails/rails/action_view_helpers.rb +114 -0
  8. data/lib/validatious-on-rails/rails.rb +2 -0
  9. data/lib/validatious-on-rails/validatious/client_side_validator.rb +14 -0
  10. data/lib/validatious-on-rails/validatious/remote_validator.rb +24 -0
  11. data/lib/validatious-on-rails/validatious/validator.rb +228 -0
  12. data/lib/validatious-on-rails/validatious/validators/exclusion_validator.rb +28 -0
  13. data/lib/validatious-on-rails/validatious/validators/format_validator.rb +33 -0
  14. data/lib/validatious-on-rails/validatious/validators/inclusion_validator.rb +28 -0
  15. data/lib/validatious-on-rails/validatious/validators/length/is_validator.rb +27 -0
  16. data/lib/validatious-on-rails/validatious/validators/length/maximum_validator.rb +27 -0
  17. data/lib/validatious-on-rails/validatious/validators/length/minimum_validator.rb +27 -0
  18. data/lib/validatious-on-rails/validatious/validators/numericality/equal_to_validator.rb +25 -0
  19. data/lib/validatious-on-rails/validatious/validators/numericality/even_validator.rb +24 -0
  20. data/lib/validatious-on-rails/validatious/validators/numericality/greater_than_or_equal_to_validator.rb +25 -0
  21. data/lib/validatious-on-rails/validatious/validators/numericality/greater_than_validator.rb +25 -0
  22. data/lib/validatious-on-rails/validatious/validators/numericality/less_than_or_equal_to_validator.rb +25 -0
  23. data/lib/validatious-on-rails/validatious/validators/numericality/less_than_validator.rb +25 -0
  24. data/lib/validatious-on-rails/validatious/validators/numericality/odd_validator.rb +24 -0
  25. data/lib/validatious-on-rails/validatious/validators/numericality/only_integer_validator.rb +24 -0
  26. data/lib/validatious-on-rails/validatious/validators.rb +3 -0
  27. data/lib/validatious-on-rails/validatious.rb +5 -0
  28. data/lib/validatious-on-rails.rb +41 -0
  29. data/rails/init.rb +2 -0
  30. data/test/test_helper.rb +93 -0
  31. data/test/validatious_on_rails/helpers_test.rb +47 -0
  32. data/test/validatious_on_rails/model_validations_test.rb +190 -0
  33. data/test/validatious_on_rails/rails/action_view_helpers_test.rb +168 -0
  34. data/test/validatious_on_rails/validatious/validator_test.rb +56 -0
  35. data/test/validatious_on_rails_test.rb +6 -0
  36. metadata +129 -0
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+
7
+ NAME = "validatious-on-rails"
8
+ SUMMARY = %Q{Rails plugin that maps model validations to class names on form elements to integrate with Validatious.}
9
+ HOMEPAGE = "http://github.com/grimen/#{NAME}"
10
+ AUTHORS = ["Christian Johansen", "Jonas Grimfelt"]
11
+ EMAIL = "christian@cjohansen.no"
12
+ SUPPORT_FILES = %w(README)
13
+
14
+ begin
15
+ gem 'technicalpickles-jeweler', '>= 1.2.1'
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gemspec|
18
+ gemspec.name = NAME
19
+ gemspec.summary = SUMMARY
20
+ gemspec.description = SUMMARY
21
+ gemspec.homepage = HOMEPAGE
22
+ gemspec.authors = AUTHORS
23
+ gemspec.email = EMAIL
24
+
25
+ gemspec.require_paths = %w{lib}
26
+ gemspec.files = SUPPORT_FILES << %w(Rakefile) <<
27
+ Dir.glob(File.join('{generators,lib,test,rails}', '**', '*').to_s).reject { |v| v =~ /\.log/i }
28
+ gemspec.executables = %w()
29
+ gemspec.extra_rdoc_files = SUPPORT_FILES
30
+ end
31
+ rescue LoadError
32
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
33
+ end
34
+
35
+ desc %Q{Default: Run unit tests for "#{NAME}".}
36
+ task :default => :test
37
+
38
+ desc %Q{Run unit tests for "#{NAME}".}
39
+ Rake::TestTask.new(:test) do |test|
40
+ test.libs << %w(lib test)
41
+ test.pattern = File.join('test', '**', '*_test.rb')
42
+ test.verbose = true
43
+ end
44
+
45
+ desc %Q{Generate documentation for "#{NAME}".}
46
+ Rake::RDocTask.new(:rdoc) do |rdoc|
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = NAME
49
+ rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
50
+ rdoc.rdoc_files.include(SUPPORT_FILES)
51
+ rdoc.rdoc_files.include(File.join('lib', '**', '*.rb'))
52
+ end
@@ -0,0 +1,7 @@
1
+ /**
2
+ * TERMS OF USE - Validatious 2.0
3
+ * Open source under the BSD License.
4
+ * Copyright 2008 Christian Johansen.
5
+ * All rights reserved.
6
+ */
7
+ var addDOMLoadEvent=(function(){var F=[],A,E,C,B,D,G=function(){C=true;clearInterval(A);while(B=F.shift()){B()}if(E){E.onreadystatechange=""}};return function(H){if(C){return H()}if(!F[0]){if(document.addEventListener){document.addEventListener("DOMContentLoaded",G,false)}if(/WebKit/i.test(navigator.userAgent)){A=setInterval(function(){if(/loaded|complete/.test(document.readyState)){G()}},10)}D=window.onload;window.onload=function(){G();if(D){D()}}}F.push(H)}})();var Base=function(){};Base.extend=function(B,E){var F=Base.prototype.extend;Base._prototyping=true;var D=new this;F.call(D,B);delete Base._prototyping;var C=D.constructor;var A=D.constructor=function(){if(!Base._prototyping){if(this._constructing||this.constructor==A){this._constructing=true;C.apply(this,arguments);delete this._constructing}else{if(arguments[0]!=null){return(arguments[0].extend||F).call(arguments[0],D)}}}};A.ancestor=this;A.extend=this.extend;A.forEach=this.forEach;A.implement=this.implement;A.prototype=D;A.toString=this.toString;A.valueOf=function(G){return(G=="object")?A:C.valueOf()};F.call(A,E);if(typeof A.init=="function"){A.init()}return A};Base.prototype={extend:function(B,H){if(arguments.length>1){var E=this[B];if(E&&(typeof H=="function")&&(!E.valueOf||E.valueOf()!=H.valueOf())&&/\bbase\b/.test(H)){var A=H.valueOf();H=function(){var K=this.base||Base.prototype.base;this.base=E;var J=A.apply(this,arguments);this.base=K;return J};H.valueOf=function(J){return(J=="object")?H:A};H.toString=Base.toString}this[B]=H}else{if(B){var G=Base.prototype.extend;if(!Base._prototyping&&typeof this!="function"){G=this.extend||G}var D={toSource:null};var F=["constructor","toString","valueOf"];var C=Base._prototyping?0:1;while(I=F[C++]){if(B[I]!=D[I]){G.call(this,I,B[I])}}for(var I in B){if(!D[I]){G.call(this,I,B[I])}}}}return this},base:function(){}};Base=Base.extend({constructor:function(){this.extend(arguments[0])}},{ancestor:Object,version:"1.1",forEach:function(A,D,C){for(var B in A){if(this.prototype[B]===undefined){D.call(C,A[B],B,A)}}},implement:function(){for(var A=0;A<arguments.length;A++){if(typeof arguments[A]=="function"){arguments[A](this.prototype)}else{this.prototype.extend(arguments[A])}}return this},toString:function(){return String(this.valueOf())}});function addEvent(B,D,C){if(B.addEventListener){B.addEventListener(D,C,false)}else{if(!C.$$guid){C.$$guid=addEvent.guid++}if(!B.events){B.events={}}var A=B.events[D];if(!A){A=B.events[D]={};if(B["on"+D]){A[0]=B["on"+D]}}A[C.$$guid]=C;B["on"+D]=handleEvent}}addEvent.guid=1;function removeEvent(A,C,B){if(A.removeEventListener){A.removeEventListener(C,B,false)}else{if(A.events&&A.events[C]){delete A.events[C][B.$$guid]}}}function handleEvent(D){var C=true;D=D||fixEvent(((this.ownerDocument||this.document||this).parentWindow||window).event);var A=this.events[D.type];for(var B in A){this.$$handleEvent=A[B];if(this.$$handleEvent(D)===false){C=false}}return C}function fixEvent(A){A.preventDefault=fixEvent.preventDefault;A.stopPropagation=fixEvent.stopPropagation;return A}fixEvent.preventDefault=function(){this.returnValue=false};fixEvent.stopPropagation=function(){this.cancelBubble=true};if(typeof v2==="undefined"||v2===null){v2={}}v2.empty=function empty(A){return typeof A=="undefined"||A===null||A===""};v2.array=function array(C){var A=[];if(C&&typeof C.item==="function"){for(var B=0;B<C.length;B++){A[B]=C[B]}C=A}return v2.empty(C)?[]:(typeof C.shift=="undefined"?[C]:C)};v2.Object={extend:function(C,A,B){B=typeof B==="undefined"?false:B;for(var D in A){if(B&&typeof C[D]!=="undefined"){continue}if(A.hasOwnProperty(D)&&typeof A[D]!=="undefined"){C[D]=A[D]}}return C}};v2.Object.extend(String.prototype,{strip:function(){return this.replace(/^\s+|\s+$/,"")}});v2.Object.extend(Array.prototype,{indexOf:function(C,A){for(var B=0;B<this.length;B++){if(this[B]===C){return B}}return -1}});v2.$=function(A,C){if(typeof A=="string"){A=document.getElementById(A)}C=typeof C=="undefined"?true:C;if(A===null||typeof A.hide!="undefined"||!C){return A}for(var B in v2.Element){if(v2.Element.hasOwnProperty(B)){(function(){var D=B;var E={};E[D]=function(){var F=[A];for(var G=0;G<arguments.length;G++){F.push(arguments[G])}return v2.Element[D].apply(v2.Element,F)};v2.Object.extend(A,E,true)})()}}return A};v2.$$=function $$(E,N){N=N||document;if(document.querySelectorAll){v2.$$=function(Q,R){return(R||document).querySelectorAll(Q)};return v2.$$(E,N)}var O=E.split(",");var J,H,F,B,K,D,A,I,C,G,L,M;var P=[];for(J=0;(E=O[J]);J++){B=E.strip().split(/[^a-zA-Z]/)[0]||"*";K=E.replace(/^\./,"_.").split(/\./);C=[];G=[];for(H=1;H<K.length;H++){C.push(K[H].split(/[^a-zA-Z\-_]/)[0])}K=E.replace("][","].[").split(/[\[\]]/);for(H=0;H<K.length-1;H+=2){D=K[H+1].split("=");D[1]=D[1]||true;G.push(D)}A=N.getElementsByTagName(B);elementLoop:for(H=0;(I=A[H]);H++){for(F=0;F<C.length;F++){if(!v2.Element.hasClassName(I,C[F])){continue elementLoop}}for(F=0;F<G.length;F++){L=G[F][0];M=G[F][1];if(I.hasAttribute){if(!I.hasAttribute(L)||(M!==true&&I.getAttribute(L)!==M)){continue elementLoop}}else{L=L==="for"?"htmlFor":L;if(typeof I[L]==="undefined"||(M!==true&&I[L]!==M)){continue elementLoop}}}if(P.indexOf(I)<0){P.push(I)}}}return P};v2.Element={observe:function(B,C,A){addEvent(B,C,A)},computedStyle:function(A,C){var B="";if(document.defaultView&&document.defaultView.getComputedStyle){B=document.defaultView.getComputedStyle(A,"").getPropertyValue(C)}else{if(A.currentStyle){C=C.replace(/\-(\w)/g,function(D,E){return E.toUpperCase()});B=A.currentStyle[C]}}return B},visible:function(A){return v2.Element.computedStyle(A,"display")!="none"&&v2.Element.computedStyle(A,"visibility")!="hidden"&&(A.parentNode===null||A.parentNode.nodeType!=1||v2.Element.visible(A.parentNode))},hasClassName:function(B,A){return new RegExp("(^|\\s)"+A+"(\\s|$)").test(B.className)},addClassName:function(B,A){B.className+=v2.Element.hasClassName(B,A)?"":" "+A;B.className=B.className.replace(" "," ").replace(/^\s|\s$/,"");return B},removeClassName:function(B,A){var C=new RegExp("(^|\\s)"+A+"(\\s|$)");B.className=B.className.replace(C," ").replace(" "," ").replace(/^\s|\s$/,"");return B},position:function(A){var B={x:0,y:0};while(A!==null){B.x+=A.offsetLeft;B.y+=A.offsetTop;A=A.offsetParent}return B},scrollTo:function(A){var B=v2.Element.position(A);window.scrollTo(B.x,B.y)},previous:function(A){do{A=A.previousSibling}while(A&&A.nodeType!==1);return A}};v2.Object.extend(Function.prototype,{bind:function(B){var A=this;A.__cc=A.__cc||[];window.__coc=window.__coc||0;if(typeof B.__id=="undefined"){B.__id=window.__coc++}A.__cc[B.__id]=A.__cc[B.__id]||function(){try{return A.apply(B,arguments)}catch(C){}};return A.__cc[B.__id]}});if(typeof addDOMLoadEvent!="undefined"){v2.addDOMLoadEvent=addDOMLoadEvent}v2.CompositeFormItem=Base.extend({type:"generic",constructor:function(){if(v2.empty(this.__validators)){this.__validators=[]}if(v2.empty(this.__errors)){this.__errors=[]}this.__passOnAny=false;this.__message=null;this.parent=null},add:function(A){this.__validators.push(A);A.parent=this},get:function(A){return !v2.empty(A)?this.__validators[A]:this.__validators},validate:function(){this.__errors=[];var A=this.test("validate");if(A){this.__errors=[];this.onSuccess()}else{this.onFailure()}return A},test:function(C){var B,A,D=0;C=C||"test";for(B=0;(A=this.__validators[B]);B++){if(A[C]()){D++}else{this.__errors.push(A)}}return this.passOnAny()&&D>0||!this.passOnAny()&&D===this.__validators.length},getInvalid:function(){return this.__errors.length===0?null:this.__errors},setMessage:function(A){this.__message=A},getMessages:function(){if(!v2.empty(this.__message)){return[this.__message]}var C=[],B,A;for(B=0,A;(A=this.__errors[B]);B++){C=C.concat(A.getMessages())}return C},passOnAny:function(A){if(typeof A!=="undefined"){this.__passOnAny=!!A}return this.__passOnAny},onSuccess:function(){},onFailure:function(){}});v2.InputElement=Base.extend({constructor:function(B,A){this.__name=null;this.__events=v2.empty(A)?["blur"]:v2.array(A);this.__elements=v2.array(B)},monitor:function(D){this.__monitored=true;var C,A,B,E;for(C=0;(B=this.__elements[C]);C++){for(A=0;(E=this.__events[A]);A++){v2.Element.observe(B,E,D)}}},getValue:function(){return this.__elements[0].value},getLabel:function(){var B=this.__elements[0];var A=v2.$$("label[for="+(B.id||B.name)+"]",B.form);return A&&A[0]},getName:function(){if(this.__name){return this.__name}var A=this.getLabel();if(A){return A.title!==""?A.title:A.innerHTML}var B=this.__elements[0];return B.id||B.name},setName:function(A){this.__name=!!A?A:null;return this},getParent:function(){return this.__elements[0].parentNode},getElements:function(){return this.__elements},visible:function(){if(!v2.$(this.getParent()).visible()){return false}for(var A=0,B;(B=this.__elements[A]);A++){if(v2.Element.visible(B)){return true}}return false}},{__fields:{},get:function(B){if(B&&B.constructor===v2.InputElement){return B}var C=v2.$(B),H,G,F,A,D,E;if(!C){A="input$, select$, textarea$";F=v2.$$(A.replace(/\$/g,"[name="+B+"]"));C=F[0]}if(!C){throw new TypeError(B+" does not resolve to an HTML element!")}H=C.name||C.id;if((G=v2.InputElement.__fields[H])){return G}if(C.options){G=new v2.SelectElement(C)}else{if(C.tagName.toLowerCase()==="textarea"){G=new v2.TextareaElement(C)}else{if(C.type&&C.type=="radio"){F=v2.$$("input[type=radio][name="+C.name+"]",C.form);G=new v2.RadioElement(F)}else{if(C.type&&C.type=="checkbox"){D=C.className;E=/\bg_([^\s]*)\b/;F=E.test(D)?v2.$$("input[type=checkbox].g_"+D.match(E)[1],C.form):[C];G=new v2.CheckboxElement(F)}else{G=new v2.InputElement(C)}}}}return(v2.InputElement.__fields[H]=G)}});v2.$f=v2.InputElement.get;v2.RadioElement=v2.InputElement.extend({constructor:function(A){this.base(A,["click","change"])},getValue:function(){for(var B=0,A;(A=this.__elements[B]);B++){if(A.checked){return A.value}}return null},getLabel:function(){var B=this.__elements[0].parentNode;if(B.tagName.toLowerCase()==="li"){return v2.$(B.parentNode).previous()}var A=v2.$(this.__elements[0]).previous();return A||this.base()},getParent:function(){var A=this.__elements[0].parentNode;return A.tagName.toLowerCase()==="li"?A.parentNode.parentNode:A}});v2.SelectElement=v2.InputElement.extend({constructor:function(A){this.base([A],"change")},getValue:function(){var A=this.__elements[0];if(!A.multiple){return A.options[A.selectedIndex].value}var B=[];for(var C=0,D;(D=A.options[C]);C++){if(D.selected){B.push(D.value)}}return B}});v2.TextareaElement=v2.InputElement.extend({});v2.CheckboxElement=v2.RadioElement.extend({getValue:function(){var A=[],B,C;for(B=0,C;(C=this.__elements[B]);B++){if(C.checked){A.push(C.value)}}return A}});v2.Message=Base.extend({constructor:function(B,C,A){this.message=B;this.params=C||[];this.values=A||[]},copy:function(){return new v2.Message(this.message,this.params,this.values)},toString:function(){var C=this.message+"";for(var A=0,B=null;(B=this.params[A]);A++){C=C.replace("${"+B+"}",this.values[A])}return C}});v2.Validator=Base.extend({constructor:function(B,C,D,E,A){this.__name=B;this.__test=C;this.__message=new v2.Message(D||"${field} does not pass "+B+" validator",E);this.__aliases=v2.array(A);this.acceptEmpty=true},test:function(C,E,D){D=typeof D==="undefined"?false:D;var B=C.getValue();var A=(this.acceptEmpty&&B==="")||this.__test(C,B,E);return(A&&!D)||(!A&&D)},getName:function(){return this.__name},getMessage:function(){return this.__message},setMessage:function(A){this.__message.message=A}},{validators:{},add:function(B){if(!B.name||!B.fn){throw new TypeError("Options object should contain name and fn")}B=v2.Object.extend({params:[],aliases:[],acceptEmpty:true},B,false);var F=v2.array(B.params);var D=new v2.Validator(B.name,B.fn,B.message,F,B.aliases);D.acceptEmpty=B.acceptEmpty;var E=v2.array(B.aliases).concat([B.name]);for(var C=0,A;(A=E[C]);C++){v2.Validator.validators[A]=D}return D},reg:function(B,C,F,D,A,E){return v2.Validator.add({name:B,fn:C,message:D,params:F,aliases:A,acceptEmpty:E})},get:function(A){var B;if(A.constructor===v2.Validator){return A}if((B=v2.Validator.validators[A])){return B}return null}});v2.$v=function(A){return v2.Validator.get(A)};v2.$msg=function(){if(arguments.length===2){return v2.Validator.get(arguments[0]).setMessage(arguments[1])}for(var A in arguments[0]){try{v2.Validator.get(A).setMessage(arguments[0][A])}catch(B){}}};v2.Field=v2.CompositeFormItem.extend({validateHidden:false,instant:false,instantWhenValidated:true,type:"field",constructor:function(B,A,C){this.base();this.__monitored=false;this.element=v2.$f(B);this.instant=typeof A!=="undefined"?A:this.instant;this.instantWhenValidated=typeof C!=="undefined"?C:this.instantWhenValidated;this.__monitor(this.instant)},test:function(){if(!this.validateHidden&&!this.element.visible()){return true}return this.base()},validate:function(){this.__monitor(this.instantWhenValidated&&!this.__monitored);return this.base()},addValidator:function(A,E,C){var B=v2.$v(A);if(B===null){throw new Error(A+" is not a valid validator")}if(typeof C!=="undefined"&&C!==null){C=new v2.Message(C,B.getMessage().params)}var D=new v2.FieldValidator(this.element,B,v2.array(E),C);this.add(D);return D},getParent:function(){return this.element.getParent()},__monitor:function(C){if(!C||this.__monitored){return }this.__monitored=true;var A=this.parent&&this.parent.type==="fieldset"?this.parent:this;var B=A.validate.bind(this);this.element.monitor(function(D){B(D)})}});v2.Fieldset=v2.CompositeFormItem.extend({type:"fieldset",constructor:function(A){this.base();this.element=A},getParent:function(){return this.element}});v2.FieldValidator=Base.extend({invert:false,constructor:function(C,A,E,D){this.__field=C;this.__validator=A;this.__params=v2.array(E);this.__message=D||A.__message.copy();var B=this.__message.params;if(B.length<1||B[0]!=="field"){this.__message.params=["field"].concat(B)}this.__message.values=[this.__field.getName()].concat(this.__params)},test:function(){return this.__validator.test(this.__field,this.__params,this.invert)},validate:function(){return this.test()},getInvalid:function(){return !this.test()?this:null},setMessage:function(A){this.__message.message=A},getMessages:function(){return[this.__message.toString()]},add:function(){},remove:function(){},get:function(){},passOnAny:function(){},onSuccess:function(){},onFailure:function(){}});v2.Form=v2.CompositeFormItem.extend({type:"form",constructor:function(A){this.base();this.__form=v2.$(A);this.__form.observe("submit",this.validate.bind(this));this.__buttons=[];this.__activeButton=null},addButton:function(A){this.__buttons.push(A);v2.Element.observe(A,"click",(function(B){this.__activeButton=A}).bind(this))},validate:function(){var D=arguments.length>0?arguments[0]:null;var C=this.__buttons;var A=this.__activeButton;this.__activeButton=null;if(C.length>0&&C.indexOf(A)<0){return true}var B=this.base();if(!B&&D){D.preventDefault();D.returnValue=false}return B}},{forms:{},get:function(C){var B=v2.$(C),A;if(B===null||B.tagName.toLowerCase()!=="form"){throw new ArgumentError("idOrElement should represent a form element")}if(!v2.empty(A=v2.Form.forms[B.id])){return A}return(v2.Form.forms[B.id]=new v2.Form(B))}});(function(){var A=v2.Validator;A.reg("alpha",function(C,B,D){return/^[a-zA-Z\u00A1-\uFFFF]*$/.test(B)});A.reg("alphanum",function(C,B,D){return/^([a-zA-Z\u00A1-\uFFFF0-9])*$/.test(B)});A.reg("confirmation-of",function(C,B,D){return B===v2.$f(D[0]).getValue()},"field-id","${field} should be an exact match",null,false);A.reg("email",function(C,B,D){return/^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(B)});A.reg("max-length",function(C,B,D){return B.length<D[0]},"max");A.reg("max-val",function(C,B,D){return B<=D[0]},"max");A.reg("min-length",function(C,B,D){return B.length>=D[0]},"min");A.reg("min-val",function(C,B,D){return B>=D[0]},"min");A.reg("numeric",function(C,B,D){return/^[0-9]*(\.[0-9]+)?$/.test(B)});A.reg("required",function(C,B,D){return !v2.empty(B)&&!(typeof B.length!=="undefined"&&B.length===0)},null,null,"not-empty",false);A.reg("url",function(C,B,D){return/^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(B)});A.reg("word",function(C,B,D){return/^([a-zA-Z\u00A1-\uFFFF0-9_\-\s\t])*$/.test(B)})})();v2.$msg({alpha:"${field} should only contain letters",alphanum:"${field} should only contain letters and numbers","car-regnum-nor":"${field} should be a valid norwegian auto registration number","confirmation-of":"${field} should be a confirmation of ${field-id}",email:"${field} should be a valid email address","max-length":"${field} should be no more than ${max} characters long","max-val":"${field} should be no bigger than ${min}","min-length":"${field} should be atleast ${min} characters long","min-val":"${field} should be atleast ${min}",numeric:"${field} should only contain numbers","phone-nor":"${field} should be a valid norwegian phone number",required:"${field} is required","ssn-nor":"${field} should be a valid norwegian social security number",url:"${field} should be a valid URL",word:"${field} should only contain letters, numbers and punctuation"});v2.ErrorReporting={displayErrors:-1,positionErrorsAbove:true,failureClass:"error",successClass:"",messagesClass:"messages",onFailure:function(){if(this.parent&&this.parent.type==="fieldset"){return }var I=this.onSuccess();var F="",C,G,A,J;var K=[];var B=this.getMessages();I.addClassName(this.failureClass);I.removeClassName(this.successClass);var D=this.displayErrors;var H=this.__errors.length;D=D<0||D>H?H:D;if(D===0){return }var E=document.createElement("ul");E.id=this.__getId();E.className=this.messagesClass;for(C=0;C<D;C++){G=B[C];if(!G||K.indexOf(G.toString())>=0){continue}K.push(G.toString());J=document.createElement("li");J.innerHTML=G.toString();E.appendChild(J)}if(this.positionErrorsAbove){I.insertBefore(E,I.firstChild)}else{I.appendChild(E)}},onSuccess:function(){if(this.parent&&this.parent.type==="fieldset"){return null}var A=v2.$(this.getParent());var B=v2.$(this.__getId());if(B&&A===B.parentNode){A.removeChild(B)}A.removeClassName(this.failureClass);A.addClassName(this.successClass);return A},__getId:function(){var A=this.element.getElements?this.element.getElements()[0]:null;var C=A||this.getParent();var B=C.className;var D=((C.id||C.name||B.replace(this.failureClass,""))+"_"+this.failureClass).replace(" ","_").replace(/_+/,"_");return D}};v2.Object.extend(v2.Field.prototype,v2.ErrorReporting);v2.Object.extend(v2.Fieldset.prototype,v2.ErrorReporting);v2.Object.extend(v2.Form.prototype,{scrollToFirstWhenFail:true,onFailure:function(){if(!this.scrollToFirstWhenFail){return }var B,A,D,C=this.__form;D=C.all||C.getElementsByTagName("*");for(B=0;(A=D[B]);B++){if(v2.Element.hasClassName(A,v2.ErrorReporting.failureClass)){v2.Element.scrollTo(A);return }}}});v2.Object.extend(v2.Validator,{prefix:""});v2.Object.extend(v2.Form,{autoValidateClass:"validate",actionButtonClass:"action"});v2.html={validateAnyClass:"validate_any",validateAllClass:"validate_all",validatorsFromString:function(I){var E=v2.Validator.prefix;var G=new RegExp("^"+E,"");var F,H,B,D,A,C=[];I=I.split(" ");for(F=0;(H=I[F]);F++){B=false;if(!v2.empty(E)&&!G.test(H)){continue}H=H.replace(G,"");if(/^not_/.test(H)){H=H.replace(/^not_/,"");B=true}D=H.split("_");A=D.shift();if(A&&(A=v2.$v(A))){C.push({validator:A,params:D,invert:B})}}return C},applyValidators:function(A,F,D){if(!v2.empty(D)){F.setMessage(D)}for(var C=0,B,E;(B=A[C]);C++){E=F.addValidator(B.validator,B.params);E.invert=B.invert}return F}};v2.html.Form=Base.extend({constructor:function(A){this.form=v2.Form.get(A);this.__parsed={};this.parseElement(A,this.form);this.form.passOnAny(v2.$(A).hasClassName(v2.html.validateAnyClass))},parseElement:function(C,K,E){var A=v2.$$("div, fieldset, input, select, textarea",C);var H,F,B,I,G,D,L,J;this.__parsed[C.id||C.name]=true;E=E||[];for(H=0;(G=A[H]);H++){if(E.length===0&&(/^\s*$/.test(G.className)||this.__parsed[G.id||G.name])){continue}D=G.tagName.toLowerCase();if(D==="input"&&v2.Element.hasClassName(G,v2.Form.actionButtonClass)){this.form.addButton(G);continue}if(D==="div"||D==="fieldset"){this.parseBlock(G,K);continue}J=E.concat(v2.html.validatorsFromString(G.className));if(J.length>0){L=new v2.Field(G);v2.html.applyValidators(J,L,G.title);this.__parsed[G.id||G.name]=true;K.add(L)}}},parseBlock:function(C,E){var D=new v2.Fieldset(C);var B=true;v2.$(C);if(C.hasClassName(v2.html.validateAnyClass)){}else{if(C.hasClassName(v2.html.validateAllClass)){B=false}else{return }}D.passOnAny(B);var A=v2.html.validatorsFromString(C.className);this.parseElement(C,D,A);if(!/^\s*$/.test(C.title)){D.setMessage(C.title)}if(D.get(0)){E.add(D)}}});v2.addDOMLoadEvent(function(){var A=document.getElementsByTagName("form");for(var B=0,C;(C=A[B]);B++){if(v2.Element.hasClassName(C,v2.Form.autoValidateClass)){new v2.html.Form(C)}}});v2.dsl={__validateTemplate:function(A){return function(){var F=arguments[0];var D=F.element||(F.item?F.item.element:null)||null;if(D===null){while(!!F&&!F.element){F=F.get(0)}}var E=new v2.dsl.Form(D.getElements()[0].form);E.item.passOnAny(A);for(var C=0,B;(B=arguments[C]);C++){E.item.add(B.item||B)}return E}},__andOr:function(A){return function(){var D=new v2.dsl.Collection();D.item.passOnAny(A);for(var C=0,B;(B=arguments[C]);C++){D.item.add(B.item)}return D}}};v2.dsl.expose=function(){var A=v2.dsl;v2.Object.extend(window,{validate:A.validate,validateAll:A.validateAll,validateAny:A.validateAny,and:A.and,or:A.or},false)};v2.dsl.validateAll=v2.dsl.validate=v2.dsl.__validateTemplate(false);v2.dsl.validateAny=v2.dsl.__validateTemplate(true);v2.dsl.and=v2.dsl.__andOr(false);v2.dsl.or=v2.dsl.__andOr(true);v2.Object.extend(String.prototype,{is:function(A,C){var B=new v2.dsl.Field(this.toString());return B.addValidator(A,C)}});(function(){var A=String.prototype;A.isA=A.isAn=A.has=A.hasA=A.hasAn=A.is})();v2.dsl.Field=Base.extend({__currentValidator:null,__and:null,constructor:function(A){this.item=new v2.Field(A)},addValidator:function(A,C,B){if(typeof B!=="undefined"){if(this.__and!==null&&this.__and!==B){throw new Error("Field previously set up with "+(this.__and?"AND":"OR")+", unable to shift")}this.__and=B;this.item.passOnAny(!B)}this.__currentValidator=this.item.addValidator(A,C);return this},and:function(A,B){if(!this.__currentValidator){throw new Error("Cannot add more validators when no validators are added yet")}return this.addValidator(A,B,true)},or:function(A,B){if(!!this.__currentValidator){throw new Error("Cannot add more validators when no validators are added yet")}return this.addValidator(A,B,false)},explain:function(A){if(!this.__currentValidator){throw new Error("No active field validator")}this.__currentValidator.setMessage(A);return this},help:function(A){this.item.setMessage(A);return this},withName:function(A){this.item.element.setName(A);return this}});(function(){var A=v2.dsl.Field.prototype;A.orIs=A.orIsA=A.orIsAn=A.orHas=A.orHasA=A.orHasAn=A.or;A.andIs=A.andIsA=A.andIsAn=A.andHas=A.andHasA=A.andHasAn=A.and})();v2.dsl.Collection=Base.extend({constructor:function(){this.item=new v2.CompositeFormItem()},explain:function(A){this.item.setMessage(A)}});v2.dsl.Form=Base.extend({constructor:function(A){this.item=v2.Form.get(A)},on:function(){var B,A;for(B=0;B<arguments.length;B++){A=v2.$(arguments[B]);this.item.addButton(A.tagName.toLowerCase()!=="input"?A.getElementsByTagName("input")[0]:A)}}});
@@ -0,0 +1,83 @@
1
+ // -------------------------------------------
2
+ // VALIDATIOUS 2.0: Configuration
3
+ // -------------------------------------------
4
+ //
5
+ // More: http://validatious.org/learn/references
6
+ //
7
+
8
+ // Auto-validate form with this class.
9
+ //
10
+ // v2.Form.autoValidateClass = 'validate';
11
+
12
+ // Trigger validation on action with this class.
13
+ //
14
+ v2.Form.actionButtonClass = 'commit'; // Formtastic: Use 'commit'
15
+
16
+ // Validate any/all of the validations in a block with this class.
17
+ //
18
+ // Example:
19
+ //
20
+ // <div class="validate_any">
21
+ // ...
22
+ // </div>
23
+ //
24
+ // v2.html.validateAnyClass = 'validate_any';
25
+ // v2.html.validateAnyClass = 'validate_all';
26
+
27
+ // Make generated validation classes namespaced to avoid clashes with other classes.
28
+ //
29
+ // v2.Validator.prefix = 'v2_';
30
+
31
+ // Validate instantly or on submit.
32
+ //
33
+ v2.Field.prototype.instant = true;
34
+ v2.Field.prototype.instantWhenValidated = true;
35
+
36
+ // Validate all hidden fields.
37
+ //
38
+ // v2.Field.prototype.validateHidden = false;
39
+
40
+ // Jump to first invalid field on error.
41
+ //
42
+ v2.Form.prototype.scrollToFirstWhenFail = true;
43
+
44
+ // Maximum number of errors at the same time.
45
+ //
46
+ // * -1, default value, display all messages
47
+ // * 0, display no messages, only append class names on failing elements
48
+ // * n, where n is any positive integer, display no more than this many messages
49
+ //
50
+ // v2.Field.prototype.displayErrors = 1;
51
+
52
+ // Position of errors.
53
+ //
54
+ v2.Field.prototype.positionErrorsAbove = false;
55
+ v2.Fieldset.prototype.positionErrorsAbove = false;
56
+
57
+ // Error classes.
58
+ //
59
+ // Example:
60
+ //
61
+ // <fieldset
62
+ // <div class="field error"
63
+ // <label for="name">Name</label
64
+ // <input type="text" name="name" id="name" class="required word"
65
+ // <ul class="errors"
66
+ // <li>Name is required</li
67
+ // </ul
68
+ // </div
69
+ // <div class="field error"
70
+ // <label for="email">E-mail</label
71
+ // <input type="text" name="email" id="email" class="email" value="name@"
72
+ // <ul class="errors"
73
+ // <li>E-mail should be a valid email address</li
74
+ // </ul
75
+ // </div
76
+ // </fieldset>
77
+ //
78
+ v2.Fieldset.prototype.messagesClass = 'errors'; // Formtastic: Use 'errors'
79
+ v2.Fieldset.prototype.failureClass = 'error'; // Formtastic: Use 'error'
80
+ v2.Fieldset.prototype.successClass = '';
81
+ v2.Field.prototype.messagesClass = 'errors'; // Formtastic: Use 'errors'
82
+ v2.Field.prototype.failureClass = 'error'; // Formtastic: Use 'error'
83
+ v2.Field.prototype.successClass = '';
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ class ValidatiousGenerator < Rails::Generator::Base
4
+
5
+ def manifest
6
+ record do |m|
7
+ m.template 'v2.standalone.full.min.js', File.join('public', 'javascripts', 'v2.standalone.full.min.js')
8
+ m.template 'validatious.config.js', File.join('public', 'javascripts', 'validatious.config.js')
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,20 @@
1
+ module ValidatiousOnRails
2
+ module Helpers
3
+
4
+ extend self
5
+
6
+ # Helper for the layout to host the custom Validatious-validators for forms rendered.
7
+ # This will only show up if the current view contains one - or more - forms that
8
+ # are triggered to be validated with Validatous (i.e. ValidatiousOnRails that is).
9
+ #
10
+ def custom_validatious_validators
11
+ if @content_for_validatious.present?
12
+ content_tag(:script, @content_for_validatious,
13
+ :type => 'text/javascript', :id => 'custom_validatious_validators')
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ ::ActionController::Base.helper ::ValidatiousOnRails::Helpers
@@ -0,0 +1,312 @@
1
+ # encoding: utf-8
2
+ begin
3
+ require 'validation_reflection'
4
+ rescue LoadError
5
+ gem 'redinger-validation_reflection', '>= 0.3.2'
6
+ require 'validation_reflection'
7
+ end
8
+
9
+ require File.join(File.dirname(__FILE__), *%w[validatious validators])
10
+
11
+ # Force this, as it seems ValidationReflection don't do this correctly. =S
12
+ #
13
+ ActiveRecord::Base.class_eval do
14
+ include ::ActiveRecordExtensions::ValidationReflection
15
+ ::ActiveRecordExtensions::ValidationReflection.load_config
16
+ ::ActiveRecordExtensions::ValidationReflection.install(self)
17
+ end
18
+
19
+ # Validatious-Rails validation translator.
20
+ #
21
+ module ValidatiousOnRails
22
+ class ModelValidations
23
+
24
+ CORE_VALIDATIONS = [
25
+ :acceptance_of,
26
+ :associated,
27
+ :confirmation_of,
28
+ :exclusion_of,
29
+ :format_of,
30
+ :inclusion_of,
31
+ :length_of,
32
+ :numericality_of,
33
+ :presence_of,
34
+ :uniqueness_of
35
+ ].freeze
36
+ SUPPORTED_VALIDATIONS = CORE_VALIDATIONS
37
+
38
+ class << self
39
+
40
+ # References:
41
+ # http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
42
+ # http://github.com/rails/rails/blob/13fb26b714dec0874303f51cc125ff62f65a2729/activerecord/lib/active_record/validations.rb
43
+
44
+ # Generates form field helper options for a specified object-attribute to
45
+ # reflect on it's validations.
46
+ #
47
+ # Input may be an ActiveRecord class, a class name (string), or an object
48
+ # name along with a method/field.
49
+ #
50
+ def options_for(object_name, attribute_method, options = {})
51
+ validation = self.from_active_record(object_name, attribute_method)
52
+ options.merge!(
53
+ :class => [options[:class], validation[:classes]].flatten.compact.uniq.join(' '),
54
+ :validators => validation[:validators].flatten.compact.uniq
55
+ )
56
+ end
57
+
58
+ # Groks Rails validations, and is able to convert a rails validation to
59
+ # a Validatious 2.0 compatible class string, and a Validatous validator
60
+ # for more complex validations. Even some of the Rails core validations
61
+ # with certain options requires this.
62
+ #
63
+ # Input may be an ActiveRecord class, a class name (string), or an object
64
+ # name along with a method/field.
65
+ #
66
+ # Returns a string that will be recognized by Validatious as a class name in
67
+ # form markup.
68
+ #
69
+ def from_active_record(object_or_class, attribute_method)
70
+ klass = object_or_class.to_s.classify.constantize
71
+ attribute_validation = {:classes => [], :validators => []}
72
+
73
+ # Iterate thorugh the validations for the current class,
74
+ # and collect validation options.
75
+ klass.reflect_on_validations_for(attribute_method.to_sym).each do |validation|
76
+ validates_type = validation.macro.to_s.sub(/^validates?_/, '')
77
+
78
+ # Skip "confirmation_of"-validation info for the attribute that
79
+ # needs to be confirmed. Validatious expects this validation rule
80
+ # on the confirmation field. *
81
+ unless validates_type =~ /^confirmation_of$/
82
+ validation_options = self.send(validates_type.to_sym, validation)
83
+ attribute_validation[:classes] << validation_options[:class]
84
+ # One or multiple validators for each validation (possible to combine validators).
85
+ attribute_validation[:validators] << validation_options[:validator] || validation_options[:validators]
86
+ end
87
+ end
88
+
89
+ # Special case for "confirmation_of"-validation (see * above).
90
+ if attribute_method.to_s =~ /(.+)_confirmation$/
91
+ confirm_attribute_method = $1
92
+ # Check if validates_confirmation_of(:hello) actually exists,
93
+ # if :hello_confirmation field exists - just to be safe.
94
+ klass.reflect_on_validations_for(confirm_attribute_method.to_sym).each do |validation|
95
+ if validation.macro.to_s =~ /^validates_confirmation_of$/
96
+ validation_options = self.confirmation_of(validation)
97
+ attribute_validation[:classes] << validation_options[:class]
98
+ break
99
+ end
100
+ end
101
+ end
102
+ attribute_validation
103
+ end
104
+
105
+ # Resolve validation from validates_acceptance_of.
106
+ #
107
+ # Alias, but might change: acceptance_of <=> presence_of
108
+ #
109
+ # TODO: Make this a custom validator - handle :accept.
110
+ #
111
+ # NOTE: Not supported:
112
+ # * :accept - TODO: not taken into consideration
113
+ # right now (but must have value "true" for db column values)
114
+ # * :allow_nil - TODO.
115
+ # * :on - TODO.
116
+ # * :if/:unless - hard to port all to client-side JavaScript
117
+ # (impossible: procs, unaccessible valiables, etc.).
118
+ #
119
+ def acceptance_of(validation)
120
+ {:class => 'required', :validator => nil}
121
+ end
122
+
123
+ # Resolve validation from validates_associated.
124
+ #
125
+ # NOTE: Not supported - low prio.
126
+ #
127
+ def associated(validation)
128
+ {:class => '', :validator => nil}
129
+ end
130
+
131
+ # Resolve validation from validates_confirmation_of.
132
+ # This validation is treated a bit differently in compare
133
+ # to the other validations. See "from_active_record".
134
+ #
135
+ # TODO: Message should be Rails I18n message, not Validatious.
136
+ #
137
+ # NOTE: Not supported:
138
+ # * :on - TODO.
139
+ # * :if/:unless - hard to port all to client-side JavaScript
140
+ # (impossible: procs, unaccessible valiables, etc.).
141
+ #
142
+ def confirmation_of(validation)
143
+ field_id_to_confirm = unless validation.active_record.present?
144
+ "#{validation.active_record.name.tableize.singularize.gsub('/', '_')}_#{validation.name}"
145
+ else
146
+ "#{validation.name}"
147
+ end
148
+ {:class => "confirmation-of_#{field_id_to_confirm}", :validator => nil}
149
+ end
150
+
151
+ # Resolve validation from validates_exclusion_of.
152
+ #
153
+ # Attaching custom validator - with a unique name based on the exclusion values.
154
+ #
155
+ # NOTE: Not supported:
156
+ # * :on - TODO.
157
+ # * :if/:unless - hard to port all to client-side JavaScript
158
+ # (impossible: procs, unaccessible valiables, etc.).
159
+ #
160
+ def exclusion_of(validation)
161
+ validator = Validatious::ExclusionValidator.new(validation)
162
+ {:class => validator.name, :validator => validator}
163
+ end
164
+
165
+ # Resolve validation from validates_format_of.
166
+ #
167
+ # Attaching custom validator, with a unique name based on the regular expression.
168
+ # Needs regexp.inspect to get it right.
169
+ #
170
+ # NOTE: Not supported:
171
+ # * :on - TODO.
172
+ # * :if/:unless - hard to port all to client-side JavaScript
173
+ # (impossible: procs, unaccessible valiables, etc.).
174
+ #
175
+ def format_of(validation)
176
+ validator = Validatious::FormatValidator.new(validation)
177
+ {:class => validator.name, :validator => validator}
178
+ end
179
+
180
+ # Resolve validation from validates_inclusion_of.
181
+ #
182
+ # Attaching custom validator - with a unique name based on the inclusion values.
183
+ #
184
+ # NOTE: Not supported:
185
+ # * :on - TODO.
186
+ # * :if/:unless - hard to port all to client-side JavaScript
187
+ # (impossible: procs, unaccessible valiables, etc.).
188
+ #
189
+ def inclusion_of(validation)
190
+ validator = Validatious::InclusionValidator.new(validation)
191
+ {:class => validator.name, :validator => validator}
192
+ end
193
+
194
+ # Resolve validation from validates_length_of.
195
+ #
196
+ # Example (of generated field classes):
197
+ # length-is_5, length-maximum_2, length-minimum_2, etc.
198
+ #
199
+ # NOTE: Not supported:
200
+ # * :tokenizer - see: :if/:unless
201
+ # * :on - TODO.
202
+ # * :if/:unless - hard to port all to client-side JavaScript
203
+ # (impossible: procs, unaccessible valiables, etc.).
204
+ #
205
+ def length_of(validation)
206
+ # TODO: DRY up this with the neat idea/combo:
207
+ # Validator#new(name, validation, *args)-idea + Validator#to_class
208
+ validators, min, max = case true
209
+ when validation.options[:is].present?
210
+ [Validatious::Length::IsValidator.new(validation),
211
+ validation.options[:is], validation.options[:is]]
212
+ when [:in, :within].any? { |k| validation.options[k].present? } ||
213
+ [:minimum, :maximum].all? { |k| validation.options[k].present? }
214
+ validation.options[:within] ||=
215
+ validation.options[:in] ||
216
+ (validation.options[:minimum].to_i..validation.options[:maximum].to_i)
217
+ [[Validatious::Length::MinimumValidator.new(validation),
218
+ Validatious::Length::MaximumValidator.new(validation)],
219
+ validation.options[:within].min, validation.options[:within].max]
220
+ when validation.options[:minimum].present?
221
+ [Validatious::Length::MinimumValidator.new(validation),
222
+ validation.options[:minimum], nil]
223
+ when validation.options[:maximum].present?
224
+ [Validatious::Length::MaximumValidator.new(validation),
225
+ nil, validation.options[:maximum]]
226
+ end
227
+ validators = [*validators]
228
+ # This piece of code is a bit diffuse, but works. =)
229
+ classes = [
230
+ ("#{validators.first.name}_#{min}" if min),
231
+ ("#{validators.last.name}_#{max}" if max)
232
+ ].compact.uniq.join(' ')
233
+ {:class => classes, :validator => validators}
234
+ end
235
+ alias :size_of :length_of
236
+
237
+ # Resolve validation from validates_numericality_of.
238
+ #
239
+ # Example (of generated field classes):
240
+ # numericality-odd, numericality-only-integer, numericality-equal-to_5, etc.
241
+ #
242
+ # NOTE: Not supported:
243
+ # * :on - TODO.
244
+ # * :if/:unless - hard to port all to client-side JavaScript
245
+ # (impossible: procs, unaccessible valiables, etc.).
246
+ #
247
+ def numericality_of(validation)
248
+ validators = []
249
+ values = {}
250
+
251
+ if validation.options[:odd] && !validation.options[:even]
252
+ validators << Validatious::Numericality::OddValidator.new(validation)
253
+ end
254
+ if validation.options[:even] && !validation.options[:odd]
255
+ validators << Validatious::Numericality::EvenValidator.new(validation)
256
+ end
257
+
258
+ (validation.options.keys & [:only_integer, :equal_to, :less_than, :less_than_or_equal_to,
259
+ :greater_than, :greater_than_or_equal_to]).each { |v|
260
+ klass = "::ValidatiousOnRails::Validatious::Numericality::#{v.to_s.classify}Validator".constantize
261
+ validators << (validator = klass.new(validation))
262
+ values.merge!(validator.name.to_sym => validation.options[v]) if validation.options[v].is_a?(::Numeric)
263
+ }
264
+
265
+ # TODO: Needs DRYer solution,
266
+ # Maybe: validator.args = [...] => validator.to_class => "#{validator.name}_params[0]_params[1]_etc..."
267
+ classes = validators.collect { |validator|
268
+ [validator.name,
269
+ (values[validator.name.to_sym] if values[validator.name.to_sym].present?)
270
+ ].compact.join('_')
271
+ }.join(' ')
272
+ {:class => classes, :validator => validators}
273
+ end
274
+
275
+ # Resolve validation from validates_presence_of.
276
+ #
277
+ # Alias, but might change: acceptance_of <=> presence_of
278
+ #
279
+ # NOTE: Not supported:
280
+ # * :on - TODO.
281
+ # * :if/:unless - hard to port all to client-side JavaScript
282
+ # (impossible: procs, unaccessible valiables, etc.).
283
+ #
284
+ def presence_of(validation)
285
+ {:class => 'required', :validator => nil}
286
+ end
287
+
288
+ # Resolve validation from validates_uniqueness_of.
289
+ #
290
+ # TODO: Implement using RemoteValidator.
291
+ #
292
+ def uniqueness_of(validation)
293
+ {:class => '', :validator => nil}
294
+ end
295
+
296
+ # Unknown validations - if no matching custom validator is found/registered.
297
+ #
298
+ def method_missing(sym, *args, &block)
299
+ ::ValidatiousOnRails.log "Unknown validation: #{sym}." <<
300
+ " No custom Validatious validator found for this validation makro. " <<
301
+ "Maybe you forgot to register you custom validation using: " <<
302
+ "ValidatiousOnRails::ModelValidations.add(<CustomValidationClass>)", :warn
303
+ {:class => '', :validator => nil}
304
+ end
305
+
306
+ # TODO: Include custom validations here...
307
+ #
308
+ # @custom_validators.each { |validator_class| ... }
309
+
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Tap into the built-in form/input helpers to add validatious class names from
4
+ # model validations.
5
+ #
6
+ module ActionView # :nodoc:
7
+ module Helpers # :nodoc:
8
+ module FormHelper # :nodoc:
9
+
10
+ FIELD_TYPES = [:text_field, :password_field, :text_area, :file_field, :radio_button].freeze
11
+
12
+ # Only altering the options hash is interesting - we want to set a validator class for fields,
13
+ # so the hooking of these helpers don't have to be very explicit.
14
+ #
15
+ FIELD_TYPES.each do |field_type|
16
+ define_method :"#{field_type}_with_validation" do |*args|
17
+ options = args.extract_options!
18
+ # Get the validation options.
19
+ options = ::ValidatiousOnRails::ModelValidations.options_for(args.first, args.second, options)
20
+
21
+ # Attach custom validator - if any - to the layout (in the <head>-tag - the unobtrusive way).
22
+ # Don't attach validator(s) if it/they already attached.
23
+ # TODO: Refactor into helper.
24
+ validators_js = options.delete(:validators).collect { |v|
25
+ v.to_s unless /#{v.name}/ =~ @content_for_validatious
26
+ }.join(' ')
27
+ content_for :validatious, validators_js if validators_js.present?
28
+
29
+ self.send :"#{field_type}_without_validation", *(args << options)
30
+ end
31
+ alias_method_chain field_type, :validation
32
+ end
33
+
34
+ # Special case...no hash as last argument.
35
+ #
36
+ def check_box_with_validation(object_name, method, options = {}, checked_value = '1', unchecked_value = '0')
37
+ # Get the validation options.
38
+ options = ::ValidatiousOnRails::ModelValidations.options_for(object_name, method, options)
39
+
40
+ # Attach custom validator - if any - to the layout (in the <head>-tag - the unobtrusive way).
41
+ # Don't attach validator(s) if it/they already attached.
42
+ # TODO: Refactor into helper.
43
+ validators_js = options.delete(:validators).collect { |v|
44
+ v.to_s unless /#{v.name}/ =~ @content_for_validatious
45
+ }.join(' ')
46
+ content_for :validatious, validators_js if validators_js.present?
47
+
48
+ self.check_box_without_validation object_name, method, options, checked_value, unchecked_value
49
+ end
50
+ alias_method_chain :check_box, :validation
51
+
52
+ # Adds the title attribute to label tags when there is no title
53
+ # set, and the label text is provided. The title is set to object_name.humanize
54
+ #
55
+ def label_with_title(object_name, method, text = nil, options = {})
56
+ options[:title] ||= method.to_s.humanize unless text.nil?
57
+ label_without_title(object_name, method, text, options)
58
+ end
59
+ alias_method_chain :label, :title
60
+
61
+ end
62
+
63
+ module FormOptionsHelper
64
+
65
+ FIELD_TYPES = [:time_zone_select, :select, :collection_select, :grouped_options_for_select].freeze
66
+
67
+ FIELD_TYPES.each do |field_type|
68
+ define_method :"#{field_type}_with_validation" do |*args|
69
+ options = args.extract_options!
70
+ # Get the validation options.
71
+ options = ::ValidatiousOnRails::ModelValidations.options_for(args.first, args.second, options)
72
+
73
+ # Attach custom validator - if any - to the layout (in the <head>-tag - the unobtrusive way).
74
+ # Don't attach validator(s) if it/they already attached.
75
+ # TODO: Refactor into helper.
76
+ validators_js = options.delete(:validators).collect { |v|
77
+ v.to_s unless /#{v.name}/ =~ @content_for_validatious
78
+ }.join(' ')
79
+ content_for :validatious, validators_js if validators_js.present?
80
+
81
+ self.send :"#{field_type}_without_validation", *(args << options)
82
+ end
83
+ alias_method_chain field_type, :validation
84
+ end
85
+
86
+ end
87
+
88
+ module DateHelper
89
+
90
+ FIELD_TYPES = [:date_select, :datetime_select, :time_select].freeze
91
+
92
+ FIELD_TYPES.each do |field_type|
93
+ define_method :"#{field_type}_with_validation" do |*args|
94
+ options = args.extract_options!
95
+ # Get the validation options.
96
+ options = ::ValidatiousOnRails::ModelValidations.options_for(args.first, args.second, options)
97
+
98
+ # Attach custom validator - if any - to the layout (in the <head>-tag - the unobtrusive way).
99
+ # Don't attach validator(s) if it/they already attached.
100
+ # TODO: Refactor into helper.
101
+ validators_js = options.delete(:validators).collect { |v|
102
+ v.to_s unless /#{v.name}/ =~ @content_for_validatious
103
+ }.join(' ')
104
+ content_for :validatious, validators_js if validators_js.present?
105
+
106
+ self.send :"#{field_type}_without_validation", *(args << options)
107
+ end
108
+ alias_method_chain field_type, :validation
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), *%w[rails action_view_helpers])