1 // Underscore.js 1.4.4
2 // http://underscorejs.org
3 // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
4 // Underscore may be freely distributed under the MIT license.
5
6 (function() {
7
8 // Baseline setup
9 // --------------
10
11 // Establish the root object, `window` in the browser, or `global` on the server.
12 var root = this;
13
14 // Save the previous value of the `_` variable.
15 var previousUnderscore = root._;
16
17 // Establish the object that gets returned to break out of a loop iteration.
18 var breaker = {};
19
20 // Save bytes in the minified (but not gzipped) version:
21 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
22
23 // Create quick reference variables for speed access to core prototypes.
24 var push = ArrayProto.push,
25 slice = ArrayProto.slice,
26 concat = ArrayProto.concat,
27 toString = ObjProto.toString,
28 hasOwnProperty = ObjProto.hasOwnProperty;
29
30 // All **ECMAScript 5** native function implementations that we hope to use
31 // are declared here.
32 var
33 nativeForEach = ArrayProto.forEach,
34 nativeMap = ArrayProto.map,
35 nativeReduce = ArrayProto.reduce,
36 nativeReduceRight = ArrayProto.reduceRight,
37 nativeFilter = ArrayProto.filter,
38 nativeEvery = ArrayProto.every,
39 nativeSome = ArrayProto.some,
40 nativeIndexOf = ArrayProto.indexOf,
41 nativeLastIndexOf = ArrayProto.lastIndexOf,
42 nativeIsArray = Array.isArray,
43 nativeKeys = Object.keys,
44 nativeBind = FuncProto.bind;
45
46 // Create a safe reference to the Underscore object for use below.
47 var _ = function(obj) {
48 if (obj instanceof _) return obj;
49 if (!(this instanceof _)) return new _(obj);
50 this._wrapped = obj;
51 };
52
53 // Export the Underscore object for **Node.js**, with
54 // backwards-compatibility for the old `require()` API. If we're in
55 // the browser, add `_` as a global object via a string identifier,
56 // for Closure Compiler "advanced" mode.
57 if (typeof exports !== 'undefined') {
58 if (typeof module !== 'undefined' && module.exports) {
59 exports = module.exports = _;
60 }
61 exports._ = _;
62 } else {
63 root._ = _;
64 }
65
66 // Current version.
67 _.VERSION = '1.4.4';
68
69 // Collection Functions
70 // --------------------
71
72 // The cornerstone, an `each` implementation, aka `forEach`.
73 // Handles objects with the built-in `forEach`, arrays, and raw objects.
74 // Delegates to **ECMAScript 5**'s native `forEach` if available.
75 var each = _.each = _.forEach = function(obj, iterator, context) {
76 if (obj == null) return;
77 if (nativeForEach && obj.forEach === nativeForEach) {
78 obj.forEach(iterator, context);
79 } else if (obj.length === +obj.length) {
80 for (var i = 0, l = obj.length; i < l; i++) {
81 if (iterator.call(context, obj[i], i, obj) === breaker) return;
82 }
83 } else {
84 for (var key in obj) {
85 if (_.has(obj, key)) {
86 if (iterator.call(context, obj[key], key, obj) === breaker) return;
87 }
88 }
89 }
90 };
91
92 // Return the results of applying the iterator to each element.
93 // Delegates to **ECMAScript 5**'s native `map` if available.
94 _.map = _.collect = function(obj, iterator, context) {
95 var results = [];
96 if (obj == null) return results;
97 if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
98 each(obj, function(value, index, list) {
99 results[results.length] = iterator.call(context, value, index, list);
100 });
101 return results;
102 };
103
104 var reduceError = 'Reduce of empty array with no initial value';
105
106 // **Reduce** builds up a single result from a list of values, aka `inject`,
107 // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
108 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
109 var initial = arguments.length > 2;
110 if (obj == null) obj = [];
111 if (nativeReduce && obj.reduce === nativeReduce) {
112 if (context) iterator = _.bind(iterator, context);
113 return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
114 }
115 each(obj, function(value, index, list) {
116 if (!initial) {
117 memo = value;
118 initial = true;
119 } else {
120 memo = iterator.call(context, memo, value, index, list);
121 }
122 });
123 if (!initial) throw new TypeError(reduceError);
124 return memo;
125 };
126
127 // The right-associative version of reduce, also known as `foldr`.
128 // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
129 _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
130 var initial = arguments.length > 2;
131 if (obj == null) obj = [];
132 if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
133 if (context) iterator = _.bind(iterator, context);
134 return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
135 }
136 var length = obj.length;
137 if (length !== +length) {
138 var keys = _.keys(obj);
139 length = keys.length;
140 }
141 each(obj, function(value, index, list) {
142 index = keys ? keys[--length] : --length;
143 if (!initial) {
144 memo = obj[index];
145 initial = true;
146 } else {
147 memo = iterator.call(context, memo, obj[index], index, list);
148 }
149 });
150 if (!initial) throw new TypeError(reduceError);
151 return memo;
152 };
153
154 // Return the first value which passes a truth test. Aliased as `detect`.
155 _.find = _.detect = function(obj, iterator, context) {
156 var result;
157 any(obj, function(value, index, list) {
158 if (iterator.call(context, value, index, list)) {
159 result = value;
160 return true;
161 }
162 });
163 return result;
164 };
165
166 // Return all the elements that pass a truth test.
167 // Delegates to **ECMAScript 5**'s native `filter` if available.
168 // Aliased as `select`.
169 _.filter = _.select = function(obj, iterator, context) {
170 var results = [];
171 if (obj == null) return results;
172 if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
173 each(obj, function(value, index, list) {
174 if (iterator.call(context, value, index, list)) results[results.length] = value;
175 });
176 return results;
177 };
178
179 // Return all the elements for which a truth test fails.
180 _.reject = function(obj, iterator, context) {
181 return _.filter(obj, function(value, index, list) {
182 return !iterator.call(context, value, index, list);
183 }, context);
184 };
185
186 // Determine whether all of the elements match a truth test.
187 // Delegates to **ECMAScript 5**'s native `every` if available.
188 // Aliased as `all`.
189 _.every = _.all = function(obj, iterator, context) {
190 iterator || (iterator = _.identity);
191 var result = true;
192 if (obj == null) return result;
193 if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
194 each(obj, function(value, index, list) {
195 if (!(result = result && iterator.call(context, value, index, list))) return breaker;
196 });
197 return !!result;
198 };
199
200 // Determine if at least one element in the object matches a truth test.
201 // Delegates to **ECMAScript 5**'s native `some` if available.
202 // Aliased as `any`.
203 var any = _.some = _.any = function(obj, iterator, context) {
204 iterator || (iterator = _.identity);
205 var result = false;
206 if (obj == null) return result;
207 if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
208 each(obj, function(value, index, list) {
209 if (result || (result = iterator.call(context, value, index, list))) return breaker;
210 });
211 return !!result;
212 };
213
214 // Determine if the array or object contains a given value (using `===`).
215 // Aliased as `include`.
216 _.contains = _.include = function(obj, target) {
217 if (obj == null) return false;
218 if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
219 return any(obj, function(value) {
220 return value === target;
221 });
222 };
223
224 // Invoke a method (with arguments) on every item in a collection.
225 _.invoke = function(obj, method) {
226 var args = slice.call(arguments, 2);
227 var isFunc = _.isFunction(method);
228 return _.map(obj, function(value) {
229 return (isFunc ? method : value[method]).apply(value, args);
230 });
231 };
232
233 // Convenience version of a common use case of `map`: fetching a property.
234 _.pluck = function(obj, key) {
235 return _.map(obj, function(value){ return value[key]; });
236 };
237
238 // Convenience version of a common use case of `filter`: selecting only objects
239 // containing specific `key:value` pairs.
240 _.where = function(obj, attrs, first) {
241 if (_.isEmpty(attrs)) return first ? null : [];
242 return _[first ? 'find' : 'filter'](obj, function(value) {
243 for (var key in attrs) {
244 if (attrs[key] !== value[key]) return false;
245 }
246 return true;
247 });
248 };
249
250 // Convenience version of a common use case of `find`: getting the first object
251 // containing specific `key:value` pairs.
252 _.findWhere = function(obj, attrs) {
253 return _.where(obj, attrs, true);
254 };
255
256 // Return the maximum element or (element-based computation).
257 // Can't optimize arrays of integers longer than 65,535 elements.
258 // See: https://bugs.webkit.org/show_bug.cgi?id=80797
259 _.max = function(obj, iterator, context) {
260 if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
261 return Math.max.apply(Math, obj);
262 }
263 if (!iterator && _.isEmpty(obj)) return -Infinity;
264 var result = {computed : -Infinity, value: -Infinity};
265 each(obj, function(value, index, list) {
266 var computed = iterator ? iterator.call(context, value, index, list) : value;
267 computed >= result.computed && (result = {value : value, computed : computed});
268 });
269 return result.value;
270 };
271
272 // Return the minimum element (or element-based computation).
273 _.min = function(obj, iterator, context) {
274 if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
275 return Math.min.apply(Math, obj);
276 }
277 if (!iterator && _.isEmpty(obj)) return Infinity;
278 var result = {computed : Infinity, value: Infinity};
279 each(obj, function(value, index, list) {
280 var computed = iterator ? iterator.call(context, value, index, list) : value;
281 computed < result.computed && (result = {value : value, computed : computed});
282 });
283 return result.value;
284 };
285
286 // Shuffle an array.
287 _.shuffle = function(obj) {
288 var rand;
289 var index = 0;
290 var shuffled = [];
291 each(obj, function(value) {
292 rand = _.random(index++);
293 shuffled[index - 1] = shuffled[rand];
294 shuffled[rand] = value;
295 });
296 return shuffled;
297 };
298
299 // An internal function to generate lookup iterators.
300 var lookupIterator = function(value) {
301 return _.isFunction(value) ? value : function(obj){ return obj[value]; };
302 };
303
304 // Sort the object's values by a criterion produced by an iterator.
305 _.sortBy = function(obj, value, context) {
306 var iterator = lookupIterator(value);
307 return _.pluck(_.map(obj, function(value, index, list) {
308 return {
309 value : value,
310 index : index,
311 criteria : iterator.call(context, value, index, list)
312 };
313 }).sort(function(left, right) {
314 var a = left.criteria;
315 var b = right.criteria;
316 if (a !== b) {
317 if (a > b || a === void 0) return 1;
318 if (a < b || b === void 0) return -1;
319 }
320 return left.index < right.index ? -1 : 1;
321 }), 'value');
322 };
323
324 // An internal function used for aggregate "group by" operations.
325 var group = function(obj, value, context, behavior) {
326 var result = {};
327 var iterator = lookupIterator(value || _.identity);
328 each(obj, function(value, index) {
329 var key = iterator.call(context, value, index, obj);
330 behavior(result, key, value);
331 });
332 return result;
333 };
334
335 // Groups the object's values by a criterion. Pass either a string attribute
336 // to group by, or a function that returns the criterion.
337 _.groupBy = function(obj, value, context) {
338 return group(obj, value, context, function(result, key, value) {
339 (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
340 });
341 };
342
343 // Counts instances of an object that group by a certain criterion. Pass
344 // either a string attribute to count by, or a function that returns the
345 // criterion.
346 _.countBy = function(obj, value, context) {
347 return group(obj, value, context, function(result, key) {
348 if (!_.has(result, key)) result[key] = 0;
349 result[key]++;
350 });
351 };
352
353 // Use a comparator function to figure out the smallest index at which
354 // an object should be inserted so as to maintain order. Uses binary search.
355 _.sortedIndex = function(array, obj, iterator, context) {
356 iterator = iterator == null ? _.identity : lookupIterator(iterator);
357 var value = iterator.call(context, obj);
358 var low = 0, high = array.length;
359 while (low < high) {
360 var mid = (low + high) >>> 1;
361 iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
362 }
363 return low;
364 };
365
366 // Safely convert anything iterable into a real, live array.
367 _.toArray = function(obj) {
368 if (!obj) return [];
369 if (_.isArray(obj)) return slice.call(obj);
370 if (obj.length === +obj.length) return _.map(obj, _.identity);
371 return _.values(obj);
372 };
373
374 // Return the number of elements in an object.
375 _.size = function(obj) {
376 if (obj == null) return 0;
377 return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
378 };
379
380 // Array Functions
381 // ---------------
382
383 // Get the first element of an array. Passing **n** will return the first N
384 // values in the array. Aliased as `head` and `take`. The **guard** check
385 // allows it to work with `_.map`.
386 _.first = _.head = _.take = function(array, n, guard) {
387 if (array == null) return void 0;
388 return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
389 };
390
391 // Returns everything but the last entry of the array. Especially useful on
392 // the arguments object. Passing **n** will return all the values in
393 // the array, excluding the last N. The **guard** check allows it to work with
394 // `_.map`.
395 _.initial = function(array, n, guard) {
396 return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
397 };
398
399 // Get the last element of an array. Passing **n** will return the last N
400 // values in the array. The **guard** check allows it to work with `_.map`.
401 _.last = function(array, n, guard) {
402 if (array == null) return void 0;
403 if ((n != null) && !guard) {
404 return slice.call(array, Math.max(array.length - n, 0));
405 } else {
406 return array[array.length - 1];
407 }
408 };
409
410 // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
411 // Especially useful on the arguments object. Passing an **n** will return
412 // the rest N values in the array. The **guard**
413 // check allows it to work with `_.map`.
414 _.rest = _.tail = _.drop = function(array, n, guard) {
415 return slice.call(array, (n == null) || guard ? 1 : n);
416 };
417
418 // Trim out all falsy values from an array.
419 _.compact = function(array) {
420 return _.filter(array, _.identity);
421 };
422
423 // Internal implementation of a recursive `flatten` function.
424 var flatten = function(input, shallow, output) {
425 each(input, function(value) {
426 if (_.isArray(value)) {
427 shallow ? push.apply(output, value) : flatten(value, shallow, output);
428 } else {
429 output.push(value);
430 }
431 });
432 return output;
433 };
434
435 // Return a completely flattened version of an array.
436 _.flatten = function(array, shallow) {
437 return flatten(array, shallow, []);
438 };
439
440 // Return a version of the array that does not contain the specified value(s).
441 _.without = function(array) {
442 return _.difference(array, slice.call(arguments, 1));
443 };
444
445 // Produce a duplicate-free version of the array. If the array has already
446 // been sorted, you have the option of using a faster algorithm.
447 // Aliased as `unique`.
448 _.uniq = _.unique = function(array, isSorted, iterator, context) {
449 if (_.isFunction(isSorted)) {
450 context = iterator;
451 iterator = isSorted;
452 isSorted = false;
453 }
454 var initial = iterator ? _.map(array, iterator, context) : array;
455 var results = [];
456 var seen = [];
457 each(initial, function(value, index) {
458 if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
459 seen.push(value);
460 results.push(array[index]);
461 }
462 });
463 return results;
464 };
465
466 // Produce an array that contains the union: each distinct element from all of
467 // the passed-in arrays.
468 _.union = function() {
469 return _.uniq(concat.apply(ArrayProto, arguments));
470 };
471
472 // Produce an array that contains every item shared between all the
473 // passed-in arrays.
474 _.intersection = function(array) {
475 var rest = slice.call(arguments, 1);
476 return _.filter(_.uniq(array), function(item) {
477 return _.every(rest, function(other) {
478 return _.indexOf(other, item) >= 0;
479 });
480 });
481 };
482
483 // Take the difference between one array and a number of other arrays.
484 // Only the elements present in just the first array will remain.
485 _.difference = function(array) {
486 var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
487 return _.filter(array, function(value){ return !_.contains(rest, value); });
488 };
489
490 // Zip together multiple lists into a single array -- elements that share
491 // an index go together.
492 _.zip = function() {
493 var args = slice.call(arguments);
494 var length = _.max(_.pluck(args, 'length'));
495 var results = new Array(length);
496 for (var i = 0; i < length; i++) {
497 results[i] = _.pluck(args, "" + i);
498 }
499 return results;
500 };
501
502 // Converts lists into objects. Pass either a single array of `[key, value]`
503 // pairs, or two parallel arrays of the same length -- one of keys, and one of
504 // the corresponding values.
505 _.object = function(list, values) {
506 if (list == null) return {};
507 var result = {};
508 for (var i = 0, l = list.length; i < l; i++) {
509 if (values) {
510 result[list[i]] = values[i];
511 } else {
512 result[list[i][0]] = list[i][1];
513 }
514 }
515 return result;
516 };
517
518 // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
519 // we need this function. Return the position of the first occurrence of an
520 // item in an array, or -1 if the item is not included in the array.
521 // Delegates to **ECMAScript 5**'s native `indexOf` if available.
522 // If the array is large and already in sort order, pass `true`
523 // for **isSorted** to use binary search.
524 _.indexOf = function(array, item, isSorted) {
525 if (array == null) return -1;
526 var i = 0, l = array.length;
527 if (isSorted) {
528 if (typeof isSorted == 'number') {
529 i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
530 } else {
531 i = _.sortedIndex(array, item);
532 return array[i] === item ? i : -1;
533 }
534 }
535 if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
536 for (; i < l; i++) if (array[i] === item) return i;
537 return -1;
538 };
539
540 // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
541 _.lastIndexOf = function(array, item, from) {
542 if (array == null) return -1;
543 var hasIndex = from != null;
544 if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
545 return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
546 }
547 var i = (hasIndex ? from : array.length);
548 while (i--) if (array[i] === item) return i;
549 return -1;
550 };
551
552 // Generate an integer Array containing an arithmetic progression. A port of
553 // the native Python `range()` function. See
554 // [the Python documentation](http://docs.python.org/library/functions.html#range).
555 _.range = function(start, stop, step) {
556 if (arguments.length <= 1) {
557 stop = start || 0;
558 start = 0;
559 }
560 step = arguments[2] || 1;
561
562 var len = Math.max(Math.ceil((stop - start) / step), 0);
563 var idx = 0;
564 var range = new Array(len);
565
566 while(idx < len) {
567 range[idx++] = start;
568 start += step;
569 }
570
571 return range;
572 };
573
574 // Function (ahem) Functions
575 // ------------------
576
577 // Create a function bound to a given object (assigning `this`, and arguments,
578 // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
579 // available.
580 _.bind = function(func, context) {
581 if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
582 var args = slice.call(arguments, 2);
583 return function() {
584 return func.apply(context, args.concat(slice.call(arguments)));
585 };
586 };
587
588 // Partially apply a function by creating a version that has had some of its
589 // arguments pre-filled, without changing its dynamic `this` context.
590 _.partial = function(func) {
591 var args = slice.call(arguments, 1);
592 return function() {
593 return func.apply(this, args.concat(slice.call(arguments)));
594 };
595 };
596
597 // Bind all of an object's methods to that object. Useful for ensuring that
598 // all callbacks defined on an object belong to it.
599 _.bindAll = function(obj) {
600 var funcs = slice.call(arguments, 1);
601 if (funcs.length === 0) funcs = _.functions(obj);
602 each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
603 return obj;
604 };
605
606 // Memoize an expensive function by storing its results.
607 _.memoize = function(func, hasher) {
608 var memo = {};
609 hasher || (hasher = _.identity);
610 return function() {
611 var key = hasher.apply(this, arguments);
612 return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
613 };
614 };
615
616 // Delays a function for the given number of milliseconds, and then calls
617 // it with the arguments supplied.
618 _.delay = function(func, wait) {
619 var args = slice.call(arguments, 2);
620 return setTimeout(function(){ return func.apply(null, args); }, wait);
621 };
622
623 // Defers a function, scheduling it to run after the current call stack has
624 // cleared.
625 _.defer = function(func) {
626 return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
627 };
628
629 // Returns a function, that, when invoked, will only be triggered at most once
630 // during a given window of time.
631 _.throttle = function(func, wait) {
632 var context, args, timeout, result;
633 var previous = 0;
634 var later = function() {
635 previous = new Date;
636 timeout = null;
637 result = func.apply(context, args);
638 };
639 return function() {
640 var now = new Date;
641 var remaining = wait - (now - previous);
642 context = this;
643 args = arguments;
644 if (remaining <= 0) {
645 clearTimeout(timeout);
646 timeout = null;
647 previous = now;
648 result = func.apply(context, args);
649 } else if (!timeout) {
650 timeout = setTimeout(later, remaining);
651 }
652 return result;
653 };
654 };
655
656 // Returns a function, that, as long as it continues to be invoked, will not
657 // be triggered. The function will be called after it stops being called for
658 // N milliseconds. If `immediate` is passed, trigger the function on the
659 // leading edge, instead of the trailing.
660 _.debounce = function(func, wait, immediate) {
661 var timeout, result;
662 return function() {
663 var context = this, args = arguments;
664 var later = function() {
665 timeout = null;
666 if (!immediate) result = func.apply(context, args);
667 };
668 var callNow = immediate && !timeout;
669 clearTimeout(timeout);
670 timeout = setTimeout(later, wait);
671 if (callNow) result = func.apply(context, args);
672 return result;
673 };
674 };
675
676 // Returns a function that will be executed at most one time, no matter how
677 // often you call it. Useful for lazy initialization.
678 _.once = function(func) {
679 var ran = false, memo;
680 return function() {
681 if (ran) return memo;
682 ran = true;
683 memo = func.apply(this, arguments);
684 func = null;
685 return memo;
686 };
687 };
688
689 // Returns the first function passed as an argument to the second,
690 // allowing you to adjust arguments, run code before and after, and
691 // conditionally execute the original function.
692 _.wrap = function(func, wrapper) {
693 return function() {
694 var args = [func];
695 push.apply(args, arguments);
696 return wrapper.apply(this, args);
697 };
698 };
699
700 // Returns a function that is the composition of a list of functions, each
701 // consuming the return value of the function that follows.
702 _.compose = function() {
703 var funcs = arguments;
704 return function() {
705 var args = arguments;
706 for (var i = funcs.length - 1; i >= 0; i--) {
707 args = [funcs[i].apply(this, args)];
708 }
709 return args[0];
710 };
711 };
712
713 // Returns a function that will only be executed after being called N times.
714 _.after = function(times, func) {
715 if (times <= 0) return func();
716 return function() {
717 if (--times < 1) {
718 return func.apply(this, arguments);
719 }
720 };
721 };
722
723 // Object Functions
724 // ----------------
725
726 // Retrieve the names of an object's properties.
727 // Delegates to **ECMAScript 5**'s native `Object.keys`
728 _.keys = nativeKeys || function(obj) {
729 if (obj !== Object(obj)) throw new TypeError('Invalid object');
730 var keys = [];
731 for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
732 return keys;
733 };
734
735 // Retrieve the values of an object's properties.
736 _.values = function(obj) {
737 var values = [];
738 for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
739 return values;
740 };
741
742 // Convert an object into a list of `[key, value]` pairs.
743 _.pairs = function(obj) {
744 var pairs = [];
745 for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
746 return pairs;
747 };
748
749 // Invert the keys and values of an object. The values must be serializable.
750 _.invert = function(obj) {
751 var result = {};
752 for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
753 return result;
754 };
755
756 // Return a sorted list of the function names available on the object.
757 // Aliased as `methods`
758 _.functions = _.methods = function(obj) {
759 var names = [];
760 for (var key in obj) {
761 if (_.isFunction(obj[key])) names.push(key);
762 }
763 return names.sort();
764 };
765
766 // Extend a given object with all the properties in passed-in object(s).
767 _.extend = function(obj) {
768 each(slice.call(arguments, 1), function(source) {
769 if (source) {
770 for (var prop in source) {
771 obj[prop] = source[prop];
772 }
773 }
774 });
775 return obj;
776 };
777
778 // Return a copy of the object only containing the whitelisted properties.
779 _.pick = function(obj) {
780 var copy = {};
781 var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
782 each(keys, function(key) {
783 if (key in obj) copy[key] = obj[key];
784 });
785 return copy;
786 };
787
788 // Return a copy of the object without the blacklisted properties.
789 _.omit = function(obj) {
790 var copy = {};
791 var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
792 for (var key in obj) {
793 if (!_.contains(keys, key)) copy[key] = obj[key];
794 }
795 return copy;
796 };
797
798 // Fill in a given object with default properties.
799 _.defaults = function(obj) {
800 each(slice.call(arguments, 1), function(source) {
801 if (source) {
802 for (var prop in source) {
803 if (obj[prop] == null) obj[prop] = source[prop];
804 }
805 }
806 });
807 return obj;
808 };
809
810 // Create a (shallow-cloned) duplicate of an object.
811 _.clone = function(obj) {
812 if (!_.isObject(obj)) return obj;
813 return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
814 };
815
816 // Invokes interceptor with the obj, and then returns obj.
817 // The primary purpose of this method is to "tap into" a method chain, in
818 // order to perform operations on intermediate results within the chain.
819 _.tap = function(obj, interceptor) {
820 interceptor(obj);
821 return obj;
822 };
823
824 // Internal recursive comparison function for `isEqual`.
825 var eq = function(a, b, aStack, bStack) {
826 // Identical objects are equal. `0 === -0`, but they aren't identical.
827 // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
828 if (a === b) return a !== 0 || 1 / a == 1 / b;
829 // A strict comparison is necessary because `null == undefined`.
830 if (a == null || b == null) return a === b;
831 // Unwrap any wrapped objects.
832 if (a instanceof _) a = a._wrapped;
833 if (b instanceof _) b = b._wrapped;
834 // Compare `[[Class]]` names.
835 var className = toString.call(a);
836 if (className != toString.call(b)) return false;
837 switch (className) {
838 // Strings, numbers, dates, and booleans are compared by value.
839 case '[object String]':
840 // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
841 // equivalent to `new String("5")`.
842 return a == String(b);
843 case '[object Number]':
844 // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
845 // other numeric values.
846 return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
847 case '[object Date]':
848 case '[object Boolean]':
849 // Coerce dates and booleans to numeric primitive values. Dates are compared by their
850 // millisecond representations. Note that invalid dates with millisecond representations
851 // of `NaN` are not equivalent.
852 return +a == +b;
853 // RegExps are compared by their source patterns and flags.
854 case '[object RegExp]':
855 return a.source == b.source &&
856 a.global == b.global &&
857 a.multiline == b.multiline &&
858 a.ignoreCase == b.ignoreCase;
859 }
860 if (typeof a != 'object' || typeof b != 'object') return false;
861 // Assume equality for cyclic structures. The algorithm for detecting cyclic
862 // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
863 var length = aStack.length;
864 while (length--) {
865 // Linear search. Performance is inversely proportional to the number of
866 // unique nested structures.
867 if (aStack[length] == a) return bStack[length] == b;
868 }
869 // Add the first object to the stack of traversed objects.
870 aStack.push(a);
871 bStack.push(b);
872 var size = 0, result = true;
873 // Recursively compare objects and arrays.
874 if (className == '[object Array]') {
875 // Compare array lengths to determine if a deep comparison is necessary.
876 size = a.length;
877 result = size == b.length;
878 if (result) {
879 // Deep compare the contents, ignoring non-numeric properties.
880 while (size--) {
881 if (!(result = eq(a[size], b[size], aStack, bStack))) break;
882 }
883 }
884 } else {
885 // Objects with different constructors are not equivalent, but `Object`s
886 // from different frames are.
887 var aCtor = a.constructor, bCtor = b.constructor;
888 if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
889 _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
890 return false;
891 }
892 // Deep compare objects.
893 for (var key in a) {
894 if (_.has(a, key)) {
895 // Count the expected number of properties.
896 size++;
897 // Deep compare each member.
898 if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
899 }
900 }
901 // Ensure that both objects contain the same number of properties.
902 if (result) {
903 for (key in b) {
904 if (_.has(b, key) && !(size--)) break;
905 }
906 result = !size;
907 }
908 }
909 // Remove the first object from the stack of traversed objects.
910 aStack.pop();
911 bStack.pop();
912 return result;
913 };
914
915 // Perform a deep comparison to check if two objects are equal.
916 _.isEqual = function(a, b) {
917 return eq(a, b, [], []);
918 };
919
920 // Is a given array, string, or object empty?
921 // An "empty" object has no enumerable own-properties.
922 _.isEmpty = function(obj) {
923 if (obj == null) return true;
924 if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
925 for (var key in obj) if (_.has(obj, key)) return false;
926 return true;
927 };
928
929 // Is a given value a DOM element?
930 _.isElement = function(obj) {
931 return !!(obj && obj.nodeType === 1);
932 };
933
934 // Is a given value an array?
935 // Delegates to ECMA5's native Array.isArray
936 _.isArray = nativeIsArray || function(obj) {
937 return toString.call(obj) == '[object Array]';
938 };
939
940 // Is a given variable an object?
941 _.isObject = function(obj) {
942 return obj === Object(obj);
943 };
944
945 // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
946 each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
947 _['is' + name] = function(obj) {
948 return toString.call(obj) == '[object ' + name + ']';
949 };
950 });
951
952 // Define a fallback version of the method in browsers (ahem, IE), where
953 // there isn't any inspectable "Arguments" type.
954 if (!_.isArguments(arguments)) {
955 _.isArguments = function(obj) {
956 return !!(obj && _.has(obj, 'callee'));
957 };
958 }
959
960 // Optimize `isFunction` if appropriate.
961 if (typeof (/./) !== 'function') {
962 _.isFunction = function(obj) {
963 return typeof obj === 'function';
964 };
965 }
966
967 // Is a given object a finite number?
968 _.isFinite = function(obj) {
969 return isFinite(obj) && !isNaN(parseFloat(obj));
970 };
971
972 // Is the given value `NaN`? (NaN is the only number which does not equal itself).
973 _.isNaN = function(obj) {
974 return _.isNumber(obj) && obj != +obj;
975 };
976
977 // Is a given value a boolean?
978 _.isBoolean = function(obj) {
979 return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
980 };
981
982 // Is a given value equal to null?
983 _.isNull = function(obj) {
984 return obj === null;
985 };
986
987 // Is a given variable undefined?
988 _.isUndefined = function(obj) {
989 return obj === void 0;
990 };
991
992 // Shortcut function for checking if an object has a given property directly
993 // on itself (in other words, not on a prototype).
994 _.has = function(obj, key) {
995 return hasOwnProperty.call(obj, key);
996 };
997
998 // Utility Functions
999 // -----------------
1000
1001 // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1002 // previous owner. Returns a reference to the Underscore object.
1003 _.noConflict = function() {
1004 root._ = previousUnderscore;
1005 return this;
1006 };
1007
1008 // Keep the identity function around for default iterators.
1009 _.identity = function(value) {
1010 return value;
1011 };
1012
1013 // Run a function **n** times.
1014 _.times = function(n, iterator, context) {
1015 var accum = Array(n);
1016 for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1017 return accum;
1018 };
1019
1020 // Return a random integer between min and max (inclusive).
1021 _.random = function(min, max) {
1022 if (max == null) {
1023 max = min;
1024 min = 0;
1025 }
1026 return min + Math.floor(Math.random() * (max - min + 1));
1027 };
1028
1029 // List of HTML entities for escaping.
1030 var entityMap = {
1031 escape: {
1032 '&': '&',
1033 '<': '<',
1034 '>': '>',
1035 '"': '"',
1036 "'": ''',
1037 '/': '/'
1038 }
1039 };
1040 entityMap.unescape = _.invert(entityMap.escape);
1041
1042 // Regexes containing the keys and values listed immediately above.
1043 var entityRegexes = {
1044 escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1045 unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1046 };
1047
1048 // Functions for escaping and unescaping strings to/from HTML interpolation.
1049 _.each(['escape', 'unescape'], function(method) {
1050 _[method] = function(string) {
1051 if (string == null) return '';
1052 return ('' + string).replace(entityRegexes[method], function(match) {
1053 return entityMap[method][match];
1054 });
1055 };
1056 });
1057
1058 // If the value of the named property is a function then invoke it;
1059 // otherwise, return it.
1060 _.result = function(object, property) {
1061 if (object == null) return null;
1062 var value = object[property];
1063 return _.isFunction(value) ? value.call(object) : value;
1064 };
1065
1066 // Add your own custom functions to the Underscore object.
1067 _.mixin = function(obj) {
1068 each(_.functions(obj), function(name){
1069 var func = _[name] = obj[name];
1070 _.prototype[name] = function() {
1071 var args = [this._wrapped];
1072 push.apply(args, arguments);
1073 return result.call(this, func.apply(_, args));
1074 };
1075 });
1076 };
1077
1078 // Generate a unique integer id (unique within the entire client session).
1079 // Useful for temporary DOM ids.
1080 var idCounter = 0;
1081 _.uniqueId = function(prefix) {
1082 var id = ++idCounter + '';
1083 return prefix ? prefix + id : id;
1084 };
1085
1086 // By default, Underscore uses ERB-style template delimiters, change the
1087 // following template settings to use alternative delimiters.
1088 _.templateSettings = {
1089 evaluate : /<%([\s\S]+?)%>/g,
1090 interpolate : /<%=([\s\S]+?)%>/g,
1091 escape : /<%-([\s\S]+?)%>/g
1092 };
1093
1094 // When customizing `templateSettings`, if you don't want to define an
1095 // interpolation, evaluation or escaping regex, we need one that is
1096 // guaranteed not to match.
1097 var noMatch = /(.)^/;
1098
1099 // Certain characters need to be escaped so that they can be put into a
1100 // string literal.
1101 var escapes = {
1102 "'": "'",
1103 '\\': '\\',
1104 '\r': 'r',
1105 '\n': 'n',
1106 '\t': 't',
1107 '\u2028': 'u2028',
1108 '\u2029': 'u2029'
1109 };
1110
1111 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
1112
1113 // JavaScript micro-templating, similar to John Resig's implementation.
1114 // Underscore templating handles arbitrary delimiters, preserves whitespace,
1115 // and correctly escapes quotes within interpolated code.
1116 _.template = function(text, data, settings) {
1117 var render;
1118 settings = _.defaults({}, settings, _.templateSettings);
1119
1120 // Combine delimiters into one regular expression via alternation.
1121 var matcher = new RegExp([
1122 (settings.escape || noMatch).source,
1123 (settings.interpolate || noMatch).source,
1124 (settings.evaluate || noMatch).source
1125 ].join('|') + '|$', 'g');
1126
1127 // Compile the template source, escaping string literals appropriately.
1128 var index = 0;
1129 var source = "__p+='";
1130 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1131 source += text.slice(index, offset)
1132 .replace(escaper, function(match) { return '\\' + escapes[match]; });
1133
1134 if (escape) {
1135 source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1136 }
1137 if (interpolate) {
1138 source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1139 }
1140 if (evaluate) {
1141 source += "';\n" + evaluate + "\n__p+='";
1142 }
1143 index = offset + match.length;
1144 return match;
1145 });
1146 source += "';\n";
1147
1148 // If a variable is not specified, place data values in local scope.
1149 if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1150
1151 source = "var __t,__p='',__j=Array.prototype.join," +
1152 "print=function(){__p+=__j.call(arguments,'');};\n" +
1153 source + "return __p;\n";
1154
1155 try {
1156 render = new Function(settings.variable || 'obj', '_', source);
1157 } catch (e) {
1158 e.source = source;
1159 throw e;
1160 }
1161
1162 if (data) return render(data, _);
1163 var template = function(data) {
1164 return render.call(this, data, _);
1165 };
1166
1167 // Provide the compiled function source as a convenience for precompilation.
1168 template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
1169
1170 return template;
1171 };
1172
1173 // Add a "chain" function, which will delegate to the wrapper.
1174 _.chain = function(obj) {
1175 return _(obj).chain();
1176 };
1177
1178 // OOP
1179 // ---------------
1180 // If Underscore is called as a function, it returns a wrapped object that
1181 // can be used OO-style. This wrapper holds altered versions of all the
1182 // underscore functions. Wrapped objects may be chained.
1183
1184 // Helper function to continue chaining intermediate results.
1185 var result = function(obj) {
1186 return this._chain ? _(obj).chain() : obj;
1187 };
1188
1189 // Add all of the Underscore functions to the wrapper object.
1190 _.mixin(_);
1191
1192 // Add all mutator Array functions to the wrapper.
1193 each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1194 var method = ArrayProto[name];
1195 _.prototype[name] = function() {
1196 var obj = this._wrapped;
1197 method.apply(obj, arguments);
1198 if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1199 return result.call(this, obj);
1200 };
1201 });
1202
1203 // Add all accessor Array functions to the wrapper.
1204 each(['concat', 'join', 'slice'], function(name) {
1205 var method = ArrayProto[name];
1206 _.prototype[name] = function() {
1207 return result.call(this, method.apply(this._wrapped, arguments));
1208 };
1209 });
1210
1211 _.extend(_.prototype, {
1212
1213 // Start chaining a wrapped Underscore object.
1214 chain: function() {
1215 this._chain = true;
1216 return this;
1217 },
1218
1219 // Extracts the result from a wrapped and chained object.
1220 value: function() {
1221 return this._wrapped;
1222 }
1223
1224 });
1225
1226 }).call(this);
Category: 主干
-

WordPress 源代码——主干(underscore-1.4.4.js)
-

WordPress 源代码——主干(underscore-1.4.1.js)
// Underscore.js 1.4.1 2 // http://underscorejs.org 3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 // Underscore may be freely distributed under the MIT license. 5 6 (function() { 7 8 // Baseline setup 9 // -------------- 10 11 // Establish the root object, `window` in the browser, or `global` on the server. 12 var root = this; 13 14 // Save the previous value of the `_` variable. 15 var previousUnderscore = root._; 16 17 // Establish the object that gets returned to break out of a loop iteration. 18 var breaker = {}; 19 20 // Save bytes in the minified (but not gzipped) version: 21 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 23 // Create quick reference variables for speed access to core prototypes. 24 var push = ArrayProto.push, 25 slice = ArrayProto.slice, 26 concat = ArrayProto.concat, 27 unshift = ArrayProto.unshift, 28 toString = ObjProto.toString, 29 hasOwnProperty = ObjProto.hasOwnProperty; 30 31 // All **ECMAScript 5** native function implementations that we hope to use 32 // are declared here. 33 var 34 nativeForEach = ArrayProto.forEach, 35 nativeMap = ArrayProto.map, 36 nativeReduce = ArrayProto.reduce, 37 nativeReduceRight = ArrayProto.reduceRight, 38 nativeFilter = ArrayProto.filter, 39 nativeEvery = ArrayProto.every, 40 nativeSome = ArrayProto.some, 41 nativeIndexOf = ArrayProto.indexOf, 42 nativeLastIndexOf = ArrayProto.lastIndexOf, 43 nativeIsArray = Array.isArray, 44 nativeKeys = Object.keys, 45 nativeBind = FuncProto.bind; 46 47 // Create a safe reference to the Underscore object for use below. 48 var _ = function(obj) { 49 if (obj instanceof _) return obj; 50 if (!(this instanceof _)) return new _(obj); 51 this._wrapped = obj; 52 }; 53 54 // Export the Underscore object for **Node.js**, with 55 // backwards-compatibility for the old `require()` API. If we're in 56 // the browser, add `_` as a global object via a string identifier, 57 // for Closure Compiler "advanced" mode. 58 if (typeof exports !== 'undefined') { 59 if (typeof module !== 'undefined' && module.exports) { 60 exports = module.exports = _; 61 } 62 exports._ = _; 63 } else { 64 root['_'] = _; 65 } 66 67 // Current version. 68 _.VERSION = '1.4.1'; 69 70 // Collection Functions 71 // -------------------- 72 73 // The cornerstone, an `each` implementation, aka `forEach`. 74 // Handles objects with the built-in `forEach`, arrays, and raw objects. 75 // Delegates to **ECMAScript 5**'s native `forEach` if available. 76 var each = _.each = _.forEach = function(obj, iterator, context) { 77 if (nativeForEach && obj.forEach === nativeForEach) { 78 obj.forEach(iterator, context); 79 } else if (obj.length === +obj.length) { 80 for (var i = 0, l = obj.length; i < l; i++) { 81 if (iterator.call(context, obj[i], i, obj) === breaker) return; 82 } 83 } else { 84 for (var key in obj) { 85 if (_.has(obj, key)) { 86 if (iterator.call(context, obj[key], key, obj) === breaker) return; 87 } 88 } 89 } 90 }; 91 92 // Return the results of applying the iterator to each element. 93 // Delegates to **ECMAScript 5**'s native `map` if available. 94 _.map = _.collect = function(obj, iterator, context) { 95 var results = []; 96 if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 97 each(obj, function(value, index, list) { 98 results[results.length] = iterator.call(context, value, index, list); 99 }); 100 return results; 101 }; 102 103 // **Reduce** builds up a single result from a list of values, aka `inject`, 104 // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 105 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 106 var initial = arguments.length > 2; 107 if (nativeReduce && obj.reduce === nativeReduce) { 108 if (context) iterator = _.bind(iterator, context); 109 return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 110 } 111 each(obj, function(value, index, list) { 112 if (!initial) { 113 memo = value; 114 initial = true; 115 } else { 116 memo = iterator.call(context, memo, value, index, list); 117 } 118 }); 119 if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 120 return memo; 121 }; 122 123 // The right-associative version of reduce, also known as `foldr`. 124 // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 125 _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 126 var initial = arguments.length > 2; 127 if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 128 if (context) iterator = _.bind(iterator, context); 129 return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 130 } 131 var length = obj.length; 132 if (length !== +length) { 133 var keys = _.keys(obj); 134 length = keys.length; 135 } 136 each(obj, function(value, index, list) { 137 index = keys ? keys[--length] : --length; 138 if (!initial) { 139 memo = obj[index]; 140 initial = true; 141 } else { 142 memo = iterator.call(context, memo, obj[index], index, list); 143 } 144 }); 145 if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 146 return memo; 147 }; 148 149 // Return the first value which passes a truth test. Aliased as `detect`. 150 _.find = _.detect = function(obj, iterator, context) { 151 var result; 152 any(obj, function(value, index, list) { 153 if (iterator.call(context, value, index, list)) { 154 result = value; 155 return true; 156 } 157 }); 158 return result; 159 }; 160 161 // Return all the elements that pass a truth test. 162 // Delegates to **ECMAScript 5**'s native `filter` if available. 163 // Aliased as `select`. 164 _.filter = _.select = function(obj, iterator, context) { 165 var results = []; 166 if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 167 each(obj, function(value, index, list) { 168 if (iterator.call(context, value, index, list)) results[results.length] = value; 169 }); 170 return results; 171 }; 172 173 // Return all the elements for which a truth test fails. 174 _.reject = function(obj, iterator, context) { 175 var results = []; 176 each(obj, function(value, index, list) { 177 if (!iterator.call(context, value, index, list)) results[results.length] = value; 178 }); 179 return results; 180 }; 181 182 // Determine whether all of the elements match a truth test. 183 // Delegates to **ECMAScript 5**'s native `every` if available. 184 // Aliased as `all`. 185 _.every = _.all = function(obj, iterator, context) { 186 iterator || (iterator = _.identity); 187 var result = true; 188 if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 189 each(obj, function(value, index, list) { 190 if (!(result = result && iterator.call(context, value, index, list))) return breaker; 191 }); 192 return !!result; 193 }; 194 195 // Determine if at least one element in the object matches a truth test. 196 // Delegates to **ECMAScript 5**'s native `some` if available. 197 // Aliased as `any`. 198 var any = _.some = _.any = function(obj, iterator, context) { 199 iterator || (iterator = _.identity); 200 var result = false; 201 if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 202 each(obj, function(value, index, list) { 203 if (result || (result = iterator.call(context, value, index, list))) return breaker; 204 }); 205 return !!result; 206 }; 207 208 // Determine if the array or object contains a given value (using `===`). 209 // Aliased as `include`. 210 _.contains = _.include = function(obj, target) { 211 var found = false; 212 if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 213 found = any(obj, function(value) { 214 return value === target; 215 }); 216 return found; 217 }; 218 219 // Invoke a method (with arguments) on every item in a collection. 220 _.invoke = function(obj, method) { 221 var args = slice.call(arguments, 2); 222 return _.map(obj, function(value) { 223 return (_.isFunction(method) ? method : value[method]).apply(value, args); 224 }); 225 }; 226 227 // Convenience version of a common use case of `map`: fetching a property. 228 _.pluck = function(obj, key) { 229 return _.map(obj, function(value){ return value[key]; }); 230 }; 231 232 // Convenience version of a common use case of `filter`: selecting only objects 233 // with specific `key:value` pairs. 234 _.where = function(obj, attrs) { 235 if (_.isEmpty(attrs)) return []; 236 return _.filter(obj, function(value) { 237 for (var key in attrs) { 238 if (attrs[key] !== value[key]) return false; 239 } 240 return true; 241 }); 242 }; 243 244 // Return the maximum element or (element-based computation). 245 // Can't optimize arrays of integers longer than 65,535 elements. 246 // See: https://bugs.webkit.org/show_bug.cgi?id=80797 247 _.max = function(obj, iterator, context) { 248 if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 249 return Math.max.apply(Math, obj); 250 } 251 if (!iterator && _.isEmpty(obj)) return -Infinity; 252 var result = {computed : -Infinity}; 253 each(obj, function(value, index, list) { 254 var computed = iterator ? iterator.call(context, value, index, list) : value; 255 computed >= result.computed && (result = {value : value, computed : computed}); 256 }); 257 return result.value; 258 }; 259 260 // Return the minimum element (or element-based computation). 261 _.min = function(obj, iterator, context) { 262 if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 263 return Math.min.apply(Math, obj); 264 } 265 if (!iterator && _.isEmpty(obj)) return Infinity; 266 var result = {computed : Infinity}; 267 each(obj, function(value, index, list) { 268 var computed = iterator ? iterator.call(context, value, index, list) : value; 269 computed < result.computed && (result = {value : value, computed : computed}); 270 }); 271 return result.value; 272 }; 273 274 // Shuffle an array. 275 _.shuffle = function(obj) { 276 var rand; 277 var index = 0; 278 var shuffled = []; 279 each(obj, function(value) { 280 rand = _.random(index++); 281 shuffled[index - 1] = shuffled[rand]; 282 shuffled[rand] = value; 283 }); 284 return shuffled; 285 }; 286 287 // An internal function to generate lookup iterators. 288 var lookupIterator = function(value) { 289 return _.isFunction(value) ? value : function(obj){ return obj[value]; }; 290 }; 291 292 // Sort the object's values by a criterion produced by an iterator. 293 _.sortBy = function(obj, value, context) { 294 var iterator = lookupIterator(value); 295 return _.pluck(_.map(obj, function(value, index, list) { 296 return { 297 value : value, 298 index : index, 299 criteria : iterator.call(context, value, index, list) 300 }; 301 }).sort(function(left, right) { 302 var a = left.criteria; 303 var b = right.criteria; 304 if (a !== b) { 305 if (a > b || a === void 0) return 1; 306 if (a < b || b === void 0) return -1; 307 } 308 return left.index < right.index ? -1 : 1; 309 }), 'value'); 310 }; 311 312 // An internal function used for aggregate "group by" operations. 313 var group = function(obj, value, context, behavior) { 314 var result = {}; 315 var iterator = lookupIterator(value); 316 each(obj, function(value, index) { 317 var key = iterator.call(context, value, index, obj); 318 behavior(result, key, value); 319 }); 320 return result; 321 }; 322 323 // Groups the object's values by a criterion. Pass either a string attribute 324 // to group by, or a function that returns the criterion. 325 _.groupBy = function(obj, value, context) { 326 return group(obj, value, context, function(result, key, value) { 327 (_.has(result, key) ? result[key] : (result[key] = [])).push(value); 328 }); 329 }; 330 331 // Counts instances of an object that group by a certain criterion. Pass 332 // either a string attribute to count by, or a function that returns the 333 // criterion. 334 _.countBy = function(obj, value, context) { 335 return group(obj, value, context, function(result, key, value) { 336 if (!_.has(result, key)) result[key] = 0; 337 result[key]++; 338 }); 339 }; 340 341 // Use a comparator function to figure out the smallest index at which 342 // an object should be inserted so as to maintain order. Uses binary search. 343 _.sortedIndex = function(array, obj, iterator, context) { 344 iterator = iterator == null ? _.identity : lookupIterator(iterator); 345 var value = iterator.call(context, obj); 346 var low = 0, high = array.length; 347 while (low < high) { 348 var mid = (low + high) >>> 1; 349 iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 350 } 351 return low; 352 }; 353 354 // Safely convert anything iterable into a real, live array. 355 _.toArray = function(obj) { 356 if (!obj) return []; 357 if (obj.length === +obj.length) return slice.call(obj); 358 return _.values(obj); 359 }; 360 361 // Return the number of elements in an object. 362 _.size = function(obj) { 363 return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 364 }; 365 366 // Array Functions 367 // --------------- 368 369 // Get the first element of an array. Passing **n** will return the first N 370 // values in the array. Aliased as `head` and `take`. The **guard** check 371 // allows it to work with `_.map`. 372 _.first = _.head = _.take = function(array, n, guard) { 373 return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 374 }; 375 376 // Returns everything but the last entry of the array. Especially useful on 377 // the arguments object. Passing **n** will return all the values in 378 // the array, excluding the last N. The **guard** check allows it to work with 379 // `_.map`. 380 _.initial = function(array, n, guard) { 381 return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 382 }; 383 384 // Get the last element of an array. Passing **n** will return the last N 385 // values in the array. The **guard** check allows it to work with `_.map`. 386 _.last = function(array, n, guard) { 387 if ((n != null) && !guard) { 388 return slice.call(array, Math.max(array.length - n, 0)); 389 } else { 390 return array[array.length - 1]; 391 } 392 }; 393 394 // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 395 // Especially useful on the arguments object. Passing an **n** will return 396 // the rest N values in the array. The **guard** 397 // check allows it to work with `_.map`. 398 _.rest = _.tail = _.drop = function(array, n, guard) { 399 return slice.call(array, (n == null) || guard ? 1 : n); 400 }; 401 402 // Trim out all falsy values from an array. 403 _.compact = function(array) { 404 return _.filter(array, function(value){ return !!value; }); 405 }; 406 407 // Internal implementation of a recursive `flatten` function. 408 var flatten = function(input, shallow, output) { 409 each(input, function(value) { 410 if (_.isArray(value)) { 411 shallow ? push.apply(output, value) : flatten(value, shallow, output); 412 } else { 413 output.push(value); 414 } 415 }); 416 return output; 417 }; 418 419 // Return a completely flattened version of an array. 420 _.flatten = function(array, shallow) { 421 return flatten(array, shallow, []); 422 }; 423 424 // Return a version of the array that does not contain the specified value(s). 425 _.without = function(array) { 426 return _.difference(array, slice.call(arguments, 1)); 427 }; 428 429 // Produce a duplicate-free version of the array. If the array has already 430 // been sorted, you have the option of using a faster algorithm. 431 // Aliased as `unique`. 432 _.uniq = _.unique = function(array, isSorted, iterator, context) { 433 var initial = iterator ? _.map(array, iterator, context) : array; 434 var results = []; 435 var seen = []; 436 each(initial, function(value, index) { 437 if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 438 seen.push(value); 439 results.push(array[index]); 440 } 441 }); 442 return results; 443 }; 444 445 // Produce an array that contains the union: each distinct element from all of 446 // the passed-in arrays. 447 _.union = function() { 448 return _.uniq(concat.apply(ArrayProto, arguments)); 449 }; 450 451 // Produce an array that contains every item shared between all the 452 // passed-in arrays. 453 _.intersection = function(array) { 454 var rest = slice.call(arguments, 1); 455 return _.filter(_.uniq(array), function(item) { 456 return _.every(rest, function(other) { 457 return _.indexOf(other, item) >= 0; 458 }); 459 }); 460 }; 461 462 // Take the difference between one array and a number of other arrays. 463 // Only the elements present in just the first array will remain. 464 _.difference = function(array) { 465 var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 466 return _.filter(array, function(value){ return !_.contains(rest, value); }); 467 }; 468 469 // Zip together multiple lists into a single array -- elements that share 470 // an index go together. 471 _.zip = function() { 472 var args = slice.call(arguments); 473 var length = _.max(_.pluck(args, 'length')); 474 var results = new Array(length); 475 for (var i = 0; i < length; i++) { 476 results[i] = _.pluck(args, "" + i); 477 } 478 return results; 479 }; 480 481 // Converts lists into objects. Pass either a single array of `[key, value]` 482 // pairs, or two parallel arrays of the same length -- one of keys, and one of 483 // the corresponding values. 484 _.object = function(list, values) { 485 var result = {}; 486 for (var i = 0, l = list.length; i < l; i++) { 487 if (values) { 488 result[list[i]] = values[i]; 489 } else { 490 result[list[i][0]] = list[i][1]; 491 } 492 } 493 return result; 494 }; 495 496 // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 497 // we need this function. Return the position of the first occurrence of an 498 // item in an array, or -1 if the item is not included in the array. 499 // Delegates to **ECMAScript 5**'s native `indexOf` if available. 500 // If the array is large and already in sort order, pass `true` 501 // for **isSorted** to use binary search. 502 _.indexOf = function(array, item, isSorted) { 503 var i = 0, l = array.length; 504 if (isSorted) { 505 if (typeof isSorted == 'number') { 506 i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); 507 } else { 508 i = _.sortedIndex(array, item); 509 return array[i] === item ? i : -1; 510 } 511 } 512 if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 513 for (; i < l; i++) if (array[i] === item) return i; 514 return -1; 515 }; 516 517 // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 518 _.lastIndexOf = function(array, item, from) { 519 var hasIndex = from != null; 520 if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 521 return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 522 } 523 var i = (hasIndex ? from : array.length); 524 while (i--) if (array[i] === item) return i; 525 return -1; 526 }; 527 528 // Generate an integer Array containing an arithmetic progression. A port of 529 // the native Python `range()` function. See 530 // [the Python documentation](http://docs.python.org/library/functions.html#range). 531 _.range = function(start, stop, step) { 532 if (arguments.length <= 1) { 533 stop = start || 0; 534 start = 0; 535 } 536 step = arguments[2] || 1; 537 538 var len = Math.max(Math.ceil((stop - start) / step), 0); 539 var idx = 0; 540 var range = new Array(len); 541 542 while(idx < len) { 543 range[idx++] = start; 544 start += step; 545 } 546 547 return range; 548 }; 549 550 // Function (ahem) Functions 551 // ------------------ 552 553 // Reusable constructor function for prototype setting. 554 var ctor = function(){}; 555 556 // Create a function bound to a given object (assigning `this`, and arguments, 557 // optionally). Binding with arguments is also known as `curry`. 558 // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 559 // We check for `func.bind` first, to fail fast when `func` is undefined. 560 _.bind = function bind(func, context) { 561 var bound, args; 562 if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 563 if (!_.isFunction(func)) throw new TypeError; 564 args = slice.call(arguments, 2); 565 return bound = function() { 566 if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 567 ctor.prototype = func.prototype; 568 var self = new ctor; 569 var result = func.apply(self, args.concat(slice.call(arguments))); 570 if (Object(result) === result) return result; 571 return self; 572 }; 573 }; 574 575 // Bind all of an object's methods to that object. Useful for ensuring that 576 // all callbacks defined on an object belong to it. 577 _.bindAll = function(obj) { 578 var funcs = slice.call(arguments, 1); 579 if (funcs.length == 0) funcs = _.functions(obj); 580 each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 581 return obj; 582 }; 583 584 // Memoize an expensive function by storing its results. 585 _.memoize = function(func, hasher) { 586 var memo = {}; 587 hasher || (hasher = _.identity); 588 return function() { 589 var key = hasher.apply(this, arguments); 590 return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 591 }; 592 }; 593 594 // Delays a function for the given number of milliseconds, and then calls 595 // it with the arguments supplied. 596 _.delay = function(func, wait) { 597 var args = slice.call(arguments, 2); 598 return setTimeout(function(){ return func.apply(null, args); }, wait); 599 }; 600 601 // Defers a function, scheduling it to run after the current call stack has 602 // cleared. 603 _.defer = function(func) { 604 return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 605 }; 606 607 // Returns a function, that, when invoked, will only be triggered at most once 608 // during a given window of time. 609 _.throttle = function(func, wait) { 610 var context, args, timeout, throttling, more, result; 611 var whenDone = _.debounce(function(){ more = throttling = false; }, wait); 612 return function() { 613 context = this; args = arguments; 614 var later = function() { 615 timeout = null; 616 if (more) { 617 result = func.apply(context, args); 618 } 619 whenDone(); 620 }; 621 if (!timeout) timeout = setTimeout(later, wait); 622 if (throttling) { 623 more = true; 624 } else { 625 throttling = true; 626 result = func.apply(context, args); 627 } 628 whenDone(); 629 return result; 630 }; 631 }; 632 633 // Returns a function, that, as long as it continues to be invoked, will not 634 // be triggered. The function will be called after it stops being called for 635 // N milliseconds. If `immediate` is passed, trigger the function on the 636 // leading edge, instead of the trailing. 637 _.debounce = function(func, wait, immediate) { 638 var timeout, result; 639 return function() { 640 var context = this, args = arguments; 641 var later = function() { 642 timeout = null; 643 if (!immediate) result = func.apply(context, args); 644 }; 645 var callNow = immediate && !timeout; 646 clearTimeout(timeout); 647 timeout = setTimeout(later, wait); 648 if (callNow) result = func.apply(context, args); 649 return result; 650 }; 651 }; 652 653 // Returns a function that will be executed at most one time, no matter how 654 // often you call it. Useful for lazy initialization. 655 _.once = function(func) { 656 var ran = false, memo; 657 return function() { 658 if (ran) return memo; 659 ran = true; 660 memo = func.apply(this, arguments); 661 func = null; 662 return memo; 663 }; 664 }; 665 666 // Returns the first function passed as an argument to the second, 667 // allowing you to adjust arguments, run code before and after, and 668 // conditionally execute the original function. 669 _.wrap = function(func, wrapper) { 670 return function() { 671 var args = [func]; 672 push.apply(args, arguments); 673 return wrapper.apply(this, args); 674 }; 675 }; 676 677 // Returns a function that is the composition of a list of functions, each 678 // consuming the return value of the function that follows. 679 _.compose = function() { 680 var funcs = arguments; 681 return function() { 682 var args = arguments; 683 for (var i = funcs.length - 1; i >= 0; i--) { 684 args = [funcs[i].apply(this, args)]; 685 } 686 return args[0]; 687 }; 688 }; 689 690 // Returns a function that will only be executed after being called N times. 691 _.after = function(times, func) { 692 if (times <= 0) return func(); 693 return function() { 694 if (--times < 1) { 695 return func.apply(this, arguments); 696 } 697 }; 698 }; 699 700 // Object Functions 701 // ---------------- 702 703 // Retrieve the names of an object's properties. 704 // Delegates to **ECMAScript 5**'s native `Object.keys` 705 _.keys = nativeKeys || function(obj) { 706 if (obj !== Object(obj)) throw new TypeError('Invalid object'); 707 var keys = []; 708 for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; 709 return keys; 710 }; 711 712 // Retrieve the values of an object's properties. 713 _.values = function(obj) { 714 var values = []; 715 for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); 716 return values; 717 }; 718 719 // Convert an object into a list of `[key, value]` pairs. 720 _.pairs = function(obj) { 721 var pairs = []; 722 for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); 723 return pairs; 724 }; 725 726 // Invert the keys and values of an object. The values must be serializable. 727 _.invert = function(obj) { 728 var result = {}; 729 for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; 730 return result; 731 }; 732 733 // Return a sorted list of the function names available on the object. 734 // Aliased as `methods` 735 _.functions = _.methods = function(obj) { 736 var names = []; 737 for (var key in obj) { 738 if (_.isFunction(obj[key])) names.push(key); 739 } 740 return names.sort(); 741 }; 742 743 // Extend a given object with all the properties in passed-in object(s). 744 _.extend = function(obj) { 745 each(slice.call(arguments, 1), function(source) { 746 for (var prop in source) { 747 obj[prop] = source[prop]; 748 } 749 }); 750 return obj; 751 }; 752 753 // Return a copy of the object only containing the whitelisted properties. 754 _.pick = function(obj) { 755 var copy = {}; 756 var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 757 each(keys, function(key) { 758 if (key in obj) copy[key] = obj[key]; 759 }); 760 return copy; 761 }; 762 763 // Return a copy of the object without the blacklisted properties. 764 _.omit = function(obj) { 765 var copy = {}; 766 var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 767 for (var key in obj) { 768 if (!_.contains(keys, key)) copy[key] = obj[key]; 769 } 770 return copy; 771 }; 772 773 // Fill in a given object with default properties. 774 _.defaults = function(obj) { 775 each(slice.call(arguments, 1), function(source) { 776 for (var prop in source) { 777 if (obj[prop] == null) obj[prop] = source[prop]; 778 } 779 }); 780 return obj; 781 }; 782 783 // Create a (shallow-cloned) duplicate of an object. 784 _.clone = function(obj) { 785 if (!_.isObject(obj)) return obj; 786 return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 787 }; 788 789 // Invokes interceptor with the obj, and then returns obj. 790 // The primary purpose of this method is to "tap into" a method chain, in 791 // order to perform operations on intermediate results within the chain. 792 _.tap = function(obj, interceptor) { 793 interceptor(obj); 794 return obj; 795 }; 796 797 // Internal recursive comparison function for `isEqual`. 798 var eq = function(a, b, aStack, bStack) { 799 // Identical objects are equal. `0 === -0`, but they aren't identical. 800 // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 801 if (a === b) return a !== 0 || 1 / a == 1 / b; 802 // A strict comparison is necessary because `null == undefined`. 803 if (a == null || b == null) return a === b; 804 // Unwrap any wrapped objects. 805 if (a instanceof _) a = a._wrapped; 806 if (b instanceof _) b = b._wrapped; 807 // Compare `[[Class]]` names. 808 var className = toString.call(a); 809 if (className != toString.call(b)) return false; 810 switch (className) { 811 // Strings, numbers, dates, and booleans are compared by value. 812 case '[object String]': 813 // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 814 // equivalent to `new String("5")`. 815 return a == String(b); 816 case '[object Number]': 817 // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 818 // other numeric values. 819 return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 820 case '[object Date]': 821 case '[object Boolean]': 822 // Coerce dates and booleans to numeric primitive values. Dates are compared by their 823 // millisecond representations. Note that invalid dates with millisecond representations 824 // of `NaN` are not equivalent. 825 return +a == +b; 826 // RegExps are compared by their source patterns and flags. 827 case '[object RegExp]': 828 return a.source == b.source && 829 a.global == b.global && 830 a.multiline == b.multiline && 831 a.ignoreCase == b.ignoreCase; 832 } 833 if (typeof a != 'object' || typeof b != 'object') return false; 834 // Assume equality for cyclic structures. The algorithm for detecting cyclic 835 // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 836 var length = aStack.length; 837 while (length--) { 838 // Linear search. Performance is inversely proportional to the number of 839 // unique nested structures. 840 if (aStack[length] == a) return bStack[length] == b; 841 } 842 // Add the first object to the stack of traversed objects. 843 aStack.push(a); 844 bStack.push(b); 845 var size = 0, result = true; 846 // Recursively compare objects and arrays. 847 if (className == '[object Array]') { 848 // Compare array lengths to determine if a deep comparison is necessary. 849 size = a.length; 850 result = size == b.length; 851 if (result) { 852 // Deep compare the contents, ignoring non-numeric properties. 853 while (size--) { 854 if (!(result = eq(a[size], b[size], aStack, bStack))) break; 855 } 856 } 857 } else { 858 // Objects with different constructors are not equivalent, but `Object`s 859 // from different frames are. 860 var aCtor = a.constructor, bCtor = b.constructor; 861 if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 862 _.isFunction(bCtor) && (bCtor instanceof bCtor))) { 863 return false; 864 } 865 // Deep compare objects. 866 for (var key in a) { 867 if (_.has(a, key)) { 868 // Count the expected number of properties. 869 size++; 870 // Deep compare each member. 871 if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 872 } 873 } 874 // Ensure that both objects contain the same number of properties. 875 if (result) { 876 for (key in b) { 877 if (_.has(b, key) && !(size--)) break; 878 } 879 result = !size; 880 } 881 } 882 // Remove the first object from the stack of traversed objects. 883 aStack.pop(); 884 bStack.pop(); 885 return result; 886 }; 887 888 // Perform a deep comparison to check if two objects are equal. 889 _.isEqual = function(a, b) { 890 return eq(a, b, [], []); 891 }; 892 893 // Is a given array, string, or object empty? 894 // An "empty" object has no enumerable own-properties. 895 _.isEmpty = function(obj) { 896 if (obj == null) return true; 897 if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 898 for (var key in obj) if (_.has(obj, key)) return false; 899 return true; 900 }; 901 902 // Is a given value a DOM element? 903 _.isElement = function(obj) { 904 return !!(obj && obj.nodeType === 1); 905 }; 906 907 // Is a given value an array? 908 // Delegates to ECMA5's native Array.isArray 909 _.isArray = nativeIsArray || function(obj) { 910 return toString.call(obj) == '[object Array]'; 911 }; 912 913 // Is a given variable an object? 914 _.isObject = function(obj) { 915 return obj === Object(obj); 916 }; 917 918 // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 919 each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 920 _['is' + name] = function(obj) { 921 return toString.call(obj) == '[object ' + name + ']'; 922 }; 923 }); 924 925 // Define a fallback version of the method in browsers (ahem, IE), where 926 // there isn't any inspectable "Arguments" type. 927 if (!_.isArguments(arguments)) { 928 _.isArguments = function(obj) { 929 return !!(obj && _.has(obj, 'callee')); 930 }; 931 } 932 933 // Optimize `isFunction` if appropriate. 934 if (typeof (/./) !== 'function') { 935 _.isFunction = function(obj) { 936 return typeof obj === 'function'; 937 }; 938 } 939 940 // Is a given object a finite number? 941 _.isFinite = function(obj) { 942 return _.isNumber(obj) && isFinite(obj); 943 }; 944 945 // Is the given value `NaN`? (NaN is the only number which does not equal itself). 946 _.isNaN = function(obj) { 947 return _.isNumber(obj) && obj != +obj; 948 }; 949 950 // Is a given value a boolean? 951 _.isBoolean = function(obj) { 952 return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 953 }; 954 955 // Is a given value equal to null? 956 _.isNull = function(obj) { 957 return obj === null; 958 }; 959 960 // Is a given variable undefined? 961 _.isUndefined = function(obj) { 962 return obj === void 0; 963 }; 964 965 // Shortcut function for checking if an object has a given property directly 966 // on itself (in other words, not on a prototype). 967 _.has = function(obj, key) { 968 return hasOwnProperty.call(obj, key); 969 }; 970 971 // Utility Functions 972 // ----------------- 973 974 // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 975 // previous owner. Returns a reference to the Underscore object. 976 _.noConflict = function() { 977 root._ = previousUnderscore; 978 return this; 979 }; 980 981 // Keep the identity function around for default iterators. 982 _.identity = function(value) { 983 return value; 984 }; 985 986 // Run a function **n** times. 987 _.times = function(n, iterator, context) { 988 for (var i = 0; i < n; i++) iterator.call(context, i); 989 }; 990 991 // Return a random integer between min and max (inclusive). 992 _.random = function(min, max) { 993 if (max == null) { 994 max = min; 995 min = 0; 996 } 997 return min + (0 | Math.random() * (max - min + 1)); 998 }; 999 1000 // List of HTML entities for escaping. 1001 var entityMap = { 1002 escape: { 1003 '&': '&', 1004 '<': '<', 1005 '>': '>', 1006 '"': '"', 1007 "'": ''', 1008 '/': '/' 1009 } 1010 }; 1011 entityMap.unescape = _.invert(entityMap.escape); 1012 1013 // Regexes containing the keys and values listed immediately above. 1014 var entityRegexes = { 1015 escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1016 unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1017 }; 1018 1019 // Functions for escaping and unescaping strings to/from HTML interpolation. 1020 _.each(['escape', 'unescape'], function(method) { 1021 _[method] = function(string) { 1022 if (string == null) return ''; 1023 return ('' + string).replace(entityRegexes[method], function(match) { 1024 return entityMap[method][match]; 1025 }); 1026 }; 1027 }); 1028 1029 // If the value of the named property is a function then invoke it; 1030 // otherwise, return it. 1031 _.result = function(object, property) { 1032 if (object == null) return null; 1033 var value = object[property]; 1034 return _.isFunction(value) ? value.call(object) : value; 1035 }; 1036 1037 // Add your own custom functions to the Underscore object. 1038 _.mixin = function(obj) { 1039 each(_.functions(obj), function(name){ 1040 var func = _[name] = obj[name]; 1041 _.prototype[name] = function() { 1042 var args = [this._wrapped]; 1043 push.apply(args, arguments); 1044 return result.call(this, func.apply(_, args)); 1045 }; 1046 }); 1047 }; 1048 1049 // Generate a unique integer id (unique within the entire client session). 1050 // Useful for temporary DOM ids. 1051 var idCounter = 0; 1052 _.uniqueId = function(prefix) { 1053 var id = idCounter++; 1054 return prefix ? prefix + id : id; 1055 }; 1056 1057 // By default, Underscore uses ERB-style template delimiters, change the 1058 // following template settings to use alternative delimiters. 1059 _.templateSettings = { 1060 evaluate : /<%([\s\S]+?)%>/g, 1061 interpolate : /<%=([\s\S]+?)%>/g, 1062 escape : /<%-([\s\S]+?)%>/g 1063 }; 1064 1065 // When customizing `templateSettings`, if you don't want to define an 1066 // interpolation, evaluation or escaping regex, we need one that is 1067 // guaranteed not to match. 1068 var noMatch = /(.)^/; 1069 1070 // Certain characters need to be escaped so that they can be put into a 1071 // string literal. 1072 var escapes = { 1073 "'": "'", 1074 '\\': '\\', 1075 '\r': 'r', 1076 '\n': 'n', 1077 '\t': 't', 1078 '\u2028': 'u2028', 1079 '\u2029': 'u2029' 1080 }; 1081 1082 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1083 1084 // JavaScript micro-templating, similar to John Resig's implementation. 1085 // Underscore templating handles arbitrary delimiters, preserves whitespace, 1086 // and correctly escapes quotes within interpolated code. 1087 _.template = function(text, data, settings) { 1088 settings = _.defaults({}, settings, _.templateSettings); 1089 1090 // Combine delimiters into one regular expression via alternation. 1091 var matcher = new RegExp([ 1092 (settings.escape || noMatch).source, 1093 (settings.interpolate || noMatch).source, 1094 (settings.evaluate || noMatch).source 1095 ].join('|') + '|$', 'g'); 1096 1097 // Compile the template source, escaping string literals appropriately. 1098 var index = 0; 1099 var source = "__p+='"; 1100 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1101 source += text.slice(index, offset) 1102 .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1103 source += 1104 escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" : 1105 interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" : 1106 evaluate ? "';\n" + evaluate + "\n__p+='" : ''; 1107 index = offset + match.length; 1108 }); 1109 source += "';\n"; 1110 1111 // If a variable is not specified, place data values in local scope. 1112 if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1113 1114 source = "var __t,__p='',__j=Array.prototype.join," + 1115 "print=function(){__p+=__j.call(arguments,'');};\n" + 1116 source + "return __p;\n"; 1117 1118 try { 1119 var render = new Function(settings.variable || 'obj', '_', source); 1120 } catch (e) { 1121 e.source = source; 1122 throw e; 1123 } 1124 1125 if (data) return render(data, _); 1126 var template = function(data) { 1127 return render.call(this, data, _); 1128 }; 1129 1130 // Provide the compiled function source as a convenience for precompilation. 1131 template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1132 1133 return template; 1134 }; 1135 1136 // Add a "chain" function, which will delegate to the wrapper. 1137 _.chain = function(obj) { 1138 return _(obj).chain(); 1139 }; 1140 1141 // OOP 1142 // --------------- 1143 // If Underscore is called as a function, it returns a wrapped object that 1144 // can be used OO-style. This wrapper holds altered versions of all the 1145 // underscore functions. Wrapped objects may be chained. 1146 1147 // Helper function to continue chaining intermediate results. 1148 var result = function(obj) { 1149 return this._chain ? _(obj).chain() : obj; 1150 }; 1151 1152 // Add all of the Underscore functions to the wrapper object. 1153 _.mixin(_); 1154 1155 // Add all mutator Array functions to the wrapper. 1156 each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1157 var method = ArrayProto[name]; 1158 _.prototype[name] = function() { 1159 var obj = this._wrapped; 1160 method.apply(obj, arguments); 1161 if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1162 return result.call(this, obj); 1163 }; 1164 }); 1165 1166 // Add all accessor Array functions to the wrapper. 1167 each(['concat', 'join', 'slice'], function(name) { 1168 var method = ArrayProto[name]; 1169 _.prototype[name] = function() { 1170 return result.call(this, method.apply(this._wrapped, arguments)); 1171 }; 1172 }); 1173 1174 _.extend(_.prototype, { 1175 1176 // Start chaining a wrapped Underscore object. 1177 chain: function() { 1178 this._chain = true; 1179 return this; 1180 }, 1181 1182 // Extracts the result from a wrapped and chained object. 1183 value: function() { 1184 return this._wrapped; 1185 } 1186 1187 }); 1188 1189 }).call(this); -

