judge 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);