ultimate-base 0.3.1.1 → 0.3.2

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.
Files changed (35) hide show
  1. data/Gemfile.lock +15 -15
  2. data/app/assets/javascripts/ultimate/backbone/lib/backbone.js +70 -56
  3. data/app/assets/javascripts/ultimate/backbone/model.js.coffee +2 -2
  4. data/app/assets/javascripts/ultimate/backbone/view.js.coffee +5 -4
  5. data/app/assets/javascripts/ultimate/backbone/views/typed-fields.js.coffee +1 -1
  6. data/app/assets/javascripts/ultimate/helpers/asset_tag.js.coffee +21 -19
  7. data/app/assets/javascripts/ultimate/helpers/form_options.js.coffee +65 -0
  8. data/app/assets/javascripts/ultimate/helpers/form_tag.js.coffee +177 -0
  9. data/app/assets/javascripts/ultimate/helpers/javascript.js.coffee +31 -0
  10. data/app/assets/javascripts/ultimate/helpers/record_tag.js.coffee +29 -15
  11. data/app/assets/javascripts/ultimate/helpers/tag.js.coffee +7 -3
  12. data/app/assets/javascripts/ultimate/helpers/url.js.coffee +62 -7
  13. data/app/assets/javascripts/ultimate/jquery-plugin-adapter.js.coffee +1 -0
  14. data/app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee +35 -16
  15. data/app/assets/javascripts/ultimate/jquery.base.js.coffee +1 -1
  16. data/app/assets/javascripts/ultimate/underscore/underscore.js +292 -192
  17. data/app/assets/javascripts/ultimate/underscore/underscore.outcasts.js.coffee +27 -2
  18. data/app/assets/javascripts/ultimate/underscore/underscore.string.js +4 -4
  19. data/app/assets/stylesheets/polyfills/PIE.htc +81 -81
  20. data/app/assets/stylesheets/polyfills/boxsizing.htc +255 -54
  21. data/app/assets/stylesheets/ultimate/mixins/_vendors.scss +9 -0
  22. data/app/assets/stylesheets/ultimate/mixins/css3.scss +39 -28
  23. data/app/assets/stylesheets/ultimate/mixins/microstructures.scss +32 -6
  24. data/lib/ultimate/base/version.rb +1 -1
  25. data/test/javascripts/tests/helpers/asset_tag_test.js.coffee +1 -1
  26. data/test/javascripts/tests/helpers/form_options_test.js.coffee +96 -0
  27. data/test/javascripts/tests/helpers/form_tag_test.js.coffee +225 -0
  28. data/test/javascripts/tests/helpers/javascript_test.js.coffee +25 -0
  29. data/test/javascripts/tests/helpers/record_tag_test.js.coffee +5 -3
  30. data/test/javascripts/tests/helpers/tag_test.js.coffee +22 -17
  31. data/test/javascripts/tests/helpers/url_test.js.coffee +50 -6
  32. data/test/javascripts/tests/underscore/underscore.outcasts.test.js.coffee +9 -0
  33. metadata +8 -4
  34. data/app/assets/javascripts/ultimate/helpers/translation.js.coffee +0 -97
  35. data/test/javascripts/tests/helpers/translation_test.js.coffee +0 -140
@@ -14,6 +14,7 @@
14
14
 
15
15
  Ultimate.createJQueryPlugin = (pluginName, pluginClass) ->
16
16
  Ultimate.debug(".createJQueryPlugin()", pluginName, pluginClass) if _.isFunction(Ultimate.debug)
17
+ pluginClass.pluginName ||= pluginName
17
18
  jQuery.fn[pluginName] = -> @ultimatePluginAdapter pluginName, pluginClass, arguments
18
19
 
19
20
 
@@ -1,8 +1,24 @@
1
+ # TODO simlify translations infrastructure
1
2
  # `pluginClass` must store propery `$el` as jQuery object wrapped on the target DOM-object in his instance.
2
3
 
3
4
  #= require ./base
4
5
 
6
+ # TODO minimize requirements
7
+ # requirements stats:
8
+ # 4 _.result
9
+ # 3 _.extend
10
+ # 2 _.isFunction
11
+ # 2 _.isObject
12
+ # 1 _.isString
13
+ # 1 _.isArray
14
+ # 1 _.bind
15
+ # 1 _.clone
16
+ # 1 _.outcasts.delete
17
+ # 1 _.string.underscored
18
+ # 1 _.string.startsWith
19
+
5
20
  class Ultimate.Plugin
21
+ el: null
6
22
  $el: null
7
23
  nodes: {}
8
24
  events: {}
@@ -13,7 +29,8 @@ class Ultimate.Plugin
13
29
  translations: {}
14
30
 
15
31
  constructor: (options) ->
16
- @$el = $(options.el)
32
+ @_configure(options || {});
33
+ @$el = $(@el)
17
34
  @findNodes()
18
35
  @initialize arguments...
19
36
  @delegateEvents()
@@ -30,7 +47,7 @@ class Ultimate.Plugin
30
47
 
31
48
  findNodes: (jRoot = @$el, nodes = @nodes) ->
32
49
  jNodes = {}