WordPress 源代码——主干(backbone-1.0.js)
// Backbone.js 1.0.0 2 3 // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc. 4 // Backbone may be freely distributed under the MIT license. 5 // For all details and documentation: 6 // http://backbonejs.org 7 8 (function(){ 9 10 // Initial Setup 11 // ------------- 12 13 // Save a reference to the global object (`window` in the browser, `exports` 14 // on the server). 15 var root = this; 16 17 // Save the previous value of the `Backbone` variable, so that it can be 18 // restored later on, if `noConflict` is used. 19 var previousBackbone = root.Backbone; 20 21 // Create local references to array methods we'll want to use later. 22 var array = []; 23 var push = array.push; 24 var slice = array.slice; 25 var splice = array.splice; 26 27 // The top-level namespace. All public Backbone classes and modules will 28 // be attached to this. Exported for both the browser and the server. 29 var Backbone; 30 if (typeof exports !== 'undefined') { 31 Backbone = exports; 32 } else { 33 Backbone = root.Backbone = {}; 34 } 35 36 // Current version of the library. Keep in sync with `package.json`. 37 Backbone.VERSION = '1.0.0'; 38 39 // Require Underscore, if we're on the server, and it's not already present. 40 var _ = root._; 41 if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); 42 43 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns 44 // the `$` variable. 45 Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$; 46 47 // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable 48 // to its previous owner. Returns a reference to this Backbone object. 49 Backbone.noConflict = function() { 50 root.Backbone = previousBackbone; 51 return this; 52 }; 53 54 // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option 55 // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and 56 // set a `X-Http-Method-Override` header. 57 Backbone.emulateHTTP = false; 58 59 // Turn on `emulateJSON` to support legacy servers that can't deal with direct 60 // `application/json` requests ... will encode the body as 61 // `application/x-www-form-urlencoded` instead and will send the model in a 62 // form param named `model`. 63 Backbone.emulateJSON = false; 64 65 // Backbone.Events 66 // --------------- 67 68 // A module that can be mixed in to *any object* in order to provide it with 69 // custom events. You may bind with `on` or remove with `off` callback 70 // functions to an event; `trigger`-ing an event fires all callbacks in 71 // succession. 72 // 73 // var object = {}; 74 // _.extend(object, Backbone.Events); 75 // object.on('expand', function(){ alert('expanded'); }); 76 // object.trigger('expand'); 77 // 78 var Events = Backbone.Events = { 79 80 // Bind an event to a `callback` function. Passing `"all"` will bind 81 // the callback to all events fired. 82 on: function(name, callback, context) { 83 if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; 84 this._events || (this._events = {}); 85 var events = this._events[name] || (this._events[name] = []); 86 events.push({callback: callback, context: context, ctx: context || this}); 87 return this; 88 }, 89 90 // Bind an event to only be triggered a single time. After the first time 91 // the callback is invoked, it will be removed. 92 once: function(name, callback, context) { 93 if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; 94 var self = this; 95 var once = _.once(function() { 96 self.off(name, once); 97 callback.apply(this, arguments); 98 }); 99 once._callback = callback; 100 return this.on(name, once, context); 101 }, 102 103 // Remove one or many callbacks. If `context` is null, removes all 104 // callbacks with that function. If `callback` is null, removes all 105 // callbacks for the event. If `name` is null, removes all bound 106 // callbacks for all events. 107 off: function(name, callback, context) { 108 var retain, ev, events, names, i, l, j, k; 109 if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; 110 if (!name && !callback && !context) { 111 this._events = {}; 112 return this; 113 } 114 115 names = name ? [name] : _.keys(this._events); 116 for (i = 0, l = names.length; i < l; i++) { 117 name = names[i]; 118 if (events = this._events[name]) { 119 this._events[name] = retain = []; 120 if (callback || context) { 121 for (j = 0, k = events.length; j < k; j++) { 122 ev = events[j]; 123 if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || 124 (context && context !== ev.context)) { 125 retain.push(ev); 126 } 127 } 128 } 129 if (!retain.length) delete this._events[name]; 130 } 131 } 132 133 return this; 134 }, 135 136 // Trigger one or many events, firing all bound callbacks. Callbacks are 137 // passed the same arguments as `trigger` is, apart from the event name 138 // (unless you're listening on `"all"`, which will cause your callback to 139 // receive the true name of the event as the first argument). 140 trigger: function(name) { 141 if (!this._events) return this; 142 var args = slice.call(arguments, 1); 143 if (!eventsApi(this, 'trigger', name, args)) return this; 144 var events = this._events[name]; 145 var allEvents = this._events.all; 146 if (events) triggerEvents(events, args); 147 if (allEvents) triggerEvents(allEvents, arguments); 148 return this; 149 }, 150 151 // Tell this object to stop listening to either specific events ... or 152 // to every object it's currently listening to. 153 stopListening: function(obj, name, callback) { 154 var listeners = this._listeners; 155 if (!listeners) return this; 156 var deleteListener = !name && !callback; 157 if (typeof name === 'object') callback = this; 158 if (obj) (listeners = {})[obj._listenerId] = obj; 159 for (var id in listeners) { 160 listeners[id].off(name, callback, this); 161 if (deleteListener) delete this._listeners[id]; 162 } 163 return this; 164 } 165 166 }; 167 168 // Regular expression used to split event strings. 169 var eventSplitter = /\s+/; 170 171 // Implement fancy features of the Events API such as multiple event 172 // names `"change blur"` and jQuery-style event maps `{change: action}` 173 // in terms of the existing API. 174 var eventsApi = function(obj, action, name, rest) { 175 if (!name) return true; 176 177 // Handle event maps. 178 if (typeof name === 'object') { 179 for (var key in name) { 180 obj[action].apply(obj, [key, name[key]].concat(rest)); 181 } 182 return false; 183 } 184 185 // Handle space separated event names. 186 if (eventSplitter.test(name)) { 187 var names = name.split(eventSplitter); 188 for (var i = 0, l = names.length; i < l; i++) { 189 obj[action].apply(obj, [names[i]].concat(rest)); 190 } 191 return false; 192 } 193 194 return true; 195 }; 196 197 // A difficult-to-believe, but optimized internal dispatch function for 198 // triggering events. Tries to keep the usual cases speedy (most internal 199 // Backbone events have 3 arguments). 200 var triggerEvents = function(events, args) { 201 var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; 202 switch (args.length) { 203 case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; 204 case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; 205 case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; 206 case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; 207 default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); 208 } 209 }; 210 211 var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; 212 213 // Inversion-of-control versions of `on` and `once`. Tell *this* object to 214 // listen to an event in another object ... keeping track of what it's 215 // listening to. 216 _.each(listenMethods, function(implementation, method) { 217 Events[method] = function(obj, name, callback) { 218 var listeners = this._listeners || (this._listeners = {}); 219 var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); 220 listeners[id] = obj; 221 if (typeof name === 'object') callback = this; 222 obj[implementation](name, callback, this); 223 return this; 224 }; 225 }); 226 227 // Aliases for backwards compatibility. 228 Events.bind = Events.on; 229 Events.unbind = Events.off; 230 231 // Allow the `Backbone` object to serve as a global event bus, for folks who 232 // want global "pubsub" in a convenient place. 233 _.extend(Backbone, Events); 234 235 // Backbone.Model 236 // -------------- 237 238 // Backbone **Models** are the basic data object in the framework -- 239 // frequently representing a row in a table in a database on your server. 240 // A discrete chunk of data and a bunch of useful, related methods for 241 // performing computations and transformations on that data. 242 243 // Create a new model with the specified attributes. A client id (`cid`) 244 // is automatically generated and assigned for you. 245 var Model = Backbone.Model = function(attributes, options) { 246 var defaults; 247 var attrs = attributes || {}; 248 options || (options = {}); 249 this.cid = _.uniqueId('c'); 250 this.attributes = {}; 251 _.extend(this, _.pick(options, modelOptions)); 252 if (options.parse) attrs = this.parse(attrs, options) || {}; 253 if (defaults = _.result(this, 'defaults')) { 254 attrs = _.defaults({}, attrs, defaults); 255 } 256 this.set(attrs, options); 257 this.changed = {}; 258 this.initialize.apply(this, arguments); 259 }; 260 261 // A list of options to be attached directly to the model, if provided. 262 var modelOptions = ['url', 'urlRoot', 'collection']; 263 264 // Attach all inheritable methods to the Model prototype. 265 _.extend(Model.prototype, Events, { 266 267 // A hash of attributes whose current and previous value differ. 268 changed: null, 269 270 // The value returned during the last failed validation. 271 validationError: null, 272 273 // The default name for the JSON `id` attribute is `"id"`. MongoDB and 274 // CouchDB users may want to set this to `"_id"`. 275 idAttribute: 'id', 276 277 // Initialize is an empty function by default. Override it with your own 278 // initialization logic. 279 initialize: function(){}, 280 281 // Return a copy of the model's `attributes` object. 282 toJSON: function(options) { 283 return _.clone(this.attributes); 284 }, 285 286 // Proxy `Backbone.sync` by default -- but override this if you need 287 // custom syncing semantics for *this* particular model. 288 sync: function() { 289 return Backbone.sync.apply(this, arguments); 290 }, 291 292 // Get the value of an attribute. 293 get: function(attr) { 294 return this.attributes[attr]; 295 }, 296 297 // Get the HTML-escaped value of an attribute. 298 escape: function(attr) { 299 return _.escape(this.get(attr)); 300 }, 301 302 // Returns `true` if the attribute contains a value that is not null 303 // or undefined. 304 has: function(attr) { 305 return this.get(attr) != null; 306 }, 307 308 // Set a hash of model attributes on the object, firing `"change"`. This is 309 // the core primitive operation of a model, updating the data and notifying 310 // anyone who needs to know about the change in state. The heart of the beast. 311 set: function(key, val, options) { 312 var attr, attrs, unset, changes, silent, changing, prev, current; 313 if (key == null) return this; 314 315 // Handle both `"key", value` and `{key: value}` -style arguments. 316 if (typeof key === 'object') { 317 attrs = key; 318 options = val; 319 } else { 320 (attrs = {})[key] = val; 321 } 322 323 options || (options = {}); 324 325 // Run validation. 326 if (!this._validate(attrs, options)) return false; 327 328 // Extract attributes and options. 329 unset = options.unset; 330 silent = options.silent; 331 changes = []; 332 changing = this._changing; 333 this._changing = true; 334 335 if (!changing) { 336 this._previousAttributes = _.clone(this.attributes); 337 this.changed = {}; 338 } 339 current = this.attributes, prev = this._previousAttributes; 340 341 // Check for changes of `id`. 342 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; 343 344 // For each `set` attribute, update or delete the current value. 345 for (attr in attrs) { 346 val = attrs[attr]; 347 if (!_.isEqual(current[attr], val)) changes.push(attr); 348 if (!_.isEqual(prev[attr], val)) { 349 this.changed[attr] = val; 350 } else { 351 delete this.changed[attr]; 352 } 353 unset ? delete current[attr] : current[attr] = val; 354 } 355 356 // Trigger all relevant attribute changes. 357 if (!silent) { 358 if (changes.length) this._pending = true; 359 for (var i = 0, l = changes.length; i < l; i++) { 360 this.trigger('change:' + changes[i], this, current[changes[i]], options); 361 } 362 } 363 364 // You might be wondering why there's a `while` loop here. Changes can 365 // be recursively nested within `"change"` events. 366 if (changing) return this; 367 if (!silent) { 368 while (this._pending) { 369 this._pending = false; 370 this.trigger('change', this, options); 371 } 372 } 373 this._pending = false; 374 this._changing = false; 375 return this; 376 }, 377 378 // Remove an attribute from the model, firing `"change"`. `unset` is a noop 379 // if the attribute doesn't exist. 380 unset: function(attr, options) { 381 return this.set(attr, void 0, _.extend({}, options, {unset: true})); 382 }, 383 384 // Clear all attributes on the model, firing `"change"`. 385 clear: function(options) { 386 var attrs = {}; 387 for (var key in this.attributes) attrs[key] = void 0; 388 return this.set(attrs, _.extend({}, options, {unset: true})); 389 }, 390 391 // Determine if the model has changed since the last `"change"` event. 392 // If you specify an attribute name, determine if that attribute has changed. 393 hasChanged: function(attr) { 394 if (attr == null) return !_.isEmpty(this.changed); 395 return _.has(this.changed, attr); 396 }, 397 398 // Return an object containing all the attributes that have changed, or 399 // false if there are no changed attributes. Useful for determining what 400 // parts of a view need to be updated and/or what attributes need to be 401 // persisted to the server. Unset attributes will be set to undefined. 402 // You can also pass an attributes object to diff against the model, 403 // determining if there *would be* a change. 404 changedAttributes: function(diff) { 405 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; 406 var val, changed = false; 407 var old = this._changing ? this._previousAttributes : this.attributes; 408 for (var attr in diff) { 409 if (_.isEqual(old[attr], (val = diff[attr]))) continue; 410 (changed || (changed = {}))[attr] = val; 411 } 412 return changed; 413 }, 414 415 // Get the previous value of an attribute, recorded at the time the last 416 // `"change"` event was fired. 417 previous: function(attr) { 418 if (attr == null || !this._previousAttributes) return null; 419 return this._previousAttributes[attr]; 420 }, 421 422 // Get all of the attributes of the model at the time of the previous 423 // `"change"` event. 424 previousAttributes: function() { 425 return _.clone(this._previousAttributes); 426 }, 427 428 // Fetch the model from the server. If the server's representation of the 429 // model differs from its current attributes, they will be overridden, 430 // triggering a `"change"` event. 431 fetch: function(options) { 432 options = options ? _.clone(options) : {}; 433 if (options.parse === void 0) options.parse = true; 434 var model = this; 435 var success = options.success; 436 options.success = function(resp) { 437 if (!model.set(model.parse(resp, options), options)) return false; 438 if (success) success(model, resp, options); 439 model.trigger('sync', model, resp, options); 440 }; 441 wrapError(this, options); 442 return this.sync('read', this, options); 443 }, 444 445 // Set a hash of model attributes, and sync the model to the server. 446 // If the server returns an attributes hash that differs, the model's 447 // state will be `set` again. 448 save: function(key, val, options) { 449 var attrs, method, xhr, attributes = this.attributes; 450 451 // Handle both `"key", value` and `{key: value}` -style arguments. 452 if (key == null || typeof key === 'object') { 453 attrs = key; 454 options = val; 455 } else { 456 (attrs = {})[key] = val; 457 } 458 459 // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`. 460 if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false; 461 462 options = _.extend({validate: true}, options); 463 464 // Do not persist invalid models. 465 if (!this._validate(attrs, options)) return false; 466 467 // Set temporary attributes if `{wait: true}`. 468 if (attrs && options.wait) { 469 this.attributes = _.extend({}, attributes, attrs); 470 } 471 472 // After a successful server-side save, the client is (optionally) 473 // updated with the server-side state. 474 if (options.parse === void 0) options.parse = true; 475 var model = this; 476 var success = options.success; 477 options.success = function(resp) { 478 // Ensure attributes are restored during synchronous saves. 479 model.attributes = attributes; 480 var serverAttrs = model.parse(resp, options); 481 if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); 482 if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { 483 return false; 484 } 485 if (success) success(model, resp, options); 486 model.trigger('sync', model, resp, options); 487 }; 488 wrapError(this, options); 489 490 method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); 491 if (method === 'patch') options.attrs = attrs; 492 xhr = this.sync(method, this, options); 493 494 // Restore attributes. 495 if (attrs && options.wait) this.attributes = attributes; 496 497 return xhr; 498 }, 499 500 // Destroy this model on the server if it was already persisted. 501 // Optimistically removes the model from its collection, if it has one. 502 // If `wait: true` is passed, waits for the server to respond before removal. 503 destroy: function(options) { 504 options = options ? _.clone(options) : {}; 505 var model = this; 506 var success = options.success; 507 508 var destroy = function() { 509 model.trigger('destroy', model, model.collection, options); 510 }; 511 512 options.success = function(resp) { 513 if (options.wait || model.isNew()) destroy(); 514 if (success) success(model, resp, options); 515 if (!model.isNew()) model.trigger('sync', model, resp, options); 516 }; 517 518 if (this.isNew()) { 519 options.success(); 520 return false; 521 } 522 wrapError(this, options); 523 524 var xhr = this.sync('delete', this, options); 525 if (!options.wait) destroy(); 526 return xhr; 527 }, 528 529 // Default URL for the model's representation on the server -- if you're 530 // using Backbone's restful methods, override this to change the endpoint 531 // that will be called. 532 url: function() { 533 var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); 534 if (this.isNew()) return base; 535 return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); 536 }, 537 538 // **parse** converts a response into the hash of attributes to be `set` on 539 // the model. The default implementation is just to pass the response along. 540 parse: function(resp, options) { 541 return resp; 542 }, 543 544 // Create a new model with identical attributes to this one. 545 clone: function() { 546 return new this.constructor(this.attributes); 547 }, 548 549 // A model is new if it has never been saved to the server, and lacks an id. 550 isNew: function() { 551 return this.id == null; 552 }, 553 554 // Check if the model is currently in a valid state. 555 isValid: function(options) { 556 return this._validate({}, _.extend(options || {}, { validate: true })); 557 }, 558 559 // Run validation against the next complete set of model attributes, 560 // returning `true` if all is well. Otherwise, fire an `"invalid"` event. 561 _validate: function(attrs, options) { 562 if (!options.validate || !this.validate) return true; 563 attrs = _.extend({}, this.attributes, attrs); 564 var error = this.validationError = this.validate(attrs, options) || null; 565 if (!error) return true; 566 this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); 567 return false; 568 } 569 570 }); 571 572 // Underscore methods that we want to implement on the Model. 573 var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; 574 575 // Mix in each Underscore method as a proxy to `Model#attributes`. 576 _.each(modelMethods, function(method) { 577 Model.prototype[method] = function() { 578 var args = slice.call(arguments); 579 args.unshift(this.attributes); 580 return _[method].apply(_, args); 581 }; 582 }); 583 584 // Backbone.Collection 585 // ------------------- 586 587 // If models tend to represent a single row of data, a Backbone Collection is 588 // more analagous to a table full of data ... or a small slice or page of that 589 // table, or a collection of rows that belong together for a particular reason 590 // -- all of the messages in this particular folder, all of the documents 591 // belonging to this particular author, and so on. Collections maintain 592 // indexes of their models, both in order, and for lookup by `id`. 593 594 // Create a new **Collection**, perhaps to contain a specific type of `model`. 595 // If a `comparator` is specified, the Collection will maintain 596 // its models in sort order, as they're added and removed. 597 var Collection = Backbone.Collection = function(models, options) { 598 options || (options = {}); 599 if (options.url) this.url = options.url; 600 if (options.model) this.model = options.model; 601 if (options.comparator !== void 0) this.comparator = options.comparator; 602 this._reset(); 603 this.initialize.apply(this, arguments); 604 if (models) this.reset(models, _.extend({silent: true}, options)); 605 }; 606 607 // Default options for `Collection#set`. 608 var setOptions = {add: true, remove: true, merge: true}; 609 var addOptions = {add: true, merge: false, remove: false}; 610 611 // Define the Collection's inheritable methods. 612 _.extend(Collection.prototype, Events, { 613 614 // The default model for a collection is just a **Backbone.Model**. 615 // This should be overridden in most cases. 616 model: Model, 617 618 // Initialize is an empty function by default. Override it with your own 619 // initialization logic. 620 initialize: function(){}, 621 622 // The JSON representation of a Collection is an array of the 623 // models' attributes. 624 toJSON: function(options) { 625 return this.map(function(model){ return model.toJSON(options); }); 626 }, 627 628 // Proxy `Backbone.sync` by default. 629 sync: function() { 630 return Backbone.sync.apply(this, arguments); 631 }, 632 633 // Add a model, or list of models to the set. 634 add: function(models, options) { 635 return this.set(models, _.defaults(options || {}, addOptions)); 636 }, 637 638 // Remove a model, or a list of models from the set. 639 remove: function(models, options) { 640 models = _.isArray(models) ? models.slice() : [models]; 641 options || (options = {}); 642 var i, l, index, model; 643 for (i = 0, l = models.length; i < l; i++) { 644 model = this.get(models[i]); 645 if (!model) continue; 646 delete this._byId[model.id]; 647 delete this._byId[model.cid]; 648 index = this.indexOf(model); 649 this.models.splice(index, 1); 650 this.length--; 651 if (!options.silent) { 652 options.index = index; 653 model.trigger('remove', model, this, options); 654 } 655 this._removeReference(model); 656 } 657 return this; 658 }, 659 660 // Update a collection by `set`-ing a new list of models, adding new ones, 661 // removing models that are no longer present, and merging models that 662 // already exist in the collection, as necessary. Similar to **Model#set**, 663 // the core operation for updating the data contained by the collection. 664 set: function(models, options) { 665 options = _.defaults(options || {}, setOptions); 666 if (options.parse) models = this.parse(models, options); 667 if (!_.isArray(models)) models = models ? [models] : []; 668 var i, l, model, attrs, existing, sort; 669 var at = options.at; 670 var sortable = this.comparator && (at == null) && options.sort !== false; 671 var sortAttr = _.isString(this.comparator) ? this.comparator : null; 672 var toAdd = [], toRemove = [], modelMap = {}; 673 674 // Turn bare objects into model references, and prevent invalid models 675 // from being added. 676 for (i = 0, l = models.length; i < l; i++) { 677 if (!(model = this._prepareModel(models[i], options))) continue; 678 679 // If a duplicate is found, prevent it from being added and 680 // optionally merge it into the existing model. 681 if (existing = this.get(model)) { 682 if (options.remove) modelMap[existing.cid] = true; 683 if (options.merge) { 684 existing.set(model.attributes, options); 685 if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; 686 } 687 688 // This is a new model, push it to the `toAdd` list. 689 } else if (options.add) { 690 toAdd.push(model); 691 692 // Listen to added models' events, and index models for lookup by 693 // `id` and by `cid`. 694 model.on('all', this._onModelEvent, this); 695 this._byId[model.cid] = model; 696 if (model.id != null) this._byId[model.id] = model; 697 } 698 } 699 700 // Remove nonexistent models if appropriate. 701 if (options.remove) { 702 for (i = 0, l = this.length; i < l; ++i) { 703 if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); 704 } 705 if (toRemove.length) this.remove(toRemove, options); 706 } 707 708 // See if sorting is needed, update `length` and splice in new models. 709 if (toAdd.length) { 710 if (sortable) sort = true; 711 this.length += toAdd.length; 712 if (at != null) { 713 splice.apply(this.models, [at, 0].concat(toAdd)); 714 } else { 715 push.apply(this.models, toAdd); 716 } 717 } 718 719 // Silently sort the collection if appropriate. 720 if (sort) this.sort({silent: true}); 721 722 if (options.silent) return this; 723 724 // Trigger `add` events. 725 for (i = 0, l = toAdd.length; i < l; i++) { 726 (model = toAdd[i]).trigger('add', model, this, options); 727 } 728 729 // Trigger `sort` if the collection was sorted. 730 if (sort) this.trigger('sort', this, options); 731 return this; 732 }, 733 734 // When you have more items than you want to add or remove individually, 735 // you can reset the entire set with a new list of models, without firing 736 // any granular `add` or `remove` events. Fires `reset` when finished. 737 // Useful for bulk operations and optimizations. 738 reset: function(models, options) { 739 options || (options = {}); 740 for (var i = 0, l = this.models.length; i < l; i++) { 741 this._removeReference(this.models[i]); 742 } 743 options.previousModels = this.models; 744 this._reset(); 745 this.add(models, _.extend({silent: true}, options)); 746 if (!options.silent) this.trigger('reset', this, options); 747 return this; 748 }, 749 750 // Add a model to the end of the collection. 751 push: function(model, options) { 752 model = this._prepareModel(model, options); 753 this.add(model, _.extend({at: this.length}, options)); 754 return model; 755 }, 756 757 // Remove a model from the end of the collection. 758 pop: function(options) { 759 var model = this.at(this.length - 1); 760 this.remove(model, options); 761 return model; 762 }, 763 764 // Add a model to the beginning of the collection. 765 unshift: function(model, options) { 766 model = this._prepareModel(model, options); 767 this.add(model, _.extend({at: 0}, options)); 768 return model; 769 }, 770 771 // Remove a model from the beginning of the collection. 772 shift: function(options) { 773 var model = this.at(0); 774 this.remove(model, options); 775 return model; 776 }, 777 778 // Slice out a sub-array of models from the collection. 779 slice: function(begin, end) { 780 return this.models.slice(begin, end); 781 }, 782 783 // Get a model from the set by id. 784 get: function(obj) { 785 if (obj == null) return void 0; 786 return this._byId[obj.id != null ? obj.id : obj.cid || obj]; 787 }, 788 789 // Get the model at the given index. 790 at: function(index) { 791 return this.models[index]; 792 }, 793 794 // Return models with matching attributes. Useful for simple cases of 795 // `filter`. 796 where: function(attrs, first) { 797 if (_.isEmpty(attrs)) return first ? void 0 : []; 798 return this[first ? 'find' : 'filter'](function(model) { 799 for (var key in attrs) { 800 if (attrs[key] !== model.get(key)) return false; 801 } 802 return true; 803 }); 804 }, 805 806 // Return the first model with matching attributes. Useful for simple cases 807 // of `find`. 808 findWhere: function(attrs) { 809 return this.where(attrs, true); 810 }, 811 812 // Force the collection to re-sort itself. You don't need to call this under 813 // normal circumstances, as the set will maintain sort order as each item 814 // is added. 815 sort: function(options) { 816 if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); 817 options || (options = {}); 818 819 // Run sort based on type of `comparator`. 820 if (_.isString(this.comparator) || this.comparator.length === 1) { 821 this.models = this.sortBy(this.comparator, this); 822 } else { 823 this.models.sort(_.bind(this.comparator, this)); 824 } 825 826 if (!options.silent) this.trigger('sort', this, options); 827 return this; 828 }, 829 830 // Figure out the smallest index at which a model should be inserted so as 831 // to maintain order. 832 sortedIndex: function(model, value, context) { 833 value || (value = this.comparator); 834 var iterator = _.isFunction(value) ? value : function(model) { 835 return model.get(value); 836 }; 837 return _.sortedIndex(this.models, model, iterator, context); 838 }, 839 840 // Pluck an attribute from each model in the collection. 841 pluck: function(attr) { 842 return _.invoke(this.models, 'get', attr); 843 }, 844 845 // Fetch the default set of models for this collection, resetting the 846 // collection when they arrive. If `reset: true` is passed, the response 847 // data will be passed through the `reset` method instead of `set`. 848 fetch: function(options) { 849 options = options ? _.clone(options) : {}; 850 if (options.parse === void 0) options.parse = true; 851 var success = options.success; 852 var collection = this; 853 options.success = function(resp) { 854 var method = options.reset ? 'reset' : 'set'; 855 collection[method](resp, options); 856 if (success) success(collection, resp, options); 857 collection.trigger('sync', collection, resp, options); 858 }; 859 wrapError(this, options); 860 return this.sync('read', this, options); 861 }, 862 863 // Create a new instance of a model in this collection. Add the model to the 864 // collection immediately, unless `wait: true` is passed, in which case we 865 // wait for the server to agree. 866 create: function(model, options) { 867 options = options ? _.clone(options) : {}; 868 if (!(model = this._prepareModel(model, options))) return false; 869 if (!options.wait) this.add(model, options); 870 var collection = this; 871 var success = options.success; 872 options.success = function(resp) { 873 if (options.wait) collection.add(model, options); 874 if (success) success(model, resp, options); 875 }; 876 model.save(null, options); 877 return model; 878 }, 879 880 // **parse** converts a response into a list of models to be added to the 881 // collection. The default implementation is just to pass it through. 882 parse: function(resp, options) { 883 return resp; 884 }, 885 886 // Create a new collection with an identical list of models as this one. 887 clone: function() { 888 return new this.constructor(this.models); 889 }, 890 891 // Private method to reset all internal state. Called when the collection 892 // is first initialized or reset. 893 _reset: function() { 894 this.length = 0; 895 this.models = []; 896 this._byId = {}; 897 }, 898 899 // Prepare a hash of attributes (or other model) to be added to this 900 // collection. 901 _prepareModel: function(attrs, options) { 902 if (attrs instanceof Model) { 903 if (!attrs.collection) attrs.collection = this; 904 return attrs; 905 } 906 options || (options = {}); 907 options.collection = this; 908 var model = new this.model(attrs, options); 909 if (!model._validate(attrs, options)) { 910 this.trigger('invalid', this, attrs, options); 911 return false; 912 } 913 return model; 914 }, 915 916 // Internal method to sever a model's ties to a collection. 917 _removeReference: function(model) { 918 if (this === model.collection) delete model.collection; 919 model.off('all', this._onModelEvent, this); 920 }, 921 922 // Internal method called every time a model in the set fires an event. 923 // Sets need to update their indexes when models change ids. All other 924 // events simply proxy through. "add" and "remove" events that originate 925 // in other collections are ignored. 926 _onModelEvent: function(event, model, collection, options) { 927 if ((event === 'add' || event === 'remove') && collection !== this) return; 928 if (event === 'destroy') this.remove(model, options); 929 if (model && event === 'change:' + model.idAttribute) { 930 delete this._byId[model.previous(model.idAttribute)]; 931 if (model.id != null) this._byId[model.id] = model; 932 } 933 this.trigger.apply(this, arguments); 934 } 935 936 }); 937 938 // Underscore methods that we want to implement on the Collection. 939 // 90% of the core usefulness of Backbone Collections is actually implemented 940 // right here: 941 var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', 942 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 943 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 944 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 945 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', 946 'isEmpty', 'chain']; 947 948 // Mix in each Underscore method as a proxy to `Collection#models`. 949 _.each(methods, function(method) { 950 Collection.prototype[method] = function() { 951 var args = slice.call(arguments); 952 args.unshift(this.models); 953 return _[method].apply(_, args); 954 }; 955 }); 956 957 // Underscore methods that take a property name as an argument. 958 var attributeMethods = ['groupBy', 'countBy', 'sortBy']; 959 960 // Use attributes instead of properties. 961 _.each(attributeMethods, function(method) { 962 Collection.prototype[method] = function(value, context) { 963 var iterator = _.isFunction(value) ? value : function(model) { 964 return model.get(value); 965 }; 966 return _[method](this.models, iterator, context); 967 }; 968 }); 969 970 // Backbone.View 971 // ------------- 972 973 // Backbone Views are almost more convention than they are actual code. A View 974 // is simply a JavaScript object that represents a logical chunk of UI in the 975 // DOM. This might be a single item, an entire list, a sidebar or panel, or 976 // even the surrounding frame which wraps your whole app. Defining a chunk of 977 // UI as a **View** allows you to define your DOM events declaratively, without 978 // having to worry about render order ... and makes it easy for the view to 979 // react to specific changes in the state of your models. 980 981 // Creating a Backbone.View creates its initial element outside of the DOM, 982 // if an existing element is not provided... 983 var View = Backbone.View = function(options) { 984 this.cid = _.uniqueId('view'); 985 this._configure(options || {}); 986 this._ensureElement(); 987 this.initialize.apply(this, arguments); 988 this.delegateEvents(); 989 }; 990 991 // Cached regex to split keys for `delegate`. 992 var delegateEventSplitter = /^(\S+)\s*(.*)$/; 993 994 // List of view options to be merged as properties. 995 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; 996 997 // Set up all inheritable **Backbone.View** properties and methods. 998 _.extend(View.prototype, Events, { 999 1000 // The default `tagName` of a View's element is `"div"`. 1001 tagName: 'div', 1002 1003 // jQuery delegate for element lookup, scoped to DOM elements within the 1004 // current view. This should be prefered to global lookups where possible. 1005 $: function(selector) { 1006 return this.$el.find(selector); 1007 }, 1008 1009 // Initialize is an empty function by default. Override it with your own 1010 // initialization logic. 1011 initialize: function(){}, 1012 1013 // **render** is the core function that your view should override, in order 1014 // to populate its element (`this.el`), with the appropriate HTML. The 1015 // convention is for **render** to always return `this`. 1016 render: function() { 1017 return this; 1018 }, 1019 1020 // Remove this view by taking the element out of the DOM, and removing any 1021 // applicable Backbone.Events listeners. 1022 remove: function() { 1023 this.$el.remove(); 1024 this.stopListening(); 1025 return this; 1026 }, 1027 1028 // Change the view's element (`this.el` property), including event 1029 // re-delegation. 1030 setElement: function(element, delegate) { 1031 if (this.$el) this.undelegateEvents(); 1032 this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); 1033 this.el = this.$el[0]; 1034 if (delegate !== false) this.delegateEvents(); 1035 return this; 1036 }, 1037 1038 // Set callbacks, where `this.events` is a hash of 1039 // 1040 // *{"event selector": "callback"}* 1041 // 1042 // { 1043 // 'mousedown .title': 'edit', 1044 // 'click .button': 'save' 1045 // 'click .open': function(e) { ... } 1046 // } 1047 // 1048 // pairs. Callbacks will be bound to the view, with `this` set properly. 1049 // Uses event delegation for efficiency. 1050 // Omitting the selector binds the event to `this.el`. 1051 // This only works for delegate-able events: not `focus`, `blur`, and 1052 // not `change`, `submit`, and `reset` in Internet Explorer. 1053 delegateEvents: function(events) { 1054 if (!(events || (events = _.result(this, 'events')))) return this; 1055 this.undelegateEvents(); 1056 for (var key in events) { 1057 var method = events[key]; 1058 if (!_.isFunction(method)) method = this[events[key]]; 1059 if (!method) continue; 1060 1061 var match = key.match(delegateEventSplitter); 1062 var eventName = match[1], selector = match[2]; 1063 method = _.bind(method, this); 1064 eventName += '.delegateEvents' + this.cid; 1065 if (selector === '') { 1066 this.$el.on(eventName, method); 1067 } else { 1068 this.$el.on(eventName, selector, method); 1069 } 1070 } 1071 return this; 1072 }, 1073 1074 // Clears all callbacks previously bound to the view with `delegateEvents`. 1075 // You usually don't need to use this, but may wish to if you have multiple 1076 // Backbone views attached to the same DOM element. 1077 undelegateEvents: function() { 1078 this.$el.off('.delegateEvents' + this.cid); 1079 return this; 1080 }, 1081 1082 // Performs the initial configuration of a View with a set of options. 1083 // Keys with special meaning *(e.g. model, collection, id, className)* are 1084 // attached directly to the view. See `viewOptions` for an exhaustive 1085 // list. 1086 _configure: function(options) { 1087 if (this.options) options = _.extend({}, _.result(this, 'options'), options); 1088 _.extend(this, _.pick(options, viewOptions)); 1089 this.options = options; 1090 }, 1091 1092 // Ensure that the View has a DOM element to render into. 1093 // If `this.el` is a string, pass it through `$()`, take the first 1094 // matching element, and re-assign it to `el`. Otherwise, create 1095 // an element from the `id`, `className` and `tagName` properties. 1096 _ensureElement: function() { 1097 if (!this.el) { 1098 var attrs = _.extend({}, _.result(this, 'attributes')); 1099 if (this.id) attrs.id = _.result(this, 'id'); 1100 if (this.className) attrs['class'] = _.result(this, 'className'); 1101 var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); 1102 this.setElement($el, false); 1103 } else { 1104 this.setElement(_.result(this, 'el'), false); 1105 } 1106 } 1107 1108 }); 1109 1110 // Backbone.sync 1111 // ------------- 1112 1113 // Override this function to change the manner in which Backbone persists 1114 // models to the server. You will be passed the type of request, and the 1115 // model in question. By default, makes a RESTful Ajax request 1116 // to the model's `url()`. Some possible customizations could be: 1117 // 1118 // * Use `setTimeout` to batch rapid-fire updates into a single request. 1119 // * Send up the models as XML instead of JSON. 1120 // * Persist models via WebSockets instead of Ajax. 1121 // 1122 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests 1123 // as `POST`, with a `_method` parameter containing the true HTTP method, 1124 // as well as all requests with the body as `application/x-www-form-urlencoded` 1125 // instead of `application/json` with the model in a param named `model`. 1126 // Useful when interfacing with server-side languages like **PHP** that make 1127 // it difficult to read the body of `PUT` requests. 1128 Backbone.sync = function(method, model, options) { 1129 var type = methodMap[method]; 1130 1131 // Default options, unless specified. 1132 _.defaults(options || (options = {}), { 1133 emulateHTTP: Backbone.emulateHTTP, 1134 emulateJSON: Backbone.emulateJSON 1135 }); 1136 1137 // Default JSON-request options. 1138 var params = {type: type, dataType: 'json'}; 1139 1140 // Ensure that we have a URL. 1141 if (!options.url) { 1142 params.url = _.result(model, 'url') || urlError(); 1143 } 1144 1145 // Ensure that we have the appropriate request data. 1146 if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { 1147 params.contentType = 'application/json'; 1148 params.data = JSON.stringify(options.attrs || model.toJSON(options)); 1149 } 1150 1151 // For older servers, emulate JSON by encoding the request into an HTML-form. 1152 if (options.emulateJSON) { 1153 params.contentType = 'application/x-www-form-urlencoded'; 1154 params.data = params.data ? {model: params.data} : {}; 1155 } 1156 1157 // For older servers, emulate HTTP by mimicking the HTTP method with `_method` 1158 // And an `X-HTTP-Method-Override` header. 1159 if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { 1160 params.type = 'POST'; 1161 if (options.emulateJSON) params.data._method = type; 1162 var beforeSend = options.beforeSend; 1163 options.beforeSend = function(xhr) { 1164 xhr.setRequestHeader('X-HTTP-Method-Override', type); 1165 if (beforeSend) return beforeSend.apply(this, arguments); 1166 }; 1167 } 1168 1169 // Don't process data on a non-GET request. 1170 if (params.type !== 'GET' && !options.emulateJSON) { 1171 params.processData = false; 1172 } 1173 1174 // If we're sending a `PATCH` request, and we're in an old Internet Explorer 1175 // that still has ActiveX enabled by default, override jQuery to use that 1176 // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. 1177 if (params.type === 'PATCH' && window.ActiveXObject && 1178 !(window.external && window.external.msActiveXFilteringEnabled)) { 1179 params.xhr = function() { 1180 return new ActiveXObject("Microsoft.XMLHTTP"); 1181 }; 1182 } 1183 1184 // Make the request, allowing the user to override any Ajax options. 1185 var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); 1186 model.trigger('request', model, xhr, options); 1187 return xhr; 1188 }; 1189 1190 // Map from CRUD to HTTP for our default `Backbone.sync` implementation. 1191 var methodMap = { 1192 'create': 'POST', 1193 'update': 'PUT', 1194 'patch': 'PATCH', 1195 'delete': 'DELETE', 1196 'read': 'GET' 1197 }; 1198 1199 // Set the default implementation of `Backbone.ajax` to proxy through to `$`. 1200 // Override this if you'd like to use a different library. 1201 Backbone.ajax = function() { 1202 return Backbone.$.ajax.apply(Backbone.$, arguments); 1203 }; 1204 1205 // Backbone.Router 1206 // --------------- 1207 1208 // Routers map faux-URLs to actions, and fire events when routes are 1209 // matched. Creating a new one sets its `routes` hash, if not set statically. 1210 var Router = Backbone.Router = function(options) { 1211 options || (options = {}); 1212 if (options.routes) this.routes = options.routes; 1213 this._bindRoutes(); 1214 this.initialize.apply(this, arguments); 1215 }; 1216 1217 // Cached regular expressions for matching named param parts and splatted 1218 // parts of route strings. 1219 var optionalParam = /\((.*?)\)/g; 1220 var namedParam = /(\(\?)?:\w+/g; 1221 var splatParam = /\*\w+/g; 1222 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; 1223 1224 // Set up all inheritable **Backbone.Router** properties and methods. 1225 _.extend(Router.prototype, Events, { 1226 1227 // Initialize is an empty function by default. Override it with your own 1228 // initialization logic. 1229 initialize: function(){}, 1230 1231 // Manually bind a single named route to a callback. For example: 1232 // 1233 // this.route('search/:query/p:num', 'search', function(query, num) { 1234 // ... 1235 // }); 1236 // 1237 route: function(route, name, callback) { 1238 if (!_.isRegExp(route)) route = this._routeToRegExp(route); 1239 if (_.isFunction(name)) { 1240 callback = name; 1241 name = ''; 1242 } 1243 if (!callback) callback = this[name]; 1244 var router = this; 1245 Backbone.history.route(route, function(fragment) { 1246 var args = router._extractParameters(route, fragment); 1247 callback && callback.apply(router, args); 1248 router.trigger.apply(router, ['route:' + name].concat(args)); 1249 router.trigger('route', name, args); 1250 Backbone.history.trigger('route', router, name, args); 1251 }); 1252 return this; 1253 }, 1254 1255 // Simple proxy to `Backbone.history` to save a fragment into the history. 1256 navigate: function(fragment, options) { 1257 Backbone.history.navigate(fragment, options); 1258 return this; 1259 }, 1260 1261 // Bind all defined routes to `Backbone.history`. We have to reverse the 1262 // order of the routes here to support behavior where the most general 1263 // routes can be defined at the bottom of the route map. 1264 _bindRoutes: function() { 1265 if (!this.routes) return; 1266 this.routes = _.result(this, 'routes'); 1267 var route, routes = _.keys(this.routes); 1268 while ((route = routes.pop()) != null) { 1269 this.route(route, this.routes[route]); 1270 } 1271 }, 1272 1273 // Convert a route string into a regular expression, suitable for matching 1274 // against the current location hash. 1275 _routeToRegExp: function(route) { 1276 route = route.replace(escapeRegExp, '\\$&') 1277 .replace(optionalParam, '(?:$1)?') 1278 .replace(namedParam, function(match, optional){ 1279 return optional ? match : '([^\/]+)'; 1280 }) 1281 .replace(splatParam, '(.*?)'); 1282 return new RegExp('^' + route + '$'); 1283 }, 1284 1285 // Given a route, and a URL fragment that it matches, return the array of 1286 // extracted decoded parameters. Empty or unmatched parameters will be 1287 // treated as `null` to normalize cross-browser behavior. 1288 _extractParameters: function(route, fragment) { 1289 var params = route.exec(fragment).slice(1); 1290 return _.map(params, function(param) { 1291 return param ? decodeURIComponent(param) : null; 1292 }); 1293 } 1294 1295 }); 1296 1297 // Backbone.History 1298 // ---------------- 1299 1300 // Handles cross-browser history management, based on either 1301 // [pushState](http://diveintohtml5.info/history.html) and real URLs, or 1302 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) 1303 // and URL fragments. If the browser supports neither (old IE, natch), 1304 // falls back to polling. 1305 var History = Backbone.History = function() { 1306 this.handlers = []; 1307 _.bindAll(this, 'checkUrl'); 1308 1309 // Ensure that `History` can be used outside of the browser. 1310 if (typeof window !== 'undefined') { 1311 this.location = window.location; 1312 this.history = window.history; 1313 } 1314 }; 1315 1316 // Cached regex for stripping a leading hash/slash and trailing space. 1317 var routeStripper = /^[#\/]|\s+$/g; 1318 1319 // Cached regex for stripping leading and trailing slashes. 1320 var rootStripper = /^\/+|\/+$/g; 1321 1322 // Cached regex for detecting MSIE. 1323 var isExplorer = /msie [\w.]+/; 1324 1325 // Cached regex for removing a trailing slash. 1326 var trailingSlash = /\/$/; 1327 1328 // Has the history handling already been started? 1329 History.started = false; 1330 1331 // Set up all inheritable **Backbone.History** properties and methods. 1332 _.extend(History.prototype, Events, { 1333 1334 // The default interval to poll for hash changes, if necessary, is 1335 // twenty times a second. 1336 interval: 50, 1337 1338 // Gets the true hash value. Cannot use location.hash directly due to bug 1339 // in Firefox where location.hash will always be decoded. 1340 getHash: function(window) { 1341 var match = (window || this).location.href.match(/#(.*)$/); 1342 return match ? match[1] : ''; 1343 }, 1344 1345 // Get the cross-browser normalized URL fragment, either from the URL, 1346 // the hash, or the override. 1347 getFragment: function(fragment, forcePushState) { 1348 if (fragment == null) { 1349 if (this._hasPushState || !this._wantsHashChange || forcePushState) { 1350 fragment = this.location.pathname; 1351 var root = this.root.replace(trailingSlash, ''); 1352 if (!fragment.indexOf(root)) fragment = fragment.substr(root.length); 1353 } else { 1354 fragment = this.getHash(); 1355 } 1356 } 1357 return fragment.replace(routeStripper, ''); 1358 }, 1359 1360 // Start the hash change handling, returning `true` if the current URL matches 1361 // an existing route, and `false` otherwise. 1362 start: function(options) { 1363 if (History.started) throw new Error("Backbone.history has already been started"); 1364 History.started = true; 1365 1366 // Figure out the initial configuration. Do we need an iframe? 1367 // Is pushState desired ... is it available? 1368 this.options = _.extend({}, {root: '/'}, this.options, options); 1369 this.root = this.options.root; 1370 this._wantsHashChange = this.options.hashChange !== false; 1371 this._wantsPushState = !!this.options.pushState; 1372 this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); 1373 var fragment = this.getFragment(); 1374 var docMode = document.documentMode; 1375 var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); 1376 1377 // Normalize root to always include a leading and trailing slash. 1378 this.root = ('/' + this.root + '/').replace(rootStripper, '/'); 1379 1380 if (oldIE && this._wantsHashChange) { 1381 this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow; 1382 this.navigate(fragment); 1383 } 1384 1385 // Depending on whether we're using pushState or hashes, and whether 1386 // 'onhashchange' is supported, determine how we check the URL state. 1387 if (this._hasPushState) { 1388 Backbone.$(window).on('popstate', this.checkUrl); 1389 } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) { 1390 Backbone.$(window).on('hashchange', this.checkUrl); 1391 } else if (this._wantsHashChange) { 1392 this._checkUrlInterval = setInterval(this.checkUrl, this.interval); 1393 } 1394 1395 // Determine if we need to change the base url, for a pushState link 1396 // opened by a non-pushState browser. 1397 this.fragment = fragment; 1398 var loc = this.location; 1399 var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; 1400 1401 // If we've started off with a route from a `pushState`-enabled browser, 1402 // but we're currently in a browser that doesn't support it... 1403 if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) { 1404 this.fragment = this.getFragment(null, true); 1405 this.location.replace(this.root + this.location.search + '#' + this.fragment); 1406 // Return immediately as browser will do redirect to new url 1407 return true; 1408 1409 // Or if we've started out with a hash-based route, but we're currently 1410 // in a browser where it could be `pushState`-based instead... 1411 } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { 1412 this.fragment = this.getHash().replace(routeStripper, ''); 1413 this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); 1414 } 1415 1416 if (!this.options.silent) return this.loadUrl(); 1417 }, 1418 1419 // Disable Backbone.history, perhaps temporarily. Not useful in a real app, 1420 // but possibly useful for unit testing Routers. 1421 stop: function() { 1422 Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl); 1423 clearInterval(this._checkUrlInterval); 1424 History.started = false; 1425 }, 1426 1427 // Add a route to be tested when the fragment changes. Routes added later 1428 // may override previous routes. 1429 route: function(route, callback) { 1430 this.handlers.unshift({route: route, callback: callback}); 1431 }, 1432 1433 // Checks the current URL to see if it has changed, and if it has, 1434 // calls `loadUrl`, normalizing across the hidden iframe. 1435 checkUrl: function(e) { 1436 var current = this.getFragment(); 1437 if (current === this.fragment && this.iframe) { 1438 current = this.getFragment(this.getHash(this.iframe)); 1439 } 1440 if (current === this.fragment) return false; 1441 if (this.iframe) this.navigate(current); 1442 this.loadUrl() || this.loadUrl(this.getHash()); 1443 }, 1444 1445 // Attempt to load the current URL fragment. If a route succeeds with a 1446 // match, returns `true`. If no defined routes matches the fragment, 1447 // returns `false`. 1448 loadUrl: function(fragmentOverride) { 1449 var fragment = this.fragment = this.getFragment(fragmentOverride); 1450 var matched = _.any(this.handlers, function(handler) { 1451 if (handler.route.test(fragment)) { 1452 handler.callback(fragment); 1453 return true; 1454 } 1455 }); 1456 return matched; 1457 }, 1458 1459 // Save a fragment into the hash history, or replace the URL state if the 1460 // 'replace' option is passed. You are responsible for properly URL-encoding 1461 // the fragment in advance. 1462 // 1463 // The options object can contain `trigger: true` if you wish to have the 1464 // route callback be fired (not usually desirable), or `replace: true`, if 1465 // you wish to modify the current URL without adding an entry to the history. 1466 navigate: function(fragment, options) { 1467 if (!History.started) return false; 1468 if (!options || options === true) options = {trigger: options}; 1469 fragment = this.getFragment(fragment || ''); 1470 if (this.fragment === fragment) return; 1471 this.fragment = fragment; 1472 var url = this.root + fragment; 1473 1474 // If pushState is available, we use it to set the fragment as a real URL. 1475 if (this._hasPushState) { 1476 this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); 1477 1478 // If hash changes haven't been explicitly disabled, update the hash 1479 // fragment to store history. 1480 } else if (this._wantsHashChange) { 1481 this._updateHash(this.location, fragment, options.replace); 1482 if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) { 1483 // Opening and closing the iframe tricks IE7 and earlier to push a 1484 // history entry on hash-tag change. When replace is true, we don't 1485 // want this. 1486 if(!options.replace) this.iframe.document.open().close(); 1487 this._updateHash(this.iframe.location, fragment, options.replace); 1488 } 1489 1490 // If you've told us that you explicitly don't want fallback hashchange- 1491 // based history, then `navigate` becomes a page refresh. 1492 } else { 1493 return this.location.assign(url); 1494 } 1495 if (options.trigger) this.loadUrl(fragment); 1496 }, 1497 1498 // Update the hash location, either replacing the current entry, or adding 1499 // a new one to the browser history. 1500 _updateHash: function(location, fragment, replace) { 1501 if (replace) { 1502 var href = location.href.replace(/(javascript:|#).*$/, ''); 1503 location.replace(href + '#' + fragment); 1504 } else { 1505 // Some browsers require that `hash` contains a leading #. 1506 location.hash = '#' + fragment; 1507 } 1508 } 1509 1510 }); 1511 1512 // Create the default Backbone.history. 1513 Backbone.history = new History; 1514 1515 // Helpers 1516 // ------- 1517 1518 // Helper function to correctly set up the prototype chain, for subclasses. 1519 // Similar to `goog.inherits`, but uses a hash of prototype properties and 1520 // class properties to be extended. 1521 var extend = function(protoProps, staticProps) { 1522 var parent = this; 1523 var child; 1524 1525 // The constructor function for the new subclass is either defined by you 1526 // (the "constructor" property in your `extend` definition), or defaulted 1527 // by us to simply call the parent's constructor. 1528 if (protoProps && _.has(protoProps, 'constructor')) { 1529 child = protoProps.constructor; 1530 } else { 1531 child = function(){ return parent.apply(this, arguments); }; 1532 } 1533 1534 // Add static properties to the constructor function, if supplied. 1535 _.extend(child, parent, staticProps); 1536 1537 // Set the prototype chain to inherit from `parent`, without calling 1538 // `parent`'s constructor function. 1539 var Surrogate = function(){ this.constructor = child; }; 1540 Surrogate.prototype = parent.prototype; 1541 child.prototype = new Surrogate; 1542 1543 // Add prototype properties (instance properties) to the subclass, 1544 // if supplied. 1545 if (protoProps) _.extend(child.prototype, protoProps); 1546 1547 // Set a convenience property in case the parent's prototype is needed 1548 // later. 1549 child.__super__ = parent.prototype; 1550 1551 return child; 1552 }; 1553 1554 // Set up inheritance for the model, collection, router, view and history. 1555 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; 1556 1557 // Throw an error when a URL is needed, and none is supplied. 1558 var urlError = function() { 1559 throw new Error('A "url" property or function must be specified'); 1560 }; 1561 1562 // Wrap an optional error callback with a fallback error event. 1563 var wrapError = function (model, options) { 1564 var error = options.error; 1565 options.error = function(resp) { 1566 if (error) error(model, resp, options); 1567 model.trigger('error', model, resp, options); 1568 }; 1569 }; 1570 1571 }).call(this); -

