ultimate-base 0.3.1.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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);