33
- nodes = nodes() if _.isFunction(nodes)
50
+ nodes = if _.isFunction(nodes) then @nodes.call(@) else _.clone(nodes)
34
51
  if _.isObject(nodes)
35
52
  for nodeName, selector of nodes
36
53
  _isObject = _.isObject(selector)
@@ -77,8 +94,9 @@ class Ultimate.Plugin
77
94
  for key, method of events
78
95
  [[], eventName, selector] = key.match(delegateEventSplitter)
79
96
  selector = _.result(@, selector)
80
- selector = selector.selector if selector instanceof jQuery
97
+ selector = selector.selector if selector instanceof jQuery
81
98
  if _.isString(selector)
99
+ selector = selector.replace(@$el.selector, '') if _.string.startsWith(selector, @$el.selector)
82
100
  key = "#{eventName} #{selector}"
83
101
  normalizedEvents[key] = method
84
102
  events = normalizedEvents
@@ -89,8 +107,9 @@ class Ultimate.Plugin
89
107
  @initTranslations()
90
108
  @reflectOptions()
91
109
 
92
- reflectOptions: (viewOptions = _.result(@, "viewOptions"), options = @options) ->
93
- @[attr] = options[attr] for attr in viewOptions when typeof options[attr] isnt "undefined"
110
+ reflectOptions: (reflectableOptions = _.result(@, "reflectableOptions"), options = @options) ->
111
+ if _.isArray(reflectableOptions)
112
+ @[attr] = options[attr] for attr in reflectableOptions when typeof options[attr] isnt "undefined"
94
113
  @[attr] = value for attr, value of options when typeof @[attr] isnt "undefined"
95
114
  @
96
115
 
@@ -98,17 +117,17 @@ class Ultimate.Plugin
98
117
  # modify and return merged data
99
118
  initTranslations: (options = @options) ->
100
119
  # if global compatible I18n
101
- if I18n? and I18n.locale and I18n.t
102
- options["locale"] ||= I18n.locale
103
- if options["locale"] is I18n.locale
104
- # pointing to defaults locales of language specified in I18n
105
- _defaultLocales = @constructor.defaultLocales?[I18n.locale] ||= {}
106
- unless _defaultLocales["loaded"]
107
- _defaultLocales["loaded"] = true
108
- # try read localized strings
109
- if _localesFromI18n = I18n.t(options["i18nKey"] or _.underscored(@constructor.pluginName or @constructor.name))
110
- # fill it from I18n
111
- _.extend _defaultLocales, _localesFromI18n
120
+ # if I18n? and I18n.locale and I18n.t
121
+ # options["locale"] ||= I18n.locale
122
+ # if options["locale"] is I18n.locale
123
+ # # pointing to defaults locales of language specified in I18n
124
+ # _defaultLocales = @constructor.defaultLocales?[I18n.locale] ||= {}
125
+ # unless _defaultLocales["loaded"]
126
+ # _defaultLocales["loaded"] = true
127
+ # # try read localized strings
128
+ # if _localesFromI18n = I18n.t(options["i18nKey"] or _.string.underscored(@constructor.pluginName or @constructor.name))
129
+ # # fill it from I18n
130
+ # _.extend _defaultLocales, _localesFromI18n
112
131
  @locale = options["locale"] if options["locale"]
113
132
  translations = if @locale then @constructor.defaultLocales?[@locale] or {} else {}
114
133
  $.extend true, options, translations: translations, options
@@ -46,7 +46,7 @@ do ($ = jQuery) =>
46
46
  $.fn.slideToggleByState = ->
47
47
  if @length
48
48
  if arguments.length > 0
49
- a = args(arguments)
49
+ a = _.toArray(arguments)
50
50
  if a.shift()
51
51
  @slideDown.apply @, a
52
52
  else
@@ -1,10 +1,7 @@
1
- // Underscore.js 1.3.3
1
+ // Underscore.js 1.4.2
2
+ // http://underscorejs.org
2
3
  // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
4
  // Underscore may be freely distributed under the MIT license.
4
- // Portions of Underscore are inspired or borrowed from Prototype,
5
- // Oliver Steele's Functional, and John Resig's Micro-Templating.
6
- // For all details and documentation:
7
- // http://documentcloud.github.com/underscore
8
5
 
9
6
  (function() {
10
7
 
@@ -26,6 +23,7 @@
26
23
  // Create quick reference variables for speed access to core prototypes.
27
24
  var push = ArrayProto.push,
28
25
  slice = ArrayProto.slice,
26
+ concat = ArrayProto.concat,
29
27
  unshift = ArrayProto.unshift,
30
28
  toString = ObjProto.toString,
31
29
  hasOwnProperty = ObjProto.hasOwnProperty;
@@ -47,7 +45,11 @@
47
45
  nativeBind = FuncProto.bind;
48
46
 
49
47
  // Create a safe reference to the Underscore object for use below.
50
- var _ = function(obj) { return new wrapper(obj); };
48
+ var _ = function(obj) {
49
+ if (obj instanceof _) return obj;
50
+ if (!(this instanceof _)) return new _(obj);
51
+ this._wrapped = obj;
52
+ };
51
53
 
52
54
  // Export the Underscore object for **Node.js**, with
53
55
  // backwards-compatibility for the old `require()` API. If we're in
@@ -63,7 +65,7 @@
63
65
  }
64
66
 
65
67
  // Current version.
66
- _.VERSION = '1.3.3';
68
+ _.VERSION = '1.4.2';
67
69
 
68
70
  // Collection Functions
69
71
  // --------------------
@@ -128,11 +130,24 @@
128
130
  if (obj == null) obj = [];
129
131
  if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
130
132
  if (context) iterator = _.bind(iterator, context);
131
- return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
133
+ return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
134
+ }
135
+ var length = obj.length;
136
+ if (length !== +length) {
137
+ var keys = _.keys(obj);
138
+ length = keys.length;
132
139
  }
