judge 1.1.0 → 1.2.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/.travis.yml +5 -1
- data/README.md +11 -4
- data/judge.gemspec +8 -9
- data/lib/generators/judge/templates/judge.js +142 -196
- data/lib/generators/judge/templates/underscore.js +138 -101
- data/lib/judge/version.rb +1 -1
- data/spec/javascripts/JudgeSpec.js +6 -5
- data/spec/javascripts/helpers/json2.js +46 -41
- data/spec/javascripts/helpers/underscore.js +305 -130
- data/spec/javascripts/support/jasmine_runner.rb +4 -15
- metadata +22 -23
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
judge
|
2
2
|
=====
|
3
3
|
|
4
|
+
[](http://travis-ci.org/joecorcoran/judge)
|
5
|
+
|
4
6
|
Client-side form validation in Rails 3.
|
5
7
|
|
6
8
|
Usage
|
@@ -8,12 +10,17 @@ Usage
|
|
8
10
|
|
9
11
|
See http://judge.joecorcoran.co.uk for documentation.
|
10
12
|
|
13
|
+
Extensions
|
14
|
+
----------
|
15
|
+
|
16
|
+
Use Judge with your favourite form building tool.
|
17
|
+
|
18
|
+
* http://github.com/joecorcoran/judge-formtastic
|
19
|
+
* http://github.com/joecorcoran/judge-simple_form
|
20
|
+
|
11
21
|
License
|
12
22
|
-------
|
13
23
|
|
14
24
|
Released under an MIT license (see LICENSE.txt).
|
15
25
|
|
16
|
-
http://
|
17
|
-
http://blog.joecorcoran.co.uk
|
18
|
-
|
19
|
-
|
26
|
+
http://blog.joecorcoran.co.uk
|
data/judge.gemspec
CHANGED
@@ -6,19 +6,18 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "judge"
|
7
7
|
s.version = Judge::VERSION
|
8
8
|
s.homepage = "http://github.com/joecorcoran/judge"
|
9
|
-
s.
|
10
|
-
s.
|
11
|
-
s.description = %Q{Validate forms on the client side, cleanly}
|
9
|
+
s.summary = "Simple client side ActiveModel::Validators"
|
10
|
+
s.description = "Validate Rails 3 forms on the client side, cleanly"
|
12
11
|
s.email = "joe@tribesports.com"
|
13
12
|
s.authors = ["Joe Corcoran"]
|
14
13
|
|
15
14
|
s.files = `git ls-files`.split("\n")
|
16
|
-
s.test_files = `git ls-files --
|
15
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
17
16
|
s.require_paths = ["lib"]
|
18
17
|
|
19
|
-
s.add_development_dependency "jasmine", "~> 1.
|
20
|
-
s.add_development_dependency "rails", "~> 3.
|
21
|
-
s.add_development_dependency "rspec", "~> 2.8
|
22
|
-
s.add_development_dependency "sqlite3-ruby", "~> 1.3.
|
23
|
-
s.add_development_dependency "
|
18
|
+
s.add_development_dependency "jasmine", "~> 1.1.2"
|
19
|
+
s.add_development_dependency "rails", "~> 3.2"
|
20
|
+
s.add_development_dependency "rspec", "~> 2.8"
|
21
|
+
s.add_development_dependency "sqlite3-ruby", "~> 1.3.3"
|
22
|
+
s.add_development_dependency "factory_girl", "~> 2.6"
|
24
23
|
end
|
@@ -1,14 +1,17 @@
|
|
1
|
+
// Judge 1.2.0
|
2
|
+
// (c) 2011–2012 Joe Corcoran
|
3
|
+
// http://raw.github.com/joecorcoran/judge/master/LICENSE.txt
|
1
4
|
// This is the JavaScript part of Judge, a client-side validation gem for Rails 3.
|
2
5
|
// You can find a guide and some more traditional API documentation at <http://joecorcoran.github.com/judge/>.
|
3
|
-
// Hopefully
|
4
|
-
|
5
|
-
/* http://raw.github.com/joecorcoran/judge/master/LICENSE.txt */
|
6
|
+
// Hopefully the comments here will help you understand what's happening under the hood.
|
6
7
|
|
7
8
|
/*jshint curly: true, evil: true, newcap: true, noarg: true, strict: false */
|
8
9
|
/*global _: false, JSON: false */
|
9
10
|
|
10
11
|
// The judge namespace.
|
11
|
-
var judge =
|
12
|
+
var judge = {};
|
13
|
+
|
14
|
+
judge.VERSION = '1.2.0';
|
12
15
|
|
13
16
|
// A judge.Watcher is a DOM element wrapper that judge uses to store validation info and instance methods.
|
14
17
|
judge.Watcher = function (element) {
|
@@ -34,7 +37,6 @@ judge.Watcher = function (element) {
|
|
34
37
|
message: '[judge][constructor] No DOM element passed to constructor'
|
35
38
|
};
|
36
39
|
}
|
37
|
-
|
38
40
|
if (element.getAttribute('data-validate') === null) {
|
39
41
|
throw {
|
40
42
|
name: 'ReferenceError',
|
@@ -42,199 +44,149 @@ judge.Watcher = function (element) {
|
|
42
44
|
};
|
43
45
|
}
|
44
46
|
|
45
|
-
// Convenient access to this Watcher.
|
46
|
-
var watcher = this;
|
47
|
-
|
48
47
|
// Watcher instance properties.
|
49
48
|
this.element = element;
|
50
49
|
this.validators = JSON.parse(this.element.getAttribute('data-validate'));
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
if (watcher.element.value.length || options.allow_blank !== true) {
|
63
|
-
var result = watcher.validates()[validator.kind](options, msgs);
|
64
|
-
if (!result.valid && result.hasOwnProperty('messages')) {
|
65
|
-
validity = false;
|
66
|
-
messages.push(result.messages);
|
67
|
-
}
|
50
|
+
};
|
51
|
+
|
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);
|
68
61
|
}
|
69
|
-
}
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
62
|
+
}
|
63
|
+
}, this);
|
64
|
+
allMessages = _(allMessages).flatten();
|
65
|
+
return {
|
66
|
+
valid: (allMessages.length < 1),
|
67
|
+
messages: allMessages,
|
68
|
+
element: this.element
|
75
69
|
};
|
76
|
-
|
77
70
|
};
|
78
71
|
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
}
|
126
|
-
|
127
|
-
|
128
|
-
// Inclusion validator ported as closely as possible
|
129
|
-
// from [ActiveModel::Validations::InclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/InclusionValidator.html).
|
130
|
-
inclusion: function(options, messages) {
|
131
|
-
var stringIn = _(options['in']).map(function(o) { return o.toString(); });
|
132
|
-
if (!_(stringIn).include(watcher.element.value)) {
|
133
|
-
return {
|
134
|
-
valid:false,
|
135
|
-
messages:[messages.inclusion]
|
136
|
-
};
|
137
|
-
} else {
|
138
|
-
return { valid:true };
|
139
|
-
}
|
140
|
-
},
|
141
|
-
|
142
|
-
// Numericality validator ported as closely as possible
|
143
|
-
// from [ActiveModel::Validations::NumericalityValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/NumericalityValidator.html).
|
144
|
-
numericality: function(options, messages) {
|
145
|
-
var operators = {
|
146
|
-
greater_than: '>',
|
147
|
-
greater_than_or_equal_to: '>=',
|
148
|
-
equal_to: '==',
|
149
|
-
less_than: '<',
|
150
|
-
less_than_or_equal_to: '<='
|
151
|
-
},
|
152
|
-
msgs = [],
|
153
|
-
value = watcher.element.value,
|
154
|
-
parsedValue = parseFloat(value, 10);
|
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);
|
155
121
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
}
|
173
|
-
});
|
174
|
-
}
|
175
|
-
return msgs.length ? { valid:false, messages:msgs } : { valid:true };
|
176
|
-
},
|
177
|
-
|
178
|
-
// Format validator ported as closely as possible
|
179
|
-
// from [ActiveModel::Validations::FormatValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/FormatValidator.html).
|
180
|
-
format: function(options, messages) {
|
181
|
-
var msgs = [],
|
182
|
-
value = watcher.element.value;
|
183
|
-
if (options.hasOwnProperty('with')) {
|
184
|
-
var withReg = judge.utils.convertRegExp(options['with']);
|
185
|
-
if (!withReg.test(value)) {
|
186
|
-
msgs.push(messages.invalid);
|
187
|
-
}
|
188
|
-
}
|
189
|
-
if (options.hasOwnProperty('without')) {
|
190
|
-
var withoutReg = judge.utils.convertRegExp(options.without);
|
191
|
-
if (withoutReg.test(value)) {
|
192
|
-
msgs.push(messages.invalid);
|
193
|
-
}
|
194
|
-
}
|
195
|
-
return msgs.length ? { valid:false, messages:msgs } : { valid:true };
|
196
|
-
},
|
197
|
-
|
198
|
-
// Acceptance validator ported as closely as possible
|
199
|
-
// from [ActiveModel::Validations::AcceptanceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/AcceptanceValidator.html).
|
200
|
-
acceptance: function(options, messages) {
|
201
|
-
if (watcher.element.checked === true) {
|
202
|
-
return { valid:true };
|
203
|
-
} else {
|
204
|
-
return {
|
205
|
-
valid:false,
|
206
|
-
messages:[messages.accepted]
|
207
|
-
};
|
208
|
-
}
|
209
|
-
},
|
210
|
-
|
211
|
-
// Confirmation validator ported as closely as possible
|
212
|
-
// from [ActiveModel::Validations::ConfirmationValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ConfirmationValidator.html).
|
213
|
-
confirmation: function(options, messages) {
|
214
|
-
var id = watcher.element.getAttribute('id'),
|
215
|
-
confId = id + '_confirmation',
|
216
|
-
confElem = document.getElementById(confId);
|
217
|
-
if (watcher.element.value === confElem.value) {
|
218
|
-
return { valid:true };
|
219
|
-
} else {
|
220
|
-
return {
|
221
|
-
valid:false,
|
222
|
-
messages:[messages.confirmation]
|
223
|
-
};
|
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);
|
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]);
|
224
138
|
}
|
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);
|
225
151
|
}
|
226
|
-
}
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
}
|
152
|
+
}
|
153
|
+
if (options.hasOwnProperty('without')) {
|
154
|
+
var withoutReg = judge.utils.convertRegExp(options.without);
|
155
|
+
if (withoutReg.test(value)) {
|
156
|
+
msgs.push(messages.invalid);
|
157
|
+
}
|
158
|
+
}
|
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
|
+
};
|
175
|
+
|
176
|
+
}());
|
233
177
|
|
234
178
|
// This object should contain any judge validation methods
|
235
179
|
// that correspond to custom validators used in the model.
|
236
180
|
judge.customValidators = {};
|
237
181
|
|
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
|
+
};
|
189
|
+
|
238
190
|
// The judge store is now open :)
|
239
191
|
judge.store = (function() {
|
240
192
|
var store = {};
|
@@ -365,19 +317,13 @@ judge.utils = {
|
|
365
317
|
// If you know of a better way (or an existing library) to convert regular expressions
|
366
318
|
// from Ruby to JavaScript, I would really love to hear from you.
|
367
319
|
convertRegExp: function(string) {
|
368
|
-
var
|
369
|
-
|
370
|
-
|
371
|
-
return new RegExp(
|
320
|
+
var parts = string.slice(1, -1).split(':'),
|
321
|
+
flags = parts.shift().replace('?', ''),
|
322
|
+
source = parts.join(':').replace(/\\\\/g, '\\');
|
323
|
+
return new RegExp(source, judge.utils.convertFlags(flags));
|
372
324
|
},
|
373
325
|
convertFlags: function(string) {
|
374
|
-
var
|
375
|
-
|
376
|
-
string = string.replace('?', '');
|
377
|
-
if (off.test(string) || !multi.test(string)) {
|
378
|
-
return '';
|
379
|
-
} else {
|
380
|
-
return 'm';
|
381
|
-
}
|
326
|
+
var on = string.split('-')[0];
|
327
|
+
return (/m/.test(on)) ? 'm' : '';
|
382
328
|
}
|
383
|
-
};
|
329
|
+
};
|