judge 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +2 -1
  3. data/Gemfile +1 -10
  4. data/Rakefile +9 -44
  5. data/judge.gemspec +17 -164
  6. data/lib/generators/judge/templates/json2.js +46 -42
  7. data/lib/generators/judge/templates/judge.js +42 -41
  8. data/lib/generators/judge/templates/underscore.js +219 -97
  9. data/lib/judge.rb +6 -4
  10. data/lib/judge/form_builder.rb +75 -0
  11. data/lib/judge/{utils.rb → message_collection.rb} +45 -28
  12. data/lib/judge/validator.rb +21 -0
  13. data/lib/judge/validator_collection.rb +28 -0
  14. data/lib/judge/version.rb +3 -0
  15. data/spec/javascripts/JudgeSpec.js +25 -23
  16. data/test/expected_elements.rb +235 -0
  17. data/test/factories.rb +23 -0
  18. data/test/setup.rb +70 -0
  19. data/test/test_form_builder.rb +69 -0
  20. data/test/test_helper.rb +8 -20
  21. data/test/test_message_collection.rb +84 -0
  22. data/test/test_validator.rb +37 -0
  23. data/test/test_validator_collection.rb +19 -0
  24. metadata +46 -137
  25. data/.document +0 -5
  26. data/Gemfile.lock +0 -116
  27. data/VERSION +0 -1
  28. data/docs/docco.css +0 -196
  29. data/docs/judge.html +0 -280
  30. data/lib/judge/form.rb +0 -59
  31. data/test/dummy/Gemfile +0 -3
  32. data/test/dummy/Gemfile.lock +0 -75
  33. data/test/dummy/Rakefile +0 -7
  34. data/test/dummy/app/controllers/application_controller.rb +0 -3
  35. data/test/dummy/app/controllers/foos_controller.rb +0 -11
  36. data/test/dummy/app/helpers/application_helper.rb +0 -2
  37. data/test/dummy/app/models/city.rb +0 -5
  38. data/test/dummy/app/models/continent.rb +0 -4
  39. data/test/dummy/app/models/country.rb +0 -6
  40. data/test/dummy/app/models/fake.rb +0 -2
  41. data/test/dummy/app/models/foo.rb +0 -19
  42. data/test/dummy/app/views/foos/new.html.erb +0 -71
  43. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  44. data/test/dummy/config.ru +0 -4
  45. data/test/dummy/config/application.rb +0 -45
  46. data/test/dummy/config/boot.rb +0 -10
  47. data/test/dummy/config/database.yml +0 -22
  48. data/test/dummy/config/environment.rb +0 -5
  49. data/test/dummy/config/environments/development.rb +0 -26
  50. data/test/dummy/config/environments/production.rb +0 -49
  51. data/test/dummy/config/environments/test.rb +0 -35
  52. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  53. data/test/dummy/config/initializers/inflections.rb +0 -10
  54. data/test/dummy/config/initializers/mime_types.rb +0 -5
  55. data/test/dummy/config/initializers/secret_token.rb +0 -7
  56. data/test/dummy/config/initializers/session_store.rb +0 -8
  57. data/test/dummy/config/locales/en.yml +0 -12
  58. data/test/dummy/config/routes.rb +0 -3
  59. data/test/dummy/db/development.sqlite3 +0 -0
  60. data/test/dummy/db/migrate/20110624115516_create_foos.rb +0 -26
  61. data/test/dummy/db/migrate/20110724201117_create_fake_collections.rb +0 -14
  62. data/test/dummy/db/migrate/20110724201548_rename_fake_collection_to_fake.rb +0 -9
  63. data/test/dummy/db/migrate/20110725082530_create_continent_country_and_city_tables.rb +0 -24
  64. data/test/dummy/db/schema.rb +0 -55
  65. data/test/dummy/db/test.sqlite3 +0 -0
  66. data/test/dummy/log/server.log +0 -0
  67. data/test/dummy/public/404.html +0 -26
  68. data/test/dummy/public/422.html +0 -26
  69. data/test/dummy/public/500.html +0 -26
  70. data/test/dummy/public/favicon.ico +0 -0
  71. data/test/dummy/public/javascripts/application.js +0 -2
  72. data/test/dummy/public/javascripts/controls.js +0 -965
  73. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  74. data/test/dummy/public/javascripts/effects.js +0 -1123
  75. data/test/dummy/public/javascripts/prototype.js +0 -6001
  76. data/test/dummy/public/javascripts/rails.js +0 -175
  77. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  78. data/test/dummy/script/rails +0 -6
  79. data/test/judge_test.rb +0 -186