133
- var reversed = _.toArray(obj).reverse();
134
- if (context && !initial) iterator = _.bind(iterator, context);
135
- return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
140
+ each(obj, function(value, index, list) {
141
+ index = keys ? keys[--length] : --length;
142
+ if (!initial) {
143
+ memo = obj[index];
144
+ initial = true;
145
+ } else {
146
+ memo = iterator.call(context, memo, obj[index], index, list);
147
+ }
148
+ });
149
+ if (!initial) throw new TypeError('Reduce of empty array with no initial value');
150
+ return memo;
136
151
  };
137
152
 
138
153
  // Return the first value which passes a truth test. Aliased as `detect`.
@@ -174,6 +189,7 @@
174
189
  // Delegates to **ECMAScript 5**'s native `every` if available.
175
190
  // Aliased as `all`.
176
191
  _.every = _.all = function(obj, iterator, context) {
192
+ iterator || (iterator = _.identity);
177
193
  var result = true;
178
194
  if (obj == null) return result;
179
195
  if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
@@ -197,9 +213,9 @@
197
213
  return !!result;
198
214
  };
199
215
 
200
- // Determine if a given value is included in the array or object using `===`.
201
- // Aliased as `contains`.
202
- _.include = _.contains = function(obj, target) {
216
+ // Determine if the array or object contains a given value (using `===`).
217
+ // Aliased as `include`.
218
+ _.contains = _.include = function(obj, target) {
203
219
  var found = false;
204
220
  if (obj == null) return found;
205
221
  if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
@@ -222,6 +238,18 @@
222
238
  return _.map(obj, function(value){ return value[key]; });
223
239
  };
224
240
 
241
+ // Convenience version of a common use case of `filter`: selecting only objects
242
+ // with specific `key:value` pairs.
243
+ _.where = function(obj, attrs) {
244
+ if (_.isEmpty(attrs)) return [];
245
+ return _.filter(obj, function(value) {
246
+ for (var key in attrs) {
247
+ if (attrs[key] !== value[key]) return false;
248
+ }
249
+ return true;
250
+ });
251
+ };
252
+
225
253
  // Return the maximum element or (element-based computation).
226
254
  // Can't optimize arrays of integers longer than 65,535 elements.
227
255
  // See: https://bugs.webkit.org/show_bug.cgi?id=80797
@@ -258,40 +286,44 @@
258
286
  var index = 0;
259
287
  var shuffled = [];
260
288
  each(obj, function(value) {
261
- rand = Math.floor(Math.random() * ++index);
289
+ rand = _.random(index++);
262
290
  shuffled[index - 1] = shuffled[rand];
263
291
  shuffled[rand] = value;
264
292
  });
265
293
  return shuffled;
266
294
  };
267
295
 
296
+ // An internal function to generate lookup iterators.
297
+ var lookupIterator = function(value) {
298
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
299
+ };
300
+
268
301
  // Sort the object's values by a criterion produced by an iterator.