WordPress 源代码——主干(backbone-0.9.2.js)
// Backbone.js 0.9.2 2 3 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 // Backbone may be freely distributed under the MIT license. 5 // For all details and documentation: 6 // http://backbonejs.org 7 8 (function(){ 9 10 // Initial Setup 11 // ------------- 12 13 // Save a reference to the global object (`window` in the browser, `global` 14 // on the server). 15 var root = this; 16 17 // Save the previous value of the `Backbone` variable, so that it can be 18 // restored later on, if `noConflict` is used. 19 var previousBackbone = root.Backbone; 20 21 // Create a local reference to slice/splice. 22 var slice = Array.prototype.slice; 23 var splice = Array.prototype.splice; 24 25 // The top-level namespace. All public Backbone classes and modules will 26 // be attached to this. Exported for both CommonJS and the browser. 27 var Backbone; 28 if (typeof exports !== 'undefined') { 29 Backbone = exports; 30 } else { 31 Backbone = root.Backbone = {}; 32 } 33 34 // Current version of the library. Keep in sync with `package.json`. 35 Backbone.VERSION = '0.9.2'; 36 37 // Require Underscore, if we're on the server, and it's not already present. 38 var _ = root._; 39 if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); 40 41 // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. 42 var $ = root.jQuery || root.Zepto || root.ender; 43 44 // Set the JavaScript library that will be used for DOM manipulation and 45 // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery, 46 // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an 47 // alternate JavaScript library (or a mock library for testing your views 48 // outside of a browser). 49 Backbone.setDomLibrary = function(lib) { 50 $ = lib; 51 }; 52 53 // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable 54 // to its previous owner. Returns a reference to this Backbone object. 55 Backbone.noConflict = function() { 56 root.Backbone = previousBackbone; 57 return this; 58 }; 59 60 // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option 61 // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and 62 // set a `X-Http-Method-Override` header. 63 Backbone.emulateHTTP = false; 64 65 // Turn on `emulateJSON` to support legacy servers that can't deal with direct 66 // `application/json` requests ... will encode the body as 67 // `application/x-www-form-urlencoded` instead and will send the model in a 68 // form param named `model`. 69 Backbone.emulateJSON = false; 70 71 // Backbone.Events 72 // ----------------- 73 74 // Regular expression used to split event strings 75 var eventSplitter = /\s+/; 76 77 // A module that can be mixed in to *any object* in order to provide it with 78 // custom events. You may bind with `on` or remove with `off` callback functions 79 // to an event; trigger`-ing an event fires all callbacks in succession. 80 // 81 // var object = {}; 82 // _.extend(object, Backbone.Events); 83 // object.on('expand', function(){ alert('expanded'); }); 84 // object.trigger('expand'); 85 // 86 var Events = Backbone.Events = { 87 88 // Bind one or more space separated events, `events`, to a `callback` 89 // function. Passing `"all"` will bind the callback to all events fired. 90 on: function(events, callback, context) { 91 92 var calls, event, node, tail, list; 93 if (!callback) return this; 94 events = events.split(eventSplitter); 95 calls = this._callbacks || (this._callbacks = {}); 96 97 // Create an immutable callback list, allowing traversal during 98 // modification. The tail is an empty object that will always be used 99 // as the next node. 100 while (event = events.shift()) { 101 list = calls[event]; 102 node = list ? list.tail : {}; 103 node.next = tail = {}; 104 node.context = context; 105 node.callback = callback; 106 calls[event] = {tail: tail, next: list ? list.next : node}; 107 } 108 109 return this; 110 }, 111 112 // Remove one or many callbacks. If `context` is null, removes all callbacks 113 // with that function. If `callback` is null, removes all callbacks for the 114 // event. If `events` is null, removes all bound callbacks for all events. 115 off: function(events, callback, context) { 116 var event, calls, node, tail, cb, ctx; 117 118 // No events, or removing *all* events. 119 if (!(calls = this._callbacks)) return; 120 if (!(events || callback || context)) { 121 delete this._callbacks; 122 return this; 123 } 124 125 // Loop through the listed events and contexts, splicing them out of the 126 // linked list of callbacks if appropriate. 127 events = events ? events.split(eventSplitter) : _.keys(calls); 128 while (event = events.shift()) { 129 node = calls[event]; 130 delete calls[event]; 131 if (!node || !(callback || context)) continue; 132 // Create a new list, omitting the indicated callbacks. 133 tail = node.tail; 134 while ((node = node.next) !== tail) { 135 cb = node.callback; 136 ctx = node.context; 137 if ((callback && cb !== callback) || (context && ctx !== context)) { 138 this.on(event, cb, ctx); 139 } 140 } 141 } 142 143 return this; 144 }, 145 146 // Trigger one or many events, firing all bound callbacks. Callbacks are 147 // passed the same arguments as `trigger` is, apart from the event name 148 // (unless you're listening on `"all"`, which will cause your callback to 149 // receive the true name of the event as the first argument). 150 trigger: function(events) { 151 var event, node, calls, tail, args, all, rest; 152 if (!(calls = this._callbacks)) return this; 153 all = calls.all; 154 events = events.split(eventSplitter); 155 rest = slice.call(arguments, 1); 156 157 // For each event, walk through the linked list of callbacks twice, 158 // first to trigger the event, then to trigger any `"all"` callbacks. 159 while (event = events.shift()) { 160 if (node = calls[event]) { 161 tail = node.tail; 162 while ((node = node.next) !== tail) { 163 node.callback.apply(node.context || this, rest); 164 } 165 } 166 if (node = all) { 167 tail = node.tail; 168 args = [event].concat(rest); 169 while ((node = node.next) !== tail) { 170 node.callback.apply(node.context || this, args); 171 } 172 } 173 } 174 175 return this; 176 } 177 178 }; 179 180 // Aliases for backwards compatibility. 181 Events.bind = Events.on; 182 Events.unbind = Events.off; 183 184 // Backbone.Model 185 // -------------- 186 187 // Create a new model, with defined attributes. A client id (`cid`) 188 // is automatically generated and assigned for you. 189 var Model = Backbone.Model = function(attributes, options) { 190 var defaults; 191 attributes || (attributes = {}); 192 if (options && options.parse) attributes = this.parse(attributes); 193 if (defaults = getValue(this, 'defaults')) { 194 attributes = _.extend({}, defaults, attributes); 195 } 196 if (options && options.collection) this.collection = options.collection; 197 this.attributes = {}; 198 this._escapedAttributes = {}; 199 this.cid = _.uniqueId('c'); 200 this.changed = {}; 201 this._silent = {}; 202 this._pending = {}; 203 this.set(attributes, {silent: true}); 204 // Reset change tracking. 205 this.changed = {}; 206 this._silent = {}; 207 this._pending = {}; 208 this._previousAttributes = _.clone(this.attributes); 209 this.initialize.apply(this, arguments); 210 }; 211 212 // Attach all inheritable methods to the Model prototype. 213 _.extend(Model.prototype, Events, { 214 215 // A hash of attributes whose current and previous value differ. 216 changed: null, 217 218 // A hash of attributes that have silently changed since the last time 219 // `change` was called. Will become pending attributes on the next call. 220 _silent: null, 221 222 // A hash of attributes that have changed since the last `'change'` event 223 // began. 224 _pending: null, 225 226 // The default name for the JSON `id` attribute is `"id"`. MongoDB and 227 // CouchDB users may want to set this to `"_id"`. 228 idAttribute: 'id', 229 230 // Initialize is an empty function by default. Override it with your own 231 // initialization logic. 232 initialize: function(){}, 233 234 // Return a copy of the model's `attributes` object. 235 toJSON: function(options) { 236 return _.clone(this.attributes); 237 }, 238 239 // Get the value of an attribute. 240 get: function(attr) { 241 return this.attributes[attr]; 242 }, 243 244 // Get the HTML-escaped value of an attribute. 245 escape: function(attr) { 246 var html; 247 if (html = this._escapedAttributes[attr]) return html; 248 var val = this.get(attr); 249 return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val); 250 }, 251 252 // Returns `true` if the attribute contains a value that is not null 253 // or undefined. 254 has: function(attr) { 255 return this.get(attr) != null; 256 }, 257 258 // Set a hash of model attributes on the object, firing `"change"` unless 259 // you choose to silence it. 260 set: function(key, value, options) { 261 var attrs, attr, val; 262 263 // Handle both `"key", value` and `{key: value}` -style arguments. 264 if (_.isObject(key) || key == null) { 265 attrs = key; 266 options = value; 267 } else { 268 attrs = {}; 269 attrs[key] = value; 270 } 271 272 // Extract attributes and options. 273 options || (options = {}); 274 if (!attrs) return this; 275 if (attrs instanceof Model) attrs = attrs.attributes; 276 if (options.unset) for (attr in attrs) attrs[attr] = void 0; 277 278 // Run validation. 279 if (!this._validate(attrs, options)) return false; 280 281 // Check for changes of `id`. 282 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; 283 284 var changes = options.changes = {}; 285 var now = this.attributes; 286 var escaped = this._escapedAttributes; 287 var prev = this._previousAttributes || {}; 288 289 // For each `set` attribute... 290 for (attr in attrs) { 291 val = attrs[attr]; 292 293 // If the new and current value differ, record the change. 294 if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) { 295 delete escaped[attr]; 296 (options.silent ? this._silent : changes)[attr] = true; 297 } 298 299 // Update or delete the current value. 300 options.unset ? delete now[attr] : now[attr] = val; 301 302 // If the new and previous value differ, record the change. If not, 303 // then remove changes for this attribute. 304 if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) { 305 this.changed[attr] = val; 306 if (!options.silent) this._pending[attr] = true; 307 } else { 308 delete this.changed[attr]; 309 delete this._pending[attr]; 310 } 311 } 312 313 // Fire the `"change"` events. 314 if (!options.silent) this.change(options); 315 return this; 316 }, 317 318 // Remove an attribute from the model, firing `"change"` unless you choose 319 // to silence it. `unset` is a noop if the attribute doesn't exist. 320 unset: function(attr, options) { 321 (options || (options = {})).unset = true; 322 return this.set(attr, null, options); 323 }, 324 325 // Clear all attributes on the model, firing `"change"` unless you choose 326 // to silence it. 327 clear: function(options) { 328 (options || (options = {})).unset = true; 329 return this.set(_.clone(this.attributes), options); 330 }, 331 332 // Fetch the model from the server. If the server's representation of the 333 // model differs from its current attributes, they will be overriden, 334 // triggering a `"change"` event. 335 fetch: function(options) { 336 options = options ? _.clone(options) : {}; 337 var model = this; 338 var success = options.success; 339 options.success = function(resp, status, xhr) { 340 if (!model.set(model.parse(resp, xhr), options)) return false; 341 if (success) success(model, resp); 342 }; 343 options.error = Backbone.wrapError(options.error, model, options); 344 return (this.sync || Backbone.sync).call(this, 'read', this, options); 345 }, 346 347 // Set a hash of model attributes, and sync the model to the server. 348 // If the server returns an attributes hash that differs, the model's 349 // state will be `set` again. 350 save: function(key, value, options) { 351 var attrs, current; 352 353 // Handle both `("key", value)` and `({key: value})` -style calls. 354 if (_.isObject(key) || key == null) { 355 attrs = key; 356 options = value; 357 } else { 358 attrs = {}; 359 attrs[key] = value; 360 } 361 options = options ? _.clone(options) : {}; 362 363 // If we're "wait"-ing to set changed attributes, validate early. 364 if (options.wait) { 365 if (!this._validate(attrs, options)) return false; 366 current = _.clone(this.attributes); 367 } 368 369 // Regular saves `set` attributes before persisting to the server. 370 var silentOptions = _.extend({}, options, {silent: true}); 371 if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { 372 return false; 373 } 374 375 // After a successful server-side save, the client is (optionally) 376 // updated with the server-side state. 377 var model = this; 378 var success = options.success; 379 options.success = function(resp, status, xhr) { 380 var serverAttrs = model.parse(resp, xhr); 381 if (options.wait) { 382 delete options.wait; 383 serverAttrs = _.extend(attrs || {}, serverAttrs); 384 } 385 if (!model.set(serverAttrs, options)) return false; 386 if (success) { 387 success(model, resp); 388 } else { 389 model.trigger('sync', model, resp, options); 390 } 391 }; 392 393 // Finish configuring and sending the Ajax request. 394 options.error = Backbone.wrapError(options.error, model, options); 395 var method = this.isNew() ? 'create' : 'update'; 396 var xhr = (this.sync || Backbone.sync).call(this, method, this, options); 397 if (options.wait) this.set(current, silentOptions); 398 return xhr; 399 }, 400 401 // Destroy this model on the server if it was already persisted. 402 // Optimistically removes the model from its collection, if it has one. 403 // If `wait: true` is passed, waits for the server to respond before removal. 404 destroy: function(options) { 405 options = options ? _.clone(options) : {}; 406 var model = this; 407 var success = options.success; 408 409 var triggerDestroy = function() { 410 model.trigger('destroy', model, model.collection, options); 411 }; 412 413 if (this.isNew()) { 414 triggerDestroy(); 415 return false; 416 } 417 418 options.success = function(resp) { 419 if (options.wait) triggerDestroy(); 420 if (success) { 421 success(model, resp); 422 } else { 423 model.trigger('sync', model, resp, options); 424 } 425 }; 426 427 options.error = Backbone.wrapError(options.error, model, options); 428 var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options); 429 if (!options.wait) triggerDestroy(); 430 return xhr; 431 }, 432 433 // Default URL for the model's representation on the server -- if you're 434 // using Backbone's restful methods, override this to change the endpoint 435 // that will be called. 436 url: function() { 437 var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError(); 438 if (this.isNew()) return base; 439 return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); 440 }, 441 442 // **parse** converts a response into the hash of attributes to be `set` on 443 // the model. The default implementation is just to pass the response along. 444 parse: function(resp, xhr) { 445 return resp; 446 }, 447 448 // Create a new model with identical attributes to this one. 449 clone: function() { 450 return new this.constructor(this.attributes); 451 }, 452 453 // A model is new if it has never been saved to the server, and lacks an id. 454 isNew: function() { 455 return this.id == null; 456 }, 457 458 // Call this method to manually fire a `"change"` event for this model and 459 // a `"change:attribute"` event for each changed attribute. 460 // Calling this will cause all objects observing the model to update. 461 change: function(options) { 462 options || (options = {}); 463 var changing = this._changing; 464 this._changing = true; 465 466 // Silent changes become pending changes. 467 for (var attr in this._silent) this._pending[attr] = true; 468 469 // Silent changes are triggered. 470 var changes = _.extend({}, options.changes, this._silent); 471 this._silent = {}; 472 for (var attr in changes) { 473 this.trigger('change:' + attr, this, this.get(attr), options); 474 } 475 if (changing) return this; 476 477 // Continue firing `"change"` events while there are pending changes. 478 while (!_.isEmpty(this._pending)) { 479 this._pending = {}; 480 this.trigger('change', this, options); 481 // Pending and silent changes still remain. 482 for (var attr in this.changed) { 483 if (this._pending[attr] || this._silent[attr]) continue; 484 delete this.changed[attr]; 485 } 486 this._previousAttributes = _.clone(this.attributes); 487 } 488 489 this._changing = false; 490 return this; 491 }, 492 493 // Determine if the model has changed since the last `"change"` event. 494 // If you specify an attribute name, determine if that attribute has changed. 495 hasChanged: function(attr) { 496 if (!arguments.length) return !_.isEmpty(this.changed); 497 return _.has(this.changed, attr); 498 }, 499 500 // Return an object containing all the attributes that have changed, or 501 // false if there are no changed attributes. Useful for determining what 502 // parts of a view need to be updated and/or what attributes need to be 503 // persisted to the server. Unset attributes will be set to undefined. 504 // You can also pass an attributes object to diff against the model, 505 // determining if there *would be* a change. 506 changedAttributes: function(diff) { 507 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; 508 var val, changed = false, old = this._previousAttributes; 509 for (var attr in diff) { 510 if (_.isEqual(old[attr], (val = diff[attr]))) continue; 511 (changed || (changed = {}))[attr] = val; 512 } 513 return changed; 514 }, 515 516 // Get the previous value of an attribute, recorded at the time the last 517 // `"change"` event was fired. 518 previous: function(attr) { 519 if (!arguments.length || !this._previousAttributes) return null; 520 return this._previousAttributes[attr]; 521 }, 522 523 // Get all of the attributes of the model at the time of the previous 524 // `"change"` event. 525 previousAttributes: function() { 526 return _.clone(this._previousAttributes); 527 }, 528 529 // Check if the model is currently in a valid state. It's only possible to 530 // get into an *invalid* state if you're using silent changes. 531 isValid: function() { 532 return !this.validate(this.attributes); 533 }, 534 535 // Run validation against the next complete set of model attributes, 536 // returning `true` if all is well. If a specific `error` callback has 537 // been passed, call that instead of firing the general `"error"` event. 538 _validate: function(attrs, options) { 539 if (options.silent || !this.validate) return true; 540 attrs = _.extend({}, this.attributes, attrs); 541 var error = this.validate(attrs, options); 542 if (!error) return true; 543 if (options && options.error) { 544 options.error(this, error, options); 545 } else { 546 this.trigger('error', this, error, options); 547 } 548 return false; 549 } 550 551 }); 552 553 // Backbone.Collection 554 // ------------------- 555 556 // Provides a standard collection class for our sets of models, ordered 557 // or unordered. If a `comparator` is specified, the Collection will maintain 558 // its models in sort order, as they're added and removed. 559 var Collection = Backbone.Collection = function(models, options) { 560 options || (options = {}); 561 if (options.model) this.model = options.model; 562 if (options.comparator) this.comparator = options.comparator; 563 this._reset(); 564 this.initialize.apply(this, arguments); 565 if (models) this.reset(models, {silent: true, parse: options.parse}); 566 }; 567 568 // Define the Collection's inheritable methods. 569 _.extend(Collection.prototype, Events, { 570 571 // The default model for a collection is just a **Backbone.Model**. 572 // This should be overridden in most cases. 573 model: Model, 574 575 // Initialize is an empty function by default. Override it with your own 576 // initialization logic. 577 initialize: function(){}, 578 579 // The JSON representation of a Collection is an array of the 580 // models' attributes. 581 toJSON: function(options) { 582 return this.map(function(model){ return model.toJSON(options); }); 583 }, 584 585 // Add a model, or list of models to the set. Pass **silent** to avoid 586 // firing the `add` event for every new model. 587 add: function(models, options) { 588 var i, index, length, model, cid, id, cids = {}, ids = {}, dups = []; 589 options || (options = {}); 590 models = _.isArray(models) ? models.slice() : [models]; 591 592 // Begin by turning bare objects into model references, and preventing 593 // invalid models or duplicate models from being added. 594 for (i = 0, length = models.length; i < length; i++) { 595 if (!(model = models[i] = this._prepareModel(models[i], options))) { 596 throw new Error("Can't add an invalid model to a collection"); 597 } 598 cid = model.cid; 599 id = model.id; 600 if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) { 601 dups.push(i); 602 continue; 603 } 604 cids[cid] = ids[id] = model; 605 } 606 607 // Remove duplicates. 608 i = dups.length; 609 while (i--) { 610 models.splice(dups[i], 1); 611 } 612 613 // Listen to added models' events, and index models for lookup by 614 // `id` and by `cid`. 615 for (i = 0, length = models.length; i < length; i++) { 616 (model = models[i]).on('all', this._onModelEvent, this); 617 this._byCid[model.cid] = model; 618 if (model.id != null) this._byId[model.id] = model; 619 } 620 621 // Insert models into the collection, re-sorting if needed, and triggering 622 // `add` events unless silenced. 623 this.length += length; 624 index = options.at != null ? options.at : this.models.length; 625 splice.apply(this.models, [index, 0].concat(models)); 626 if (this.comparator) this.sort({silent: true}); 627 if (options.silent) return this; 628 for (i = 0, length = this.models.length; i < length; i++) { 629 if (!cids[(model = this.models[i]).cid]) continue; 630 options.index = i; 631 model.trigger('add', model, this, options); 632 } 633 return this; 634 }, 635 636 // Remove a model, or a list of models from the set. Pass silent to avoid 637 // firing the `remove` event for every model removed. 638 remove: function(models, options) { 639 var i, l, index, model; 640 options || (options = {}); 641 models = _.isArray(models) ? models.slice() : [models]; 642 for (i = 0, l = models.length; i < l; i++) { 643 model = this.getByCid(models[i]) || this.get(models[i]); 644 if (!model) continue; 645 delete this._byId[model.id]; 646 delete this._byCid[model.cid]; 647 index = this.indexOf(model); 648 this.models.splice(index, 1); 649 this.length--; 650 if (!options.silent) { 651 options.index = index; 652 model.trigger('remove', model, this, options); 653 } 654 this._removeReference(model); 655 } 656 return this; 657 }, 658 659 // Add a model to the end of the collection. 660 push: function(model, options) { 661 model = this._prepareModel(model, options); 662 this.add(model, options); 663 return model; 664 }, 665 666 // Remove a model from the end of the collection. 667 pop: function(options) { 668 var model = this.at(this.length - 1); 669 this.remove(model, options); 670 return model; 671 }, 672 673 // Add a model to the beginning of the collection. 674 unshift: function(model, options) { 675 model = this._prepareModel(model, options); 676 this.add(model, _.extend({at: 0}, options)); 677 return model; 678 }, 679 680 // Remove a model from the beginning of the collection. 681 shift: function(options) { 682 var model = this.at(0); 683 this.remove(model, options); 684 return model; 685 }, 686 687 // Get a model from the set by id. 688 get: function(id) { 689 if (id == null) return void 0; 690 return this._byId[id.id != null ? id.id : id]; 691 }, 692 693 // Get a model from the set by client id. 694 getByCid: function(cid) { 695 return cid && this._byCid[cid.cid || cid]; 696 }, 697 698 // Get the model at the given index. 699 at: function(index) { 700 return this.models[index]; 701 }, 702 703 // Return models with matching attributes. Useful for simple cases of `filter`. 704 where: function(attrs) { 705 if (_.isEmpty(attrs)) return []; 706 return this.filter(function(model) { 707 for (var key in attrs) { 708 if (attrs[key] !== model.get(key)) return false; 709 } 710 return true; 711 }); 712 }, 713 714 // Force the collection to re-sort itself. You don't need to call this under 715 // normal circumstances, as the set will maintain sort order as each item 716 // is added. 717 sort: function(options) { 718 options || (options = {}); 719 if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); 720 var boundComparator = _.bind(this.comparator, this); 721 if (this.comparator.length == 1) { 722 this.models = this.sortBy(boundComparator); 723 } else { 724 this.models.sort(boundComparator); 725 } 726 if (!options.silent) this.trigger('reset', this, options); 727 return this; 728 }, 729 730 // Pluck an attribute from each model in the collection. 731 pluck: function(attr) { 732 return _.map(this.models, function(model){ return model.get(attr); }); 733 }, 734 735 // When you have more items than you want to add or remove individually, 736 // you can reset the entire set with a new list of models, without firing 737 // any `add` or `remove` events. Fires `reset` when finished. 738 reset: function(models, options) { 739 models || (models = []); 740 options || (options = {}); 741 for (var i = 0, l = this.models.length; i < l; i++) { 742 this._removeReference(this.models[i]); 743 } 744 this._reset(); 745 this.add(models, _.extend({silent: true}, options)); 746 if (!options.silent) this.trigger('reset', this, options); 747 return this; 748 }, 749 750 // Fetch the default set of models for this collection, resetting the 751 // collection when they arrive. If `add: true` is passed, appends the 752 // models to the collection instead of resetting. 753 fetch: function(options) { 754 options = options ? _.clone(options) : {}; 755 if (options.parse === undefined) options.parse = true; 756 var collection = this; 757 var success = options.success; 758 options.success = function(resp, status, xhr) { 759 collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); 760 if (success) success(collection, resp); 761 }; 762 options.error = Backbone.wrapError(options.error, collection, options); 763 return (this.sync || Backbone.sync).call(this, 'read', this, options); 764 }, 765 766 // Create a new instance of a model in this collection. Add the model to the 767 // collection immediately, unless `wait: true` is passed, in which case we 768 // wait for the server to agree. 769 create: function(model, options) { 770 var coll = this; 771 options = options ? _.clone(options) : {}; 772 model = this._prepareModel(model, options); 773 if (!model) return false; 774 if (!options.wait) coll.add(model, options); 775 var success = options.success; 776 options.success = function(nextModel, resp, xhr) { 777 if (options.wait) coll.add(nextModel, options); 778 if (success) { 779 success(nextModel, resp); 780 } else { 781 nextModel.trigger('sync', model, resp, options); 782 } 783 }; 784 model.save(null, options); 785 return model; 786 }, 787 788 // **parse** converts a response into a list of models to be added to the 789 // collection. The default implementation is just to pass it through. 790 parse: function(resp, xhr) { 791 return resp; 792 }, 793 794 // Proxy to _'s chain. Can't be proxied the same way the rest of the 795 // underscore methods are proxied because it relies on the underscore 796 // constructor. 797 chain: function () { 798 return _(this.models).chain(); 799 }, 800 801 // Reset all internal state. Called when the collection is reset. 802 _reset: function(options) { 803 this.length = 0; 804 this.models = []; 805 this._byId = {}; 806 this._byCid = {}; 807 }, 808 809 // Prepare a model or hash of attributes to be added to this collection. 810 _prepareModel: function(model, options) { 811 options || (options = {}); 812 if (!(model instanceof Model)) { 813 var attrs = model; 814 options.collection = this; 815 model = new this.model(attrs, options); 816 if (!model._validate(model.attributes, options)) model = false; 817 } else if (!model.collection) { 818 model.collection = this; 819 } 820 return model; 821 }, 822 823 // Internal method to remove a model's ties to a collection. 824 _removeReference: function(model) { 825 if (this == model.collection) { 826 delete model.collection; 827 } 828 model.off('all', this._onModelEvent, this); 829 }, 830 831 // Internal method called every time a model in the set fires an event. 832 // Sets need to update their indexes when models change ids. All other 833 // events simply proxy through. "add" and "remove" events that originate 834 // in other collections are ignored. 835 _onModelEvent: function(event, model, collection, options) { 836 if ((event == 'add' || event == 'remove') && collection != this) return; 837 if (event == 'destroy') { 838 this.remove(model, options); 839 } 840 if (model && event === 'change:' + model.idAttribute) { 841 delete this._byId[model.previous(model.idAttribute)]; 842 this._byId[model.id] = model; 843 } 844 this.trigger.apply(this, arguments); 845 } 846 847 }); 848 849 // Underscore methods that we want to implement on the Collection. 850 var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 851 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 852 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 853 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', 854 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; 855 856 // Mix in each Underscore method as a proxy to `Collection#models`. 857 _.each(methods, function(method) { 858 Collection.prototype[method] = function() { 859 return _[method].apply(_, [this.models].concat(_.toArray(arguments))); 860 }; 861 }); 862 863 // Backbone.Router 864 // ------------------- 865 866 // Routers map faux-URLs to actions, and fire events when routes are 867 // matched. Creating a new one sets its `routes` hash, if not set statically. 868 var Router = Backbone.Router = function(options) { 869 options || (options = {}); 870 if (options.routes) this.routes = options.routes; 871 this._bindRoutes(); 872 this.initialize.apply(this, arguments); 873 }; 874 875 // Cached regular expressions for matching named param parts and splatted 876 // parts of route strings. 877 var namedParam = /:\w+/g; 878 var splatParam = /\*\w+/g; 879 var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; 880 881 // Set up all inheritable **Backbone.Router** properties and methods. 882 _.extend(Router.prototype, Events, { 883 884 // Initialize is an empty function by default. Override it with your own 885 // initialization logic. 886 initialize: function(){}, 887 888 // Manually bind a single named route to a callback. For example: 889 // 890 // this.route('search/:query/p:num', 'search', function(query, num) { 891 // ... 892 // }); 893 // 894 route: function(route, name, callback) { 895 Backbone.history || (Backbone.history = new History); 896 if (!_.isRegExp(route)) route = this._routeToRegExp(route); 897 if (!callback) callback = this[name]; 898 Backbone.history.route(route, _.bind(function(fragment) { 899 var args = this._extractParameters(route, fragment); 900 callback && callback.apply(this, args); 901 this.trigger.apply(this, ['route:' + name].concat(args)); 902 Backbone.history.trigger('route', this, name, args); 903 }, this)); 904 return this; 905 }, 906 907 // Simple proxy to `Backbone.history` to save a fragment into the history. 908 navigate: function(fragment, options) { 909 Backbone.history.navigate(fragment, options); 910 }, 911 912 // Bind all defined routes to `Backbone.history`. We have to reverse the 913 // order of the routes here to support behavior where the most general 914 // routes can be defined at the bottom of the route map. 915 _bindRoutes: function() { 916 if (!this.routes) return; 917 var routes = []; 918 for (var route in this.routes) { 919 routes.unshift([route, this.routes[route]]); 920 } 921 for (var i = 0, l = routes.length; i < l; i++) { 922 this.route(routes[i][0], routes[i][1], this[routes[i][1]]); 923 } 924 }, 925 926 // Convert a route string into a regular expression, suitable for matching 927 // against the current location hash. 928 _routeToRegExp: function(route) { 929 route = route.replace(escapeRegExp, '\\$&') 930 .replace(namedParam, '([^\/]+)') 931 .replace(splatParam, '(.*?)'); 932 return new RegExp('^' + route + '$'); 933 }, 934 935 // Given a route, and a URL fragment that it matches, return the array of 936 // extracted parameters. 937 _extractParameters: function(route, fragment) { 938 return route.exec(fragment).slice(1); 939 } 940 941 }); 942 943 // Backbone.History 944 // ---------------- 945 946 // Handles cross-browser history management, based on URL fragments. If the 947 // browser does not support `onhashchange`, falls back to polling. 948 var History = Backbone.History = function() { 949 this.handlers = []; 950 _.bindAll(this, 'checkUrl'); 951 }; 952 953 // Cached regex for cleaning leading hashes and slashes . 954 var routeStripper = /^[#\/]/; 955 956 // Cached regex for detecting MSIE. 957 var isExplorer = /msie [\w.]+/; 958 959 // Has the history handling already been started? 960 History.started = false; 961 962 // Set up all inheritable **Backbone.History** properties and methods. 963 _.extend(History.prototype, Events, { 964 965 // The default interval to poll for hash changes, if necessary, is 966 // twenty times a second. 967 interval: 50, 968 969 // Gets the true hash value. Cannot use location.hash directly due to bug 970 // in Firefox where location.hash will always be decoded. 971 getHash: function(windowOverride) { 972 var loc = windowOverride ? windowOverride.location : window.location; 973 var match = loc.href.match(/#(.*)$/); 974 return match ? match[1] : ''; 975 }, 976 977 // Get the cross-browser normalized URL fragment, either from the URL, 978 // the hash, or the override. 979 getFragment: function(fragment, forcePushState) { 980 if (fragment == null) { 981 if (this._hasPushState || forcePushState) { 982 fragment = window.location.pathname; 983 var search = window.location.search; 984 if (search) fragment += search; 985 } else { 986 fragment = this.getHash(); 987 } 988 } 989 if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length); 990 return fragment.replace(routeStripper, ''); 991 }, 992 993 // Start the hash change handling, returning `true` if the current URL matches 994 // an existing route, and `false` otherwise. 995 start: function(options) { 996 if (History.started) throw new Error("Backbone.history has already been started"); 997 History.started = true; 998 999 // Figure out the initial configuration. Do we need an iframe? 1000 // Is pushState desired ... is it available? 1001 this.options = _.extend({}, {root: '/'}, this.options, options); 1002 this._wantsHashChange = this.options.hashChange !== false; 1003 this._wantsPushState = !!this.options.pushState; 1004 this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); 1005 var fragment = this.getFragment(); 1006 var docMode = document.documentMode; 1007 var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); 1008 1009 if (oldIE) { 1010 this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow; 1011 this.navigate(fragment); 1012 } 1013 1014 // Depending on whether we're using pushState or hashes, and whether 1015 // 'onhashchange' is supported, determine how we check the URL state. 1016 if (this._hasPushState) { 1017 $(window).bind('popstate', this.checkUrl); 1018 } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) { 1019 $(window).bind('hashchange', this.checkUrl); 1020 } else if (this._wantsHashChange) { 1021 this._checkUrlInterval = setInterval(this.checkUrl, this.interval); 1022 } 1023 1024 // Determine if we need to change the base url, for a pushState link 1025 // opened by a non-pushState browser. 1026 this.fragment = fragment; 1027 var loc = window.location; 1028 var atRoot = loc.pathname == this.options.root; 1029 1030 // If we've started off with a route from a `pushState`-enabled browser, 1031 // but we're currently in a browser that doesn't support it... 1032 if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) { 1033 this.fragment = this.getFragment(null, true); 1034 window.location.replace(this.options.root + '#' + this.fragment); 1035 // Return immediately as browser will do redirect to new url 1036 return true; 1037 1038 // Or if we've started out with a hash-based route, but we're currently 1039 // in a browser where it could be `pushState`-based instead... 1040 } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { 1041 this.fragment = this.getHash().replace(routeStripper, ''); 1042 window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment); 1043 } 1044 1045 if (!this.options.silent) { 1046 return this.loadUrl(); 1047 } 1048 }, 1049 1050 // Disable Backbone.history, perhaps temporarily. Not useful in a real app, 1051 // but possibly useful for unit testing Routers. 1052 stop: function() { 1053 $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl); 1054 clearInterval(this._checkUrlInterval); 1055 History.started = false; 1056 }, 1057 1058 // Add a route to be tested when the fragment changes. Routes added later 1059 // may override previous routes. 1060 route: function(route, callback) { 1061 this.handlers.unshift({route: route, callback: callback}); 1062 }, 1063 1064 // Checks the current URL to see if it has changed, and if it has, 1065 // calls `loadUrl`, normalizing across the hidden iframe. 1066 checkUrl: function(e) { 1067 var current = this.getFragment(); 1068 if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe)); 1069 if (current == this.fragment) return false; 1070 if (this.iframe) this.navigate(current); 1071 this.loadUrl() || this.loadUrl(this.getHash()); 1072 }, 1073 1074 // Attempt to load the current URL fragment. If a route succeeds with a 1075 // match, returns `true`. If no defined routes matches the fragment, 1076 // returns `false`. 1077 loadUrl: function(fragmentOverride) { 1078 var fragment = this.fragment = this.getFragment(fragmentOverride); 1079 var matched = _.any(this.handlers, function(handler) { 1080 if (handler.route.test(fragment)) { 1081 handler.callback(fragment); 1082 return true; 1083 } 1084 }); 1085 return matched; 1086 }, 1087 1088 // Save a fragment into the hash history, or replace the URL state if the 1089 // 'replace' option is passed. You are responsible for properly URL-encoding 1090 // the fragment in advance. 1091 // 1092 // The options object can contain `trigger: true` if you wish to have the 1093 // route callback be fired (not usually desirable), or `replace: true`, if 1094 // you wish to modify the current URL without adding an entry to the history. 1095 navigate: function(fragment, options) { 1096 if (!History.started) return false; 1097 if (!options || options === true) options = {trigger: options}; 1098 var frag = (fragment || '').replace(routeStripper, ''); 1099 if (this.fragment == frag) return; 1100 1101 // If pushState is available, we use it to set the fragment as a real URL. 1102 if (this._hasPushState) { 1103 if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag; 1104 this.fragment = frag; 1105 window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag); 1106 1107 // If hash changes haven't been explicitly disabled, update the hash 1108 // fragment to store history. 1109 } else if (this._wantsHashChange) { 1110 this.fragment = frag; 1111 this._updateHash(window.location, frag, options.replace); 1112 if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) { 1113 // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change. 1114 // When replace is true, we don't want this. 1115 if(!options.replace) this.iframe.document.open().close(); 1116 this._updateHash(this.iframe.location, frag, options.replace); 1117 } 1118 1119 // If you've told us that you explicitly don't want fallback hashchange- 1120 // based history, then `navigate` becomes a page refresh. 1121 } else { 1122 window.location.assign(this.options.root + fragment); 1123 } 1124 if (options.trigger) this.loadUrl(fragment); 1125 }, 1126 1127 // Update the hash location, either replacing the current entry, or adding 1128 // a new one to the browser history. 1129 _updateHash: function(location, fragment, replace) { 1130 if (replace) { 1131 location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment); 1132 } else { 1133 location.hash = fragment; 1134 } 1135 } 1136 }); 1137 1138 // Backbone.View 1139 // ------------- 1140 1141 // Creating a Backbone.View creates its initial element outside of the DOM, 1142 // if an existing element is not provided... 1143 var View = Backbone.View = function(options) { 1144 this.cid = _.uniqueId('view'); 1145 this._configure(options || {}); 1146 this._ensureElement(); 1147 this.initialize.apply(this, arguments); 1148 this.delegateEvents(); 1149 }; 1150 1151 // Cached regex to split keys for `delegate`. 1152 var delegateEventSplitter = /^(\S+)\s*(.*)$/; 1153 1154 // List of view options to be merged as properties. 1155 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; 1156 1157 // Set up all inheritable **Backbone.View** properties and methods. 1158 _.extend(View.prototype, Events, { 1159 1160 // The default `tagName` of a View's element is `"div"`. 1161 tagName: 'div', 1162 1163 // jQuery delegate for element lookup, scoped to DOM elements within the 1164 // current view. This should be prefered to global lookups where possible. 1165 $: function(selector) { 1166 return this.$el.find(selector); 1167 }, 1168 1169 // Initialize is an empty function by default. Override it with your own 1170 // initialization logic. 1171 initialize: function(){}, 1172 1173 // **render** is the core function that your view should override, in order 1174 // to populate its element (`this.el`), with the appropriate HTML. The 1175 // convention is for **render** to always return `this`. 1176 render: function() { 1177 return this; 1178 }, 1179 1180 // Remove this view from the DOM. Note that the view isn't present in the 1181 // DOM by default, so calling this method may be a no-op. 1182 remove: function() { 1183 this.$el.remove(); 1184 return this; 1185 }, 1186 1187 // For small amounts of DOM Elements, where a full-blown template isn't 1188 // needed, use **make** to manufacture elements, one at a time. 1189 // 1190 // var el = this.make('li', {'class': 'row'}, this.model.escape('title')); 1191 // 1192 make: function(tagName, attributes, content) { 1193 var el = document.createElement(tagName); 1194 if (attributes) $(el).attr(attributes); 1195 if (content) $(el).html(content); 1196 return el; 1197 }, 1198 1199 // Change the view's element (`this.el` property), including event 1200 // re-delegation. 1201 setElement: function(element, delegate) { 1202 if (this.$el) this.undelegateEvents(); 1203 this.$el = (element instanceof $) ? element : $(element); 1204 this.el = this.$el[0]; 1205 if (delegate !== false) this.delegateEvents(); 1206 return this; 1207 }, 1208 1209 // Set callbacks, where `this.events` is a hash of 1210 // 1211 // *{"event selector": "callback"}* 1212 // 1213 // { 1214 // 'mousedown .title': 'edit', 1215 // 'click .button': 'save' 1216 // 'click .open': function(e) { ... } 1217 // } 1218 // 1219 // pairs. Callbacks will be bound to the view, with `this` set properly. 1220 // Uses event delegation for efficiency. 1221 // Omitting the selector binds the event to `this.el`. 1222 // This only works for delegate-able events: not `focus`, `blur`, and 1223 // not `change`, `submit`, and `reset` in Internet Explorer. 1224 delegateEvents: function(events) { 1225 if (!(events || (events = getValue(this, 'events')))) return; 1226 this.undelegateEvents(); 1227 for (var key in events) { 1228 var method = events[key]; 1229 if (!_.isFunction(method)) method = this[events[key]]; 1230 if (!method) throw new Error('Method "' + events[key] + '" does not exist'); 1231 var match = key.match(delegateEventSplitter); 1232 var eventName = match[1], selector = match[2]; 1233 method = _.bind(method, this); 1234 eventName += '.delegateEvents' + this.cid; 1235 if (selector === '') { 1236 this.$el.bind(eventName, method); 1237 } else { 1238 this.$el.delegate(selector, eventName, method); 1239 } 1240 } 1241 }, 1242 1243 // Clears all callbacks previously bound to the view with `delegateEvents`. 1244 // You usually don't need to use this, but may wish to if you have multiple 1245 // Backbone views attached to the same DOM element. 1246 undelegateEvents: function() { 1247 this.$el.unbind('.delegateEvents' + this.cid); 1248 }, 1249 1250 // Performs the initial configuration of a View with a set of options. 1251 // Keys with special meaning *(model, collection, id, className)*, are 1252 // attached directly to the view. 1253 _configure: function(options) { 1254 if (this.options) options = _.extend({}, this.options, options); 1255 for (var i = 0, l = viewOptions.length; i < l; i++) { 1256 var attr = viewOptions[i]; 1257 if (options[attr]) this[attr] = options[attr]; 1258 } 1259 this.options = options; 1260 }, 1261 1262 // Ensure that the View has a DOM element to render into. 1263 // If `this.el` is a string, pass it through `$()`, take the first 1264 // matching element, and re-assign it to `el`. Otherwise, create 1265 // an element from the `id`, `className` and `tagName` properties. 1266 _ensureElement: function() { 1267 if (!this.el) { 1268 var attrs = getValue(this, 'attributes') || {}; 1269 if (this.id) attrs.id = this.id; 1270 if (this.className) attrs['class'] = this.className; 1271 this.setElement(this.make(this.tagName, attrs), false); 1272 } else { 1273 this.setElement(this.el, false); 1274 } 1275 } 1276 1277 }); 1278 1279 // The self-propagating extend function that Backbone classes use. 1280 var extend = function (protoProps, classProps) { 1281 var child = inherits(this, protoProps, classProps); 1282 child.extend = this.extend; 1283 return child; 1284 }; 1285 1286 // Set up inheritance for the model, collection, and view. 1287 Model.extend = Collection.extend = Router.extend = View.extend = extend; 1288 1289 // Backbone.sync 1290 // ------------- 1291 1292 // Map from CRUD to HTTP for our default `Backbone.sync` implementation. 1293 var methodMap = { 1294 'create': 'POST', 1295 'update': 'PUT', 1296 'delete': 'DELETE', 1297 'read': 'GET' 1298 }; 1299 1300 // Override this function to change the manner in which Backbone persists 1301 // models to the server. You will be passed the type of request, and the 1302 // model in question. By default, makes a RESTful Ajax request 1303 // to the model's `url()`. Some possible customizations could be: 1304 // 1305 // * Use `setTimeout` to batch rapid-fire updates into a single request. 1306 // * Send up the models as XML instead of JSON. 1307 // * Persist models via WebSockets instead of Ajax. 1308 // 1309 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests 1310 // as `POST`, with a `_method` parameter containing the true HTTP method, 1311 // as well as all requests with the body as `application/x-www-form-urlencoded` 1312 // instead of `application/json` with the model in a param named `model`. 1313 // Useful when interfacing with server-side languages like **PHP** that make 1314 // it difficult to read the body of `PUT` requests. 1315 Backbone.sync = function(method, model, options) { 1316 var type = methodMap[method]; 1317 1318 // Default options, unless specified. 1319 options || (options = {}); 1320 1321 // Default JSON-request options. 1322 var params = {type: type, dataType: 'json'}; 1323 1324 // Ensure that we have a URL. 1325 if (!options.url) { 1326 params.url = getValue(model, 'url') || urlError(); 1327 } 1328 1329 // Ensure that we have the appropriate request data. 1330 if (!options.data && model && (method == 'create' || method == 'update')) { 1331 params.contentType = 'application/json'; 1332 params.data = JSON.stringify(model.toJSON()); 1333 } 1334 1335 // For older servers, emulate JSON by encoding the request into an HTML-form. 1336 if (Backbone.emulateJSON) { 1337 params.contentType = 'application/x-www-form-urlencoded'; 1338 params.data = params.data ? {model: params.data} : {}; 1339 } 1340 1341 // For older servers, emulate HTTP by mimicking the HTTP method with `_method` 1342 // And an `X-HTTP-Method-Override` header. 1343 if (Backbone.emulateHTTP) { 1344 if (type === 'PUT' || type === 'DELETE') { 1345 if (Backbone.emulateJSON) params.data._method = type; 1346 params.type = 'POST'; 1347 params.beforeSend = function(xhr) { 1348 xhr.setRequestHeader('X-HTTP-Method-Override', type); 1349 }; 1350 } 1351 } 1352 1353 // Don't process data on a non-GET request. 1354 if (params.type !== 'GET' && !Backbone.emulateJSON) { 1355 params.processData = false; 1356 } 1357 1358 // Make the request, allowing the user to override any Ajax options. 1359 return $.ajax(_.extend(params, options)); 1360 }; 1361 1362 // Wrap an optional error callback with a fallback error event. 1363 Backbone.wrapError = function(onError, originalModel, options) { 1364 return function(model, resp) { 1365 resp = model === originalModel ? resp : model; 1366 if (onError) { 1367 onError(originalModel, resp, options); 1368 } else { 1369 originalModel.trigger('error', originalModel, resp, options); 1370 } 1371 }; 1372 }; 1373 1374 // Helpers 1375 // ------- 1376 1377 // Shared empty constructor function to aid in prototype-chain creation. 1378 var ctor = function(){}; 1379 1380 // Helper function to correctly set up the prototype chain, for subclasses. 1381 // Similar to `goog.inherits`, but uses a hash of prototype properties and 1382 // class properties to be extended. 1383 var inherits = function(parent, protoProps, staticProps) { 1384 var child; 1385 1386 // The constructor function for the new subclass is either defined by you 1387 // (the "constructor" property in your `extend` definition), or defaulted 1388 // by us to simply call the parent's constructor. 1389 if (protoProps && protoProps.hasOwnProperty('constructor')) { 1390 child = protoProps.constructor; 1391 } else { 1392 child = function(){ parent.apply(this, arguments); }; 1393 } 1394 1395 // Inherit class (static) properties from parent. 1396 _.extend(child, parent); 1397 1398 // Set the prototype chain to inherit from `parent`, without calling 1399 // `parent`'s constructor function. 1400 ctor.prototype = parent.prototype; 1401 child.prototype = new ctor(); 1402 1403 // Add prototype properties (instance properties) to the subclass, 1404 // if supplied. 1405 if (protoProps) _.extend(child.prototype, protoProps); 1406 1407 // Add static properties to the constructor function, if supplied. 1408 if (staticProps) _.extend(child, staticProps); 1409 1410 // Correctly set child's `prototype.constructor`. 1411 child.prototype.constructor = child; 1412 1413 // Set a convenience property in case the parent's prototype is needed later. 1414 child.__super__ = parent.prototype; 1415 1416 return child; 1417 }; 1418 1419 // Helper function to get a value from a Backbone object as a property 1420 // or as a function. 1421 var getValue = function(object, prop) { 1422 if (!(object && object[prop])) return null; 1423 return _.isFunction(object[prop]) ? object[prop]() : object[prop]; 1424 }; 1425 1426 // Throw an error when a URL is needed, and none is supplied. 1427 var urlError = function() { 1428 throw new Error('A "url" property or function must be specified'); 1429 }; 1430 1431 }).call(this);