judge 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/generators/judge/templates/judge.js +257 -256
- data/lib/judge/version.rb +1 -1
- data/spec/javascripts/JudgeSpec.js +211 -375
- metadata +14 -14
@@ -1,4 +1,4 @@
|
|
1
|
-
// Judge 1.
|
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
|
-
|
12
|
-
var judge = {};
|
11
|
+
(function(root) {
|
13
12
|
|
14
|
-
judge
|
13
|
+
// The judge namespace.
|
14
|
+
var judge = {};
|
15
15
|
|
16
|
-
|
17
|
-
judge.Watcher = function (element) {
|
16
|
+
judge.VERSION = '1.3.0';
|
18
17
|
|
19
|
-
//
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
}
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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
|
-
|
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
|
-
//
|
191
|
-
|
192
|
-
|
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
|
-
|
195
|
+
// The judge store is now open :)
|
196
|
+
judge.store = (function() {
|
197
|
+
var store = {};
|
195
198
|
|
196
|
-
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
}
|
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
|
-
|
266
|
-
|
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
|
-
//
|
278
|
-
|
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
|
282
|
-
var type =
|
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
|
-
|
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
|
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
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
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… 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
|
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,
|
324
|
-
}
|
325
|
-
convertFlags
|
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);
|