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.
- 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);
|