269
- _.sortBy = function(obj, val, context) {
270
- var iterator = lookupIterator(obj, val);
302
+ _.sortBy = function(obj, value, context) {
303
+ var iterator = lookupIterator(value);
271
304
  return _.pluck(_.map(obj, function(value, index, list) {
272
305
  return {
273
306
  value : value,
307
+ index : index,
274
308
  criteria : iterator.call(context, value, index, list)
275
309
  };
276
310
  }).sort(function(left, right) {
277
- var a = left.criteria, b = right.criteria;
278
- if (a === void 0) return 1;
279
- if (b === void 0) return -1;
280
- return a < b ? -1 : a > b ? 1 : 0;
311
+ var a = left.criteria;
312
+ var b = right.criteria;
313
+ if (a !== b) {
314
+ if (a > b || a === void 0) return 1;
315
+ if (a < b || b === void 0) return -1;
316
+ }
317
+ return left.index < right.index ? -1 : 1;
281
318
  }), 'value');
282
319
  };
283
320
 
284
- // An internal function to generate lookup iterators.
285
- var lookupIterator = function(obj, val) {
286
- return _.isFunction(val) ? val : function(obj) { return obj[val]; };
287
- };
288
-
289
321
  // An internal function used for aggregate "group by" operations.
290
- var group = function(obj, val, behavior) {
322
+ var group = function(obj, value, context, behavior) {
291
323
  var result = {};
292
- var iterator = lookupIterator(obj, val);
324
+ var iterator = lookupIterator(value);
293
325
  each(obj, function(value, index) {
294
- var key = iterator(value, index);
326
+ var key = iterator.call(context, value, index, obj);
295
327
  behavior(result, key, value);
296
328
  });
297
329
  return result;
@@ -299,47 +331,46 @@
299
331
 
300
332
  // Groups the object's values by a criterion. Pass either a string attribute
301
333
  // to group by, or a function that returns the criterion.
302
- _.groupBy = function(obj, val) {
303
- return group(obj, val, function(result, key, value) {
304
- (result[key] || (result[key] = [])).push(value);
334
+ _.groupBy = function(obj, value, context) {
335
+ return group(obj, value, context, function(result, key, value) {
336
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
305
337
  });
306
338
  };
307
339
 
308
340
  // Counts instances of an object that group by a certain criterion. Pass
309
341
  // either a string attribute to count by, or a function that returns the
310
342
  // criterion.
311
- _.countBy = function(obj, val) {
312
- return group(obj, val, function(result, key, value) {
313
- result[key] || (result[key] = 0);
343
+ _.countBy = function(obj, value, context) {
344
+ return group(obj, value, context, function(result, key, value) {
345
+ if (!_.has(result, key)) result[key] = 0;
314
346
  result[key]++;
315
347
  });
316
348
  };
317
349
 
318
350
  // Use a comparator function to figure out the smallest index at which
319
351
  // an object should be inserted so as to maintain order. Uses binary search.
320
- _.sortedIndex = function(array, obj, iterator) {
321
- iterator || (iterator = _.identity);
322
- var value = iterator(obj);
352
+ _.sortedIndex = function(array, obj, iterator, context) {
353
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
354
+ var value = iterator.call(context, obj);
323
355
  var low = 0, high = array.length;
324
356
  while (low < high) {
325
- var mid = (low + high) >> 1;
326
- iterator(array[mid]) < value ? low = mid + 1 : high = mid;
357
+ var mid = (low + high) >>> 1;
358
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
327
359
  }
328
360
  return low;
329
361
  };
330
362
 
331
363
  // Safely convert anything iterable into a real, live array.
332
364
  _.toArray = function(obj) {
333
- if (!obj) return [];
334
- if (_.isArray(obj)) return slice.call(obj);
335
- if (_.isArguments(obj)) return slice.call(obj);
336
- if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
365
+ if (!obj) return [];
366
+ if (obj.length === +obj.length) return slice.call(obj);
337
367
  return _.values(obj);
338
368
  };
339
369
 
340
370
  // Return the number of elements in an object.
341
371
  _.size = function(obj) {
342
- return _.isArray(obj) ? obj.length : _.keys(obj).length;
372
+ if (obj == null) return 0;
373
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
343
374
  };
344
375
 
345
376
  // Array Functions
@@ -349,6 +380,7 @@
349
380
  // values in the array. Aliased as `head` and `take`. The **guard** check
350
381
  // allows it to work with `_.map`.
351
382
  _.first = _.head = _.take = function(array, n, guard) {
383
+ if (array == null) return void 0;
352
384
  return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
353
385
  };
354
386
 
@@ -363,6 +395,7 @@
363
395
  // Get the last element of an array. Passing **n** will return the last N
364
396
  // values in the array. The **guard** check allows it to work with `_.map`.
365
397
  _.last = function(array, n, guard) {
398
+ if (array == null) return void 0;
366
399
  if ((n != null) && !guard) {
367
400
  return slice.call(array, Math.max(array.length - n, 0));
368
401
  } else {
@@ -370,12 +403,12 @@
370
403
  }
371
404
  };
372
405
 
373
- // Returns everything but the first entry of the array. Aliased as `tail`.
374
- // Especially useful on the arguments object. Passing an **index** will return
375
- // the rest of the values in the array from that index onward. The **guard**
406
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
407
+ // Especially useful on the arguments object. Passing an **n** will return
408
+ // the rest N values in the array. The **guard**
376
409
  // check allows it to work with `_.map`.
377
- _.rest = _.tail = function(array, index, guard) {
378
- return slice.call(array, (index == null) || guard ? 1 : index);
410
+ _.rest = _.tail = _.drop = function(array, n, guard) {
411
+ return slice.call(array, (n == null) || guard ? 1 : n);
379
412
  };
380
413
 
381
414
  // Trim out all falsy values from an array.
@@ -408,23 +441,23 @@
408
441
  // Produce a duplicate-free version of the array. If the array has already
409
442
  // been sorted, you have the option of using a faster algorithm.
410
443
  // Aliased as `unique`.
411
- _.uniq = _.unique = function(array, isSorted, iterator) {
412
- var initial = iterator ? _.map(array, iterator) : array;
444
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
445
+ var initial = iterator ? _.map(array, iterator, context) : array;
413
446
  var results = [];
414
- _.reduce(initial, function(memo, value, index) {
415
- if (isSorted ? (_.last(memo) !== value || !memo.length) : !_.include(memo, value)) {
416
- memo.push(value);
447
+ var seen = [];
448
+ each(initial, function(value, index) {
449
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
450
+ seen.push(value);
417
451
  results.push(array[index]);
418
452
  }
419
- return memo;
420
- }, []);
453
+ });
421
454
  return results;
422
455
  };
423
456
 
424
457
  // Produce an array that contains the union: each distinct element from all of
425
458
  // the passed-in arrays.
426
459
  _.union = function() {
427
- return _.uniq(flatten(arguments, true, []));
460
+ return _.uniq(concat.apply(ArrayProto, arguments));
428
461
  };
429
462
 
430
463
  // Produce an array that contains every item shared between all the
@@ -441,8 +474,8 @@
441
474
  // Take the difference between one array and a number of other arrays.
442
475
  // Only the elements present in just the first array will remain.
443
476
  _.difference = function(array) {
444
- var rest = flatten(slice.call(arguments, 1), true, []);
445
- return _.filter(array, function(value){ return !_.include(rest, value); });
477
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
478
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
446
479
  };
447
480
 
448
481
  // Zip together multiple lists into a single array -- elements that share
@@ -457,12 +490,18 @@
457
490
  return results;
458
491
  };
459
492
 
460
- // Zip together two arrays -- an array of keys and an array of values -- into
461
- // a single object.
462
- _.zipObject = function(keys, values) {
493
+ // Converts lists into objects. Pass either a single array of `[key, value]`
494
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
495
+ // the corresponding values.
496
+ _.object = function(list, values) {
497
+ if (list == null) return {};
463
498
  var result = {};
464
- for (var i = 0, l = keys.length; i < l; i++) {
465
- result[keys[i]] = values[i];
499
+ for (var i = 0, l = list.length; i < l; i++) {
500
+ if (values) {
501
+ result[list[i]] = values[i];
502
+ } else {
503
+ result[list[i][0]] = list[i][1];
504
+ }
466
505
  }
467
506
  return result;
468
507
  };
@@ -475,21 +514,28 @@
475
514
  // for **isSorted** to use binary search.
476
515
  _.indexOf = function(array, item, isSorted) {
477
516
  if (array == null) return -1;
478
- var i, l;
517
+ var i = 0, l = array.length;
479
518
  if (isSorted) {
480
- i = _.sortedIndex(array, item);
481
- return array[i] === item ? i : -1;
519
+ if (typeof isSorted == 'number') {
520
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
521
+ } else {
522
+ i = _.sortedIndex(array, item);
523
+ return array[i] === item ? i : -1;
524
+ }
482
525
  }
483
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
484
- for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
526
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
527
+ for (; i < l; i++) if (array[i] === item) return i;
485
528
  return -1;
486
529
  };
487
530
 
488
531
  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
489
- _.lastIndexOf = function(array, item) {
532
+ _.lastIndexOf = function(array, item, from) {
490
533
  if (array == null) return -1;
491
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
492
- var i = array.length;
534
+ var hasIndex = from != null;
535
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
536
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
537
+ }
538
+ var i = (hasIndex ? from : array.length);
493
539
  while (i--) if (array[i] === item) return i;
494
540
  return -1;
495
541
  };
@@ -582,7 +628,9 @@
582
628
  context = this; args = arguments;
583
629
  var later = function() {
584
630
  timeout = null;
585
- if (more) func.apply(context, args);
631
+ if (more) {
632
+ result = func.apply(context, args);
633
+ }
586
634
  whenDone();
587
635
  };
588
636
  if (!timeout) timeout = setTimeout(later, wait);
@@ -602,17 +650,18 @@
602
650
  // N milliseconds. If `immediate` is passed, trigger the function on the
603
651
  // leading edge, instead of the trailing.
604
652
  _.debounce = function(func, wait, immediate) {
605
- var timeout;
653
+ var timeout, result;
606
654
  return function() {
607
655
  var context = this, args = arguments;
608
656
  var later = function() {
609
657
  timeout = null;
610
- if (!immediate) func.apply(context, args);
658
+ if (!immediate) result = func.apply(context, args);
611
659
  };
612
660
  var callNow = immediate && !timeout;
613
661
  clearTimeout(timeout);
614
662
  timeout = setTimeout(later, wait);
615
- if (callNow) func.apply(context, args);
663
+ if (callNow) result = func.apply(context, args);
664
+ return result;
616
665
  };
617
666
  };
618
667
 
@@ -623,7 +672,9 @@
623
672
  return function() {
624
673
  if (ran) return memo;
625
674
  ran = true;
626
- return memo = func.apply(this, arguments);
675
+ memo = func.apply(this, arguments);
676
+ func = null;
677
+ return memo;
627
678
  };
628
679
  };
629
680
 
@@ -632,7 +683,8 @@
632
683
  // conditionally execute the original function.
633
684
  _.wrap = function(func, wrapper) {
634
685
  return function() {
635
- var args = [func].concat(slice.call(arguments, 0));
686
+ var args = [func];
687
+ push.apply(args, arguments);
636
688
  return wrapper.apply(this, args);
637
689
  };
638
690
  };
@@ -674,7 +726,23 @@
674
726
 
675
727
  // Retrieve the values of an object's properties.
676
728
  _.values = function(obj) {
677
- return _.map(obj, _.identity);
729
+ var values = [];
730
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
731
+ return values;
732
+ };
733
+
734
+ // Convert an object into a list of `[key, value]` pairs.
735
+ _.pairs = function(obj) {
736
+ var pairs = [];
737
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
738
+ return pairs;
739
+ };
740
+
741
+ // Invert the keys and values of an object. The values must be serializable.
742
+ _.invert = function(obj) {
743
+ var result = {};
744
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
745
+ return result;
678
746
  };
679
747
 
680
748
  // Return a sorted list of the function names available on the object.
@@ -699,11 +767,22 @@
699
767
 
700
768
  // Return a copy of the object only containing the whitelisted properties.
701
769
  _.pick = function(obj) {
702
- var result = {};
703
- each(flatten(slice.call(arguments, 1), true, []), function(key) {
704
- if (key in obj) result[key] = obj[key];
770
+ var copy = {};
771
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
772
+ each(keys, function(key) {
773
+ if (key in obj) copy[key] = obj[key];
705
774
  });
706
- return result;
775
+ return copy;
776
+ };
777
+
778
+ // Return a copy of the object without the blacklisted properties.
779
+ _.omit = function(obj) {
780
+ var copy = {};
781
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
782
+ for (var key in obj) {
783
+ if (!_.contains(keys, key)) copy[key] = obj[key];
784
+ }
785
+ return copy;
707
786
  };
708
787
 
709
788
  // Fill in a given object with default properties.
@@ -731,18 +810,15 @@
731
810
  };
732
811
 
733
812
  // Internal recursive comparison function for `isEqual`.
734
- var eq = function(a, b, stack) {
813
+ var eq = function(a, b, aStack, bStack) {
735
814
  // Identical objects are equal. `0 === -0`, but they aren't identical.
736
815
  // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
737
816
  if (a === b) return a !== 0 || 1 / a == 1 / b;
738
817
  // A strict comparison is necessary because `null == undefined`.
739
818
  if (a == null || b == null) return a === b;
740
819
  // Unwrap any wrapped objects.
741
- if (a._chain) a = a._wrapped;
742
- if (b._chain) b = b._wrapped;
743
- // Invoke a custom `isEqual` method if one is provided.
744
- if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
745
- if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
820
+ if (a instanceof _) a = a._wrapped;
821
+ if (b instanceof _) b = b._wrapped;
746
822
  // Compare `[[Class]]` names.
747
823
  var className = toString.call(a);
748
824
  if (className != toString.call(b)) return false;
@@ -772,14 +848,15 @@
772
848
  if (typeof a != 'object' || typeof b != 'object') return false;
773
849
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
774
850
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
775
- var length = stack.length;
851
+ var length = aStack.length;
776
852
  while (length--) {
777
853
  // Linear search. Performance is inversely proportional to the number of
778
854
  // unique nested structures.
779
- if (stack[length] == a) return true;
855
+ if (aStack[length] == a) return bStack[length] == b;
780
856
  }
781
857
  // Add the first object to the stack of traversed objects.
782
- stack.push(a);
858
+ aStack.push(a);
859
+ bStack.push(b);
783
860
  var size = 0, result = true;
784
861
  // Recursively compare objects and arrays.
785
862
  if (className == '[object Array]') {
@@ -789,20 +866,24 @@
789
866
  if (result) {
790
867
  // Deep compare the contents, ignoring non-numeric properties.
791
868
  while (size--) {
792
- // Ensure commutative equality for sparse arrays.
793
- if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
869
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
794
870
  }
795
871
  }
796
872
  } else {
797
- // Objects with different constructors are not equivalent.
798
- if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
873
+ // Objects with different constructors are not equivalent, but `Object`s
874
+ // from different frames are.
875
+ var aCtor = a.constructor, bCtor = b.constructor;
876
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
877
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
878
+ return false;
879
+ }
799
880
  // Deep compare objects.
800
881
  for (var key in a) {
801
882
  if (_.has(a, key)) {
802
883
  // Count the expected number of properties.
803
884
  size++;
804
885
  // Deep compare each member.
805
- if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
886
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
806
887
  }
807
888
  }
808
889
  // Ensure that both objects contain the same number of properties.
@@ -814,13 +895,14 @@
814
895
  }
815
896
  }
816
897
  // Remove the first object from the stack of traversed objects.
817
- stack.pop();
898
+ aStack.pop();
899
+ bStack.pop();
818
900
  return result;
819
901
  };
820
902
 
821
903
  // Perform a deep comparison to check if two objects are equal.
822
904
  _.isEqual = function(a, b) {
823
- return eq(a, b, []);
905
+ return eq(a, b, [], []);
824
906
  };
825
907
 
826
908
  // Is a given array, string, or object empty?
@@ -834,7 +916,7 @@
834
916
 
835
917
  // Is a given value a DOM element?
836
918
  _.isElement = function(obj) {
837
- return !!(obj && obj.nodeType == 1);
919
+ return !!(obj && obj.nodeType === 1);
838
920
  };
839
921
 
840
922
  // Is a given value an array?
@@ -863,15 +945,21 @@
863
945
  };
864
946
  }
865
947
 
948
+ // Optimize `isFunction` if appropriate.
949
+ if (typeof (/./) !== 'function') {
950
+ _.isFunction = function(obj) {
951
+ return typeof obj === 'function';
952
+ };
953
+ }
954
+
866
955
  // Is a given object a finite number?
867
956
  _.isFinite = function(obj) {
868
957
  return _.isNumber(obj) && isFinite(obj);
869
958
  };
870
959
 
871
- // Is the given value `NaN`?
960
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
872
961
  _.isNaN = function(obj) {
873
- // `NaN` is the only value for which `===` is not reflexive.
874
- return obj !== obj;
962
+ return _.isNumber(obj) && obj != +obj;
875
963
  };
876
964
 
877
965
  // Is a given value a boolean?
@@ -915,25 +1003,43 @@
915
1003
  for (var i = 0; i < n; i++) iterator.call(context, i);
916
1004
  };
917
1005
 
1006
+ // Return a random integer between min and max (inclusive).
1007
+ _.random = function(min, max) {
1008
+ if (max == null) {
1009
+ max = min;
1010
+ min = 0;
1011
+ }
1012
+ return min + (0 | Math.random() * (max - min + 1));
1013
+ };
1014
+
918
1015
  // List of HTML entities for escaping.
919
- var htmlEscapes = {
920
- '&': '&amp;',
921
- '<': '&lt;',
922
- '>': '&gt;',
923
- '"': '&quot;',
924
- "'": '&#x27;',
925
- '/': '&#x2F;'
926
- };
927
-
928
- // Regex containing the keys listed immediately above.
929
- var htmlEscaper = /[&<>"'\/]/g;
930
-
931
- // Escape a string for HTML interpolation.
932
- _.escape = function(string) {
933
- return ('' + string).replace(htmlEscaper, function(match) {
934
- return htmlEscapes[match];
935
- });
1016
+ var entityMap = {
1017
+ escape: {
1018
+ '&': '&amp;',
1019
+ '<': '&lt;',
1020
+ '>': '&gt;',
1021
+ '"': '&quot;',
1022
+ "'": '&#x27;',
1023
+ '/': '&#x2F;'
1024
+ }
936
1025
  };
1026
+ entityMap.unescape = _.invert(entityMap.escape);
1027
+
1028
+ // Regexes containing the keys and values listed immediately above.
1029
+ var entityRegexes = {
1030
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1031
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1032
+ };
1033
+
1034
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
1035
+ _.each(['escape', 'unescape'], function(method) {
1036
+ _[method] = function(string) {
1037
+ if (string == null) return '';
1038
+ return ('' + string).replace(entityRegexes[method], function(match) {
1039
+ return entityMap[method][match];
1040
+ });
1041
+ };
1042
+ });
937
1043
 
938
1044
  // If the value of the named property is a function then invoke it;
939
1045
  // otherwise, return it.
@@ -943,11 +1049,15 @@
943
1049
  return _.isFunction(value) ? value.call(object) : value;
944
1050
  };
945
1051
 
946
- // Add your own custom functions to the Underscore object, ensuring that
947
- // they're correctly added to the OOP wrapper as well.
1052
+ // Add your own custom functions to the Underscore object.
948
1053
  _.mixin = function(obj) {
949
1054
  each(_.functions(obj), function(name){
950
- addToWrapper(name, _[name] = obj[name]);
1055
+ var func = _[name] = obj[name];
1056
+ _.prototype[name] = function() {
1057
+ var args = [this._wrapped];
1058
+ push.apply(args, arguments);
1059
+ return result.call(this, func.apply(_, args));
1060
+ };
951
1061
  });
952
1062
  };
953
1063
 
@@ -970,63 +1080,63 @@
970
1080
  // When customizing `templateSettings`, if you don't want to define an
971
1081
  // interpolation, evaluation or escaping regex, we need one that is
972
1082
  // guaranteed not to match.
973
- var noMatch = /.^/;
1083
+ var noMatch = /(.)^/;
974
1084
 
975
1085
  // Certain characters need to be escaped so that they can be put into a
976
1086
  // string literal.
977
1087
  var escapes = {
978
- '\\': '\\',
979
- "'": "'",
980
- r: '\r',
981
- n: '\n',
982
- t: '\t',
983
- u2028: '\u2028',
984
- u2029: '\u2029'
1088
+ "'": "'",
1089
+ '\\': '\\',
1090
+ '\r': 'r',
1091
+ '\n': 'n',
1092
+ '\t': 't',
1093
+ '\u2028': 'u2028',
1094
+ '\u2029': 'u2029'
985
1095
  };
986
1096
 
987
- for (var key in escapes) escapes[escapes[key]] = key;
988
1097
  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
989
- var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
990
-
991
- // Within an interpolation, evaluation, or escaping, remove HTML escaping
992
- // that had been previously added.
993
- var unescape = function(code) {
994
- return code.replace(unescaper, function(match, escape) {
995
- return escapes[escape];
996
- });
997
- };
998
1098
 
999
1099
  // JavaScript micro-templating, similar to John Resig's implementation.
1000
1100
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
1001
1101
  // and correctly escapes quotes within interpolated code.
1002
1102
  _.template = function(text, data, settings) {
1003
- settings = _.defaults(settings || {}, _.templateSettings);
1004
-
1005
- // Compile the template source, taking care to escape characters that
1006
- // cannot be included in a string literal and then unescape them in code
1007
- // blocks.
1008
- var source = "__p+='" + text
1009
- .replace(escaper, function(match) {
1010
- return '\\' + escapes[match];
1011
- })
1012
- .replace(settings.escape || noMatch, function(match, code) {
1013
- return "'+\n((__t=(" + unescape(code) + "))==null?'':_.escape(__t))+\n'";
1014
- })
1015
- .replace(settings.interpolate || noMatch, function(match, code) {
1016
- return "'+\n((__t=(" + unescape(code) + "))==null?'':__t)+\n'";
1017
- })
1018
- .replace(settings.evaluate || noMatch, function(match, code) {
1019
- return "';\n" + unescape(code) + "\n__p+='";
1020
- }) + "';\n";
1103
+ settings = _.defaults({}, settings, _.templateSettings);
1104
+
1105
+ // Combine delimiters into one regular expression via alternation.
1106
+ var matcher = new RegExp([
1107
+ (settings.escape || noMatch).source,
1108
+ (settings.interpolate || noMatch).source,
1109
+ (settings.evaluate || noMatch).source
1110
+ ].join('|') + '|$', 'g');
1111
+
1112
+ // Compile the template source, escaping string literals appropriately.
1113
+ var index = 0;
1114
+ var source = "__p+='";
1115
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1116
+ source += text.slice(index, offset)
1117
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
1118
+ source +=
1119
+ escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
1120
+ interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
1121
+ evaluate ? "';\n" + evaluate + "\n__p+='" : '';
1122
+ index = offset + match.length;
1123
+ });
1124
+ source += "';\n";
1021
1125
 
1022
1126
  // If a variable is not specified, place data values in local scope.
1023
1127
  if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1024
1128
 
1025
1129
  source = "var __t,__p='',__j=Array.prototype.join," +
1026
- "print=function(){__p+=__j.call(arguments,'')};\n" +
1130
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
1027
1131
  source + "return __p;\n";
1028
1132
 
1029
- var render = new Function(settings.variable || 'obj', '_', source);
1133
+ try {
1134
+ var render = new Function(settings.variable || 'obj', '_', source);
1135
+ } catch (e) {
1136
+ e.source = source;
1137
+ throw e;
1138
+ }
1139
+
1030
1140
  if (data) return render(data, _);
1031
1141
  var template = function(data) {
1032
1142
  return render.call(this, data, _);
@@ -1043,29 +1153,15 @@
1043
1153
  return _(obj).chain();
1044
1154
  };
1045
1155
 
1046
- // The OOP Wrapper
1156
+ // OOP
1047
1157
  // ---------------
1048
-
1049
1158
  // If Underscore is called as a function, it returns a wrapped object that
1050
1159
  // can be used OO-style. This wrapper holds altered versions of all the
1051
1160
  // underscore functions. Wrapped objects may be chained.
1052
- var wrapper = function(obj) { this._wrapped = obj; };
1053
-
1054
- // Expose `wrapper.prototype` as `_.prototype`
1055
- _.prototype = wrapper.prototype;
1056
1161
 
1057
1162
  // Helper function to continue chaining intermediate results.
1058
- var result = function(obj, chain) {
1059
- return chain ? _(obj).chain() : obj;
1060
- };
1061
-
1062
- // A method to easily add functions to the OOP wrapper.
1063
- var addToWrapper = function(name, func) {
1064
- wrapper.prototype[name] = function() {
1065
- var args = slice.call(arguments);
1066
- unshift.call(args, this._wrapped);
1067
- return result(func.apply(_, args), this._chain);
1068
- };
1163
+ var result = function(obj) {
1164
+ return this._chain ? _(obj).chain() : obj;
1069
1165
  };
1070
1166
 
1071
1167
  // Add all of the Underscore functions to the wrapper object.
@@ -1074,31 +1170,35 @@
1074
1170
  // Add all mutator Array functions to the wrapper.
1075
1171
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1076
1172
  var method = ArrayProto[name];
1077
- wrapper.prototype[name] = function() {
1173
+ _.prototype[name] = function() {
1078
1174
  var obj = this._wrapped;
1079
1175
  method.apply(obj, arguments);
1080
1176
  if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1081
- return result(obj, this._chain);
1177
+ return result.call(this, obj);
1082
1178
  };
1083
1179
  });
1084
1180
 
1085
1181
  // Add all accessor Array functions to the wrapper.
1086
1182
  each(['concat', 'join', 'slice'], function(name) {
1087
1183
  var method = ArrayProto[name];
1088
- wrapper.prototype[name] = function() {
1089
- return result(method.apply(this._wrapped, arguments), this._chain);
1184
+ _.prototype[name] = function() {
1185
+ return result.call(this, method.apply(this._wrapped, arguments));
1090
1186
  };
1091
1187
  });
1092
1188
 
1093
- // Start chaining a wrapped Underscore object.
1094
- wrapper.prototype.chain = function() {
1095
- this._chain = true;
1096
- return this;
1097
- };
1189
+ _.extend(_.prototype, {
1098
1190
 
1099
- // Extracts the result from a wrapped and chained object.
1100
- wrapper.prototype.value = function() {
1101
- return this._wrapped;
1102
- };
1191
+ // Start chaining a wrapped Underscore object.
1192
+ chain: function() {
1193
+ this._chain = true;
1194
+ return this;
1195
+ },
1196
+
1197
+ // Extracts the result from a wrapped and chained object.
1198
+ value: function() {
1199
+ return this._wrapped;
1200
+ }
1201
+
1202
+ });
1103
1203
 
1104
1204
  }).call(this);