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.
- data/Gemfile.lock +15 -15
- data/app/assets/javascripts/ultimate/backbone/lib/backbone.js +70 -56
- data/app/assets/javascripts/ultimate/backbone/model.js.coffee +2 -2
- data/app/assets/javascripts/ultimate/backbone/view.js.coffee +5 -4
- data/app/assets/javascripts/ultimate/backbone/views/typed-fields.js.coffee +1 -1
- data/app/assets/javascripts/ultimate/helpers/asset_tag.js.coffee +21 -19
- data/app/assets/javascripts/ultimate/helpers/form_options.js.coffee +65 -0
- data/app/assets/javascripts/ultimate/helpers/form_tag.js.coffee +177 -0
- data/app/assets/javascripts/ultimate/helpers/javascript.js.coffee +31 -0
- data/app/assets/javascripts/ultimate/helpers/record_tag.js.coffee +29 -15
- data/app/assets/javascripts/ultimate/helpers/tag.js.coffee +7 -3
- data/app/assets/javascripts/ultimate/helpers/url.js.coffee +62 -7
- data/app/assets/javascripts/ultimate/jquery-plugin-adapter.js.coffee +1 -0
- data/app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee +35 -16
- data/app/assets/javascripts/ultimate/jquery.base.js.coffee +1 -1
- data/app/assets/javascripts/ultimate/underscore/underscore.js +292 -192
- data/app/assets/javascripts/ultimate/underscore/underscore.outcasts.js.coffee +27 -2
- data/app/assets/javascripts/ultimate/underscore/underscore.string.js +4 -4
- data/app/assets/stylesheets/polyfills/PIE.htc +81 -81
- data/app/assets/stylesheets/polyfills/boxsizing.htc +255 -54
- data/app/assets/stylesheets/ultimate/mixins/_vendors.scss +9 -0
- data/app/assets/stylesheets/ultimate/mixins/css3.scss +39 -28
- data/app/assets/stylesheets/ultimate/mixins/microstructures.scss +32 -6
- data/lib/ultimate/base/version.rb +1 -1
- data/test/javascripts/tests/helpers/asset_tag_test.js.coffee +1 -1
- data/test/javascripts/tests/helpers/form_options_test.js.coffee +96 -0
- data/test/javascripts/tests/helpers/form_tag_test.js.coffee +225 -0
- data/test/javascripts/tests/helpers/javascript_test.js.coffee +25 -0
- data/test/javascripts/tests/helpers/record_tag_test.js.coffee +5 -3
- data/test/javascripts/tests/helpers/tag_test.js.coffee +22 -17
- data/test/javascripts/tests/helpers/url_test.js.coffee +50 -6
- data/test/javascripts/tests/underscore/underscore.outcasts.test.js.coffee +9 -0
- metadata +8 -4
- data/app/assets/javascripts/ultimate/helpers/translation.js.coffee +0 -97
- 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
|
-
|
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()
|
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
|
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: (
|
93
|
-
|
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
|
@@ -1,10 +1,7 @@
|
|
1
|
-
// Underscore.js 1.
|
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) {
|
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.
|
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
|
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
|
-
|
134
|
-
|
135
|
-
|
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
|
201
|
-
// Aliased as `
|
202
|
-
_.
|
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 =
|
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,
|
270
|
-
var iterator = lookupIterator(
|
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
|
278
|
-
|
279
|
-
if (
|
280
|
-
|
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,
|
322
|
+
var group = function(obj, value, context, behavior) {
|
291
323
|
var result = {};
|
292
|
-
var iterator = lookupIterator(
|
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,
|
303
|
-
return group(obj,
|
304
|
-
(result[key]
|
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,
|
312
|
-
return group(obj,
|
313
|
-
result
|
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
|
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)
|
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)
|
334
|
-
if (
|
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
|
-
|
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 **
|
375
|
-
// the rest
|
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,
|
378
|
-
return slice.call(array, (
|
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
|
-
|
415
|
-
|
416
|
-
|
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
|
-
|
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(
|
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 =
|
445
|
-
return _.filter(array, function(value){ return !_.
|
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
|
-
//
|
461
|
-
//
|
462
|
-
|
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 =
|
465
|
-
|
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
|
-
|
481
|
-
|
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 (
|
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
|
-
|
492
|
-
|
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)
|
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
|
-
|
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]
|
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
|
-
|
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
|
703
|
-
|
704
|
-
|
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
|
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,
|
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
|
742
|
-
if (b
|
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 =
|
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 (
|
855
|
+
if (aStack[length] == a) return bStack[length] == b;
|
780
856
|
}
|
781
857
|
// Add the first object to the stack of traversed objects.
|
782
|
-
|
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
|
-
|
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
|
-
|
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],
|
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
|
-
|
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
|
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
|
-
|
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
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
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
|
+
'&': '&',
|
1019
|
+
'<': '<',
|
1020
|
+
'>': '>',
|
1021
|
+
'"': '"',
|
1022
|
+
"'": ''',
|
1023
|
+
'/': '/'
|
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
|
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
|
-
|
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:
|
981
|
-
n:
|
982
|
-
t:
|
983
|
-
u2028:
|
984
|
-
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(
|
1004
|
-
|
1005
|
-
//
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
.
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
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
|
-
|
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
|
-
//
|
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
|
1059
|
-
return
|
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
|
-
|
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(
|
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
|
-
|
1089
|
-
return result(method.apply(this._wrapped, arguments)
|
1184
|
+
_.prototype[name] = function() {
|
1185
|
+
return result.call(this, method.apply(this._wrapped, arguments));
|
1090
1186
|
};
|
1091
1187
|
});
|
1092
1188
|
|
1093
|
-
|
1094
|
-
wrapper.prototype.chain = function() {
|
1095
|
-
this._chain = true;
|
1096
|
-
return this;
|
1097
|
-
};
|
1189
|
+
_.extend(_.prototype, {
|
1098
1190
|
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
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);
|