judge 0.3.1 → 0.4.0

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.
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{judge}
8
- s.version = "0.3.1"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Joe Corcoran"]
12
- s.date = %q{2011-07-26}
12
+ s.date = %q{2011-08-03}
13
13
  s.description = %q{Validate forms in-place using your model validations}
14
14
  s.email = %q{joe@tribesports.com}
15
15
  s.extra_rdoc_files = [
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
25
25
  "README.md",
26
26
  "Rakefile",
27
27
  "VERSION",
28
+ "docs/docco.css",
29
+ "docs/judge.html",
28
30
  "judge.gemspec",
29
31
  "lib/generators/judge/judge_generator.rb",
30
32
  "lib/generators/judge/templates/json2.js",
@@ -78,8 +80,6 @@ Gem::Specification.new do |s|
78
80
  "test/dummy/db/migrate/20110725082530_create_continent_country_and_city_tables.rb",
79
81
  "test/dummy/db/schema.rb",
80
82
  "test/dummy/db/test.sqlite3",
81
- "test/dummy/log/development.log",
82
- "test/dummy/log/production.log",
83
83
  "test/dummy/log/server.log",
84
84
  "test/dummy/public/404.html",
85
85
  "test/dummy/public/422.html",
@@ -1,13 +1,17 @@
1
+ // This is the JavaScript part of Judge, a client-side validation gem for Rails 3.
2
+ // You can find a guide and some more traditional API documentation at <http://joecorcoran.github.com/judge/>.
3
+ // Hopefully this page will help you understand what's happening under the hood.
4
+
1
5
  /*jshint curly: true, evil: true, newcap: true, noarg: true */
2
- /*global _: false, JSON: false */
6
+ /*global _: false, JSON: false */
3
7
 
4
- // global namespace
8
+ // The judge namespace.
5
9
  var judge = judge || {};
6
10
 
7
- // Watcher is a DOM element wrapper that judge uses to store validation info and instance methods
11
+ // A judge.Watcher is a DOM element wrapper that judge uses to store validation info and instance methods.
8
12
  judge.Watcher = function (element) {
9
13
 
10
- // dependency errors
14
+ // Throw dependency errors.
11
15
  if (!window.hasOwnProperty('_')) {
12
16
  throw new ReferenceError('[judge] Underscore.js not found');
13
17
  }
@@ -15,7 +19,7 @@ judge.Watcher = function (element) {
15
19
  throw new ReferenceError('[judge] JSON global object not found');
16
20
  }
17
21
 
18
- // constructor usage errors
22
+ // Throw some constructor usage errors.
19
23
  if (_(element).isUndefined()) {
20
24
  throw new ReferenceError('[judge] No DOM element passed to constructor');
21
25
  }
@@ -23,22 +27,24 @@ judge.Watcher = function (element) {
23
27
  throw new TypeError('[judge] Cannot construct new Watcher for object of this type');
24
28
  }
25
29
  if (element.getAttribute('data-validate') === null) {
26
- throw new ReferenceError('[judge] Cannot construct new Watcher for this element, please use judge form builder methods in your view');
30
+ throw new ReferenceError('[judge] Cannot construct Watcher for this element, use judge form builders');
27
31
  }
28
32
  if (element.form.getAttribute('data-error-messages') === null) {
29
- throw new ReferenceError('[judge] Element parent form was not created using judge form helper, please amend');
33
+ throw new ReferenceError('[judge] Parent form was not created using judge form helper, please amend');
30
34
  }
31
35
 
32
36
 
33
- // convenience accessor
37
+ // Convenient access to this Watcher.
34
38
  var instance = this;
35
39
 
36
- // common instance properties
40
+ // Watcher instance properties.
37
41
  this.element = element;
38
42
  this.validators = JSON.parse(this.element.getAttribute('data-validate'));
39
43
  this.defaultMessages = JSON.parse(this.element.form.getAttribute('data-error-messages'));
40
44
 
41
- // instance methods
45
+ // This instance method returns the validity of the watched element,
46
+ // represented as an array of objects containing validity information
47
+ // for each validator found on the element.
42
48
  this.validate = function() {
43
49
  instance.errorMessages = [];
44
50
  var validators = instance.validators,
@@ -63,11 +69,14 @@ judge.Watcher = function (element) {
63
69
 
64
70
  };
65
71
 
66
- // instance methods
72
+ // Watcher prototype methods.
67
73
  judge.Watcher.prototype.validates = function() {
68
74
  var instance = this,
69
75
  extendedMethods = judge.customValidators,
70
76
  methods = {
77
+
78
+ // Presence validator ported as closely as possible
79
+ // from [ActiveModel::Validations::PresenceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/PresenceValidator.html).
71
80
  presence: function(options) {
72
81
  if (instance.element.value.length) {
73
82
  return { valid:true };
@@ -76,6 +85,8 @@ judge.Watcher.prototype.validates = function() {
76
85
  }
77
86
  },
78
87
 
88
+ // Length validator ported as closely as possible
89
+ // from [ActiveModel::Validations::LengthValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/LengthValidator.html).
79
90
  length: function(options) {
80
91
  var msgs = [],
81
92
  length = instance.element.value.length,
@@ -85,32 +96,45 @@ judge.Watcher.prototype.validates = function() {
85
96
  is: { operator: '!=', message: 'wrong_length' }
86
97
  };
87
98
  _(types).each(function(properties, type) {
88
- if (options.hasOwnProperty(type) && judge.utils.operate(length, properties.operator, options[type])) {
99
+ var invalid = judge.utils.operate(length, properties.operator, options[type]);
100
+ if (options.hasOwnProperty(type) && invalid) {
89
101
  var m = options[properties.message] || instance.defaultMessages[properties.message];
90
102
  msgs.push(judge.utils.countMsg(m, options[type]));
91
103
  }
92
104
  });
93
105
  return msgs.length ? { valid:false, messages:msgs } : { valid:true };
94
106
  },
95
-
107
+
108
+ // Exclusion validator ported as closely as possible
109
+ // from [ActiveModel::Validations::ExclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ExclusionValidator.html).
96
110
  exclusion: function(options) {
97
111
  var stringIn = _(options['in']).map(function(o) { return o.toString(); });
98
112
  if (_(stringIn).include(instance.element.value)) {
99
- return { valid:false, messages:[options.message || instance.defaultMessages.exclusion] };
113
+ return {
114
+ valid:false,
115
+ messages:[options.message || instance.defaultMessages.exclusion]
116
+ };
100
117
  } else {
101
118
  return { valid:true };
102
119
  }
103
120
  },
104
-
121
+
122
+ // Inclusion validator ported as closely as possible
123
+ // from [ActiveModel::Validations::InclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/InclusionValidator.html).
105
124
  inclusion: function(options) {
106
125
  var stringIn = _(options['in']).map(function(o) { return o.toString(); });
107
126
  if (!_(stringIn).include(instance.element.value)) {
108
- return { valid:false, messages:[options.message || instance.defaultMessages.inclusion] };
127
+ return {
128
+ valid:false,
129
+ messages:[options.message || instance.defaultMessages.inclusion]
130
+ };
109
131
  } else {
110
132
  return { valid:true };
111
133
  }
112
134
  },
113
-
135
+
136
+ // Numericality validator ported as closely as possible
137
+ // from [ActiveModel::Validations::NumericalityValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/NumericalityValidator.html).
114
138
  numericality: function(options) {
115
139
  var operators = {
116
140
  greater_than: '>',
@@ -121,16 +145,23 @@ judge.Watcher.prototype.validates = function() {
121
145
  },
122
146
  msgs = [],
123
147
  value = instance.element.value,
124
- parsedValue = parseFloat(value);
148
+ parsedValue = parseFloat(value, 10);
125
149
 
126
150
  if (isNaN(Number(value))) {
127
151
  msgs.push(options.message || instance.defaultMessages.not_a_number);
128
152
  } else {
129
- if (options.odd && judge.utils.isEven(parsedValue)) { msgs.push(instance.defaultMessages.odd); }
130
- if (options.even && judge.utils.isOdd(parsedValue)) { msgs.push(instance.defaultMessages.even); }
131
- if (options.only_integer && !judge.utils.isInt(parsedValue)) { msgs.push(instance.defaultMessages.not_an_integer); }
153
+ if (options.odd && judge.utils.isEven(parsedValue)) {
154
+ msgs.push(instance.defaultMessages.odd);
155
+ }
156
+ if (options.even && judge.utils.isOdd(parsedValue)) {
157
+ msgs.push(instance.defaultMessages.even);
158
+ }
159
+ if (options.only_integer && !judge.utils.isInt(parsedValue)) {
160
+ msgs.push(instance.defaultMessages.not_an_integer);
161
+ }
132
162
  _(operators).each(function(operator, key) {
133
- if (options.hasOwnProperty(key) && !judge.utils.operate(parsedValue, operators[key], parseFloat(options[key]))) {
163
+ var valid = judge.utils.operate(parsedValue, operators[key], parseFloat(options[key], 10));
164
+ if (options.hasOwnProperty(key) && !valid) {
134
165
  var m = options.message || instance.defaultMessages[key];
135
166
  msgs.push(judge.utils.countMsg(m, options[key]));
136
167
  }
@@ -138,29 +169,42 @@ judge.Watcher.prototype.validates = function() {
138
169
  }
139
170
  return msgs.length? { valid:false, messages:msgs } : { valid:true };
140
171
  },
141
-
172
+
173
+ // Format validator ported as closely as possible
174
+ // from [ActiveModel::Validations::FormatValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/FormatValidator.html).
142
175
  format: function(options) {
143
176
  var msgs = [],
144
177
  value = instance.element.value;
145
178
  if (options.hasOwnProperty('with')) {
146
179
  var withReg = judge.utils.convertRegExp(options['with']);
147
- if (!withReg.test(value)) { msgs.push(options.message || instance.defaultMessages.invalid); }
180
+ if (!withReg.test(value)) {
181
+ msgs.push(options.message || instance.defaultMessages.invalid);
182
+ }
148
183
  }
149
184
  if (options.hasOwnProperty('without')) {
150
185
  var withoutReg = judge.utils.convertRegExp(options.without);
151
- if (withoutReg.test(value)) { msgs.push(options.message || instance.defaultMessages.invalid); }
186
+ if (withoutReg.test(value)) {
187
+ msgs.push(options.message || instance.defaultMessages.invalid);
188
+ }
152
189
  }
153
190
  return msgs.length ? { valid:false, messages:msgs } : { valid:true };
154
191
  },
155
-
192
+
193
+ // Acceptance validator ported as closely as possible
194
+ // from [ActiveModel::Validations::AcceptanceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/AcceptanceValidator.html).
156
195
  acceptance: function(options) {
157
196
  if (instance.element.checked === true) {
158
197
  return { valid:true };
159
198
  } else {
160
- return { valid:false, messages:[options.message || instance.defaultMessages.accepted] };
199
+ return {
200
+ valid:false,
201
+ messages:[options.message || instance.defaultMessages.accepted]
202
+ };
161
203
  }
162
204
  },
163
-
205
+
206
+ // Confirmation validator ported as closely as possible
207
+ // from [ActiveModel::Validations::ConfirmationValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ConfirmationValidator.html).
164
208
  confirmation: function(options) {
165
209
  var id = instance.element.getAttribute('id'),
166
210
  confId = id + '_confirmation',
@@ -168,25 +212,31 @@ judge.Watcher.prototype.validates = function() {
168
212
  if (instance.element.value === confElem.value) {
169
213
  return { valid:true };
170
214
  } else {
171
- return { valid:false, messages:[options.message || instance.defaultMessages.confirmation] };
215
+ return {
216
+ valid:false,
217
+ messages:[options.message || instance.defaultMessages.confirmation]
218
+ };
172
219
  }
173
220
  }
174
221
  };
222
+ // Return all validation methods, including those found in judge.customValidators.
223
+ // If you name a custom validation method the same as a default one, for example
224
+ // `judge.customValidators.presence = function() {};`
225
+ // then the custom method _will overwrite_ the default one, so be careful!
175
226
  return _.extend(methods, extendedMethods);
176
227
  };
177
228
 
178
- // static properties
229
+ // This object should contain any judge validation methods
230
+ // that correspond to custom validators used in the model.
179
231
  judge.customValidators = {};
180
232
 
181
- // storage
233
+ // The judge store is now open :)
182
234
  judge.store = (function() {
183
- // private
184
235
  var store = {};
185
236
 
186
- // public methods
187
237
  return {
188
238
 
189
- // store Watcher for element against user defined key
239
+ // Stores a Watcher for an element against a user defined key.
190
240
  save: function(key, element) {
191
241
  var elements = judge.utils.isCollection(element) ? element : [element];
192
242
  _(elements).each(function(element) {
@@ -200,7 +250,7 @@ judge.store = (function() {
200
250
  return store;
201
251
  },
202
252
 
203
- // remove individual stored Watcher
253
+ // Removes an individual stored Watcher.
204
254
  remove: function(key, element) {
205
255
  if (!store.hasOwnProperty(key)) {
206
256
  return null;
@@ -210,13 +260,15 @@ judge.store = (function() {
210
260
  return store;
211
261
  },
212
262
 
213
- // get entire store object, or get array of Watchers stored against key
263
+ // Returns the entire store object, or an array of Watchers stored against
264
+ // the given key.
214
265
  get: function(key) {
215
266
  if (_(key).isUndefined()) { return store; }
216
267
  return store.hasOwnProperty(key) ? store[key] : null;
217
268
  },
218
269
 
219
- // get store object with watchers converted to elements, or DOM elements stored within all watchers stored against key
270
+ // Returns the entire store object with Watchers converted to elements,
271
+ // or all DOM elements stored within all Watchers stored against the given key.
220
272
  getDOM: function(key) {
221
273
  if (_(key).isUndefined()) {
222
274
  var convertedStore = {};
@@ -228,7 +280,8 @@ judge.store = (function() {
228
280
  return store.hasOwnProperty(key) ? _(store[key]).pluck('element') : null;
229
281
  },
230
282
 
231
- // wipe entire store object, or wipe all Watchers stored against key
283
+ // Wipes the entire store object, or wipes all Watchers stored against
284
+ // the given key.
232
285
  clear: function(key) {
233
286
  if (_(key).isUndefined()) {
234
287
  store = {};
@@ -242,7 +295,8 @@ judge.store = (function() {
242
295
  };
243
296
  }());
244
297
 
245
- // validate element(s) without storing
298
+ // Validate all given element(s) without storing. Watcher creation is handled for you,
299
+ // but the created Watchers will not be returned, so it's less flexible.
246
300
  judge.validate = function(elements) {
247
301
  var results = [];
248
302
  elements = judge.utils.isCollection(elements) ? elements : [elements];
@@ -253,9 +307,10 @@ judge.validate = function(elements) {
253
307
  return results;
254
308
  };
255
309
 
256
- // utils
310
+ // The obligatory utilities.
257
311
  judge.utils = {
258
- // determine whether object is a DOM element of the types that judge can work with
312
+
313
+ // Determines whether an object is a DOM element of the types that judge can work with.
259
314
  isValidatable: function(object) {
260
315
  var type = judge.utils.getObjectString(object);
261
316
  return (
@@ -265,7 +320,7 @@ judge.utils = {
265
320
  );
266
321
  },
267
322
 
268
- // a way of checking isArray but including NodeList
323
+ // A way of checking isArray but including NodeList.
269
324
  isCollection: function(object) {
270
325
  var type = judge.utils.getObjectString(object);
271
326
  return (
@@ -273,27 +328,32 @@ judge.utils = {
273
328
  );
274
329
  },
275
330
 
276
- // return object type as represented in Object.prototype.toString
331
+ // Returns the object type as represented in `Object.prototype.toString`.
277
332
  getObjectString: function(object) {
278
333
  var string = Object.prototype.toString.call(object);
279
334
  return string.replace(/\[|\]/g, '').split(' ')[1];
280
335
  },
281
336
 
282
- // (3, '<', 4) => '3 < 4' => true
337
+ // OMG! How can you use eval, you monster! It's totally evil!
338
+ // It's used for stuff like `(3, '<', 4) => '3 < 4' => true`.
283
339
  operate: function(input, operator, validInput) {
284
340
  return eval(input+' '+operator+' '+validInput);
285
341
  },
286
342
 
287
- // sub expected value into error message
343
+ // Sub the expected value into an error message.
288
344
  countMsg: function(message, count) { return message.replace(/\%\{count\}/, count); },
289
345
 
290
- // numerical helpers
346
+ // Some nifty numerical helpers.
291
347
  isInt: function(value) { return value === +value && value === (value|0); },
292
348
  isFloat: function(value) { return value === +value && value !== (value|0); },
293
349
  isEven: function(value) { return (value % 2 === 0) ? true : false; },
294
350
  isOdd: function(value) { return !judge.utils.isEven(value); },
295
351
 
296
- // convert ruby flag-first, string formatted RegExp into JS
352
+ // Converts a ruby flag-first formatted regular expression, given as a string, into JavaScript.
353
+ // This is rudimentary at best, as there are many, many difference between Ruby
354
+ // and JavaScript when it comes to regexp-fu. Basically, if you validate your records
355
+ // records using complex regular expressions AND you hope that this will "just work"
356
+ // on the client-side too&hellip; you are going to be pretty disappointed.
297
357
  convertRegExp: function(string) {
298
358
  var p = string.slice(1, -1).split(':'),
299
359
  o = p.shift(),
@@ -4,7 +4,7 @@ module Judge
4
4
 
5
5
  include ActionView::Helpers::TagHelper
6
6
 
7
- %w{text_field text_area}.each do |type|
7
+ %w{text_field text_area password_field}.each do |type|
8
8
  helper = <<-END
9
9
  def validated_#{type}(method, options = {})
10
10
  options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(options)
@@ -19,7 +19,6 @@ module Judge
19
19
  @template.radio_button(@object_name, method, tag_value, objectify_options(options))
20
20
  end
21
21
 
22
-
23
22
  def validated_check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
24
23
  options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(options)
25
24
  @template.check_box(self.object_name, method, objectify_options(options), checked_value, unchecked_value)
@@ -39,6 +38,22 @@ module Judge
39
38
  html_options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(html_options)
40
39
  @template.grouped_collection_select(self.object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
41
40
  end
41
+
42
+ %w{date_select datetime_select time_select}.each do |type|
43
+ helper = <<-END
44
+ def validated_#{type}(method, options = {}, html_options = {})
45
+ html_options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(html_options)
46
+ @template.#{type}(self.object_name, method, objectify_options(options), html_options)
47
+ end
48
+ END
49
+ class_eval helper, __FILE__, __LINE__
50
+ end
51
+
52
+ def validated_time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
53
+ html_options = { "data-validate" => Judge::Utils.jsonify_validators(self.object, method) }.merge(html_options)
54
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
55
+ end
56
+
42
57
 
43
58
  end
44
59
 
@@ -8,6 +8,12 @@ class Foo < ActiveRecord::Base
8
8
  validates :five, :format => { :without => /[A-Za-z]+/ }
9
9
  validates :six, :acceptance => true
10
10
  validates :seven, :confirmation => true
11
+ validates :eight, :confirmation => true
12
+
13
+ validates :nine, :presence => true
14
+ validates :ten, :presence => true
15
+ validates :eleven, :presence => true
16
+ validates :twelve, :presence => true
11
17
 
12
18
  end
13
19
 
@@ -41,6 +41,27 @@
41
41
  <%= f.label :seven %><br />
42
42
  <%= f.validated_text_field :seven %>
43
43
  </div>
44
+ <div class="field">
45
+ <%= f.label :eight %><br />
46
+ <%= f.validated_password_field :eight %>
47
+ </div>
48
+ <div class="field">
49
+ <%= f.label :nine %><br />
50
+ <%= f.validated_time_zone_select :nine, ActiveSupport::TimeZone.us_zones, :include_blank => true %>
51
+ </div>
52
+ <div class="field">
53
+ <%= f.label :ten %><br />
54
+ <%= f.validated_time_select :ten, :include_blank => true, :ignore_date => true %>
55
+ </div>
56
+ <div class="field">
57
+ <%= f.label :eleven %><br />
58
+ <%= f.validated_datetime_select :eleven, :include_blank => true %>
59
+ </div>
60
+ <div class="field">
61
+ <%= f.label :twelve %><br />
62
+ <%= f.validated_date_select :twelve, :include_blank => true %>
63
+ </div>
64
+
44
65
  <div class="actions">
45
66
  <%= f.submit %>
46
67
  </div>