judge 1.2.0 → 1.3.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.
@@ -1,4 +1,4 @@
1
- // Judge 1.2.0
1
+ // Judge 1.3.0
2
2
  // (c) 2011–2012 Joe Corcoran
3
3
  // http://raw.github.com/joecorcoran/judge/master/LICENSE.txt
4
4
  // This is the JavaScript part of Judge, a client-side validation gem for Rails 3.
@@ -8,278 +8,274 @@
8
8
  /*jshint curly: true, evil: true, newcap: true, noarg: true, strict: false */
9
9
  /*global _: false, JSON: false */
10
10
 
11
- // The judge namespace.
12
- var judge = {};
11
+ (function(root) {
13
12
 
14
- judge.VERSION = '1.2.0';
13
+ // The judge namespace.
14
+ var judge = {};
15
15
 
16
- // A judge.Watcher is a DOM element wrapper that judge uses to store validation info and instance methods.
17
- judge.Watcher = function (element) {
16
+ judge.VERSION = '1.3.0';
18
17
 
19
- // Throw dependency errors.
20
- if (typeof window._ === 'undefined') {
21
- throw {
22
- name: 'ReferenceError',
23
- message: '[judge][dependency] Underscore.js not found'
24
- };
25
- }
26
- if (_(window.JSON).isUndefined()) {
27
- throw {
28
- name: 'ReferenceError',
29
- message: '[judge][dependency] JSON global object not found'
30
- };
31
- }
18
+ // A judge.Watcher is a DOM element wrapper that judge uses to store validation info and instance methods.
19
+ judge.Watcher = function (element) {
20
+ // Throw dependency errors.
21
+ if (typeof root._ === 'undefined') {
22
+ throw new DependencyError('Ensure underscore.js is loaded');
23
+ }
24
+ if (_(root.JSON).isUndefined()) {
25
+ throw new DependencyError('Ensure that your browser provides the JSON object or that json2.js is loaded');
26
+ }
32
27
 
33
- // Throw some constructor usage errors.
34
- if (_(element).isUndefined()) {
35
- throw {
36
- name: 'ReferenceError',
37
- message: '[judge][constructor] No DOM element passed to constructor'
38
- };
39
- }
40
- if (element.getAttribute('data-validate') === null) {
41
- throw {
42
- name: 'ReferenceError',
43
- message: '[judge][constructor] Cannot construct Watcher for this element, use judge form builders'
44
- };
45
- }
28
+ // Throw some constructor usage errors.
29
+ if (_(element).isUndefined()) {
30
+ throw new ReferenceError('No DOM element passed to judge.Watcher constructor');
31
+ }
32
+ if (element.getAttribute('data-validate') === null) {
33
+ throw new ReferenceError('DOM element does not have data-validate attribute – please use Judge::FormBuilder');
34
+ }
46
35
 
47
- // Watcher instance properties.
48
- this.element = element;
49
- this.validators = JSON.parse(this.element.getAttribute('data-validate'));
50
- };
36
+ // Watcher instance properties.
37
+ this.element = element;
38
+ this.validators = JSON.parse(this.element.getAttribute('data-validate'));
39
+ };
51
40
 
52
- // The `validate` method returns an object which describes the current validity of the
53
- // value of the watched element.
54
- judge.Watcher.prototype.validate = function() {
55
- var allMessages = [];
56
- _(this.validators).each(function(v) {
57
- if (this.element.value.length || v.options.allow_blank !== true) {
58
- var messages = this.validates()[v.kind](this.element.value, v.options, v.messages);
59
- if (messages.length) {
60
- allMessages.push(messages);
41
+ // The `validate` method returns an object which describes the current validity of the
42
+ // value of the watched element.
43
+ judge.Watcher.prototype.validate = function(callback) {
44
+ var allMessages = [], isValid;
45
+ _(this.validators).each(function(v) {
46
+ if (this.element.value.length || v.options.allow_blank !== true) {
47
+ var messages = this.validates()[v.kind](this.element.value, v.options, v.messages);
48
+ if (messages.length) {
49
+ allMessages.push(messages);
50
+ }
61
51
  }
52
+ }, this);
53
+ allMessages = _(allMessages).flatten();
54
+ isValid = (allMessages.length < 1);
55
+ if (_.isFunction(callback)) {
56
+ callback(isValid, allMessages, this.element);
62
57
  }
63
- }, this);
64
- allMessages = _(allMessages).flatten();
65
- return {
66
- valid: (allMessages.length < 1),
67
- messages: allMessages,
68
- element: this.element
58
+ return {
59
+ valid: isValid,
60
+ messages: allMessages,
61
+ element: this.element
62
+ };
69
63
  };
70
- };
71
64
 
72
- // Ported ActiveModel validators.
73
- // See <http://api.rubyonrails.org/classes/ActiveModel/Validations.html> for the originals.
74
- judge.eachValidators = (function() {
75
- return {
76
- // ActiveModel::Validations::PresenceValidator
77
- presence: function(value, options, messages) {
78
- return (value.length) ? [] : [messages.blank];
79
- },
80
-
81
- // ActiveModel::Validations::LengthValidator
82
- length: function(value, options, messages) {
83
- var msgs = [],
84
- types = {
85
- minimum: { operator: '<', message: 'too_short' },
86
- maximum: { operator: '>', message: 'too_long' },
87
- is: { operator: '!=', message: 'wrong_length' }
88
- };
89
- _(types).each(function(properties, type) {
90
- var invalid = judge.utils.operate(value.length, properties.operator, options[type]);
91
- if (options.hasOwnProperty(type) && invalid) {
92
- msgs.push(messages[properties.message]);
93
- }
94
- });
95
- return msgs;
96
- },
97
-
98
- // ActiveModel::Validations::ExclusionValidator
99
- exclusion: function(value, options, messages) {
100
- var stringIn = _(options['in']).map(function(o) { return o.toString(); });
101
- return (_(stringIn).include(value)) ? [messages.exclusion] : [];
102
- },
103
-
104
- // ActiveModel::Validations::InclusionValidator
105
- inclusion: function(value, options, messages) {
106
- var stringIn = _(options['in']).map(function(o) { return o.toString(); });
107
- return (!_(stringIn).include(value)) ? [messages.inclusion] : [];
108
- },
109
-
110
- // ActiveModel::Validations::NumericalityValidator
111
- numericality: function(value, options, messages) {
112
- var operators = {
113
- greater_than: '>',
114
- greater_than_or_equal_to: '>=',
115
- equal_to: '==',
116
- less_than: '<',
117
- less_than_or_equal_to: '<='
118
- },
119
- msgs = [],
120
- parsedValue = parseFloat(value, 10);
65
+ // Ported ActiveModel validators.
66
+ // See <http://api.rubyonrails.org/classes/ActiveModel/Validations.html> for the originals.
67
+ judge.eachValidators = (function() {
68
+ return {
69
+ // ActiveModel::Validations::PresenceValidator
70
+ presence: function(value, options, messages) {
71
+ return (value.length) ? [] : [messages.blank];
72
+ },
73
+
74
+ // ActiveModel::Validations::LengthValidator
75
+ length: function(value, options, messages) {
76
+ var msgs = [],
77
+ types = {
78
+ minimum: { operator: '<', message: 'too_short' },
79
+ maximum: { operator: '>', message: 'too_long' },
80
+ is: { operator: '!=', message: 'wrong_length' }
81
+ };
82
+ _(types).each(function(properties, type) {
83
+ var invalid = operate(value.length, properties.operator, options[type]);
84
+ if (_(options).has(type) && invalid) {
85
+ msgs.push(messages[properties.message]);
86
+ }
87
+ });
88
+ return msgs;
89
+ },
90
+
91
+ // ActiveModel::Validations::ExclusionValidator
92
+ exclusion: function(value, options, messages) {
93
+ var stringIn = _(options['in']).map(function(o) { return o.toString(); });
94
+ return (_(stringIn).include(value)) ? [messages.exclusion] : [];
95
+ },
96
+
97
+ // ActiveModel::Validations::InclusionValidator
98
+ inclusion: function(value, options, messages) {
99
+ var stringIn = _(options['in']).map(function(o) { return o.toString(); });
100
+ return (!_(stringIn).include(value)) ? [messages.inclusion] : [];
101
+ },
102
+
103
+ // ActiveModel::Validations::NumericalityValidator
104
+ numericality: function(value, options, messages) {
105
+ var operators = {
106
+ greater_than: '>',
107
+ greater_than_or_equal_to: '>=',
108
+ equal_to: '==',
109
+ less_than: '<',
110
+ less_than_or_equal_to: '<='
111
+ },
112
+ msgs = [],
113
+ parsedValue = parseFloat(value, 10);
121
114
 
122
- if (isNaN(Number(value))) {
123
- msgs.push(messages.not_a_number);
124
- } else {
125
- if (options.odd && judge.utils.isEven(parsedValue)) {
126
- msgs.push(messages.odd);
127
- }
128
- if (options.even && judge.utils.isOdd(parsedValue)) {
129
- msgs.push(messages.even);
130
- }
131
- if (options.only_integer && !judge.utils.isInt(parsedValue)) {
132
- msgs.push(messages.not_an_integer);
115
+ if (isNaN(Number(value))) {
116
+ msgs.push(messages.not_a_number);
117
+ } else {
118
+ if (options.odd && isEven(parsedValue)) {
119
+ msgs.push(messages.odd);
120
+ }
121
+ if (options.even && isOdd(parsedValue)) {
122
+ msgs.push(messages.even);
123
+ }
124
+ if (options.only_integer && !isInt(parsedValue)) {
125
+ msgs.push(messages.not_an_integer);
126
+ }
127
+ _(operators).each(function(operator, key) {
128
+ var valid = operate(parsedValue, operators[key], parseFloat(options[key], 10));
129
+ if (_(options).has(key) && !valid) {
130
+ msgs.push(messages[key]);
131
+ }
132
+ });
133
133
  }
134
- _(operators).each(function(operator, key) {
135
- var valid = judge.utils.operate(parsedValue, operators[key], parseFloat(options[key], 10));
136
- if (options.hasOwnProperty(key) && !valid) {
137
- msgs.push(messages[key]);
134
+ return msgs;
135
+ },
136
+
137
+ // ActiveModel::Validations::FormatValidator
138
+ format: function(value, options, messages) {
139
+ var msgs = [];
140
+ if (_(options).has('with')) {
141
+ var withReg = convertRegExp(options['with']);
142
+ if (!withReg.test(value)) {
143
+ msgs.push(messages.invalid);
138
144
  }
139
- });
140
- }
141
- return msgs;
142
- },
143
-
144
- // ActiveModel::Validations::FormatValidator
145
- format: function(value, options, messages) {
146
- var msgs = [];
147
- if (options.hasOwnProperty('with')) {
148
- var withReg = judge.utils.convertRegExp(options['with']);
149
- if (!withReg.test(value)) {
150
- msgs.push(messages.invalid);
151
145
  }
152
- }
153
- if (options.hasOwnProperty('without')) {
154
- var withoutReg = judge.utils.convertRegExp(options.without);
155
- if (withoutReg.test(value)) {
156
- msgs.push(messages.invalid);
146
+ if (_(options).has('without')) {
147
+ var withoutReg = convertRegExp(options.without);
148
+ if (withoutReg.test(value)) {
149
+ msgs.push(messages.invalid);
150
+ }
157
151
  }
152
+ return msgs;
153
+ },
154
+
155
+ // ActiveModel::Validations::AcceptanceValidator
156
+ acceptance: function(value, options, messages) {
157
+ return (this._element.checked === true) ? [] : [messages.accepted];
158
+ },
159
+
160
+ // ActiveModel::Validations::ConfirmationValidator
161
+ confirmation: function(value, options, messages) {
162
+ var id = this._element.getAttribute('id'),
163
+ confId = id + '_confirmation',
164
+ confElem = document.getElementById(confId);
165
+ return (value === confElem.value) ? [] : [messages.confirmation];
158
166
  }
159
- return msgs;
160
- },
161
-
162
- // ActiveModel::Validations::AcceptanceValidator
163
- acceptance: function(value, options, messages) {
164
- return (this._element.checked === true) ? [] : [messages.accepted];
165
- },
166
-
167
- // ActiveModel::Validations::ConfirmationValidator
168
- confirmation: function(value, options, messages) {
169
- var id = this._element.getAttribute('id'),
170
- confId = id + '_confirmation',
171
- confElem = document.getElementById(confId);
172
- return (value === confElem.value) ? [] : [messages.confirmation];
173
- }
174
- };
167
+ };
175
168
 
176
- }());
169
+ })();
177
170
 
178
- // This object should contain any judge validation methods
179
- // that correspond to custom validators used in the model.
180
- judge.customValidators = {};
171
+ // This object should contain any judge validation methods
172
+ // that correspond to custom validators used in the model.
173
+ judge.customValidators = {};
181
174
 
182
- // Return all validation methods, including those found in judge.customValidators.
183
- // If you name a custom validation method the same as a default one, for example
184
- // `judge.customValidators.presence = function() {};`
185
- // then the custom method _will overwrite_ the default one, so be careful!
186
- judge.Watcher.prototype.validates = function() {
187
- return _.extend({_element: this.element}, judge.eachValidators, judge.customValidators);
188
- };
175
+ // Return all validation methods, including those found in judge.customValidators.
176
+ // If you name a custom validation method the same as a default one, for example
177
+ // `judge.customValidators.presence = function() {};`
178
+ // then the custom method _will overwrite_ the default one, so be careful!
179
+ judge.Watcher.prototype.validates = function() {
180
+ return _.extend({_element: this.element}, judge.eachValidators, judge.customValidators);
181
+ };
189
182
 
190
- // The judge store is now open :)
191
- judge.store = (function() {
192
- var store = {};
183
+ // Validate all given element(s) without storing. Watcher creation is handled for you,
184
+ // but the created Watchers will not be returned, so it's less flexible.
185
+ judge.validate = function(elements, callback) {
186
+ var results = [];
187
+ elements = isCollection(elements) ? elements : [elements];
188
+ _(elements).each(function(element) {
189
+ var j = new judge.Watcher(element);
190
+ results.push(j.validate(callback));
191
+ });
192
+ return results;
193
+ };
193
194
 
194
- return {
195
+ // The judge store is now open :)
196
+ judge.store = (function() {
197
+ var store = {};
195
198
 
196
- // Stores watcher(s) for element(s) against a user defined key.
197
- save: function(key, element) {
198
- var elements = judge.utils.isCollection(element) ? element : [element];
199
- _(elements).each(function(element) {
200
- if (!store.hasOwnProperty(key)) { store[key] = []; }
201
- var watchedInstance = new judge.Watcher(element),
202
- currentStored = _(store[key]).pluck('element');
203
- if (!_(currentStored).include(element)) {
204
- store[key].push(watchedInstance);
205
- }
206
- });
207
- return store;
208
- },
199
+ return {
209
200
 
210
- // Removes stored watcher(s).
211
- remove: function(key, element) {
212
- if (!store.hasOwnProperty(key)) {
213
- return null;
214
- }
215
- var elements = judge.utils.isCollection(element) ? element : [element];
216
- store[key] = _(store[key]).reject(function(j) { return _(elements).include(j.element); });
217
- if (store[key].length === 0) {
218
- delete store[key];
219
- }
220
- return store[key];
221
- },
201
+ // Stores watcher(s) for element(s) against a user defined key.
202
+ save: function(key, element) {
203
+ var elements = isCollection(element) ? element : [element];
204
+ _(elements).each(function(element) {
205
+ if (!_(store).has(key)) { store[key] = []; }
206
+ var watchedInstance = new judge.Watcher(element),
207
+ currentStored = _(store[key]).pluck('element');
208
+ if (!_(currentStored).include(element)) {
209
+ store[key].push(watchedInstance);
210
+ }
211
+ });
212
+ return store;
213
+ },
222
214
 
223
- // Returns the entire store object, or an array of watchers stored against
224
- // the given key.
225
- get: function(key) {
226
- if (_(key).isUndefined()) { return store; }
227
- return store.hasOwnProperty(key) ? store[key] : null;
228
- },
215
+ // Removes stored watcher(s).
216
+ remove: function(key, element) {
217
+ if (!_(store).has(key)) { return null; }
218
+ var elements = isCollection(element) ? element : [element];
219
+ store[key] = _(store[key]).reject(function(j) { return _(elements).include(j.element); });
220
+ if (store[key].length === 0) {
221
+ delete store[key];
222
+ }
223
+ return store[key];
224
+ },
229
225
 
230
- // Returns the entire store object with watchers converted to elements,
231
- // or all DOM elements stored within all watchers stored against the given key.
232
- getDOM: function(key) {
233
- if (_(key).isUndefined()) {
234
- var convertedStore = {};
235
- _(store).each(function(array, key) {
236
- convertedStore[key] = _(array).pluck('element');
237
- });
238
- return convertedStore;
239
- }
240
- return store.hasOwnProperty(key) ? _(store[key]).pluck('element') : null;
241
- },
226
+ // Returns the entire store object, or an array of watchers stored against
227
+ // the given key.
228
+ get: function(key) {
229
+ if (_(key).isUndefined()) { return store; }
230
+ return _(store).has(key) ? store[key] : null;
231
+ },
242
232
 
243
- // Shortcut for `judge.validate(judge.store.getDOM(key));`.
244
- // Returns null if no stored elements are found for the given key.
245
- validate: function(key) {
246
- var elements = judge.store.getDOM(key);
247
- return (key && !_(elements).isNull()) ? judge.validate(elements) : null;
248
- },
233
+ // Returns the entire store object with watchers converted to elements,
234
+ // or all DOM elements stored within all watchers stored against the given key.
235
+ getDOM: function(key) {
236
+ if (_(key).isUndefined()) {
237
+ var convertedStore = {};
238
+ _(store).each(function(array, key) {
239
+ convertedStore[key] = _(array).pluck('element');
240
+ });
241
+ return convertedStore;
242
+ }
243
+ return _(store).has(key) ? _(store[key]).pluck('element') : null;
244
+ },
249
245
 
250
- // Wipes the entire store object, or wipes all watchers stored against
251
- // the given key.
252
- clear: function(key) {
253
- if (_(key).isUndefined()) {
254
- store = {};
255
- } else {
256
- if (!store.hasOwnProperty(key)) { return null; }
257
- delete store[key];
258
- }
259
- return store;
260
- }
246
+ // Shortcut for `judge.validate(judge.store.getDOM(key));`.
247
+ // Returns null if no stored elements are found for the given key.
248
+ validate: function(key) {
249
+ var elements = judge.store.getDOM(key);
250
+ return (key && !_(elements).isNull()) ? judge.validate(elements) : null;
251
+ },
261
252
 
262
- };
263
- }());
253
+ // Wipes the entire store object, or wipes all watchers stored against
254
+ // the given key.
255
+ clear: function(key) {
256
+ if (_(key).isUndefined()) {
257
+ store = {};
258
+ } else {
259
+ if (!_(store).has(key)) { return null; }
260
+ delete store[key];
261
+ }
262
+ return store;
263
+ }
264
264
 
265
- // Validate all given element(s) without storing. Watcher creation is handled for you,
266
- // but the created Watchers will not be returned, so it's less flexible.
267
- judge.validate = function(elements) {
268
- var results = [];
269
- elements = judge.utils.isCollection(elements) ? elements : [elements];
270
- _(elements).each(function(element) {
271
- var j = new judge.Watcher(element);
272
- results.push(j.validate());
273
- });
274
- return results;
275
- };
265
+ };
266
+ })();
276
267
 
277
- // The obligatory utilities.
278
- judge.utils = {
268
+ // Trying to be a bit more descriptive than the basic error types allow.
269
+ var DependencyError = function(message) {
270
+ this.name = 'DependencyError';
271
+ this.message = message;
272
+ };
273
+ DependencyError.prototype = new Error();
274
+ DependencyError.prototype.constructor = DependencyError;
279
275
 
280
276
  // A way of checking isArray, but including weird object types that are returned from collection queries.
281
- isCollection: function(object) {
282
- var type = judge.utils.getObjectString(object),
277
+ var isCollection = function(object) {
278
+ var type = objectString(object),
283
279
  types = [
284
280
  'Array',
285
281
  'NodeList',
@@ -289,25 +285,26 @@ judge.utils = {
289
285
  'HTMLAllCollection'
290
286
  ];
291
287
  return _(types).include(type);
292
- },
288
+ };
293
289
 
294
290
  // Returns the object type as represented in `Object.prototype.toString`.
295
- getObjectString: function(object) {
291
+ var objectString = function(object) {
296
292
  var string = Object.prototype.toString.call(object);
297
293
  return string.replace(/\[|\]/g, '').split(' ')[1];
298
- },
294
+ };
299
295
 
300
296
  // OMG! How can you use eval, you monster! It's totally evil!
301
297
  // (It's used here for stuff like `(3, '<', 4) => '3 < 4' => true`.)
302
- operate: function(input, operator, validInput) {
298
+ var operate = function(input, operator, validInput) {
303
299
  return eval(input+' '+operator+' '+validInput);
304
- },
300
+ };
305
301
 
306
302
  // Some nifty numerical helpers.
307
- isInt: function(value) { return value === +value && value === (value|0); },
308
- isFloat: function(value) { return value === +value && value !== (value|0); },
309
- isEven: function(value) { return (value % 2 === 0) ? true : false; },
310
- isOdd: function(value) { return !judge.utils.isEven(value); },
303
+ var
304
+ isInt = function(value) { return value === +value && value === (value|0); },
305
+ isFloat = function(value) { return value === +value && value !== (value|0); },
306
+ isEven = function(value) { return (value % 2 === 0) ? true : false; },
307
+ isOdd = function(value) { return !isEven(value); };
311
308
 
312
309
  // Converts a Ruby regular expression, given as a string, into JavaScript.
313
310
  // This is rudimentary at best, as there are many, many differences between Ruby
@@ -316,14 +313,18 @@ judge.utils = {
316
313
  // on the client-side too&hellip; you are going to be pretty disappointed.
317
314
  // If you know of a better way (or an existing library) to convert regular expressions
318
315
  // from Ruby to JavaScript, I would really love to hear from you.
319
- convertRegExp: function(string) {
316
+ var convertRegExp = function(string) {
320
317
  var parts = string.slice(1, -1).split(':'),
321
318
  flags = parts.shift().replace('?', ''),
322
319
  source = parts.join(':').replace(/\\\\/g, '\\');
323
- return new RegExp(source, judge.utils.convertFlags(flags));
324
- },
325
- convertFlags: function(string) {
320
+ return new RegExp(source, convertFlags(flags));
321
+ };
322
+ var convertFlags = function(string) {
326
323
  var on = string.split('-')[0];
327
324
  return (/m/.test(on)) ? 'm' : '';
328
- }
329
- };
325
+ };
326
+
327
+ // Make judge object available.
328
+ root.judge = judge;
329
+
330
+ })(window);