@@ -2,6 +2,8 @@
2
2
  // You can find a guide and some more traditional API documentation at <http://joecorcoran.github.com/judge/>.
3
3
  // Hopefully this page will help you understand what's happening under the hood.
4
4
 
5
+ /* http://raw.github.com/joecorcoran/judge/master/LICENSE.txt */
6
+
5
7
  /*jshint curly: true, evil: true, newcap: true, noarg: true, strict: false */
6
8
  /*global _: false, JSON: false */
7
9
 
@@ -41,7 +43,7 @@ judge.Watcher = function (element) {
41
43
  }
42
44
 
43
45
  // Convenient access to this Watcher.
44
- var instance = this;
46
+ var watcher = this;
45
47
 
46
48
  // Watcher instance properties.
47
49
  this.element = element;
@@ -50,15 +52,15 @@ judge.Watcher = function (element) {
50
52
  // The `validate` instance method returns the validity of the watched element,
51
53
  // represented as an object containing the element itself and some validity information.
52
54
  this.validate = function() {
53
- instance.errorMessages = [];
54
- var validators = instance.validators,
55
+ watcher.errorMessages = [];
56
+ var validators = watcher.validators,
55
57
  validity = true,
56
58
  messages = [];
57
59
  _(validators).each(function(validator) {
58
60
  var options = validator.options,
59
61
  msgs = validator.messages;
60
- if (instance.element.value.length || options.allow_blank !== true) {
61
- var result = instance.validates()[validator.kind](options, msgs);
62
+ if (watcher.element.value.length || options.allow_blank !== true) {
63
+ var result = watcher.validates()[validator.kind](options, msgs);
62
64
  if (!result.valid && result.hasOwnProperty('messages')) {
63
65
  validity = false;
64
66
  messages.push(result.messages);
@@ -68,7 +70,7 @@ judge.Watcher = function (element) {
68
70
  return {
69
71
  valid: validity,
70
72
  messages: _(messages).flatten(),
71
- element: instance.element
73
+ element: watcher.element
72
74
  };
73
75
  };
74
76
 
@@ -76,14 +78,14 @@ judge.Watcher = function (element) {
76
78
 
77
79
  // Watcher prototype methods.
78
80
  judge.Watcher.prototype.validates = function() {
79
- var instance = this,
81
+ var watcher = this,
80
82
  extendedMethods = judge.customValidators,
81
83
  methods = {
82
84
 
83
85
  // Presence validator ported as closely as possible
84
86
  // from [ActiveModel::Validations::PresenceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/PresenceValidator.html).
85
87
  presence: function(options, messages) {
86
- if (instance.element.value.length) {
88
+ if (watcher.element.value.length) {
87
89
  return { valid:true };
88
90
  } else{
89
91
  return { valid:false, messages:[messages.blank] };
@@ -94,7 +96,7 @@ judge.Watcher.prototype.validates = function() {
94
96
  // from [ActiveModel::Validations::LengthValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/LengthValidator.html).
95
97
  length: function(options, messages) {
96
98
  var msgs = [],
97
- length = instance.element.value.length,
99
+ length = watcher.element.value.length,
98
100
  types = {
99
101
  minimum: { operator: '<', message: 'too_short' },
100
102
  maximum: { operator: '>', message: 'too_long' },
@@ -113,7 +115,7 @@ judge.Watcher.prototype.validates = function() {
113
115
  // from [ActiveModel::Validations::ExclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ExclusionValidator.html).
114
116
  exclusion: function(options, messages) {
115
117
  var stringIn = _(options['in']).map(function(o) { return o.toString(); });
116
- if (_(stringIn).include(instance.element.value)) {
118
+ if (_(stringIn).include(watcher.element.value)) {
117
119
  return {
118
120
  valid:false,
119
121
  messages:[messages.exclusion]
@@ -127,7 +129,7 @@ judge.Watcher.prototype.validates = function() {
127
129
  // from [ActiveModel::Validations::InclusionValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/InclusionValidator.html).
128
130
  inclusion: function(options, messages) {
129
131
  var stringIn = _(options['in']).map(function(o) { return o.toString(); });
130
- if (!_(stringIn).include(instance.element.value)) {
132
+ if (!_(stringIn).include(watcher.element.value)) {
131
133
  return {
132
134
  valid:false,
133
135
  messages:[messages.inclusion]
@@ -148,7 +150,7 @@ judge.Watcher.prototype.validates = function() {
148
150
  less_than_or_equal_to: '<='
149
151
  },
150
152
  msgs = [],
151
- value = instance.element.value,
153
+ value = watcher.element.value,
152
154
  parsedValue = parseFloat(value, 10);
153
155
 
154
156
  if (isNaN(Number(value))) {
@@ -170,14 +172,14 @@ judge.Watcher.prototype.validates = function() {
170
172
  }
171
173
  });
172
174
  }
173
- return msgs.length? { valid:false, messages:msgs } : { valid:true };
175
+ return msgs.length ? { valid:false, messages:msgs } : { valid:true };
174
176
  },
175
177
 
176
178
  // Format validator ported as closely as possible
177
179
  // from [ActiveModel::Validations::FormatValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/FormatValidator.html).
178
180
  format: function(options, messages) {
179
181
  var msgs = [],
180
- value = instance.element.value;
182
+ value = watcher.element.value;
181
183
  if (options.hasOwnProperty('with')) {
182
184
  var withReg = judge.utils.convertRegExp(options['with']);
183
185
  if (!withReg.test(value)) {
@@ -196,7 +198,7 @@ judge.Watcher.prototype.validates = function() {
196
198
  // Acceptance validator ported as closely as possible
197
199
  // from [ActiveModel::Validations::AcceptanceValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/AcceptanceValidator.html).
198
200
  acceptance: function(options, messages) {
199
- if (instance.element.checked === true) {
201
+ if (watcher.element.checked === true) {
200
202
  return { valid:true };
201
203
  } else {
202
204
  return {
@@ -209,10 +211,10 @@ judge.Watcher.prototype.validates = function() {
209
211
  // Confirmation validator ported as closely as possible
210
212
  // from [ActiveModel::Validations::ConfirmationValidator](http://api.rubyonrails.org/classes/ActiveModel/Validations/ConfirmationValidator.html).
211
213
  confirmation: function(options, messages) {
212
- var id = instance.element.getAttribute('id'),
214
+ var id = watcher.element.getAttribute('id'),
213
215
  confId = id + '_confirmation',
214
216
  confElem = document.getElementById(confId);
215
- if (instance.element.value === confElem.value) {
217
+ if (watcher.element.value === confElem.value) {
216
218
  return { valid:true };
217
219
  } else {
218
220
  return {
@@ -239,7 +241,7 @@ judge.store = (function() {
239
241
 
240
242
  return {
241
243
 
242
- // Stores a Watcher for an element against a user defined key.
244
+ // Stores watcher(s) for element(s) against a user defined key.
243
245
  save: function(key, element) {
244
246
  var elements = judge.utils.isCollection(element) ? element : [element];
245
247
  _(elements).each(function(element) {
@@ -253,25 +255,28 @@ judge.store = (function() {
253
255
  return store;
254
256
  },
255
257
 
256
- // Removes an individual stored Watcher.
258
+ // Removes stored watcher(s).
257
259
  remove: function(key, element) {
258
260
  if (!store.hasOwnProperty(key)) {
259
261
  return null;
260
262
  }
261
263
  var elements = judge.utils.isCollection(element) ? element : [element];
262
264
  store[key] = _(store[key]).reject(function(j) { return _(elements).include(j.element); });
263
- return store;
265
+ if (store[key].length === 0) {
266
+ delete store[key];
267
+ }
268
+ return store[key];
264
269
  },
265
270
 
266
- // Returns the entire store object, or an array of Watchers stored against
271
+ // Returns the entire store object, or an array of watchers stored against
267
272
  // the given key.
268
273
  get: function(key) {
269
274
  if (_(key).isUndefined()) { return store; }
270
275
  return store.hasOwnProperty(key) ? store[key] : null;
271
276
  },
272
277
 
273
- // Returns the entire store object with Watchers converted to elements,
274
- // or all DOM elements stored within all Watchers stored against the given key.
278
+ // Returns the entire store object with watchers converted to elements,
279
+ // or all DOM elements stored within all watchers stored against the given key.
275
280
  getDOM: function(key) {
276
281
  if (_(key).isUndefined()) {
277
282
  var convertedStore = {};
@@ -283,14 +288,21 @@ judge.store = (function() {
283
288
  return store.hasOwnProperty(key) ? _(store[key]).pluck('element') : null;
284
289
  },
285
290
 
286
- // Wipes the entire store object, or wipes all Watchers stored against
291
+ // Shortcut for `judge.validate(judge.store.getDOM(key));`.
292
+ // Returns null if no stored elements are found for the given key.
293
+ validate: function(key) {
294
+ var elements = judge.store.getDOM(key);
295
+ return (key && !_(elements).isNull()) ? judge.validate(elements) : null;
296
+ },
297
+
298
+ // Wipes the entire store object, or wipes all watchers stored against
287
299
  // the given key.
288
300
  clear: function(key) {
289
301
  if (_(key).isUndefined()) {
290
302
  store = {};
291
303
  } else {
292
304
  if (!store.hasOwnProperty(key)) { return null; }
293
- store[key] = [];
305
+ delete store[key];
294
306
  }
295
307
  return store;
296
308
  }
@@ -312,17 +324,6 @@ judge.validate = function(elements) {
312
324
 
313
325
  // The obligatory utilities.
314
326
  judge.utils = {
315
-
316
- // Determines whether an object is a DOM element of the types that judge can work with.
317
- isValidatable: function(object) {
318
- var type = judge.utils.getObjectString(object),
319
- types = [
320
- 'HTMLInputElement',
321
- 'HTMLTextAreaElement',
322
- 'HTMLSelectElement'
323
- ];
324
- return _(types).include(type);
325
- },
326
327
 
327
328
  // A way of checking isArray, but including weird object types that are returned from collection queries.
328
329
  isCollection: function(object) {
@@ -356,11 +357,13 @@ judge.utils = {
356
357
  isEven: function(value) { return (value % 2 === 0) ? true : false; },
357
358
  isOdd: function(value) { return !judge.utils.isEven(value); },
358
359
 
359
- // Converts a ruby flag-first formatted regular expression, given as a string, into JavaScript.
360
+ // Converts a Ruby regular expression, given as a string, into JavaScript.
360
361
  // This is rudimentary at best, as there are many, many differences between Ruby
361
362
  // and JavaScript when it comes to regexp-fu. Basically, if you validate your records
362
363
  // using complex regular expressions AND you hope that this will "just work"
363
364
  // on the client-side too&hellip; you are going to be pretty disappointed.
365
+ // If you know of a better way (or an existing library) to convert regular expressions
366
+ // from Ruby to JavaScript, I would really love to hear from you.
364
367
  convertRegExp: function(string) {
365
368
  var p = string.slice(1, -1).split(':'),
366
369
  o = p.shift(),
@@ -368,8 +371,8 @@ judge.utils = {
368
371
  return new RegExp(r, judge.utils.convertFlags(o));
369
372
  },
370
373
  convertFlags: function(string) {
371
- var off = new RegExp('-'),
372
- multi = new RegExp('m');
374
+ var off = new RegExp('-'),
375
+ multi = new RegExp('m');
373
376
  string = string.replace('?', '');
374
377
  if (off.test(string) || !multi.test(string)) {
375
378
  return '';
@@ -378,5 +381,3 @@ judge.utils = {
378
381
  }
379
382
  }
380
383
  };
381
-
382
-
@@ -1,4 +1,4 @@
1
- // Underscore.js 1.1.7
1
+ // Underscore.js 1.2.1
2
2
  // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
3
3
  // Underscore is freely distributable under the MIT license.
4
4
  // Portions of Underscore are inspired or borrowed from Prototype,
@@ -48,19 +48,26 @@
48
48
  // Create a safe reference to the Underscore object for use below.
49
49
  var _ = function(obj) { return new wrapper(obj); };
50
50
 
51
- // Export the Underscore object for **CommonJS**, with backwards-compatibility
52
- // for the old `require()` API. If we're not in CommonJS, add `_` to the
53
- // global object.
54
- if (typeof module !== 'undefined' && module.exports) {
55
- module.exports = _;
56
- _._ = _;
51
+ // Export the Underscore object for **Node.js** and **"CommonJS"**, with
52
+ // backwards-compatibility for the old `require()` API. If we're not in
53
+ // CommonJS, add `_` to the global object.
54
+ if (typeof exports !== 'undefined') {
55
+ if (typeof module !== 'undefined' && module.exports) {
56
+ exports = module.exports = _;
57
+ }
58
+ exports._ = _;
59
+ } else if (typeof define === 'function' && define.amd) {
60
+ // Register as a named module with AMD.
61
+ define('underscore', function() {
62
+ return _;
63
+ });
57
64
  } else {
58
65
  // Exported as a string, for Closure Compiler "advanced" mode.
59
66
  root['_'] = _;
60
67
  }
61
68
 
62
69
  // Current version.
63
- _.VERSION = '1.1.7';
70
+ _.VERSION = '1.2.1';
64
71
 
65
72
  // Collection Functions
66
73
  // --------------------
@@ -198,8 +205,8 @@
198
205
  var found = false;
199
206
  if (obj == null) return found;
200
207
  if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
201
- any(obj, function(value) {
202
- if (found = value === target) return true;
208
+ found = any(obj, function(value) {
209
+ return value === target;
203
210
  });
204
211
  return found;
205
212
  };
@@ -220,6 +227,7 @@
220
227
  // Return the maximum element or (element-based computation).
221
228
  _.max = function(obj, iterator, context) {
222
229
  if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
230
+ if (!iterator && _.isEmpty(obj)) return -Infinity;
223
231
  var result = {computed : -Infinity};
224
232
  each(obj, function(value, index, list) {
225
233
  var computed = iterator ? iterator.call(context, value, index, list) : value;
@@ -231,6 +239,7 @@
231
239
  // Return the minimum element (or element-based computation).
232
240
  _.min = function(obj, iterator, context) {
233
241
  if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
242
+ if (!iterator && _.isEmpty(obj)) return Infinity;
234
243
  var result = {computed : Infinity};
235
244
  each(obj, function(value, index, list) {
236
245
  var computed = iterator ? iterator.call(context, value, index, list) : value;
@@ -239,6 +248,21 @@
239
248
  return result.value;
240
249
  };
241
250
 
251
+ // Shuffle an array.
252
+ _.shuffle = function(obj) {
253
+ var shuffled = [], rand;
254
+ each(obj, function(value, index, list) {
255
+ if (index == 0) {
256
+ shuffled[0] = value;
257
+ } else {
258
+ rand = Math.floor(Math.random() * (index + 1));
259
+ shuffled[index] = shuffled[rand];
260
+ shuffled[rand] = value;
261
+ }
262
+ });
263
+ return shuffled;
264
+ };
265
+
242
266
  // Sort the object's values by a criterion produced by an iterator.
243
267
  _.sortBy = function(obj, iterator, context) {
244
268
  return _.pluck(_.map(obj, function(value, index, list) {
@@ -252,9 +276,11 @@
252
276
  }), 'value');
253
277
  };
254
278
 
255
- // Groups the object's values by a criterion produced by an iterator
256
- _.groupBy = function(obj, iterator) {
279
+ // Groups the object's values by a criterion. Pass either a string attribute
280
+ // to group by, or a function that returns the criterion.
281
+ _.groupBy = function(obj, val) {
257
282
  var result = {};
283
+ var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
258
284
  each(obj, function(value, index) {
259
285
  var key = iterator(value, index);
260
286
  (result[key] || (result[key] = [])).push(value);
@@ -298,6 +324,20 @@
298
324
  return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
299
325
  };
300
326
 
327
+ // Returns everything but the last entry of the array. Especcialy useful on
328
+ // the arguments object. Passing **n** will return all the values in
329
+ // the array, excluding the last N. The **guard** check allows it to work with
330
+ // `_.map`.
331
+ _.initial = function(array, n, guard) {
332
+ return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
333
+ };
334
+
335
+ // Get the last element of an array. Passing **n** will return the last N
336
+ // values in the array. The **guard** check allows it to work with `_.map`.
337
+ _.last = function(array, n, guard) {
338
+ return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1];
339
+ };
340
+
301
341
  // Returns everything but the first entry of the array. Aliased as `tail`.
302
342
  // Especially useful on the arguments object. Passing an **index** will return
303
343
  // the rest of the values in the array from that index onward. The **guard**
@@ -306,20 +346,15 @@
306
346
  return slice.call(array, (index == null) || guard ? 1 : index);
307
347
  };
308
348
 
309
- // Get the last element of an array.
310
- _.last = function(array) {
311
- return array[array.length - 1];
312
- };
313
-
314
349
  // Trim out all falsy values from an array.
315
350
  _.compact = function(array) {
316
351
  return _.filter(array, function(value){ return !!value; });
317
352
  };
318
353
 
319
354
  // Return a completely flattened version of an array.
320
- _.flatten = function(array) {
355
+ _.flatten = function(array, shallow) {
321
356
  return _.reduce(array, function(memo, value) {
322
- if (_.isArray(value)) return memo.concat(_.flatten(value));
357
+ if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
323
358
  memo[memo.length] = value;
324
359
  return memo;
325
360
  }, []);
@@ -333,17 +368,23 @@
333
368
  // Produce a duplicate-free version of the array. If the array has already
334
369
  // been sorted, you have the option of using a faster algorithm.
335
370
  // Aliased as `unique`.
336
- _.uniq = _.unique = function(array, isSorted) {
337
- return _.reduce(array, function(memo, el, i) {
338
- if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
371
+ _.uniq = _.unique = function(array, isSorted, iterator) {
372
+ var initial = iterator ? _.map(array, iterator) : array;
373
+ var result = [];
374
+ _.reduce(initial, function(memo, el, i) {
375
+ if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
376
+ memo[memo.length] = el;
377
+ result[result.length] = array[i];
378
+ }
339
379
  return memo;
340
380
  }, []);
381
+ return result;
341
382
  };
342
383
 
343
384
  // Produce an array that contains the union: each distinct element from all of
344
385
  // the passed-in arrays.
345
386
  _.union = function() {
346
- return _.uniq(_.flatten(arguments));
387
+ return _.uniq(_.flatten(arguments, true));
347
388
  };
348
389
 
349
390
  // Produce an array that contains every item shared between all the
@@ -391,7 +432,6 @@
391
432
  return -1;
392
433
  };
393
434
 
394
-
395
435
  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
396
436
  _.lastIndexOf = function(array, item) {
397
437
  if (array == null) return -1;
@@ -426,15 +466,25 @@
426
466
  // Function (ahem) Functions
427
467
  // ------------------
428
468
 
469
+ // Reusable constructor function for prototype setting.
470
+ var ctor = function(){};
471
+
429
472
  // Create a function bound to a given object (assigning `this`, and arguments,
430
473
  // optionally). Binding with arguments is also known as `curry`.
431
474
  // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
432
475
  // We check for `func.bind` first, to fail fast when `func` is undefined.
433
- _.bind = function(func, obj) {
476
+ _.bind = function bind(func, context) {
477
+ var bound, args;
434
478
  if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
435
- var args = slice.call(arguments, 2);
436
- return function() {
437
- return func.apply(obj, args.concat(slice.call(arguments)));
479
+ if (!_.isFunction(func)) throw new TypeError;
480
+ args = slice.call(arguments, 2);
481
+ return bound = function() {
482
+ if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
483
+ ctor.prototype = func.prototype;
484
+ var self = new ctor;
485
+ var result = func.apply(self, args.concat(slice.call(arguments)));
486
+ if (Object(result) === result) return result;
487
+ return self;
438
488
  };
439
489
  };
440
490
 
@@ -470,31 +520,43 @@
470
520
  return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
471
521
  };
472
522
 
473
- // Internal function used to implement `_.throttle` and `_.debounce`.
474
- var limit = function(func, wait, debounce) {
475
- var timeout;
523
+ // Returns a function, that, when invoked, will only be triggered at most once
524
+ // during a given window of time.
525
+ _.throttle = function(func, wait) {
526
+ var context, args, timeout, throttling, more;
527
+ var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
476
528
  return function() {
477
- var context = this, args = arguments;
478
- var throttler = function() {
529
+ context = this; args = arguments;
530
+ var later = function() {
479
531
  timeout = null;
480
- func.apply(context, args);
532
+ if (more) func.apply(context, args);
533
+ whenDone();
481
534
  };
482
- if (debounce) clearTimeout(timeout);
483
- if (debounce || !timeout) timeout = setTimeout(throttler, wait);
535
+ if (!timeout) timeout = setTimeout(later, wait);
536
+ if (throttling) {
537
+ more = true;
538
+ } else {
539
+ func.apply(context, args);
540
+ }
541
+ whenDone();
542
+ throttling = true;
484
543
  };
485
544
  };
486
545
 
487
- // Returns a function, that, when invoked, will only be triggered at most once
488
- // during a given window of time.
489
- _.throttle = function(func, wait) {
490
- return limit(func, wait, false);
491
- };
492
-
493
546
  // Returns a function, that, as long as it continues to be invoked, will not
494
547
  // be triggered. The function will be called after it stops being called for
495
548
  // N milliseconds.
496
549
  _.debounce = function(func, wait) {
497
- return limit(func, wait, true);
550
+ var timeout;
551
+ return function() {
552
+ var context = this, args = arguments;
553
+ var later = function() {
554
+ timeout = null;
555
+ func.apply(context, args);
556
+ };
557
+ clearTimeout(timeout);
558
+ timeout = setTimeout(later, wait);
559
+ };
498
560
  };
499
561
 
500
562
  // Returns a function that will be executed at most one time, no matter how
@@ -538,7 +600,6 @@
538
600
  };
539
601
  };
540
602
 
541
-
542
603
  // Object Functions
543
604
  // ----------------
544
605
 
@@ -588,6 +649,7 @@
588
649
 
589
650
  // Create a (shallow-cloned) duplicate of an object.
590
651
  _.clone = function(obj) {
652
+ if (!_.isObject(obj)) return obj;
591
653
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
592
654
  };
593
655
 
@@ -599,47 +661,93 @@
599
661
  return obj;
600
662
  };
601
663
 
602
- // Perform a deep comparison to check if two objects are equal.
603
- _.isEqual = function(a, b) {
604
- // Check object identity.
605
- if (a === b) return true;
606
- // Different types?
607
- var atype = typeof(a), btype = typeof(b);
608
- if (atype != btype) return false;
609
- // Basic equality test (watch out for coercions).
610
- if (a == b) return true;
611
- // One is falsy and the other truthy.
612
- if ((!a && b) || (a && !b)) return false;
664
+ // Internal recursive comparison function.
665
+ function eq(a, b, stack) {
666
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
667
+ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
668
+ if (a === b) return a !== 0 || 1 / a == 1 / b;
669
+ // A strict comparison is necessary because `null == undefined`.
670
+ if ((a == null) || (b == null)) return a === b;
613
671
  // Unwrap any wrapped objects.
614
672
  if (a._chain) a = a._wrapped;
615
673
  if (b._chain) b = b._wrapped;
616
- // One of them implements an isEqual()?
617
- if (a.isEqual) return a.isEqual(b);
618
- if (b.isEqual) return b.isEqual(a);
619
- // Check dates' integer values.
620
- if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
621
- // Both are NaN?
622
- if (_.isNaN(a) && _.isNaN(b)) return false;
623
- // Compare regular expressions.
624
- if (_.isRegExp(a) && _.isRegExp(b))
625
- return a.source === b.source &&
626
- a.global === b.global &&
627
- a.ignoreCase === b.ignoreCase &&
628
- a.multiline === b.multiline;
629
- // If a is not an object by this point, we can't handle it.
630
- if (atype !== 'object') return false;
631
- // Check for different array lengths before comparing contents.
632
- if (a.length && (a.length !== b.length)) return false;
633
- // Nothing else worked, deep compare the contents.
634
- var aKeys = _.keys(a), bKeys = _.keys(b);
635
- // Different object sizes?
636
- if (aKeys.length != bKeys.length) return false;
637
- // Recursive comparison of contents.
638
- for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
639
- return true;
674
+ // Invoke a custom `isEqual` method if one is provided.
675
+ if (_.isFunction(a.isEqual)) return a.isEqual(b);
676
+ if (_.isFunction(b.isEqual)) return b.isEqual(a);
677
+ // Compare object types.
678
+ var typeA = typeof a;
679
+ if (typeA != typeof b) return false;
680
+ // Optimization; ensure that both values are truthy or falsy.
681
+ if (!a != !b) return false;
682
+ // `NaN` values are equal.
683
+ if (_.isNaN(a)) return _.isNaN(b);
684
+ // Compare string objects by value.
685
+ var isStringA = _.isString(a), isStringB = _.isString(b);
686
+ if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b);
687
+ // Compare number objects by value.
688
+ var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b);
689
+ if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b;
690
+ // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0.
691
+ var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b);
692
+ if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b;
693
+ // Compare dates by their millisecond values.
694
+ var isDateA = _.isDate(a), isDateB = _.isDate(b);
695
+ if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime();
696
+ // Compare RegExps by their source patterns and flags.
697
+ var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b);
698
+ if (isRegExpA || isRegExpB) {
699
+ // Ensure commutative equality for RegExps.
700
+ return isRegExpA && isRegExpB &&
701
+ a.source == b.source &&
702
+ a.global == b.global &&
703
+ a.multiline == b.multiline &&
704
+ a.ignoreCase == b.ignoreCase;
705
+ }
706
+ // Ensure that both values are objects.
707
+ if (typeA != 'object') return false;
708
+ // Arrays or Arraylikes with different lengths are not equal.
709
+ if (a.length !== b.length) return false;
710
+ // Objects with different constructors are not equal.
711
+ if (a.constructor !== b.constructor) return false;
712
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
713
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
714
+ var length = stack.length;
715
+ while (length--) {
716
+ // Linear search. Performance is inversely proportional to the number of
717
+ // unique nested structures.
718
+ if (stack[length] == a) return true;
719
+ }
720
+ // Add the first object to the stack of traversed objects.
721
+ stack.push(a);
722
+ var size = 0, result = true;
723
+ // Deep compare objects.
724
+ for (var key in a) {
725
+ if (hasOwnProperty.call(a, key)) {
726
+ // Count the expected number of properties.
727
+ size++;
728
+ // Deep compare each member.
729
+ if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
730
+ }
731
+ }
732
+ // Ensure that both objects contain the same number of properties.
733
+ if (result) {
734
+ for (key in b) {
735
+ if (hasOwnProperty.call(b, key) && !(size--)) break;
736
+ }
737
+ result = !size;
738
+ }
739
+ // Remove the first object from the stack of traversed objects.
740
+ stack.pop();
741
+ return result;
742
+ }
743
+
744
+ // Perform a deep comparison to check if two objects are equal.
745
+ _.isEqual = function(a, b) {
746
+ return eq(a, b, []);
640
747
  };
641
748
 
642
- // Is a given array or object empty?
749
+ // Is a given array, string, or object empty?
750
+ // An "empty" object has no enumerable own-properties.
643
751
  _.isEmpty = function(obj) {
644
752
  if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
645
753
  for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
@@ -654,7 +762,7 @@
654
762
  // Is a given value an array?
655
763
  // Delegates to ECMA5's native Array.isArray
656
764
  _.isArray = nativeIsArray || function(obj) {
657
- return toString.call(obj) === '[object Array]';
765
+ return toString.call(obj) == '[object Array]';
658
766
  };
659
767
 
660
768
  // Is a given variable an object?
@@ -663,44 +771,50 @@
663
771
  };
664
772
 
665
773
  // Is a given variable an arguments object?
666
- _.isArguments = function(obj) {
667
- return !!(obj && hasOwnProperty.call(obj, 'callee'));
668
- };
774
+ if (toString.call(arguments) == '[object Arguments]') {
775
+ _.isArguments = function(obj) {
776
+ return toString.call(obj) == '[object Arguments]';
777
+ };
778
+ } else {
779
+ _.isArguments = function(obj) {
780
+ return !!(obj && hasOwnProperty.call(obj, 'callee'));
781
+ };
782
+ }
669
783
 
670
784
  // Is a given value a function?
671
785
  _.isFunction = function(obj) {
672
- return !!(obj && obj.constructor && obj.call && obj.apply);
786
+ return toString.call(obj) == '[object Function]';
673
787
  };
674
788
 
675
789
  // Is a given value a string?
676
790
  _.isString = function(obj) {
677
- return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
791
+ return toString.call(obj) == '[object String]';
678
792
  };
679
793
 
680
794
  // Is a given value a number?
681
795
  _.isNumber = function(obj) {
682
- return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
796
+ return toString.call(obj) == '[object Number]';
683
797
  };
684
798
 
685
- // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
686
- // that does not equal itself.
799
+ // Is the given value `NaN`?
687
800
  _.isNaN = function(obj) {
801
+ // `NaN` is the only value for which `===` is not reflexive.
688
802
  return obj !== obj;
689
803
  };
690
804
 
691
805
  // Is a given value a boolean?
692
806
  _.isBoolean = function(obj) {
693
- return obj === true || obj === false;
807
+ return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
694
808
  };
695
809
 
696
810
  // Is a given value a date?
697
811
  _.isDate = function(obj) {
698
- return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
812
+ return toString.call(obj) == '[object Date]';
699
813
  };
700
814
 
701
815
  // Is the given value a regular expression?
702
816
  _.isRegExp = function(obj) {
703
- return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
817
+ return toString.call(obj) == '[object RegExp]';
704
818
  };
705
819
 
706
820
  // Is a given value equal to null?
@@ -733,6 +847,11 @@
733
847
  for (var i = 0; i < n; i++) iterator.call(context, i);
734
848
  };
735
849
 
850
+ // Escape a string for HTML interpolation.
851
+ _.escape = function(string) {
852
+ return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
853
+ };
854
+
736
855
  // Add your own custom functions to the Underscore object, ensuring that
737
856
  // they're correctly added to the OOP wrapper as well.
738
857
  _.mixin = function(obj) {
@@ -753,7 +872,8 @@
753
872
  // following template settings to use alternative delimiters.
754
873
  _.templateSettings = {
755
874
  evaluate : /<%([\s\S]+?)%>/g,
756
- interpolate : /<%=([\s\S]+?)%>/g
875
+ interpolate : /<%=([\s\S]+?)%>/g,
876
+ escape : /<%-([\s\S]+?)%>/g
757
877
  };
758
878
 
759
879
  // JavaScript micro-templating, similar to John Resig's implementation.
@@ -765,6 +885,9 @@
765
885
  'with(obj||{}){__p.push(\'' +
766
886
  str.replace(/\\/g, '\\\\')
767
887
  .replace(/'/g, "\\'")
888
+ .replace(c.escape, function(match, code) {
889
+ return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
890
+ })
768
891
  .replace(c.interpolate, function(match, code) {
769
892
  return "'," + code.replace(/\\'/g, "'") + ",'";
770
893
  })
@@ -776,8 +899,8 @@
776
899
  .replace(/\n/g, '\\n')
777
900
  .replace(/\t/g, '\\t')
778
901
  + "');}return __p.join('');";
779
- var func = new Function('obj', tmpl);
780
- return data ? func(data) : func;
902
+ var func = new Function('obj', '_', tmpl);
903
+ return data ? func(data, _) : function(data) { return func(data, _) };
781
904
  };
782
905
 
783
906
  // The OOP Wrapper
@@ -836,5 +959,4 @@
836
959
  return this._wrapped;
837
960
  };
838
961
 
839
- })();
840
-
962
+ }).call(this);