validatious-on-rails 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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])