client_side_validations 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,210 @@
1
+
2
+ // JSpec - XHR - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
3
+
4
+ (function(){
5
+
6
+ var lastRequest
7
+
8
+ // --- Original XMLHttpRequest
9
+
10
+ var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
11
+ XMLHttpRequest :
12
+ function(){}
13
+ var OriginalActiveXObject = 'ActiveXObject' in this ?
14
+ ActiveXObject :
15
+ undefined
16
+
17
+ // --- MockXMLHttpRequest
18
+
19
+ var MockXMLHttpRequest = function() {
20
+ this.requestHeaders = {}
21
+ }
22
+
23
+ MockXMLHttpRequest.prototype = {
24
+ status: 0,
25
+ async: true,
26
+ readyState: 0,
27
+ responseXML: null,
28
+ responseText: '',
29
+ abort: function(){},
30
+ onreadystatechange: function(){},
31
+
32
+ /**
33
+ * Return response headers hash.
34
+ */
35
+
36
+ getAllResponseHeaders : function(){
37
+ return JSpec.inject(this.responseHeaders, '', function(buf, key, val){
38
+ return buf + key + ': ' + val + '\r\n'
39
+ })
40
+ },
41
+
42
+ /**
43
+ * Return case-insensitive value for header _name_.
44
+ */
45
+
46
+ getResponseHeader : function(name) {
47
+ return this.responseHeaders[name.toLowerCase()]
48
+ },
49
+
50
+ /**
51
+ * Set case-insensitive _value_ for header _name_.
52
+ */
53
+
54
+ setRequestHeader : function(name, value) {
55
+ this.requestHeaders[name.toLowerCase()] = value
56
+ },
57
+
58
+ /**
59
+ * Open mock request.
60
+ */
61
+
62
+ open : function(method, url, async, user, password) {
63
+ this.user = user
64
+ this.password = password
65
+ this.url = url
66
+ this.readyState = 1
67
+ this.method = method.toUpperCase()
68
+ if (async != undefined) this.async = async
69
+ if (this.async) this.onreadystatechange()
70
+ },
71
+
72
+ /**
73
+ * Send request _data_.
74
+ */
75
+
76
+ send : function(data) {
77
+ var self = this
78
+ this.data = data
79
+ this.readyState = 4
80
+ if (this.method == 'HEAD') this.responseText = null
81
+ this.responseHeaders['content-length'] = (this.responseText || '').length
82
+ if(this.async) this.onreadystatechange()
83
+ this.populateResponseXML()
84
+ lastRequest = function(){
85
+ return self
86
+ }
87
+ },
88
+
89
+ /**
90
+ * Parse request body and populate responseXML if response-type is xml
91
+ * Based on the standard specification : http://www.w3.org/TR/XMLHttpRequest/
92
+ */
93
+ populateResponseXML: function() {
94
+ var type = this.getResponseHeader("content-type")
95
+ if (!type || !this.responseText || !type.match(/(text\/xml|application\/xml|\+xml$)/g))
96
+ return
97
+ this.responseXML = JSpec.parseXML(this.responseText)
98
+ }
99
+ }
100
+
101
+ // --- Response status codes
102
+
103
+ JSpec.statusCodes = {
104
+ 100: 'Continue',
105
+ 101: 'Switching Protocols',
106
+ 200: 'OK',
107
+ 201: 'Created',
108
+ 202: 'Accepted',
109
+ 203: 'Non-Authoritative Information',
110
+ 204: 'No Content',
111
+ 205: 'Reset Content',
112
+ 206: 'Partial Content',
113
+ 300: 'Multiple Choice',
114
+ 301: 'Moved Permanently',
115
+ 302: 'Found',
116
+ 303: 'See Other',
117
+ 304: 'Not Modified',
118
+ 305: 'Use Proxy',
119
+ 307: 'Temporary Redirect',
120
+ 400: 'Bad Request',
121
+ 401: 'Unauthorized',
122
+ 402: 'Payment Required',
123
+ 403: 'Forbidden',
124
+ 404: 'Not Found',
125
+ 405: 'Method Not Allowed',
126
+ 406: 'Not Acceptable',
127
+ 407: 'Proxy Authentication Required',
128
+ 408: 'Request Timeout',
129
+ 409: 'Conflict',
130
+ 410: 'Gone',
131
+ 411: 'Length Required',
132
+ 412: 'Precondition Failed',
133
+ 413: 'Request Entity Too Large',
134
+ 414: 'Request-URI Too Long',
135
+ 415: 'Unsupported Media Type',
136
+ 416: 'Requested Range Not Satisfiable',
137
+ 417: 'Expectation Failed',
138
+ 422: 'Unprocessable Entity',
139
+ 500: 'Internal Server Error',
140
+ 501: 'Not Implemented',
141
+ 502: 'Bad Gateway',
142
+ 503: 'Service Unavailable',
143
+ 504: 'Gateway Timeout',
144
+ 505: 'HTTP Version Not Supported'
145
+ }
146
+
147
+ /**
148
+ * Mock XMLHttpRequest requests.
149
+ *
150
+ * mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' })
151
+ *
152
+ * @return {hash}
153
+ * @api public
154
+ */
155
+
156
+ function mockRequest() {
157
+ return { and_return : function(body, type, status, headers) {
158
+ XMLHttpRequest = MockXMLHttpRequest
159
+ ActiveXObject = false
160
+ status = status || 200
161
+ headers = headers || {}
162
+ headers['content-type'] = type
163
+ JSpec.extend(XMLHttpRequest.prototype, {
164
+ responseText: body,
165
+ responseHeaders: headers,
166
+ status: status,
167
+ statusText: JSpec.statusCodes[status]
168
+ })
169
+ }}
170
+ }
171
+
172
+ /**
173
+ * Unmock XMLHttpRequest requests.
174
+ *
175
+ * @api public
176
+ */
177
+
178
+ function unmockRequest() {
179
+ XMLHttpRequest = OriginalXMLHttpRequest
180
+ ActiveXObject = OriginalActiveXObject
181
+ }
182
+
183
+ JSpec.include({
184
+ name: 'Mock XHR',
185
+
186
+ // --- Utilities
187
+
188
+ utilities : {
189
+ mockRequest: mockRequest,
190
+ unmockRequest: unmockRequest
191
+ },
192
+
193
+ // --- Hooks
194
+
195
+ afterSpec : function() {
196
+ unmockRequest()
197
+ },
198
+
199
+ // --- DSLs
200
+
201
+ DSLs : {
202
+ snake : {
203
+ mock_request: mockRequest,
204
+ unmock_request: unmockRequest,
205
+ last_request: function(){ return lastRequest() }
206
+ }
207
+ }
208
+
209
+ })
210
+ })()
@@ -0,0 +1,40 @@
1
+ module DNCLabs
2
+ module ClientSideValidations
3
+ module Adapters
4
+ module ActionView
5
+ module BaseMethods
6
+
7
+ def client_side_validations(object_name, options = {})
8
+ url = options.delete(:url)
9
+ raise "No URL Specified!" unless url
10
+ adapter = options.delete(:adapter) || 'jquery.validate'
11
+ <<-JS
12
+ <script type="text/javascript">
13
+ $(document).ready(function() {
14
+ $('##{dom_id(options[:object])}').clientSideValidations('#{url}', '#{adapter}');
15
+ })
16
+ </script>
17
+ JS
18
+ end
19
+
20
+ end # BaseMethods
21
+
22
+ module FormBuilderMethods
23
+
24
+ def client_side_validations(options = {})
25
+ @template.send(:client_side_validations, @object_name, objectify_options(options))
26
+ end
27
+
28
+ end # FormBuilderMethods
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ ActionView::Base.class_eval do
35
+ include DNCLabs::ClientSideValidations::Adapters::ActionView::BaseMethods
36
+ end
37
+
38
+ ActionView::Helpers::FormBuilder.class_eval do
39
+ include DNCLabs::ClientSideValidations::Adapters::ActionView::FormBuilderMethods
40
+ end
@@ -0,0 +1,90 @@
1
+ module DNCLabs
2
+ module ClientSideValidations
3
+ module Adapters
4
+ class ActiveModel
5
+ attr_accessor :base
6
+
7
+ def initialize(base)
8
+ self.base = base
9
+ end
10
+
11
+ def validation_to_hash(_attr, _options = {})
12
+ validation_hash = {}
13
+ base._validators[_attr.to_sym].each do |validation|
14
+ message = get_validation_message(validation, _options[:locale])
15
+ validation.options.delete(:message)
16
+ options = get_validation_options(validation.options)
17
+ method = get_validation_method(validation.kind)
18
+ if conditional_method = remove_reserved_conditionals(options['if'])
19
+ if base.instance_eval(conditional_method.to_s)
20
+ options.delete('if')
21
+ validation_hash[method] = { 'message' => message }.merge(options)
22
+ end
23
+ elsif conditional_method = options['unless']
24
+ unless base.instance_eval(conditional_method.to_s)
25
+ options.delete('unless')
26
+ validation_hash[method] = { 'message' => message }.merge(options)
27
+ end
28
+ else
29
+ options.delete('if')
30
+ options.delete('unless')
31
+ validation_hash[method] = { 'message' => message }.merge(options)
32
+ end
33
+ end
34
+
35
+ validation_hash
36
+ end
37
+
38
+ private
39
+
40
+ def get_validation_message(validation, locale)
41
+ default = case validation.kind
42
+ when :presence
43
+ I18n.translate('errors.messages.blank', :locale => locale)
44
+ when :format
45
+ I18n.translate('errors.messages.invalid', :locale => locale)
46
+ when :length
47
+ if count = validation.options[:minimum]
48
+ I18n.translate('errors.messages.too_short', :locale => locale).sub('{{count}}', count.to_s)
49
+ elsif count = validation.options[:maximum]
50
+ I18n.translate('errors.messages.too_long', :locale => locale).sub('{{count}}', count.to_s)
51
+ end
52
+ when :numericality
53
+ I18n.translate('errors.messages.not_a_number', :locale => locale)
54
+ end
55
+
56
+ message = validation.options[:message]
57
+ if message.kind_of?(String)
58
+ message
59
+ elsif message.kind_of?(Symbol)
60
+ I18n.translate("errors.models.#{base.class.to_s.downcase}.attributes.#{validation.attributes.first}.#{message}", :locale => locale)
61
+ else
62
+ default
63
+ end
64
+ end
65
+
66
+ def get_validation_options(options)
67
+ options = options.stringify_keys
68
+ options.delete('on')
69
+ options.delete('tokenizer')
70
+ options.delete('only_integer')
71
+ options.delete('allow_nil')
72
+ if options['with'].kind_of?(Regexp)
73
+ options['with'] = options['with'].inspect.to_s.sub("\\A","^").sub("\\Z","$").sub(%r{^/},"").sub(%r{/i?$}, "")
74
+ end
75
+ options
76
+ end
77
+
78
+ def get_validation_method(kind)
79
+ kind.to_s
80
+ end
81
+
82
+ def remove_reserved_conditionals(*conditionals)
83
+ conditionals.flatten!
84
+ conditionals.delete_if { |conditional| conditional =~ /@_/ }
85
+ conditionals.first
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,90 @@
1
+ require 'validation_reflection'
2
+
3
+ module DNCLabs
4
+ module ClientSideValidations
5
+ module Adapters
6
+ class ActiveRecord2
7
+ attr_accessor :base
8
+
9
+ def initialize(base)
10
+ self.base = base
11
+ end
12
+
13
+ def validation_to_hash(_attr, _options = {})
14
+ validation_hash = {}
15
+ base.class.reflect_on_validations_for(_attr).each do |validation|
16
+ message = get_validation_message(validation, _options[:locale])
17
+ validation.options.delete(:message)
18
+ options = get_validation_options(validation.options)
19
+ method = get_validation_method(validation.macro)
20
+ if conditional_method = options['if']
21
+ if base.instance_eval(conditional_method.to_s)
22
+ options.delete('if')
23
+ validation_hash[method] = { 'message' => message }.merge(options)
24
+ end
25
+ elsif conditional_method = options['unless']
26
+ unless base.instance_eval(conditional_method.to_s)
27
+ options.delete('unless')
28
+ validation_hash[method] = { 'message' => message }.merge(options)
29
+ end
30
+ else
31
+ validation_hash[method] = { 'message' => message }.merge(options)
32
+ end
33
+ end
34
+
35
+ validation_hash
36
+ end
37
+
38
+ private
39
+
40
+ def get_validation_message(validation, locale)
41
+ default = case validation.macro.to_s
42
+ when 'validates_presence_of'
43
+ I18n.translate('activerecord.errors.messages.blank', :locale => locale)
44
+ when 'validates_format_of'
45
+ I18n.translate('activerecord.errors.messages.invalid', :locale => locale)
46
+ when 'validates_length_of'
47
+ if count = validation.options[:minimum]
48
+ I18n.translate('activerecord.errors.messages.too_short', :locale => locale).sub('{{count}}', count.to_s)
49
+ elsif count = validation.options[:maximum]
50
+ I18n.translate('activerecord.errors.messages.too_long', :locale => locale).sub('{{count}}', count.to_s)
51
+ end
52
+ when 'validates_numericality_of'
53
+ I18n.translate('activerecord.errors.messages.not_a_number', :locale => locale)
54
+ end
55
+
56
+ message = validation.options[:message]
57
+ if message.kind_of?(String)
58
+ message
59
+ elsif message.kind_of?(Symbol)
60
+ I18n.translate("activerecord.errors.models.#{base.class.to_s.downcase}.attributes.#{validation.name}.#{message}", :locale => locale)
61
+ else
62
+ default
63
+ end
64
+ end
65
+
66
+ def get_validation_options(options)
67
+ options = options.stringify_keys
68
+ options.delete('on')
69
+ if options['with'].kind_of?(Regexp)
70
+ options['with'] = options['with'].inspect.to_s.sub("\\A","^").sub("\\Z","$").sub(%r{^/},"").sub(%r{/i?$}, "")
71
+ end
72
+ options
73
+ end
74
+
75
+ def get_validation_method(method)
76
+ case method.to_s
77
+ when 'validates_presence_of'
78
+ 'presence'
79
+ when 'validates_format_of'
80
+ 'format'
81
+ when 'validates_numericality_of'
82
+ 'numericality'
83
+ when 'validates_length_of'
84
+ 'length'
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,47 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+
3
+ module DNCLabs
4
+ module ClientSideValidations
5
+ def validations_to_json(*attrs)
6
+ hash = Hash.new { |h, attribute| h[attribute] = {} }
7
+ attrs.each do |attr|
8
+ hash[attr].merge!(validation_to_hash(attr))
9
+ end
10
+ hash.to_json
11
+ end
12
+
13
+ def validation_to_hash(_attr, _options = {})
14
+ @dnc_csv_adapter ||= Adapter.new(self)
15
+ @dnc_csv_adapter.validation_to_hash(_attr, _options)
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+ # ORM
22
+
23
+ if defined?(ActiveModel)
24
+ require 'adapters/active_model'
25
+ unless Object.respond_to?(:to_json)
26
+ require 'active_support/json/encoding'
27
+ end
28
+ DNCLabs::ClientSideValidations::Adapter = DNCLabs::ClientSideValidations::Adapters::ActiveModel
29
+ klass = ActiveModel::Validations
30
+ elsif defined?(ActiveRecord)
31
+ if ActiveRecord::VERSION::MAJOR == 2
32
+ require 'adapters/active_record_2'
33
+ DNCLabs::ClientSideValidations::Adapter = DNCLabs::ClientSideValidations::Adapters::ActiveRecord2
34
+ klass = ActiveRecord::Base
35
+ end
36
+ end
37
+
38
+ klass.class_eval do
39
+ include DNCLabs::ClientSideValidations
40
+ end
41
+
42
+
43
+ # Template
44
+
45
+ if defined?(ActionView)
46
+ require 'adapters/action_view'
47
+ end