parsejs-rails 1.2.2.0 → 1.2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Parse JavaScript SDK
3
- * Version: 1.2.2
4
- * Built: Tue Mar 05 2013 13:27:37
3
+ * Version: 1.2.7
4
+ * Built: Wed Apr 17 2013 14:48:27
5
5
  * http://parse.com
6
6
  *
7
7
  * Copyright 2013 Parse, Inc.
@@ -13,17 +13,12 @@
13
13
  */
14
14
  (function(root) {
15
15
  root.Parse = root.Parse || {};
16
- root.Parse.VERSION = "js1.2.2";
16
+ root.Parse.VERSION = "js1.2.7";
17
17
  }(this));
18
-
19
-
20
- // Underscore.js 1.3.3
21
- // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
22
- // Underscore is freely distributable under the MIT license.
23
- // Portions of Underscore are inspired or borrowed from Prototype,
24
- // Oliver Steele's Functional, and John Resig's Micro-Templating.
25
- // For all details and documentation:
26
- // http://documentcloud.github.com/underscore
18
+ // Underscore.js 1.4.4
19
+ // http://underscorejs.org
20
+ // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
21
+ // Underscore may be freely distributed under the MIT license.
27
22
 
28
23
  (function() {
29
24
 
@@ -43,8 +38,9 @@
43
38
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
44
39
 
45
40
  // Create quick reference variables for speed access to core prototypes.
46
- var slice = ArrayProto.slice,
47
- unshift = ArrayProto.unshift,
41
+ var push = ArrayProto.push,
42
+ slice = ArrayProto.slice,
43
+ concat = ArrayProto.concat,
48
44
  toString = ObjProto.toString,
49
45
  hasOwnProperty = ObjProto.hasOwnProperty;
50
46
 
@@ -65,7 +61,11 @@
65
61
  nativeBind = FuncProto.bind;
66
62
 
67
63
  // Create a safe reference to the Underscore object for use below.
68
- var _ = function(obj) { return new wrapper(obj); };
64
+ var _ = function(obj) {
65
+ if (obj instanceof _) return obj;
66
+ if (!(this instanceof _)) return new _(obj);
67
+ this._wrapped = obj;
68
+ };
69
69
 
70
70
  // Export the Underscore object for **Node.js**, with
71
71
  // backwards-compatibility for the old `require()` API. If we're in
@@ -77,11 +77,11 @@
77
77
  }
78
78
  exports._ = _;
79
79
  } else {
80
- root['_'] = _;
80
+ root._ = _;
81
81
  }
82
82
 
83
83
  // Current version.
84
- _.VERSION = '1.3.3';
84
+ _.VERSION = '1.4.4';
85
85
 
86
86
  // Collection Functions
87
87
  // --------------------
@@ -95,7 +95,7 @@
95
95
  obj.forEach(iterator, context);
96
96
  } else if (obj.length === +obj.length) {
97
97
  for (var i = 0, l = obj.length; i < l; i++) {
98
- if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
98
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
99
99
  }
100
100
  } else {
101
101
  for (var key in obj) {
@@ -115,10 +115,11 @@
115
115
  each(obj, function(value, index, list) {
116
116
  results[results.length] = iterator.call(context, value, index, list);
117
117
  });
118
- if (obj.length === +obj.length) results.length = obj.length;
119
118
  return results;
120
119
  };
121
120
 
121
+ var reduceError = 'Reduce of empty array with no initial value';
122
+
122
123
  // **Reduce** builds up a single result from a list of values, aka `inject`,
123
124
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
124
125
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
@@ -136,7 +137,7 @@
136
137
  memo = iterator.call(context, memo, value, index, list);
137
138
  }
138
139
  });
139
- if (!initial) throw new TypeError('Reduce of empty array with no initial value');
140
+ if (!initial) throw new TypeError(reduceError);
140
141
  return memo;
141
142
  };
142
143
 
@@ -149,9 +150,22 @@
149
150
  if (context) iterator = _.bind(iterator, context);
150
151
  return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
151
152
  }
152
- var reversed = _.toArray(obj).reverse();
153
- if (context && !initial) iterator = _.bind(iterator, context);
154
- return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
153
+ var length = obj.length;
154
+ if (length !== +length) {
155
+ var keys = _.keys(obj);
156
+ length = keys.length;
157
+ }
158
+ each(obj, function(value, index, list) {
159
+ index = keys ? keys[--length] : --length;
160
+ if (!initial) {
161
+ memo = obj[index];
162
+ initial = true;
163
+ } else {
164
+ memo = iterator.call(context, memo, obj[index], index, list);
165
+ }
166
+ });
167
+ if (!initial) throw new TypeError(reduceError);
168
+ return memo;
155
169
  };
156
170
 
157
171
  // Return the first value which passes a truth test. Aliased as `detect`.
@@ -181,18 +195,16 @@
181
195
 
182
196
  // Return all the elements for which a truth test fails.
183
197
  _.reject = function(obj, iterator, context) {
184
- var results = [];
185
- if (obj == null) return results;
186
- each(obj, function(value, index, list) {
187
- if (!iterator.call(context, value, index, list)) results[results.length] = value;
188
- });
189
- return results;
198
+ return _.filter(obj, function(value, index, list) {
199
+ return !iterator.call(context, value, index, list);
200
+ }, context);
190
201
  };
191
202
 
192
203
  // Determine whether all of the elements match a truth test.
193
204
  // Delegates to **ECMAScript 5**'s native `every` if available.
194
205
  // Aliased as `all`.
195
206
  _.every = _.all = function(obj, iterator, context) {
207
+ iterator || (iterator = _.identity);
196
208
  var result = true;
197
209
  if (obj == null) return result;
198
210
  if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
@@ -216,23 +228,22 @@
216
228
  return !!result;
217
229
  };
218
230
 
219
- // Determine if a given value is included in the array or object using `===`.
220
- // Aliased as `contains`.
221
- _.include = _.contains = function(obj, target) {
222
- var found = false;
223
- if (obj == null) return found;
231
+ // Determine if the array or object contains a given value (using `===`).
232
+ // Aliased as `include`.
233
+ _.contains = _.include = function(obj, target) {
234
+ if (obj == null) return false;
224
235
  if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
225
- found = any(obj, function(value) {
236
+ return any(obj, function(value) {
226
237
  return value === target;
227
238
  });
228
- return found;
229
239
  };
230
240
 
231
241
  // Invoke a method (with arguments) on every item in a collection.
232
242
  _.invoke = function(obj, method) {
233
243
  var args = slice.call(arguments, 2);
244
+ var isFunc = _.isFunction(method);
234
245
  return _.map(obj, function(value) {
235
- return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
246
+ return (isFunc ? method : value[method]).apply(value, args);
236
247
  });
237
248
  };
238
249
 
@@ -241,11 +252,33 @@
241
252
  return _.map(obj, function(value){ return value[key]; });
242
253
  };
243
254
 
255
+ // Convenience version of a common use case of `filter`: selecting only objects
256
+ // containing specific `key:value` pairs.
257
+ _.where = function(obj, attrs, first) {
258
+ if (_.isEmpty(attrs)) return first ? null : [];
259
+ return _[first ? 'find' : 'filter'](obj, function(value) {
260
+ for (var key in attrs) {
261
+ if (attrs[key] !== value[key]) return false;
262
+ }
263
+ return true;
264
+ });
265
+ };
266
+
267
+ // Convenience version of a common use case of `find`: getting the first object
268
+ // containing specific `key:value` pairs.
269
+ _.findWhere = function(obj, attrs) {
270
+ return _.where(obj, attrs, true);
271
+ };
272
+
244
273
  // Return the maximum element or (element-based computation).
274
+ // Can't optimize arrays of integers longer than 65,535 elements.
275
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
245
276
  _.max = function(obj, iterator, context) {
246
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
277
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
278
+ return Math.max.apply(Math, obj);
279
+ }
247
280
  if (!iterator && _.isEmpty(obj)) return -Infinity;
248
- var result = {computed : -Infinity};
281
+ var result = {computed : -Infinity, value: -Infinity};
249
282
  each(obj, function(value, index, list) {
250
283
  var computed = iterator ? iterator.call(context, value, index, list) : value;
251
284
  computed >= result.computed && (result = {value : value, computed : computed});
@@ -255,9 +288,11 @@
255
288
 
256
289
  // Return the minimum element (or element-based computation).
257
290
  _.min = function(obj, iterator, context) {
258
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
291
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
292
+ return Math.min.apply(Math, obj);
293
+ }
259
294
  if (!iterator && _.isEmpty(obj)) return Infinity;
260
- var result = {computed : Infinity};
295
+ var result = {computed : Infinity, value: Infinity};
261
296
  each(obj, function(value, index, list) {
262
297
  var computed = iterator ? iterator.call(context, value, index, list) : value;
263
298
  computed < result.computed && (result = {value : value, computed : computed});
@@ -267,67 +302,96 @@
267
302
 
268
303
  // Shuffle an array.
269
304
  _.shuffle = function(obj) {
270
- var shuffled = [], rand;
271
- each(obj, function(value, index, list) {
272
- rand = Math.floor(Math.random() * (index + 1));
273
- shuffled[index] = shuffled[rand];
305
+ var rand;
306
+ var index = 0;
307
+ var shuffled = [];
308
+ each(obj, function(value) {
309
+ rand = _.random(index++);
310
+ shuffled[index - 1] = shuffled[rand];
274
311
  shuffled[rand] = value;
275
312
  });
276
313
  return shuffled;
277
314
  };
278
315
 
316
+ // An internal function to generate lookup iterators.
317
+ var lookupIterator = function(value) {
318
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
319
+ };
320
+
279
321
  // Sort the object's values by a criterion produced by an iterator.
280
- _.sortBy = function(obj, val, context) {
281
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
322
+ _.sortBy = function(obj, value, context) {
323
+ var iterator = lookupIterator(value);
282
324
  return _.pluck(_.map(obj, function(value, index, list) {
283
325
  return {
284
326
  value : value,
327
+ index : index,
285
328
  criteria : iterator.call(context, value, index, list)
286
329
  };
287
330
  }).sort(function(left, right) {
288
- var a = left.criteria, b = right.criteria;
289
- if (a === void 0) return 1;
290
- if (b === void 0) return -1;
291
- return a < b ? -1 : a > b ? 1 : 0;
331
+ var a = left.criteria;
332
+ var b = right.criteria;
333
+ if (a !== b) {
334
+ if (a > b || a === void 0) return 1;
335
+ if (a < b || b === void 0) return -1;
336
+ }
337
+ return left.index < right.index ? -1 : 1;
292
338
  }), 'value');
293
339
  };
294
340
 
295
- // Groups the object's values by a criterion. Pass either a string attribute
296
- // to group by, or a function that returns the criterion.
297
- _.groupBy = function(obj, val) {
341
+ // An internal function used for aggregate "group by" operations.
342
+ var group = function(obj, value, context, behavior) {
298
343
  var result = {};
299
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
344
+ var iterator = lookupIterator(value || _.identity);
300
345
  each(obj, function(value, index) {
301
- var key = iterator(value, index);
302
- (result[key] || (result[key] = [])).push(value);
346
+ var key = iterator.call(context, value, index, obj);
347
+ behavior(result, key, value);
303
348
  });
304
349
  return result;
305
350
  };
306
351
 
307
- // Use a comparator function to figure out at what index an object should
308
- // be inserted so as to maintain order. Uses binary search.
309
- _.sortedIndex = function(array, obj, iterator) {
310
- iterator || (iterator = _.identity);
352
+ // Groups the object's values by a criterion. Pass either a string attribute
353
+ // to group by, or a function that returns the criterion.
354
+ _.groupBy = function(obj, value, context) {
355
+ return group(obj, value, context, function(result, key, value) {
356
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
357
+ });
358
+ };
359
+
360
+ // Counts instances of an object that group by a certain criterion. Pass
361
+ // either a string attribute to count by, or a function that returns the
362
+ // criterion.
363
+ _.countBy = function(obj, value, context) {
364
+ return group(obj, value, context, function(result, key) {
365
+ if (!_.has(result, key)) result[key] = 0;
366
+ result[key]++;
367
+ });
368
+ };
369
+
370
+ // Use a comparator function to figure out the smallest index at which
371
+ // an object should be inserted so as to maintain order. Uses binary search.
372
+ _.sortedIndex = function(array, obj, iterator, context) {
373
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
374
+ var value = iterator.call(context, obj);
311
375
  var low = 0, high = array.length;
312
376
  while (low < high) {
313
- var mid = (low + high) >> 1;
314
- iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
377
+ var mid = (low + high) >>> 1;
378
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
315
379
  }
316
380
  return low;
317
381
  };
318
382
 
319
383
  // Safely convert anything iterable into a real, live array.
320
384
  _.toArray = function(obj) {
321
- if (!obj) return [];
322
- if (_.isArray(obj)) return slice.call(obj);
323
- if (_.isArguments(obj)) return slice.call(obj);
324
- if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
385
+ if (!obj) return [];
386
+ if (_.isArray(obj)) return slice.call(obj);
387
+ if (obj.length === +obj.length) return _.map(obj, _.identity);
325
388
  return _.values(obj);
326
389
  };
327
390
 
328
391
  // Return the number of elements in an object.
329
392
  _.size = function(obj) {
330
- return _.isArray(obj) ? obj.length : _.keys(obj).length;
393
+ if (obj == null) return 0;
394
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
331
395
  };
332
396
 
333
397
  // Array Functions
@@ -337,10 +401,11 @@
337
401
  // values in the array. Aliased as `head` and `take`. The **guard** check
338
402
  // allows it to work with `_.map`.
339
403
  _.first = _.head = _.take = function(array, n, guard) {
404
+ if (array == null) return void 0;
340
405
  return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
341
406
  };
342
407
 
343
- // Returns everything but the last entry of the array. Especcialy useful on
408
+ // Returns everything but the last entry of the array. Especially useful on
344
409
  // the arguments object. Passing **n** will return all the values in
345
410
  // the array, excluding the last N. The **guard** check allows it to work with
346
411
  // `_.map`.
@@ -351,6 +416,7 @@
351
416
  // Get the last element of an array. Passing **n** will return the last N
352
417
  // values in the array. The **guard** check allows it to work with `_.map`.
353
418
  _.last = function(array, n, guard) {
419
+ if (array == null) return void 0;
354
420
  if ((n != null) && !guard) {
355
421
  return slice.call(array, Math.max(array.length - n, 0));
356
422
  } else {
@@ -358,26 +424,34 @@
358
424
  }
359
425
  };
360
426
 
361
- // Returns everything but the first entry of the array. Aliased as `tail`.
362
- // Especially useful on the arguments object. Passing an **index** will return
363
- // the rest of the values in the array from that index onward. The **guard**
427
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
428
+ // Especially useful on the arguments object. Passing an **n** will return
429
+ // the rest N values in the array. The **guard**
364
430
  // check allows it to work with `_.map`.
365
- _.rest = _.tail = function(array, index, guard) {
366
- return slice.call(array, (index == null) || guard ? 1 : index);
431
+ _.rest = _.tail = _.drop = function(array, n, guard) {
432
+ return slice.call(array, (n == null) || guard ? 1 : n);
367
433
  };
368
434
 
369
435
  // Trim out all falsy values from an array.
370
436
  _.compact = function(array) {
371
- return _.filter(array, function(value){ return !!value; });
437
+ return _.filter(array, _.identity);
438
+ };
439
+
440
+ // Internal implementation of a recursive `flatten` function.
441
+ var flatten = function(input, shallow, output) {
442
+ each(input, function(value) {
443
+ if (_.isArray(value)) {
444
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
445
+ } else {
446
+ output.push(value);
447
+ }
448
+ });
449
+ return output;
372
450
  };
373
451
 
374
452
  // Return a completely flattened version of an array.
375
453
  _.flatten = function(array, shallow) {
376
- return _.reduce(array, function(memo, value) {
377
- if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
378
- memo[memo.length] = value;
379
- return memo;
380
- }, []);
454
+ return flatten(array, shallow, []);
381
455
  };
382
456
 
383
457
  // Return a version of the array that does not contain the specified value(s).
@@ -388,30 +462,33 @@
388
462
  // Produce a duplicate-free version of the array. If the array has already
389
463
  // been sorted, you have the option of using a faster algorithm.
390
464
  // Aliased as `unique`.
391
- _.uniq = _.unique = function(array, isSorted, iterator) {
392
- var initial = iterator ? _.map(array, iterator) : array;
465
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
466
+ if (_.isFunction(isSorted)) {
467
+ context = iterator;
468
+ iterator = isSorted;
469
+ isSorted = false;
470
+ }
471
+ var initial = iterator ? _.map(array, iterator, context) : array;
393
472
  var results = [];
394
- // The `isSorted` flag is irrelevant if the array only contains two elements.
395
- if (array.length < 3) isSorted = true;
396
- _.reduce(initial, function (memo, value, index) {
397
- if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
398
- memo.push(value);
473
+ var seen = [];
474
+ each(initial, function(value, index) {
475
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
476
+ seen.push(value);
399
477
  results.push(array[index]);
400
478
  }
401
- return memo;
402
- }, []);
479
+ });
403
480
  return results;
404
481
  };
405
482
 
406
483
  // Produce an array that contains the union: each distinct element from all of
407
484
  // the passed-in arrays.
408
485
  _.union = function() {
409
- return _.uniq(_.flatten(arguments, true));
486
+ return _.uniq(concat.apply(ArrayProto, arguments));
410
487
  };
411
488
 
412
489
  // Produce an array that contains every item shared between all the
413
- // passed-in arrays. (Aliased as "intersect" for back-compat.)
414
- _.intersection = _.intersect = function(array) {
490
+ // passed-in arrays.
491
+ _.intersection = function(array) {
415
492
  var rest = slice.call(arguments, 1);
416
493
  return _.filter(_.uniq(array), function(item) {
417
494
  return _.every(rest, function(other) {
@@ -423,8 +500,8 @@
423
500
  // Take the difference between one array and a number of other arrays.
424
501
  // Only the elements present in just the first array will remain.
425
502
  _.difference = function(array) {
426
- var rest = _.flatten(slice.call(arguments, 1), true);
427
- return _.filter(array, function(value){ return !_.include(rest, value); });
503
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
504
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
428
505
  };
429
506
 
430
507
  // Zip together multiple lists into a single array -- elements that share
@@ -433,10 +510,28 @@
433
510
  var args = slice.call(arguments);
434
511
  var length = _.max(_.pluck(args, 'length'));
435
512
  var results = new Array(length);
436
- for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
513
+ for (var i = 0; i < length; i++) {
514
+ results[i] = _.pluck(args, "" + i);
515
+ }
437
516
  return results;
438
517
  };
439
518
 
519
+ // Converts lists into objects. Pass either a single array of `[key, value]`
520
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
521
+ // the corresponding values.
522
+ _.object = function(list, values) {
523
+ if (list == null) return {};
524
+ var result = {};
525
+ for (var i = 0, l = list.length; i < l; i++) {
526
+ if (values) {
527
+ result[list[i]] = values[i];
528
+ } else {
529
+ result[list[i][0]] = list[i][1];
530
+ }
531
+ }
532
+ return result;
533
+ };
534
+
440
535
  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
441
536
  // we need this function. Return the position of the first occurrence of an
442
537
  // item in an array, or -1 if the item is not included in the array.
@@ -445,22 +540,29 @@
445
540
  // for **isSorted** to use binary search.
446
541
  _.indexOf = function(array, item, isSorted) {
447
542
  if (array == null) return -1;
448
- var i, l;
543
+ var i = 0, l = array.length;
449
544
  if (isSorted) {
450
- i = _.sortedIndex(array, item);
451
- return array[i] === item ? i : -1;
545
+ if (typeof isSorted == 'number') {
546
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
547
+ } else {
548
+ i = _.sortedIndex(array, item);
549
+ return array[i] === item ? i : -1;
550
+ }
452
551
  }
453
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
454
- for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
552
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
553
+ for (; i < l; i++) if (array[i] === item) return i;
455
554
  return -1;
456
555
  };
457
556
 
458
557
  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
459
- _.lastIndexOf = function(array, item) {
558
+ _.lastIndexOf = function(array, item, from) {
460
559
  if (array == null) return -1;
461
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
462
- var i = array.length;
463
- while (i--) if (i in array && array[i] === item) return i;
560
+ var hasIndex = from != null;
561
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
562
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
563
+ }
564
+ var i = (hasIndex ? from : array.length);
565
+ while (i--) if (array[i] === item) return i;
464
566
  return -1;
465
567
  };
466
568
 
@@ -489,25 +591,23 @@
489
591
  // Function (ahem) Functions
490
592
  // ------------------
491
593
 
492
- // Reusable constructor function for prototype setting.
493
- var ctor = function(){};
494
-
495
594
  // Create a function bound to a given object (assigning `this`, and arguments,
496
- // optionally). Binding with arguments is also known as `curry`.
497
- // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
498
- // We check for `func.bind` first, to fail fast when `func` is undefined.
499
- _.bind = function bind(func, context) {
500
- var bound, args;
595
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
596
+ // available.
597
+ _.bind = function(func, context) {
501
598
  if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
502
- if (!_.isFunction(func)) throw new TypeError;
503
- args = slice.call(arguments, 2);
504
- return bound = function() {
505
- if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
506
- ctor.prototype = func.prototype;
507
- var self = new ctor;
508
- var result = func.apply(self, args.concat(slice.call(arguments)));
509
- if (Object(result) === result) return result;
510
- return self;
599
+ var args = slice.call(arguments, 2);
600
+ return function() {
601
+ return func.apply(context, args.concat(slice.call(arguments)));
602
+ };
603
+ };
604
+
605
+ // Partially apply a function by creating a version that has had some of its
606
+ // arguments pre-filled, without changing its dynamic `this` context.
607
+ _.partial = function(func) {
608
+ var args = slice.call(arguments, 1);
609
+ return function() {
610
+ return func.apply(this, args.concat(slice.call(arguments)));
511
611
  };
512
612
  };
513
613
 
@@ -515,7 +615,7 @@
515
615
  // all callbacks defined on an object belong to it.
516
616
  _.bindAll = function(obj) {
517
617
  var funcs = slice.call(arguments, 1);
518
- if (funcs.length == 0) funcs = _.functions(obj);
618
+ if (funcs.length === 0) funcs = _.functions(obj);
519
619
  each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
520
620
  return obj;
521
621
  };
@@ -546,23 +646,26 @@
546
646
  // Returns a function, that, when invoked, will only be triggered at most once
547
647
  // during a given window of time.
548
648
  _.throttle = function(func, wait) {
549
- var context, args, timeout, throttling, more, result;
550
- var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
649
+ var context, args, timeout, result;
650
+ var previous = 0;
651
+ var later = function() {
652
+ previous = new Date;
653
+ timeout = null;
654
+ result = func.apply(context, args);
655
+ };
551
656
  return function() {
552
- context = this; args = arguments;
553
- var later = function() {
657
+ var now = new Date;
658
+ var remaining = wait - (now - previous);
659
+ context = this;
660
+ args = arguments;
661
+ if (remaining <= 0) {
662
+ clearTimeout(timeout);
554
663
  timeout = null;
555
- if (more) func.apply(context, args);
556
- whenDone();
557
- };
558
- if (!timeout) timeout = setTimeout(later, wait);
559
- if (throttling) {
560
- more = true;
561
- } else {
664
+ previous = now;
562
665
  result = func.apply(context, args);
666
+ } else if (!timeout) {
667
+ timeout = setTimeout(later, remaining);
563
668
  }
564
- whenDone();
565
- throttling = true;
566
669
  return result;
567
670
  };
568
671
  };
@@ -572,16 +675,18 @@
572
675
  // N milliseconds. If `immediate` is passed, trigger the function on the
573
676
  // leading edge, instead of the trailing.
574
677
  _.debounce = function(func, wait, immediate) {
575
- var timeout;
678
+ var timeout, result;
576
679
  return function() {
577
680
  var context = this, args = arguments;
578
681
  var later = function() {
579
682
  timeout = null;
580
- if (!immediate) func.apply(context, args);
683
+ if (!immediate) result = func.apply(context, args);
581
684
  };
582
- if (immediate && !timeout) func.apply(context, args);
685
+ var callNow = immediate && !timeout;
583
686
  clearTimeout(timeout);
584
687
  timeout = setTimeout(later, wait);
688
+ if (callNow) result = func.apply(context, args);
689
+ return result;
585
690
  };
586
691
  };
587
692
 
@@ -592,7 +697,9 @@
592
697
  return function() {
593
698
  if (ran) return memo;
594
699
  ran = true;
595
- return memo = func.apply(this, arguments);
700
+ memo = func.apply(this, arguments);
701
+ func = null;
702
+ return memo;
596
703
  };
597
704
  };
598
705
 
@@ -601,7 +708,8 @@
601
708
  // conditionally execute the original function.
602
709
  _.wrap = function(func, wrapper) {
603
710
  return function() {
604
- var args = [func].concat(slice.call(arguments, 0));
711
+ var args = [func];
712
+ push.apply(args, arguments);
605
713
  return wrapper.apply(this, args);
606
714
  };
607
715
  };
@@ -623,7 +731,9 @@
623
731
  _.after = function(times, func) {
624
732
  if (times <= 0) return func();
625
733
  return function() {
626
- if (--times < 1) { return func.apply(this, arguments); }
734
+ if (--times < 1) {
735
+ return func.apply(this, arguments);
736
+ }
627
737
  };
628
738
  };
629
739
 
@@ -641,7 +751,23 @@
641
751
 
642
752
  // Retrieve the values of an object's properties.
643
753
  _.values = function(obj) {
644
- return _.map(obj, _.identity);
754
+ var values = [];
755
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
756
+ return values;
757
+ };
758
+
759
+ // Convert an object into a list of `[key, value]` pairs.
760
+ _.pairs = function(obj) {
761
+ var pairs = [];
762
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
763
+ return pairs;
764
+ };
765
+
766
+ // Invert the keys and values of an object. The values must be serializable.
767
+ _.invert = function(obj) {
768
+ var result = {};
769
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
770
+ return result;
645
771
  };
646
772
 
647
773
  // Return a sorted list of the function names available on the object.
@@ -657,8 +783,10 @@
657
783
  // Extend a given object with all the properties in passed-in object(s).
658
784
  _.extend = function(obj) {
659
785
  each(slice.call(arguments, 1), function(source) {
660
- for (var prop in source) {
661
- obj[prop] = source[prop];
786
+ if (source) {
787
+ for (var prop in source) {
788
+ obj[prop] = source[prop];
789
+ }
662
790
  }
663
791
  });
664
792
  return obj;
@@ -666,18 +794,31 @@
666
794
 
667
795
  // Return a copy of the object only containing the whitelisted properties.
668
796
  _.pick = function(obj) {
669
- var result = {};
670
- each(_.flatten(slice.call(arguments, 1)), function(key) {
671
- if (key in obj) result[key] = obj[key];
797
+ var copy = {};
798
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
799
+ each(keys, function(key) {
800
+ if (key in obj) copy[key] = obj[key];
672
801
  });
673
- return result;
802
+ return copy;
803
+ };
804
+
805
+ // Return a copy of the object without the blacklisted properties.
806
+ _.omit = function(obj) {
807
+ var copy = {};
808
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
809
+ for (var key in obj) {
810
+ if (!_.contains(keys, key)) copy[key] = obj[key];
811
+ }
812
+ return copy;
674
813
  };
675
814
 
676
815
  // Fill in a given object with default properties.
677
816
  _.defaults = function(obj) {
678
817
  each(slice.call(arguments, 1), function(source) {
679
- for (var prop in source) {
680
- if (obj[prop] == null) obj[prop] = source[prop];
818
+ if (source) {
819
+ for (var prop in source) {
820
+ if (obj[prop] == null) obj[prop] = source[prop];
821
+ }
681
822
  }
682
823
  });
683
824
  return obj;
@@ -697,19 +838,16 @@
697
838
  return obj;
698
839
  };
699
840
 
700
- // Internal recursive comparison function.
701
- function eq(a, b, stack) {
841
+ // Internal recursive comparison function for `isEqual`.
842
+ var eq = function(a, b, aStack, bStack) {
702
843
  // Identical objects are equal. `0 === -0`, but they aren't identical.
703
844
  // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
704
845
  if (a === b) return a !== 0 || 1 / a == 1 / b;
705
846
  // A strict comparison is necessary because `null == undefined`.
706
847
  if (a == null || b == null) return a === b;
707
848
  // Unwrap any wrapped objects.
708
- if (a._chain) a = a._wrapped;
709
- if (b._chain) b = b._wrapped;
710
- // Invoke a custom `isEqual` method if one is provided.
711
- if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
712
- if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
849
+ if (a instanceof _) a = a._wrapped;
850
+ if (b instanceof _) b = b._wrapped;
713
851
  // Compare `[[Class]]` names.
714
852
  var className = toString.call(a);
715
853
  if (className != toString.call(b)) return false;
@@ -739,14 +877,15 @@
739
877
  if (typeof a != 'object' || typeof b != 'object') return false;
740
878
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
741
879
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
742
- var length = stack.length;
880
+ var length = aStack.length;
743
881
  while (length--) {
744
882
  // Linear search. Performance is inversely proportional to the number of
745
883
  // unique nested structures.
746
- if (stack[length] == a) return true;
884
+ if (aStack[length] == a) return bStack[length] == b;
747
885
  }
748
886
  // Add the first object to the stack of traversed objects.
749
- stack.push(a);
887
+ aStack.push(a);
888
+ bStack.push(b);
750
889
  var size = 0, result = true;
751
890
  // Recursively compare objects and arrays.
752
891
  if (className == '[object Array]') {
@@ -756,20 +895,24 @@
756
895
  if (result) {
757
896
  // Deep compare the contents, ignoring non-numeric properties.
758
897
  while (size--) {
759
- // Ensure commutative equality for sparse arrays.
760
- if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
898
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
761
899
  }
762
900
  }
763
901
  } else {
764
- // Objects with different constructors are not equivalent.
765
- if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
902
+ // Objects with different constructors are not equivalent, but `Object`s
903
+ // from different frames are.
904
+ var aCtor = a.constructor, bCtor = b.constructor;
905
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
906
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
907
+ return false;
908
+ }
766
909
  // Deep compare objects.
767
910
  for (var key in a) {
768
911
  if (_.has(a, key)) {
769
912
  // Count the expected number of properties.
770
913
  size++;
771
914
  // Deep compare each member.
772
- if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
915
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
773
916
  }
774
917
  }
775
918
  // Ensure that both objects contain the same number of properties.
@@ -781,13 +924,14 @@
781
924
  }
782
925
  }
783
926
  // Remove the first object from the stack of traversed objects.
784
- stack.pop();
927
+ aStack.pop();
928
+ bStack.pop();
785
929
  return result;
786
- }
930
+ };
787
931
 
788
932
  // Perform a deep comparison to check if two objects are equal.
789
933
  _.isEqual = function(a, b) {
790
- return eq(a, b, []);
934
+ return eq(a, b, [], []);
791
935
  };
792
936
 
793
937
  // Is a given array, string, or object empty?
@@ -801,7 +945,7 @@
801
945
 
802
946
  // Is a given value a DOM element?
803
947
  _.isElement = function(obj) {
804
- return !!(obj && obj.nodeType == 1);
948
+ return !!(obj && obj.nodeType === 1);
805
949
  };
806
950
 
807
951
  // Is a given value an array?
@@ -815,40 +959,36 @@
815
959
  return obj === Object(obj);
816
960
  };
817
961
 
818
- // Is a given variable an arguments object?
819
- _.isArguments = function(obj) {
820
- return toString.call(obj) == '[object Arguments]';
821
- };
962
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
963
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
964
+ _['is' + name] = function(obj) {
965
+ return toString.call(obj) == '[object ' + name + ']';
966
+ };
967
+ });
968
+
969
+ // Define a fallback version of the method in browsers (ahem, IE), where
970
+ // there isn't any inspectable "Arguments" type.
822
971
  if (!_.isArguments(arguments)) {
823
972
  _.isArguments = function(obj) {
824
973
  return !!(obj && _.has(obj, 'callee'));
825
974
  };
826
975
  }
827
976
 
828
- // Is a given value a function?
829
- _.isFunction = function(obj) {
830
- return toString.call(obj) == '[object Function]';
831
- };
832
-
833
- // Is a given value a string?
834
- _.isString = function(obj) {
835
- return toString.call(obj) == '[object String]';
836
- };
837
-
838
- // Is a given value a number?
839
- _.isNumber = function(obj) {
840
- return toString.call(obj) == '[object Number]';
841
- };
977
+ // Optimize `isFunction` if appropriate.
978
+ if (typeof (/./) !== 'function') {
979
+ _.isFunction = function(obj) {
980
+ return typeof obj === 'function';
981
+ };
982
+ }
842
983
 
843
984
  // Is a given object a finite number?
844
985
  _.isFinite = function(obj) {
845
- return _.isNumber(obj) && isFinite(obj);
986
+ return isFinite(obj) && !isNaN(parseFloat(obj));
846
987
  };
847
988
 
848
- // Is the given value `NaN`?
989
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
849
990
  _.isNaN = function(obj) {
850
- // `NaN` is the only value for which `===` is not reflexive.
851
- return obj !== obj;
991
+ return _.isNumber(obj) && obj != +obj;
852
992
  };
853
993
 
854
994
  // Is a given value a boolean?
@@ -856,16 +996,6 @@
856
996
  return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
857
997
  };
858
998
 
859
- // Is a given value a date?
860
- _.isDate = function(obj) {
861
- return toString.call(obj) == '[object Date]';
862
- };
863
-
864
- // Is the given value a regular expression?
865
- _.isRegExp = function(obj) {
866
- return toString.call(obj) == '[object RegExp]';
867
- };
868
-
869
999
  // Is a given value equal to null?
870
1000
  _.isNull = function(obj) {
871
1001
  return obj === null;
@@ -876,7 +1006,8 @@
876
1006
  return obj === void 0;
877
1007
  };
878
1008
 
879
- // Has own property?
1009
+ // Shortcut function for checking if an object has a given property directly
1010
+ // on itself (in other words, not on a prototype).
880
1011
  _.has = function(obj, key) {
881
1012
  return hasOwnProperty.call(obj, key);
882
1013
  };
@@ -897,15 +1028,50 @@
897
1028
  };
898
1029
 
899
1030
  // Run a function **n** times.
900
- _.times = function (n, iterator, context) {
901
- for (var i = 0; i < n; i++) iterator.call(context, i);
1031
+ _.times = function(n, iterator, context) {
1032
+ var accum = Array(n);
1033
+ for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1034
+ return accum;
902
1035
  };
903
1036
 
904
- // Escape a string for HTML interpolation.
905
- _.escape = function(string) {
906
- return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
1037
+ // Return a random integer between min and max (inclusive).
1038
+ _.random = function(min, max) {
1039
+ if (max == null) {
1040
+ max = min;
1041
+ min = 0;
1042
+ }
1043
+ return min + Math.floor(Math.random() * (max - min + 1));
1044
+ };
1045
+
1046
+ // List of HTML entities for escaping.
1047
+ var entityMap = {
1048
+ escape: {
1049
+ '&': '&amp;',
1050
+ '<': '&lt;',
1051
+ '>': '&gt;',
1052
+ '"': '&quot;',
1053
+ "'": '&#x27;',
1054
+ '/': '&#x2F;'
1055
+ }
1056
+ };
1057
+ entityMap.unescape = _.invert(entityMap.escape);
1058
+
1059
+ // Regexes containing the keys and values listed immediately above.
1060
+ var entityRegexes = {
1061
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1062
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
907
1063
  };
908
1064
 
1065
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
1066
+ _.each(['escape', 'unescape'], function(method) {
1067
+ _[method] = function(string) {
1068
+ if (string == null) return '';
1069
+ return ('' + string).replace(entityRegexes[method], function(match) {
1070
+ return entityMap[method][match];
1071
+ });
1072
+ };
1073
+ });
1074
+
909
1075
  // If the value of the named property is a function then invoke it;
910
1076
  // otherwise, return it.
911
1077
  _.result = function(object, property) {
@@ -914,11 +1080,15 @@
914
1080
  return _.isFunction(value) ? value.call(object) : value;
915
1081
  };
916
1082
 
917
- // Add your own custom functions to the Underscore object, ensuring that
918
- // they're correctly added to the OOP wrapper as well.
1083
+ // Add your own custom functions to the Underscore object.
919
1084
  _.mixin = function(obj) {
920
1085
  each(_.functions(obj), function(name){
921
- addToWrapper(name, _[name] = obj[name]);
1086
+ var func = _[name] = obj[name];
1087
+ _.prototype[name] = function() {
1088
+ var args = [this._wrapped];
1089
+ push.apply(args, arguments);
1090
+ return result.call(this, func.apply(_, args));
1091
+ };
922
1092
  });
923
1093
  };
924
1094
 
@@ -926,7 +1096,7 @@
926
1096
  // Useful for temporary DOM ids.
927
1097
  var idCounter = 0;
928
1098
  _.uniqueId = function(prefix) {
929
- var id = idCounter++;
1099
+ var id = ++idCounter + '';
930
1100
  return prefix ? prefix + id : id;
931
1101
  };
932
1102
 
@@ -941,72 +1111,78 @@
941
1111
  // When customizing `templateSettings`, if you don't want to define an
942
1112
  // interpolation, evaluation or escaping regex, we need one that is
943
1113
  // guaranteed not to match.
944
- var noMatch = /.^/;
1114
+ var noMatch = /(.)^/;
945
1115
 
946
1116
  // Certain characters need to be escaped so that they can be put into a
947
1117
  // string literal.
948
1118
  var escapes = {
949
- '\\': '\\',
950
- "'": "'",
951
- 'r': '\r',
952
- 'n': '\n',
953
- 't': '\t',
954
- 'u2028': '\u2028',
955
- 'u2029': '\u2029'
1119
+ "'": "'",
1120
+ '\\': '\\',
1121
+ '\r': 'r',
1122
+ '\n': 'n',
1123
+ '\t': 't',
1124
+ '\u2028': 'u2028',
1125
+ '\u2029': 'u2029'
956
1126
  };
957
1127
 
958
- for (var p in escapes) escapes[escapes[p]] = p;
959
1128
  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
960
- var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
961
-
962
- // Within an interpolation, evaluation, or escaping, remove HTML escaping
963
- // that had been previously added.
964
- var unescape = function(code) {
965
- return code.replace(unescaper, function(match, escape) {
966
- return escapes[escape];
967
- });
968
- };
969
1129
 
970
1130
  // JavaScript micro-templating, similar to John Resig's implementation.
971
1131
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
972
1132
  // and correctly escapes quotes within interpolated code.
973
1133
  _.template = function(text, data, settings) {
974
- settings = _.defaults(settings || {}, _.templateSettings);
975
-
976
- // Compile the template source, taking care to escape characters that
977
- // cannot be included in a string literal and then unescape them in code
978
- // blocks.
979
- var source = "__p+='" + text
980
- .replace(escaper, function(match) {
981
- return '\\' + escapes[match];
982
- })
983
- .replace(settings.escape || noMatch, function(match, code) {
984
- return "'+\n_.escape(" + unescape(code) + ")+\n'";
985
- })
986
- .replace(settings.interpolate || noMatch, function(match, code) {
987
- return "'+\n(" + unescape(code) + ")+\n'";
988
- })
989
- .replace(settings.evaluate || noMatch, function(match, code) {
990
- return "';\n" + unescape(code) + "\n;__p+='";
991
- }) + "';\n";
1134
+ var render;
1135
+ settings = _.defaults({}, settings, _.templateSettings);
1136
+
1137
+ // Combine delimiters into one regular expression via alternation.
1138
+ var matcher = new RegExp([
1139
+ (settings.escape || noMatch).source,
1140
+ (settings.interpolate || noMatch).source,
1141
+ (settings.evaluate || noMatch).source
1142
+ ].join('|') + '|$', 'g');
1143
+
1144
+ // Compile the template source, escaping string literals appropriately.
1145
+ var index = 0;
1146
+ var source = "__p+='";
1147
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1148
+ source += text.slice(index, offset)
1149
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
1150
+
1151
+ if (escape) {
1152
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1153
+ }
1154
+ if (interpolate) {
1155
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1156
+ }
1157
+ if (evaluate) {
1158
+ source += "';\n" + evaluate + "\n__p+='";
1159
+ }
1160
+ index = offset + match.length;
1161
+ return match;
1162
+ });
1163
+ source += "';\n";
992
1164
 
993
1165
  // If a variable is not specified, place data values in local scope.
994
1166
  if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
995
1167
 
996
- source = "var __p='';" +
997
- "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
1168
+ source = "var __t,__p='',__j=Array.prototype.join," +
1169
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
998
1170
  source + "return __p;\n";
999
1171
 
1000
- var render = new Function(settings.variable || 'obj', '_', source);
1172
+ try {
1173
+ render = new Function(settings.variable || 'obj', '_', source);
1174
+ } catch (e) {
1175
+ e.source = source;
1176
+ throw e;
1177
+ }
1178
+
1001
1179
  if (data) return render(data, _);
1002
1180
  var template = function(data) {
1003
1181
  return render.call(this, data, _);
1004
1182
  };
1005
1183
 
1006
- // Provide the compiled function source as a convenience for build time
1007
- // precompilation.
1008
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
1009
- source + '}';
1184
+ // Provide the compiled function source as a convenience for precompilation.
1185
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
1010
1186
 
1011
1187
  return template;
1012
1188
  };
@@ -1016,29 +1192,15 @@
1016
1192
  return _(obj).chain();
1017
1193
  };
1018
1194
 
1019
- // The OOP Wrapper
1195
+ // OOP
1020
1196
  // ---------------
1021
-
1022
1197
  // If Underscore is called as a function, it returns a wrapped object that
1023
1198
  // can be used OO-style. This wrapper holds altered versions of all the
1024
1199
  // underscore functions. Wrapped objects may be chained.
1025
- var wrapper = function(obj) { this._wrapped = obj; };
1026
-
1027
- // Expose `wrapper.prototype` as `_.prototype`
1028
- _.prototype = wrapper.prototype;
1029
1200
 
1030
1201
  // Helper function to continue chaining intermediate results.
1031
- var result = function(obj, chain) {
1032
- return chain ? _(obj).chain() : obj;
1033
- };
1034
-
1035
- // A method to easily add functions to the OOP wrapper.
1036
- var addToWrapper = function(name, func) {
1037
- wrapper.prototype[name] = function() {
1038
- var args = slice.call(arguments);
1039
- unshift.call(args, this._wrapped);
1040
- return result(func.apply(_, args), this._chain);
1041
- };
1202
+ var result = function(obj) {
1203
+ return this._chain ? _(obj).chain() : obj;
1042
1204
  };
1043
1205
 
1044
1206
  // Add all of the Underscore functions to the wrapper object.
@@ -1047,38 +1209,42 @@
1047
1209
  // Add all mutator Array functions to the wrapper.
1048
1210
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1049
1211
  var method = ArrayProto[name];
1050
- wrapper.prototype[name] = function() {
1051
- var wrapped = this._wrapped;
1052
- method.apply(wrapped, arguments);
1053
- var length = wrapped.length;
1054
- if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
1055
- return result(wrapped, this._chain);
1212
+ _.prototype[name] = function() {
1213
+ var obj = this._wrapped;
1214
+ method.apply(obj, arguments);
1215
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1216
+ return result.call(this, obj);
1056
1217
  };
1057
1218
  });
1058
1219
 
1059
1220
  // Add all accessor Array functions to the wrapper.
1060
1221
  each(['concat', 'join', 'slice'], function(name) {
1061
1222
  var method = ArrayProto[name];
1062
- wrapper.prototype[name] = function() {
1063
- return result(method.apply(this._wrapped, arguments), this._chain);
1223
+ _.prototype[name] = function() {
1224
+ return result.call(this, method.apply(this._wrapped, arguments));
1064
1225
  };
1065
1226
  });
1066
1227
 
1067
- // Start chaining a wrapped Underscore object.
1068
- wrapper.prototype.chain = function() {
1069
- this._chain = true;
1070
- return this;
1071
- };
1228
+ _.extend(_.prototype, {
1072
1229
 
1073
- // Extracts the result from a wrapped and chained object.
1074
- wrapper.prototype.value = function() {
1075
- return this._wrapped;
1076
- };
1230
+ // Start chaining a wrapped Underscore object.
1231
+ chain: function() {
1232
+ this._chain = true;
1233
+ return this;
1234
+ },
1235
+
1236
+ // Extracts the result from a wrapped and chained object.
1237
+ value: function() {
1238
+ return this._wrapped;
1239
+ }
1240
+
1241
+ });
1077
1242
 
1078
1243
  }).call(this);
1079
1244
 
1080
- /*global _: false, $: false, localStorage: false, XMLHttpRequest: false,
1081
- XDomainRequest: false, exports: false, require: false */
1245
+ /*global _: false, $: false, localStorage: false, process: true,
1246
+ XMLHttpRequest: false, XDomainRequest: false, exports: false,
1247
+ require: false */
1082
1248
  (function(root) {
1083
1249
  root.Parse = root.Parse || {};
1084
1250
  /**
@@ -1167,13 +1333,25 @@
1167
1333
  // Set the server for Parse to talk to.
1168
1334
  Parse.serverURL = "https://api.parse.com";
1169
1335
 
1336
+ // Check whether we are running in Node.js.
1337
+ if (typeof(process) !== "undefined" &&
1338
+ process.versions &&
1339
+ process.versions.node) {
1340
+ Parse._isNode = true;
1341
+ }
1342
+
1170
1343
  /**
1171
1344
  * Call this method first to set up your authentication tokens for Parse.
1172
1345
  * You can get your keys from the Data Browser on parse.com.
1173
1346
  * @param {String} applicationId Your Parse Application ID.
1174
1347
  * @param {String} javaScriptKey Your Parse JavaScript Key.
1348
+ * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
1175
1349
  */
1176
- Parse.initialize = function(applicationId, javaScriptKey) {
1350
+ Parse.initialize = function(applicationId, javaScriptKey, masterKey) {
1351
+ if (masterKey) {
1352
+ throw "Parse.initialize() was passed a Master Key, which is only " +
1353
+ "allowed from within Node.js.";
1354
+ }
1177
1355
  Parse._initialize(applicationId, javaScriptKey);
1178
1356
  };
1179
1357
 
@@ -1191,6 +1369,23 @@
1191
1369
  Parse._useMasterKey = false;
1192
1370
  };
1193
1371
 
1372
+ // If we're running in node.js, allow using the master key.
1373
+ if (Parse._isNode) {
1374
+ Parse.initialize = Parse._initialize;
1375
+
1376
+ Parse.Cloud = Parse.Cloud || {};
1377
+ /**
1378
+ * Switches the Parse SDK to using the Master key. The Master key grants
1379
+ * priveleged access to the data in Parse and can be used to bypass ACLs and
1380
+ * other restrictions that are applied to the client SDKs.
1381
+ * <p><strong><em>Available in Cloud Code and Node.js only.</em></strong>
1382
+ * </p>
1383
+ */
1384
+ Parse.Cloud.useMasterKey = function() {
1385
+ Parse._useMasterKey = true;
1386
+ };
1387
+ }
1388
+
1194
1389
  /**
1195
1390
  * Returns prefix for localStorage keys used by this instance of Parse.
1196
1391
  * @param {String} path The relative suffix to append to it.
@@ -1266,7 +1461,7 @@
1266
1461
  return new Date(Date.UTC(year, month, day, hour, minute, second, milli));
1267
1462
  };
1268
1463
 
1269
- Parse._ajaxIE8 = function(method, url, data, success, error) {
1464
+ Parse._ajaxIE8 = function(method, url, data) {
1270
1465
  var promise = new Parse.Promise();
1271
1466
  var xdr = new XDomainRequest();
1272
1467
  xdr.onload = function() {
@@ -1274,22 +1469,13 @@
1274
1469
  try {
1275
1470
  response = JSON.parse(xdr.responseText);
1276
1471
  } catch (e) {
1277
- if (error) {
1278
- error(xdr);
1279
- }
1280
1472
  promise.reject(e);
1281
1473
  }
1282
1474
  if (response) {
1283
- if (success) {
1284
- success(response, xdr);
1285
- }
1286
1475
  promise.resolve(response);
1287
1476
  }
1288
1477
  };
1289
1478
  xdr.onerror = xdr.ontimeout = function() {
1290
- if (error) {
1291
- error(xdr);
1292
- }
1293
1479
  promise.reject(xdr);
1294
1480
  };
1295
1481
  xdr.onprogress = function() {};
@@ -1298,9 +1484,15 @@
1298
1484
  return promise;
1299
1485
  };
1300
1486
 
1487
+
1301
1488
  Parse._ajax = function(method, url, data, success, error) {
1489
+ var options = {
1490
+ success: success,
1491
+ error: error
1492
+ };
1493
+
1302
1494
  if (typeof(XDomainRequest) !== "undefined") {
1303
- return Parse._ajaxIE8(method, url, data, success, error);
1495
+ return Parse._ajaxIE8(method, url, data)._thenRunCallbacks(options);
1304
1496
  }
1305
1497
 
1306
1498
  var promise = new Parse.Promise();
@@ -1319,29 +1511,26 @@
1319
1511
  try {
1320
1512
  response = JSON.parse(xhr.responseText);
1321
1513
  } catch (e) {
1322
- if (error) {
1323
- error(xhr);
1324
- }
1325
1514
  promise.reject(e);
1326
1515
  }
1327
1516
  if (response) {
1328
- if (success) {
1329
- success(response, xhr.status, xhr);
1330
- }
1331
1517
  promise.resolve(response, xhr.status, xhr);
1332
1518
  }
1333
1519
  } else {
1334
- if (error) {
1335
- error(xhr);
1336
- }
1337
1520
  promise.reject(xhr);
1338
1521
  }
1339
1522
  }
1340
1523
  };
1341
1524
  xhr.open(method, url, true);
1342
1525
  xhr.setRequestHeader("Content-Type", "text/plain"); // avoid pre-flight.
1526
+ if (Parse._isNode) {
1527
+ // Add a special user agent just for request from node.js.
1528
+ xhr.setRequestHeader("User-Agent",
1529
+ "Parse/" + Parse.VERSION +
1530
+ " (NodeJS " + process.versions.node + ")");
1531
+ }
1343
1532
  xhr.send(data);
1344
- return promise;
1533
+ return promise._thenRunCallbacks(options);
1345
1534
  };
1346
1535
 
1347
1536
  // A self-propagating extend function.
@@ -1356,11 +1545,9 @@
1356
1545
  * objectId is null if there is no associated objectId.
1357
1546
  * method is the http method for the REST API.
1358
1547
  * dataObject is the payload as an object, or null if there is none.
1359
- * options is just a success/error callback hash.
1360
1548
  * @ignore
1361
1549
  */
1362
- Parse._request = function(route, className, objectId, method, dataObject,
1363
- options) {
1550
+ Parse._request = function(route, className, objectId, method, dataObject) {
1364
1551
  if (!Parse.applicationId) {
1365
1552
  throw "You must specify your applicationId using Parse.initialize";
1366
1553
  }
@@ -1372,6 +1559,7 @@
1372
1559
 
1373
1560
  if (route !== "batch" &&
1374
1561
  route !== "classes" &&
1562
+ route !== "files" &&
1375
1563
  route !== "functions" &&
1376
1564
  route !== "login" &&
1377
1565
  route !== "push" &&
@@ -1397,6 +1585,7 @@
1397
1585
  dataObject._method = method;
1398
1586
  method = "POST";
1399
1587
  }
1588
+
1400
1589
  dataObject._ApplicationId = Parse.applicationId;
1401
1590
  if (!Parse._useMasterKey) {
1402
1591
  dataObject._JavaScriptKey = Parse.javaScriptKey;
@@ -1413,8 +1602,25 @@
1413
1602
  }
1414
1603
  var data = JSON.stringify(dataObject);
1415
1604
 
1416
- options = options || {};
1417
- return Parse._ajax(method, url, data, options.success, options.error);
1605
+ return Parse._ajax(method, url, data).then(null, function(response) {
1606
+ // Transform the error into an instance of Parse.Error by trying to parse
1607
+ // the error string as JSON.
1608
+ var error;
1609
+ if (response && response.responseText) {
1610
+ try {
1611
+ var errorJSON = JSON.parse(response.responseText);
1612
+ if (errorJSON) {
1613
+ error = new Parse.Error(errorJSON.code, errorJSON.error);
1614
+ }
1615
+ } catch (e) {
1616
+ // If we fail to parse the error text, that's okay.
1617
+ }
1618
+ }
1619
+ error = error || new Parse.Error(-1, response.responseText);
1620
+ // By explicitly returning a rejected Promise, this will work with
1621
+ // either jQuery or Promises/A semantics.
1622
+ return Parse.Promise.error(error);
1623
+ });
1418
1624
  };
1419
1625
 
1420
1626
  // Helper function to get a value from a Backbone object as a property
@@ -1443,39 +1649,56 @@
1443
1649
  }
1444
1650
  if (!seenObjects || _.include(seenObjects, value) || !value._hasData) {
1445
1651
  return value._toPointer();
1446
- } else if (!value.dirty()) {
1652
+ }
1653
+ if (!value.dirty()) {
1447
1654
  seenObjects = seenObjects.concat(value);
1448
1655
  return Parse._encode(value._toFullJSON(seenObjects),
1449
1656
  seenObjects,
1450
1657
  disallowObjects);
1451
- } else {
1452
- throw "Can't fully embed a dirty object";
1453
1658
  }
1454
- } else if (value instanceof Parse.ACL) {
1659
+ throw "Tried to save an object with a pointer to a new, unsaved object.";
1660
+ }
1661
+ if (value instanceof Parse.ACL) {
1455
1662
  return value.toJSON();
1456
- } else if (_.isDate(value)) {
1663
+ }
1664
+ if (_.isDate(value)) {
1457
1665
  return { "__type": "Date", "iso": value.toJSON() };
1458
- } else if (value instanceof Parse.GeoPoint) {
1666
+ }
1667
+ if (value instanceof Parse.GeoPoint) {
1459
1668
  return value.toJSON();
1460
- } else if (_.isArray(value)) {
1669
+ }
1670
+ if (_.isArray(value)) {
1461
1671
  return _.map(value, function(x) {
1462
1672
  return Parse._encode(x, seenObjects, disallowObjects);
1463
1673
  });
1464
- } else if (_.isRegExp(value)) {
1674
+ }
1675
+ if (_.isRegExp(value)) {
1465
1676
  return value.source;
1466
- } else if (value instanceof Parse.Relation) {
1677
+ }
1678
+ if (value instanceof Parse.Relation) {
1467
1679
  return value.toJSON();
1468
- } else if (value instanceof Parse.Op) {
1680
+ }
1681
+ if (value instanceof Parse.Op) {
1469
1682
  return value.toJSON();
1470
- } else if (value instanceof Object) {
1683
+ }
1684
+ if (value instanceof Parse.File) {
1685
+ if (!value.url()) {
1686
+ throw "Tried to save an object containing an unsaved file.";
1687
+ }
1688
+ return {
1689
+ __type: "File",
1690
+ name: value.name(),
1691
+ url: value.url()
1692
+ };
1693
+ }
1694
+ if (_.isObject(value)) {
1471
1695
  var output = {};
1472
- Parse._each(value, function(v, k) {
1696
+ Parse._objectEach(value, function(v, k) {
1473
1697
  output[k] = Parse._encode(v, seenObjects, disallowObjects);
1474
1698
  });
1475
1699
  return output;
1476
- } else {
1477
- return value;
1478
1700
  }
1701
+ return value;
1479
1702
  };
1480
1703
 
1481
1704
  /**
@@ -1486,23 +1709,31 @@
1486
1709
  var _ = Parse._;
1487
1710
  if (!_.isObject(value)) {
1488
1711
  return value;
1489
- } else if (_.isArray(value)) {
1490
- Parse._each(value, function(v, k) {
1712
+ }
1713
+ if (_.isArray(value)) {
1714
+ Parse._arrayEach(value, function(v, k) {
1491
1715
  value[k] = Parse._decode(k, v);
1492
1716
  });
1493
1717
  return value;
1494
- } else if (value instanceof Parse.Object) {
1718
+ }
1719
+ if (value instanceof Parse.Object) {
1720
+ return value;
1721
+ }
1722
+ if (value instanceof Parse.File) {
1495
1723
  return value;
1496
- } else if (value instanceof Parse.Op) {
1724
+ }
1725
+ if (value instanceof Parse.Op) {
1497
1726
  return value;
1498
- } else if (value.__op) {
1499
- // Must be a Op.
1727
+ }
1728
+ if (value.__op) {
1500
1729
  return Parse.Op._decode(value);
1501
- } else if (value.__type === "Pointer") {
1730
+ }
1731
+ if (value.__type === "Pointer") {
1502
1732
  var pointer = Parse.Object._create(value.className);
1503
1733
  pointer._finishFetch({ objectId: value.objectId }, false);
1504
1734
  return pointer;
1505
- } else if (value.__type === "Object") {
1735
+ }
1736
+ if (value.__type === "Object") {
1506
1737
  // It's an Object included in a query result.
1507
1738
  var className = value.className;
1508
1739
  delete value.__type;
@@ -1510,69 +1741,83 @@
1510
1741
  var object = Parse.Object._create(className);
1511
1742
  object._finishFetch(value, true);
1512
1743
  return object;
1513
- } else if (value.__type === "Date") {
1744
+ }
1745
+ if (value.__type === "Date") {
1514
1746
  return Parse._parseDate(value.iso);
1515
- } else if (value.__type === "GeoPoint") {
1747
+ }
1748
+ if (value.__type === "GeoPoint") {
1516
1749
  return new Parse.GeoPoint({
1517
1750
  latitude: value.latitude,
1518
1751
  longitude: value.longitude
1519
1752
  });
1520
- } else if (key === "ACL") {
1753
+ }
1754
+ if (key === "ACL") {
1521
1755
  if (value instanceof Parse.ACL) {
1522
1756
  return value;
1523
- } else {
1524
- return new Parse.ACL(value);
1525
1757
  }
1526
- } else if (value.__type === "Relation") {
1758
+ return new Parse.ACL(value);
1759
+ }
1760
+ if (value.__type === "Relation") {
1527
1761
  var relation = new Parse.Relation(null, key);
1528
1762
  relation.targetClassName = value.className;
1529
1763
  return relation;
1530
- } else {
1531
- Parse._each(value, function(v, k) {
1532
- value[k] = Parse._decode(k, v);
1533
- });
1534
- return value;
1535
1764
  }
1765
+ if (value.__type === "File") {
1766
+ var file = new Parse.File(value.name);
1767
+ file._url = value.url;
1768
+ return file;
1769
+ }
1770
+ Parse._objectEach(value, function(v, k) {
1771
+ value[k] = Parse._decode(k, v);
1772
+ });
1773
+ return value;
1536
1774
  };
1537
1775
 
1776
+ Parse._arrayEach = Parse._.each;
1777
+
1538
1778
  /**
1539
- * Does a deep traversal of every item in object, calling func on all
1540
- * instances of Parse.Object.
1779
+ * Does a deep traversal of every item in object, calling func on every one.
1541
1780
  * @param {Object} object The object or array to traverse deeply.
1542
- * @param {Function} func The function to call for every Parse.Object. It will
1543
- * be passed the Parse Object. If it returns a truthy value, that value
1544
- * will replace the object in its parent container.
1545
- * @returns {} Undefined, unless object is a Parse.Object, in which case it
1546
- * returns the result of calling func on that object.
1781
+ * @param {Function} func The function to call for every item. It will
1782
+ * be passed the item as an argument. If it returns a truthy value, that
1783
+ * value will replace the item in its parent container.
1784
+ * @returns {} the result of calling func on the top-level object itself.
1547
1785
  */
1548
- Parse._traverse = function(object, func) {
1786
+ Parse._traverse = function(object, func, seen) {
1549
1787
  if (object instanceof Parse.Object) {
1550
- Parse._traverse(object.attributes, func);
1788
+ seen = seen || [];
1789
+ if (Parse._.indexOf(seen, object) >= 0) {
1790
+ // We've already visited this object in this call.
1791
+ return;
1792
+ }
1793
+ seen.push(object);
1794
+ Parse._traverse(object.attributes, func, seen);
1551
1795
  return func(object);
1552
1796
  }
1553
1797
  if (object instanceof Parse.Relation) {
1554
1798
  // Nothing needs to be done, but we don't want to recurse into the
1555
1799
  // relation's parent infinitely, so we catch this case.
1556
- return;
1800
+ return func(object);
1557
1801
  }
1558
1802
  if (Parse._.isArray(object)) {
1559
1803
  Parse._.each(object, function(child, index) {
1560
- var newChild = Parse._traverse(child, func);
1804
+ var newChild = Parse._traverse(child, func, seen);
1561
1805
  if (newChild) {
1562
1806
  object[index] = newChild;
1563
1807
  }
1564
1808
  });
1565
- return;
1809
+ return func(object);
1566
1810
  }
1567
1811
  if (Parse._.isObject(object)) {
1568
1812
  Parse._each(object, function(child, key) {
1569
- var newChild = Parse._traverse(child, func);
1813
+ var newChild = Parse._traverse(child, func, seen);
1570
1814
  if (newChild) {
1571
1815
  object[key] = newChild;
1572
1816
  }
1573
1817
  });
1574
- return;
1818
+ return func(object);
1575
1819
  }
1820
+ return func(object);
1576
1821
  };
1577
1822
 
1578
1823
  /**
@@ -1580,7 +1825,7 @@
1580
1825
  * * it doesn't work for so-called array-like objects,
1581
1826
  * * it does work for dictionaries with a "length" attribute.
1582
1827
  */
1583
- Parse._each = function(obj, callback) {
1828
+ Parse._objectEach = Parse._each = function(obj, callback) {
1584
1829
  var _ = Parse._;
1585
1830
  if (_.isObject(obj)) {
1586
1831
  _.each(_.keys(obj), function(key) {
@@ -2196,20 +2441,18 @@
2196
2441
  * @param {Object} options An object with success and error callbacks.
2197
2442
  */
2198
2443
  Parse.GeoPoint.current = function(options) {
2199
- var success = function(location) {
2200
- if (options.success) {
2201
- options.success(new Parse.GeoPoint({
2202
- latitude: location.coords.latitude,
2203
- longitude: location.coords.longitude
2204
- }));
2205
- }
2206
- };
2207
- var error = function(e) {
2208
- if (options.error) {
2209
- options.error(e);
2210
- }
2211
- };
2212
- navigator.geolocation.getCurrentPosition(success, error);
2444
+ var promise = new Parse.Promise();
2445
+ navigator.geolocation.getCurrentPosition(function(location) {
2446
+ promise.resolve(new Parse.GeoPoint({
2447
+ latitude: location.coords.latitude,
2448
+ longitude: location.coords.longitude
2449
+ }));
2450
+
2451
+ }, function(error) {
2452
+ promise.reject(error);
2453
+ });
2454
+
2455
+ return promise._thenRunCallbacks(options);
2213
2456
  };
2214
2457
 
2215
2458
  Parse.GeoPoint.prototype = {
@@ -2302,12 +2545,12 @@
2302
2545
  if (_.isFunction(arg1)) {
2303
2546
  throw "Parse.ACL() called with a function. Did you forget ()?";
2304
2547
  }
2305
- Parse._each(arg1, function(accessList, userId) {
2548
+ Parse._objectEach(arg1, function(accessList, userId) {
2306
2549
  if (!_.isString(userId)) {
2307
2550
  throw "Tried to create an ACL with an invalid userId.";
2308
2551
  }
2309
2552
  self.permissionsById[userId] = {};
2310
- Parse._each(accessList, function(allowed, permission) {
2553
+ Parse._objectEach(accessList, function(allowed, permission) {
2311
2554
  if (permission !== "read" && permission !== "write") {
2312
2555
  throw "Tried to create an ACL with an invalid permission type.";
2313
2556
  }
@@ -2591,7 +2834,7 @@
2591
2834
  */
2592
2835
  Parse.Op._registerDecoder("Batch", function(json) {
2593
2836
  var op = null;
2594
- _.each(json.ops, function(nextOp) {
2837
+ Parse._arrayEach(json.ops, function(nextOp) {
2595
2838
  nextOp = Parse.Op._decode(nextOp);
2596
2839
  op = nextOp._mergeWithPrevious(op);
2597
2840
  });
@@ -2877,7 +3120,7 @@
2877
3120
  } else {
2878
3121
  var newValue = _.difference(oldValue, this.objects());
2879
3122
  // If there are saved Parse Objects being removed, also remove them.
2880
- _.each(this.objects(), function(obj) {
3123
+ Parse._arrayEach(this.objects(), function(obj) {
2881
3124
  if (obj instanceof Parse.Object && obj.id) {
2882
3125
  newValue = _.reject(newValue, function(other) {
2883
3126
  return (other instanceof Parse.Object) && (other.id === obj.id);
@@ -3245,7 +3488,7 @@
3245
3488
  }
3246
3489
  };
3247
3490
 
3248
- _.each(objects, function(object, i) {
3491
+ Parse._arrayEach(objects, function(object, i) {
3249
3492
  if (Parse.Promise.is(object)) {
3250
3493
  object.then(function(result) {
3251
3494
  results[i] = result;
@@ -3295,7 +3538,7 @@
3295
3538
  this._resolved = true;
3296
3539
  this._result = arguments;
3297
3540
  var results = arguments;
3298
- _.each(this._resolvedCallbacks, function(resolvedCallback) {
3541
+ Parse._arrayEach(this._resolvedCallbacks, function(resolvedCallback) {
3299
3542
  resolvedCallback.apply(this, results);
3300
3543
  });
3301
3544
  this._resolvedCallbacks = [];
@@ -3313,7 +3556,7 @@
3313
3556
  }
3314
3557
  this._rejected = true;
3315
3558
  this._error = error;
3316
- _.each(this._rejectedCallbacks, function(rejectedCallback) {
3559
+ Parse._arrayEach(this._rejectedCallbacks, function(rejectedCallback) {
3317
3560
  rejectedCallback(error);
3318
3561
  });
3319
3562
  this._resolvedCallbacks = [];
@@ -3395,10 +3638,12 @@
3395
3638
  * @param optionsOrCallback {} A Backbone-style options callback, or a
3396
3639
  * callback function. If this is an options object and contains a "model"
3397
3640
  * attributes, that will be passed to error callbacks as the first argument.
3641
+ * @param model {} If truthy, this will be passed as the first result of
3642
+ * error callbacks. This is for Backbone-compatability.
3398
3643
  * @return {Parse.Promise} A promise that will be resolved after the
3399
3644
  * callbacks are run, with the same result as this.
3400
3645
  */
3401
- _thenRunCallbacks: function(optionsOrCallback) {
3646
+ _thenRunCallbacks: function(optionsOrCallback, model) {
3402
3647
  var options;
3403
3648
  if (_.isFunction(optionsOrCallback)) {
3404
3649
  var callback = optionsOrCallback;
@@ -3417,38 +3662,423 @@
3417
3662
 
3418
3663
  return this.then(function(result) {
3419
3664
  if (options.success) {
3420
- options.success(result);
3665
+ options.success.apply(this, arguments);
3666
+ } else if (model) {
3667
+ // When there's no callback, a sync event should be triggered.
3668
+ model.trigger('sync', model, result, options);
3421
3669
  }
3422
- return result;
3670
+ return Parse.Promise.as.apply(Parse.Promise, arguments);
3423
3671
  }, function(error) {
3424
3672
  if (options.error) {
3425
- if (options.model) {
3426
- options.error(options.model, error);
3673
+ if (!_.isUndefined(model)) {
3674
+ options.error(model, error);
3427
3675
  } else {
3428
3676
  options.error(error);
3429
3677
  }
3678
+ } else if (model) {
3679
+ // When there's no error callback, an error event should be triggered.
3680
+ model.trigger('error', model, error, options);
3430
3681
  }
3682
+ // By explicitly returning a rejected Promise, this will work with
3683
+ // either jQuery or Promises/A semantics.
3431
3684
  return Parse.Promise.error(error);
3432
3685
  });
3433
3686
  },
3434
3687
 
3435
3688
  /**
3436
- * Adds a callback function that should be called regardless of whether
3437
- * this promise failed or succeeded. The callback will be given either the
3438
- * array of results for its first argument, or the error as its second,
3439
- * depending on whether this Promise was rejected or resolved. Returns a
3440
- * new Promise, like "then" would.
3441
- * @param {Function} continuation the callback.
3689
+ * Adds a callback function that should be called regardless of whether
3690
+ * this promise failed or succeeded. The callback will be given either the
3691
+ * array of results for its first argument, or the error as its second,
3692
+ * depending on whether this Promise was rejected or resolved. Returns a
3693
+ * new Promise, like "then" would.
3694
+ * @param {Function} continuation the callback.
3695
+ */
3696
+ _continueWith: function(continuation) {
3697
+ return this.then(function() {
3698
+ return continuation(arguments, null);
3699
+ }, function(error) {
3700
+ return continuation(null, error);
3701
+ });
3702
+ }
3703
+
3704
+ });
3705
+
3706
+ }(this));
3707
+
3708
+ /*jshint bitwise:false *//*global FileReader: true, File: true */
3709
+ (function(root) {
3710
+ root.Parse = root.Parse || {};
3711
+ var Parse = root.Parse;
3712
+ var _ = Parse._;
3713
+
3714
+ var b64Digit = function(number) {
3715
+ if (number < 26) {
3716
+ return String.fromCharCode(65 + number);
3717
+ }
3718
+ if (number < 52) {
3719
+ return String.fromCharCode(97 + (number - 26));
3720
+ }
3721
+ if (number < 62) {
3722
+ return String.fromCharCode(48 + (number - 52));
3723
+ }
3724
+ if (number === 62) {
3725
+ return "+";
3726
+ }
3727
+ if (number === 63) {
3728
+ return "/";
3729
+ }
3730
+ throw "Tried to encode large digit " + number + " in base64.";
3731
+ };
3732
+
3733
+ var encodeBase64 = function(array) {
3734
+ var chunks = [];
3735
+ chunks.length = Math.ceil(array.length / 3);
3736
+ _.times(chunks.length, function(i) {
3737
+ var b1 = array[i * 3];
3738
+ var b2 = array[i * 3 + 1] || 0;
3739
+ var b3 = array[i * 3 + 2] || 0;
3740
+
3741
+ var has2 = (i * 3 + 1) < array.length;
3742
+ var has3 = (i * 3 + 2) < array.length;
3743
+
3744
+ chunks[i] = [
3745
+ b64Digit((b1 >> 2) & 0x3F),
3746
+ b64Digit(((b1 << 4) & 0x30) | ((b2 >> 4) & 0x0F)),
3747
+ has2 ? b64Digit(((b2 << 2) & 0x3C) | ((b3 >> 6) & 0x03)) : "=",
3748
+ has3 ? b64Digit(b3 & 0x3F) : "="
3749
+ ].join("");
3750
+ });
3751
+ return chunks.join("");
3752
+ };
3753
+
3754
+
3755
+ // A list of file extensions to mime types as found here:
3756
+ // http://stackoverflow.com/questions/58510/using-net-how-can-you-find-the-
3757
+ // mime-type-of-a-file-based-on-the-file-signature
3758
+ var mimeTypes = {
3759
+ ai: "application/postscript",
3760
+ aif: "audio/x-aiff",
3761
+ aifc: "audio/x-aiff",
3762
+ aiff: "audio/x-aiff",
3763
+ asc: "text/plain",
3764
+ atom: "application/atom+xml",
3765
+ au: "audio/basic",
3766
+ avi: "video/x-msvideo",
3767
+ bcpio: "application/x-bcpio",
3768
+ bin: "application/octet-stream",
3769
+ bmp: "image/bmp",
3770
+ cdf: "application/x-netcdf",
3771
+ cgm: "image/cgm",
3772
+ "class": "application/octet-stream",
3773
+ cpio: "application/x-cpio",
3774
+ cpt: "application/mac-compactpro",
3775
+ csh: "application/x-csh",
3776
+ css: "text/css",
3777
+ dcr: "application/x-director",
3778
+ dif: "video/x-dv",
3779
+ dir: "application/x-director",
3780
+ djv: "image/vnd.djvu",
3781
+ djvu: "image/vnd.djvu",
3782
+ dll: "application/octet-stream",
3783
+ dmg: "application/octet-stream",
3784
+ dms: "application/octet-stream",
3785
+ doc: "application/msword",
3786
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml." +
3787
+ "document",
3788
+ dotx: "application/vnd.openxmlformats-officedocument.wordprocessingml." +
3789
+ "template",
3790
+ docm: "application/vnd.ms-word.document.macroEnabled.12",
3791
+ dotm: "application/vnd.ms-word.template.macroEnabled.12",
3792
+ dtd: "application/xml-dtd",
3793
+ dv: "video/x-dv",
3794
+ dvi: "application/x-dvi",
3795
+ dxr: "application/x-director",
3796
+ eps: "application/postscript",
3797
+ etx: "text/x-setext",
3798
+ exe: "application/octet-stream",
3799
+ ez: "application/andrew-inset",
3800
+ gif: "image/gif",
3801
+ gram: "application/srgs",
3802
+ grxml: "application/srgs+xml",
3803
+ gtar: "application/x-gtar",
3804
+ hdf: "application/x-hdf",
3805
+ hqx: "application/mac-binhex40",
3806
+ htm: "text/html",
3807
+ html: "text/html",
3808
+ ice: "x-conference/x-cooltalk",
3809
+ ico: "image/x-icon",
3810
+ ics: "text/calendar",
3811
+ ief: "image/ief",
3812
+ ifb: "text/calendar",
3813
+ iges: "model/iges",
3814
+ igs: "model/iges",
3815
+ jnlp: "application/x-java-jnlp-file",
3816
+ jp2: "image/jp2",
3817
+ jpe: "image/jpeg",
3818
+ jpeg: "image/jpeg",
3819
+ jpg: "image/jpeg",
3820
+ js: "application/x-javascript",
3821
+ kar: "audio/midi",
3822
+ latex: "application/x-latex",
3823
+ lha: "application/octet-stream",
3824
+ lzh: "application/octet-stream",
3825
+ m3u: "audio/x-mpegurl",
3826
+ m4a: "audio/mp4a-latm",
3827
+ m4b: "audio/mp4a-latm",
3828
+ m4p: "audio/mp4a-latm",
3829
+ m4u: "video/vnd.mpegurl",
3830
+ m4v: "video/x-m4v",
3831
+ mac: "image/x-macpaint",
3832
+ man: "application/x-troff-man",
3833
+ mathml: "application/mathml+xml",
3834
+ me: "application/x-troff-me",
3835
+ mesh: "model/mesh",
3836
+ mid: "audio/midi",
3837
+ midi: "audio/midi",
3838
+ mif: "application/vnd.mif",
3839
+ mov: "video/quicktime",
3840
+ movie: "video/x-sgi-movie",
3841
+ mp2: "audio/mpeg",
3842
+ mp3: "audio/mpeg",
3843
+ mp4: "video/mp4",
3844
+ mpe: "video/mpeg",
3845
+ mpeg: "video/mpeg",
3846
+ mpg: "video/mpeg",
3847
+ mpga: "audio/mpeg",
3848
+ ms: "application/x-troff-ms",
3849
+ msh: "model/mesh",
3850
+ mxu: "video/vnd.mpegurl",
3851
+ nc: "application/x-netcdf",
3852
+ oda: "application/oda",
3853
+ ogg: "application/ogg",
3854
+ pbm: "image/x-portable-bitmap",
3855
+ pct: "image/pict",
3856
+ pdb: "chemical/x-pdb",
3857
+ pdf: "application/pdf",
3858
+ pgm: "image/x-portable-graymap",
3859
+ pgn: "application/x-chess-pgn",
3860
+ pic: "image/pict",
3861
+ pict: "image/pict",
3862
+ png: "image/png",
3863
+ pnm: "image/x-portable-anymap",
3864
+ pnt: "image/x-macpaint",
3865
+ pntg: "image/x-macpaint",
3866
+ ppm: "image/x-portable-pixmap",
3867
+ ppt: "application/vnd.ms-powerpoint",
3868
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml." +
3869
+ "presentation",
3870
+ potx: "application/vnd.openxmlformats-officedocument.presentationml." +
3871
+ "template",
3872
+ ppsx: "application/vnd.openxmlformats-officedocument.presentationml." +
3873
+ "slideshow",
3874
+ ppam: "application/vnd.ms-powerpoint.addin.macroEnabled.12",
3875
+ pptm: "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
3876
+ potm: "application/vnd.ms-powerpoint.template.macroEnabled.12",
3877
+ ppsm: "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
3878
+ ps: "application/postscript",
3879
+ qt: "video/quicktime",
3880
+ qti: "image/x-quicktime",
3881
+ qtif: "image/x-quicktime",
3882
+ ra: "audio/x-pn-realaudio",
3883
+ ram: "audio/x-pn-realaudio",
3884
+ ras: "image/x-cmu-raster",
3885
+ rdf: "application/rdf+xml",
3886
+ rgb: "image/x-rgb",
3887
+ rm: "application/vnd.rn-realmedia",
3888
+ roff: "application/x-troff",
3889
+ rtf: "text/rtf",
3890
+ rtx: "text/richtext",
3891
+ sgm: "text/sgml",
3892
+ sgml: "text/sgml",
3893
+ sh: "application/x-sh",
3894
+ shar: "application/x-shar",
3895
+ silo: "model/mesh",
3896
+ sit: "application/x-stuffit",
3897
+ skd: "application/x-koan",
3898
+ skm: "application/x-koan",
3899
+ skp: "application/x-koan",
3900
+ skt: "application/x-koan",
3901
+ smi: "application/smil",
3902
+ smil: "application/smil",
3903
+ snd: "audio/basic",
3904
+ so: "application/octet-stream",
3905
+ spl: "application/x-futuresplash",
3906
+ src: "application/x-wais-source",
3907
+ sv4cpio: "application/x-sv4cpio",
3908
+ sv4crc: "application/x-sv4crc",
3909
+ svg: "image/svg+xml",
3910
+ swf: "application/x-shockwave-flash",
3911
+ t: "application/x-troff",
3912
+ tar: "application/x-tar",
3913
+ tcl: "application/x-tcl",
3914
+ tex: "application/x-tex",
3915
+ texi: "application/x-texinfo",
3916
+ texinfo: "application/x-texinfo",
3917
+ tif: "image/tiff",
3918
+ tiff: "image/tiff",
3919
+ tr: "application/x-troff",
3920
+ tsv: "text/tab-separated-values",
3921
+ txt: "text/plain",
3922
+ ustar: "application/x-ustar",
3923
+ vcd: "application/x-cdlink",
3924
+ vrml: "model/vrml",
3925
+ vxml: "application/voicexml+xml",
3926
+ wav: "audio/x-wav",
3927
+ wbmp: "image/vnd.wap.wbmp",
3928
+ wbmxl: "application/vnd.wap.wbxml",
3929
+ wml: "text/vnd.wap.wml",
3930
+ wmlc: "application/vnd.wap.wmlc",
3931
+ wmls: "text/vnd.wap.wmlscript",
3932
+ wmlsc: "application/vnd.wap.wmlscriptc",
3933
+ wrl: "model/vrml",
3934
+ xbm: "image/x-xbitmap",
3935
+ xht: "application/xhtml+xml",
3936
+ xhtml: "application/xhtml+xml",
3937
+ xls: "application/vnd.ms-excel",
3938
+ xml: "application/xml",
3939
+ xpm: "image/x-xpixmap",
3940
+ xsl: "application/xml",
3941
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
3942
+ xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml." +
3943
+ "template",
3944
+ xlsm: "application/vnd.ms-excel.sheet.macroEnabled.12",
3945
+ xltm: "application/vnd.ms-excel.template.macroEnabled.12",
3946
+ xlam: "application/vnd.ms-excel.addin.macroEnabled.12",
3947
+ xlsb: "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
3948
+ xslt: "application/xslt+xml",
3949
+ xul: "application/vnd.mozilla.xul+xml",
3950
+ xwd: "image/x-xwindowdump",
3951
+ xyz: "chemical/x-xyz",
3952
+ zip: "application/zip"
3953
+ };
3954
+
3955
+ /**
3956
+ * Reads a File using a FileReader.
3957
+ * @param file {File} the File to read.
3958
+ * @param type {String} (optional) the mimetype to override with.
3959
+ * @return {Parse.Promise} A Promise that will be fulfilled with a
3960
+ * base64-encoded string of the data and its mime type.
3961
+ */
3962
+ var readAsync = function(file, type) {
3963
+ var promise = new Parse.Promise();
3964
+
3965
+ if (typeof(FileReader) === "undefined") {
3966
+ return Parse.Promise.error(new Parse.Error(
3967
+ -1, "Attempted to use a FileReader on an unsupported browser."));
3968
+ }
3969
+
3970
+ var reader = new FileReader();
3971
+ reader.onloadend = function() {
3972
+ if (reader.readyState !== 2) {
3973
+ promise.reject(new Parse.Error(-1, "Error reading file."));
3974
+ return;
3975
+ }
3976
+
3977
+ var dataURL = reader.result;
3978
+ var matches = /^data:([^;]*);base64,(.*)$/.exec(dataURL);
3979
+ if (!matches) {
3980
+ promise.reject(
3981
+ new Parse.Error(-1, "Unable to interpret data URL: " + dataURL));
3982
+ return;
3983
+ }
3984
+
3985
+ promise.resolve(matches[2], type || matches[1]);
3986
+ };
3987
+ reader.readAsDataURL(file);
3988
+ return promise;
3989
+ };
3990
+
3991
+ /**
3992
+ * A Parse.File is a local representation of a file that is saved to the Parse
3993
+ * cloud.
3994
+ * @param name {String} The file's name. This will change to a unique value
3995
+ * once the file has finished saving.
3996
+ * @param data {Array} The data for the file, as either:
3997
+ * 1. an Array of byte value Numbers, or
3998
+ * 2. an Object like { base64: "..." } with a base64-encoded String.
3999
+ * 3. a File object selected with a file upload control. (3) only works
4000
+ * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
4001
+ * For example:<pre>
4002
+ * var fileUploadControl = $("#profilePhotoFileUpload")[0];
4003
+ * if (fileUploadControl.files.length > 0) {
4004
+ * var file = fileUploadControl.files[0];
4005
+ * var name = "photo.jpg";
4006
+ * var parseFile = new Parse.File(name, file);
4007
+ * parseFile.save().then(function() {
4008
+ * // The file has been saved to Parse.
4009
+ * }, function(error) {
4010
+ * // The file either could not be read, or could not be saved to Parse.
4011
+ * });
4012
+ * }</pre>
4013
+ * @param type {String} Optional Content-Type header to use for the file. If
4014
+ * this is omitted, the content type will be inferred from the name's
4015
+ * extension.
4016
+ */
4017
+ Parse.File = function(name, data, type) {
4018
+ this._name = name;
4019
+
4020
+ // Guess the content type from the extension if we need to.
4021
+ var extension = /\.([^.]*)$/.exec(name);
4022
+ if (extension) {
4023
+ extension = extension[1].toLowerCase();
4024
+ }
4025
+ var guessedType = type || mimeTypes[extension] || "text/plain";
4026
+
4027
+ if (_.isArray(data)) {
4028
+ this._source = Parse.Promise.as(encodeBase64(data), guessedType);
4029
+ } else if (data && data.base64) {
4030
+ this._source = Parse.Promise.as(data.base64, guessedType);
4031
+ } else if (typeof(File) !== "undefined" && data instanceof File) {
4032
+ this._source = readAsync(data, type);
4033
+ } else if (_.isString(data)) {
4034
+ throw "Creating a Parse.File from a String is not yet supported.";
4035
+ }
4036
+ };
4037
+
4038
+ Parse.File.prototype = {
4039
+
4040
+ /**
4041
+ * Gets the name of the file. Before save is called, this is the filename
4042
+ * given by the user. After save is called, that name gets prefixed with a
4043
+ * unique identifier.
4044
+ */
4045
+ name: function() {
4046
+ return this._name;
4047
+ },
4048
+
4049
+ /**
4050
+ * Gets the url of the file. It is only available after you save the file or
4051
+ * after you get the file from a Parse.Object.
4052
+ * @return {String}
3442
4053
  */
3443
- _continueWith: function(continuation) {
3444
- return this.then(function() {
3445
- return continuation(arguments, null);
3446
- }, function(error) {
3447
- return continuation(null, error);
3448
- });
3449
- }
4054
+ url: function() {
4055
+ return this._url;
4056
+ },
3450
4057
 
3451
- });
4058
+ /**
4059
+ * Saves the file to the Parse cloud.
4060
+ * @param {Object} options A Backbone-style options object.
4061
+ * @return {Parse.Promise} Promise that is resolved when the save finishes.
4062
+ */
4063
+ save: function(options) {
4064
+ var self = this;
4065
+ if (!self._previousSave) {
4066
+ self._previousSave = self._source.then(function(base64, type) {
4067
+ var data = {
4068
+ base64: base64,
4069
+ _ContentType: type
4070
+ };
4071
+ return Parse._request("files", self._name, null, 'POST', data);
4072
+
4073
+ }).then(function(response) {
4074
+ self._name = response.name;
4075
+ self._url = response.url;
4076
+ return self;
4077
+ });
4078
+ }
4079
+ return self._previousSave._thenRunCallbacks(options);
4080
+ }
4081
+ };
3452
4082
 
3453
4083
  }(this));
3454
4084
 
@@ -3580,17 +4210,17 @@
3580
4210
  */
3581
4211
  toJSON: function() {
3582
4212
  var json = this._toFullJSON();
3583
- _.each(["__type", "className"],
3584
- function(key) { delete json[key]; });
4213
+ Parse._arrayEach(["__type", "className"],
4214
+ function(key) { delete json[key]; });
3585
4215
  return json;
3586
4216
  },
3587
4217
 
3588
4218
  _toFullJSON: function(seenObjects) {
3589
4219
  var json = _.clone(this.attributes);
3590
- Parse._each(json, function(val, key) {
4220
+ Parse._objectEach(json, function(val, key) {
3591
4221
  json[key] = Parse._encode(val, seenObjects);
3592
4222
  });
3593
- Parse._each(this._operations, function(val, key) {
4223
+ Parse._objectEach(this._operations, function(val, key) {
3594
4224
  json[key] = val;
3595
4225
  });
3596
4226
 
@@ -3623,7 +4253,11 @@
3623
4253
  */
3624
4254
  _refreshCache: function() {
3625
4255
  var self = this;
3626
- Parse._each(this.attributes, function(value, key) {
4256
+ if (self._refreshingCache) {
4257
+ return;
4258
+ }
4259
+ self._refreshingCache = true;
4260
+ Parse._objectEach(this.attributes, function(value, key) {
3627
4261
  if (value instanceof Parse.Object) {
3628
4262
  value._refreshCache();
3629
4263
  } else if (_.isObject(value)) {
@@ -3632,6 +4266,7 @@
3632
4266
  }
3633
4267
  }
3634
4268
  });
4269
+ delete self._refreshingCache;
3635
4270
  },
3636
4271
 
3637
4272
  /**
@@ -3732,7 +4367,8 @@
3732
4367
  _mergeMagicFields: function(attrs) {
3733
4368
  // Check for changes of magic fields.
3734
4369
  var model = this;
3735
- _.each(["id", "objectId", "createdAt", "updatedAt"], function(attr) {
4370
+ var specialFields = ["id", "objectId", "createdAt", "updatedAt"];
4371
+ Parse._arrayEach(specialFields, function(attr) {
3736
4372
  if (attrs[attr]) {
3737
4373
  if (attr === "objectId") {
3738
4374
  model.id = attrs[attr];
@@ -3771,7 +4407,7 @@
3771
4407
  var failedChanges = _.first(this._opSetQueue);
3772
4408
  this._opSetQueue = _.rest(this._opSetQueue);
3773
4409
  var nextChanges = _.first(this._opSetQueue);
3774
- Parse._each(failedChanges, function(op, key) {
4410
+ Parse._objectEach(failedChanges, function(op, key) {
3775
4411
  var op1 = failedChanges[key];
3776
4412
  var op2 = nextChanges[key];
3777
4413
  if (op1 && op2) {
@@ -3795,7 +4431,7 @@
3795
4431
  // same object, but that's a risk we have to take.
3796
4432
  var fetchedObjects = {};
3797
4433
  Parse._traverse(this.attributes, function(object) {
3798
- if (object.id && object._hasData) {
4434
+ if (object instanceof Parse.Object && object.id && object._hasData) {
3799
4435
  fetchedObjects[object.id] = object;
3800
4436
  }
3801
4437
  });
@@ -3805,13 +4441,13 @@
3805
4441
  this._applyOpSet(savedChanges, this._serverData);
3806
4442
  this._mergeMagicFields(serverData);
3807
4443
  var self = this;
3808
- Parse._each(serverData, function(value, key) {
4444
+ Parse._objectEach(serverData, function(value, key) {
3809
4445
  self._serverData[key] = Parse._decode(key, value);
3810
4446
 
3811
4447
  // Look for any objects that might have become unfetched and fix them
3812
4448
  // by replacing their values with the previously observed values.
3813
4449
  var fetched = Parse._traverse(self._serverData[key], function(object) {
3814
- if (fetchedObjects[object.id]) {
4450
+ if (object instanceof Parse.Object && fetchedObjects[object.id]) {
3815
4451
  return fetchedObjects[object.id];
3816
4452
  }
3817
4453
  });
@@ -3834,7 +4470,7 @@
3834
4470
  // Bring in all the new server data.
3835
4471
  this._mergeMagicFields(serverData);
3836
4472
  var self = this;
3837
- Parse._each(serverData, function(value, key) {
4473
+ Parse._objectEach(serverData, function(value, key) {
3838
4474
  self._serverData[key] = Parse._decode(key, value);
3839
4475
  });
3840
4476
 
@@ -3853,7 +4489,7 @@
3853
4489
  */
3854
4490
  _applyOpSet: function(opSet, target) {
3855
4491
  var self = this;
3856
- Parse._.each(opSet, function(change, key) {
4492
+ Parse._objectEach(opSet, function(change, key) {
3857
4493
  target[key] = change._estimate(target[key], self, key);
3858
4494
  if (target[key] === Parse.Op._UNSET) {
3859
4495
  delete target[key];
@@ -3867,7 +4503,9 @@
3867
4503
  */
3868
4504
  _resetCacheForKey: function(key) {
3869
4505
  var value = this.attributes[key];
3870
- if (_.isObject(value) && !(value instanceof Parse.Object)) {
4506
+ if (_.isObject(value) &&
4507
+ !(value instanceof Parse.Object) &&
4508
+ !(value instanceof Parse.File)) {
3871
4509
  value = value.toJSON ? value.toJSON() : value;
3872
4510
  var json = JSON.stringify(value);
3873
4511
  if (this._hashedJSON[key] !== json) {
@@ -3889,7 +4527,7 @@
3889
4527
  if (this._serverData[key]) {
3890
4528
  this.attributes[key] = this._serverData[key];
3891
4529
  }
3892
- _.each(this._opSetQueue, function(opSet) {
4530
+ Parse._arrayEach(this._opSetQueue, function(opSet) {
3893
4531
  var op = opSet[key];
3894
4532
  if (op) {
3895
4533
  self.attributes[key] = op._estimate(self.attributes[key], self, key);
@@ -3913,20 +4551,20 @@
3913
4551
  var previousAttributes = _.clone(this.attributes);
3914
4552
 
3915
4553
  this.attributes = _.clone(this._serverData);
3916
- _.each(this._opSetQueue, function(opSet) {
4554
+ Parse._arrayEach(this._opSetQueue, function(opSet) {
3917
4555
  self._applyOpSet(opSet, self.attributes);
3918
- _.each(opSet, function(op, key) {
4556
+ Parse._objectEach(opSet, function(op, key) {
3919
4557
  self._resetCacheForKey(key);
3920
4558
  });
3921
4559
  });
3922
4560
 
3923
4561
  // Trigger change events for anything that changed because of the fetch.
3924
- _.each(previousAttributes, function(oldValue, key) {
4562
+ Parse._objectEach(previousAttributes, function(oldValue, key) {
3925
4563
  if (self.attributes[key] !== oldValue) {
3926
4564
  self.trigger('change:' + key, self, self.attributes[key], {});
3927
4565
  }
3928
4566
  });
3929
- _.each(this.attributes, function(newValue, key) {
4567
+ Parse._objectEach(this.attributes, function(newValue, key) {
3930
4568
  if (!_.has(previousAttributes, key)) {
3931
4569
  self.trigger('change:' + key, self, newValue, {});
3932
4570
  }
@@ -3969,7 +4607,7 @@
3969
4607
  var attrs, attr;
3970
4608
  if (_.isObject(key) || Parse._isNullOrUndefined(key)) {
3971
4609
  attrs = key;
3972
- Parse._each(attrs, function(v, k) {
4610
+ Parse._objectEach(attrs, function(v, k) {
3973
4611
  attrs[k] = Parse._decode(k, v);
3974
4612
  });
3975
4613
  options = value;
@@ -3989,7 +4627,7 @@
3989
4627
 
3990
4628
  // If the unset option is used, every attribute should be a Unset.
3991
4629
  if (options.unset) {
3992
- Parse._each(attrs, function(unused_value, key) {
4630
+ Parse._objectEach(attrs, function(unused_value, key) {
3993
4631
  attrs[key] = new Parse.Op.Unset();
3994
4632
  });
3995
4633
  }
@@ -3997,7 +4635,7 @@
3997
4635
  // Apply all the attributes to get the estimated values.
3998
4636
  var dataToValidate = _.clone(attrs);
3999
4637
  var self = this;
4000
- Parse._each(dataToValidate, function(value, key) {
4638
+ Parse._objectEach(dataToValidate, function(value, key) {
4001
4639
  if (value instanceof Parse.Op) {
4002
4640
  dataToValidate[key] = value._estimate(self.attributes[key],
4003
4641
  self, key);
@@ -4019,7 +4657,7 @@
4019
4657
  var prev = this._previousAttributes || {};
4020
4658
 
4021
4659
  // Update attributes.
4022
- Parse._each(_.keys(attrs), function(attr) {
4660
+ Parse._arrayEach(_.keys(attrs), function(attr) {
4023
4661
  var val = attrs[attr];
4024
4662
 
4025
4663
  // If this is a relation object we need to set the parent correctly,
@@ -4158,7 +4796,7 @@
4158
4796
  */
4159
4797
  _getSaveJSON: function() {
4160
4798
  var json = _.clone(_.first(this._opSetQueue));
4161
- Parse._each(json, function(op, key) {
4799
+ Parse._objectEach(json, function(op, key) {
4162
4800
  json[key] = op.toJSON();
4163
4801
  });
4164
4802
  return json;
@@ -4179,22 +4817,12 @@
4179
4817
  * completes.
4180
4818
  */
4181
4819
  fetch: function(options) {
4182
- var promise = new Parse.Promise();
4183
- options = options ? _.clone(options) : {};
4184
- var model = this;
4185
- var success = options.success;
4186
- options.success = function(resp, status, xhr) {
4187
- model._finishFetch(model.parse(resp, status, xhr), true);
4188
- if (success) {
4189
- success(model, resp);
4190
- }
4191
- promise.resolve(model);
4192
- };
4193
- options.error = Parse.Object._wrapError(options.error, model, options,
4194
- promise);
4195
- Parse._request(
4196
- "classes", model.className, model.id, 'GET', null, options);
4197
- return promise;
4820
+ var self = this;
4821
+ var request = Parse._request("classes", this.className, this.id, 'GET');
4822
+ return request.then(function(response, status, xhr) {
4823
+ self._finishFetch(self.parse(response, status, xhr), true);
4824
+ return self;
4825
+ })._thenRunCallbacks(options, this);
4198
4826
  },
4199
4827
 
4200
4828
  /**
@@ -4236,8 +4864,6 @@
4236
4864
  * @see Parse.Error
4237
4865
  */
4238
4866
  save: function(arg1, arg2, arg3) {
4239
- var promise = new Parse.Promise();
4240
-
4241
4867
  var i, attrs, current, options, saved;
4242
4868
  if (_.isObject(arg1) || Parse._isNullOrUndefined(arg1)) {
4243
4869
  attrs = arg1;
@@ -4269,25 +4895,22 @@
4269
4895
  }
4270
4896
  }
4271
4897
 
4272
- options = options ? _.clone(options) : {};
4898
+ options = _.clone(options) || {};
4273
4899
  if (options.wait) {
4274
4900
  current = _.clone(this.attributes);
4275
4901
  }
4276
- var setOptions = _.clone(options);
4902
+
4903
+ var setOptions = _.clone(options) || {};
4277
4904
  if (setOptions.wait) {
4278
4905
  setOptions.silent = true;
4279
4906
  }
4907
+ var setError;
4280
4908
  setOptions.error = function(model, error) {
4281
- if (options.error) {
4282
- options.error.apply(this, arguments);
4283
- }
4284
- promise.reject(error);
4909
+ setError = error;
4285
4910
  };
4286
4911
  if (attrs && !this.set(attrs, setOptions)) {
4287
- return promise;
4912
+ return Parse.Promise.error(setError)._thenRunCallbacks(options, this);
4288
4913
  }
4289
- var oldOptions = options; // Psuedonym more accurate in some contexts.
4290
- var newOptions = _.clone(options);
4291
4914
 
4292
4915
  var model = this;
4293
4916
 
@@ -4296,53 +4919,24 @@
4296
4919
 
4297
4920
 
4298
4921
 
4299
- var unsavedChildren = Parse.Object._findUnsavedChildren(model.attributes);
4300
- if (unsavedChildren.length > 0) {
4922
+ var unsavedChildren = [];
4923
+ var unsavedFiles = [];
4924
+ Parse.Object._findUnsavedChildren(model.attributes,
4925
+ unsavedChildren,
4926
+ unsavedFiles);
4927
+ if (unsavedChildren.length + unsavedFiles.length > 0) {
4301
4928
  return Parse.Object._deepSaveAsync(this.attributes).then(function() {
4302
- return model.save(null, oldOptions);
4929
+ return model.save(null, options);
4303
4930
  }, function(error) {
4304
- if (options.error) {
4305
- options.error.apply(this, arguments);
4306
- }
4307
- return Parse.Promise.error(error);
4931
+ return Parse.Promise.error(error)._thenRunCallbacks(options, model);
4308
4932
  });
4309
4933
  }
4310
4934
 
4311
- /** ignore */
4312
- newOptions.success = function(resp, status, xhr) {
4313
- var serverAttrs = model.parse(resp, status, xhr);
4314
- if (newOptions.wait) {
4315
- serverAttrs = _.extend(attrs || {}, serverAttrs);
4316
- }
4317
-
4318
- model._finishSave(serverAttrs);
4319
-
4320
- if (oldOptions.success) {
4321
- oldOptions.success(model, resp);
4322
- } else {
4323
- model.trigger('sync', model, resp, newOptions);
4324
- }
4325
-
4326
- promise.resolve(model);
4327
- };
4328
-
4329
- newOptions.error = function(model, error) {
4330
- model._cancelSave();
4331
- if (oldOptions.error) {
4332
- oldOptions.error.apply(this, arguments);
4333
- }
4334
-
4335
- promise.reject(error);
4336
- };
4337
-
4338
- newOptions.error = Parse.Object._wrapError(newOptions.error, model,
4339
- newOptions);
4340
-
4341
4935
  this._startSave();
4342
4936
  this._saving = (this._saving || 0) + 1;
4343
4937
 
4344
4938
  this._allPreviousSaves = this._allPreviousSaves || Parse.Promise.as();
4345
- this._allPreviousSaves._continueWith(function() {
4939
+ this._allPreviousSaves = this._allPreviousSaves._continueWith(function() {
4346
4940
  var method = model.id ? 'PUT' : 'POST';
4347
4941
 
4348
4942
  var json = model._getSaveJSON();
@@ -4354,17 +4948,28 @@
4354
4948
  route = "users";
4355
4949
  className = null;
4356
4950
  }
4357
- var request =
4358
- Parse._request(route, className, model.id, method, json, newOptions);
4359
- if (newOptions.wait) {
4360
- model.set(current, setOptions);
4361
- }
4362
- return request;
4951
+ var request = Parse._request(route, className, model.id, method, json);
4363
4952
 
4364
- });
4365
- this._allPreviousSaves = promise;
4953
+ request = request.then(function(resp, status, xhr) {
4954
+ var serverAttrs = model.parse(resp, status, xhr);
4955
+ if (options.wait) {
4956
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
4957
+ }
4958
+ model._finishSave(serverAttrs);
4959
+ if (options.wait) {
4960
+ model.set(current, setOptions);
4961
+ }
4962
+ return model;
4366
4963
 
4367
- return promise;
4964
+ }, function(error) {
4965
+ model._cancelSave();
4966
+ return Parse.Promise.error(error);
4967
+
4968
+ })._thenRunCallbacks(options, model);
4969
+
4970
+ return request;
4971
+ });
4972
+ return this._allPreviousSaves;
4368
4973
  },
4369
4974
 
4370
4975
  /**
@@ -4377,10 +4982,8 @@
4377
4982
  * completes.
4378
4983
  */
4379
4984
  destroy: function(options) {
4380
- var promise = new Parse.Promise();
4381
- options = options ? _.clone(options) : {};
4985
+ options = options || {};
4382
4986
  var model = this;
4383
- var success = options.success;
4384
4987
 
4385
4988
  var triggerDestroy = function() {
4386
4989
  model.trigger('destroy', model, model.collection, options);
@@ -4389,27 +4992,19 @@
4389
4992
  if (!this.id) {
4390
4993
  return triggerDestroy();
4391
4994
  }
4392
- /** ignore */
4393
- options.success = function(resp) {
4394
- if (options.wait) {
4395
- triggerDestroy();
4396
- }
4397
- if (success) {
4398
- success(model, resp);
4399
- } else {
4400
- model.trigger('sync', model, resp, options);
4401
- }
4402
- promise.resolve(model);
4403
- };
4404
- options.error = Parse.Object._wrapError(options.error, model, options,
4405
- promise);
4406
4995
 
4407
- Parse._request("classes", this.className, this.id, 'DELETE', null,
4408
- options);
4409
4996
  if (!options.wait) {
4410
4997
  triggerDestroy();
4411
4998
  }
4412
- return promise;
4999
+
5000
+ var request =
5001
+ Parse._request("classes", this.className, this.id, 'DELETE');
5002
+ return request.then(function() {
5003
+ if (options.wait) {
5004
+ triggerDestroy();
5005
+ }
5006
+ return model;
5007
+ })._thenRunCallbacks(options, this);
4413
5008
  },
4414
5009
 
4415
5010
  /**
@@ -4460,14 +5055,14 @@
4460
5055
 
4461
5056
  // Silent changes become pending changes.
4462
5057
  var self = this;
4463
- Parse._each(this._silent, function(attr) {
5058
+ Parse._objectEach(this._silent, function(attr) {
4464
5059
  self._pending[attr] = true;
4465
5060
  });
4466
5061
 
4467
5062
  // Silent changes are triggered.
4468
5063
  var changes = _.extend({}, options.changes, this._silent);
4469
5064
  this._silent = {};
4470
- Parse._each(changes, function(unused_value, attr) {
5065
+ Parse._objectEach(changes, function(unused_value, attr) {
4471
5066
  self.trigger('change:' + attr, self, self.get(attr), options);
4472
5067
  });
4473
5068
  if (changing) {
@@ -4486,7 +5081,7 @@
4486
5081
  this._pending = {};
4487
5082
  this.trigger('change', this, options);
4488
5083
  // Pending and silent changes still remain.
4489
- Parse._each(this.changed, deleteChanged);
5084
+ Parse._objectEach(this.changed, deleteChanged);
4490
5085
  self._previousAttributes = _.clone(this.attributes);
4491
5086
  }
4492
5087
 
@@ -4531,7 +5126,7 @@
4531
5126
  }
4532
5127
  var changed = {};
4533
5128
  var old = this._previousAttributes;
4534
- Parse._each(diff, function(diffVal, attr) {
5129
+ Parse._objectEach(diff, function(diffVal, attr) {
4535
5130
  if (!_.isEqual(old[attr], diffVal)) {
4536
5131
  changed[attr] = diffVal;
4537
5132
  }
@@ -4732,41 +5327,23 @@
4732
5327
  return NewClassObject;
4733
5328
  };
4734
5329
 
4735
- /**
4736
- * Wrap an optional error callback with a fallback error event.
4737
- */
4738
- Parse.Object._wrapError = function(onError, originalModel, options, promise) {
4739
- return function(model, response) {
4740
- if (model !== originalModel) {
4741
- response = model;
4742
- }
4743
- var error = new Parse.Error(-1, response.responseText);
4744
- if (response.responseText) {
4745
- var errorJSON = JSON.parse(response.responseText);
4746
- if (errorJSON) {
4747
- error = new Parse.Error(errorJSON.code, errorJSON.error);
5330
+ Parse.Object._findUnsavedChildren = function(object, children, files) {
5331
+ Parse._traverse(object, function(object) {
5332
+ if (object instanceof Parse.Object) {
5333
+ object._refreshCache();
5334
+ if (object.dirty()) {
5335
+ children.push(object);
4748
5336
  }
5337
+ return;
4749
5338
  }
4750
- if (onError) {
4751
- onError(originalModel, error, options);
4752
- } else {
4753
- originalModel.trigger('error', originalModel, error, options);
4754
- }
4755
- if (promise) {
4756
- promise.reject(error);
4757
- }
4758
- };
4759
- };
4760
5339
 
4761
- Parse.Object._findUnsavedChildren = function(object) {
4762
- var results = [];
4763
- Parse._traverse(object, function(object) {
4764
- object._refreshCache();
4765
- if (object.dirty()) {
4766
- results.push(object);
5340
+ if (object instanceof Parse.File) {
5341
+ if (!object.url()) {
5342
+ files.push(object);
5343
+ }
5344
+ return;
4767
5345
  }
4768
5346
  });
4769
- return results;
4770
5347
  };
4771
5348
 
4772
5349
  Parse.Object._canBeSerializedAsValue = function(object) {
@@ -4776,14 +5353,14 @@
4776
5353
  canBeSerializedAsValue = !!object.id;
4777
5354
 
4778
5355
  } else if (_.isArray(object)) {
4779
- _.each(object, function(child) {
5356
+ Parse._arrayEach(object, function(child) {
4780
5357
  if (!Parse.Object._canBeSerializedAsValue(child)) {
4781
5358
  canBeSerializedAsValue = false;
4782
5359
  }
4783
5360
  });
4784
5361
 
4785
5362
  } else if (_.isObject(object)) {
4786
- Parse._each(object, function(child) {
5363
+ Parse._objectEach(object, function(child) {
4787
5364
  if (!Parse.Object._canBeSerializedAsValue(child)) {
4788
5365
  canBeSerializedAsValue = false;
4789
5366
  }
@@ -4794,97 +5371,108 @@
4794
5371
  };
4795
5372
 
4796
5373
  Parse.Object._deepSaveAsync = function(object) {
4797
- var objects = _.uniq(Parse.Object._findUnsavedChildren(object));
5374
+ var unsavedChildren = [];
5375
+ var unsavedFiles = [];
5376
+ Parse.Object._findUnsavedChildren(object, unsavedChildren, unsavedFiles);
5377
+
5378
+ var promise = Parse.Promise.as();
5379
+ _.each(unsavedFiles, function(file) {
5380
+ promise = promise.then(function() {
5381
+ return file.save();
5382
+ });
5383
+ });
5384
+
5385
+ var objects = _.uniq(unsavedChildren);
4798
5386
  var remaining = _.uniq(objects);
4799
5387
 
4800
- return Parse.Promise._continueWhile(function() {
4801
- return remaining.length > 0;
4802
- }, function() {
4803
-
4804
- // Gather up all the objects that can be saved in this batch.
4805
- var batch = [];
4806
- var newRemaining = [];
4807
- _.each(remaining, function(object) {
4808
- // Limit batches to 20 objects.
4809
- if (batch.length > 20) {
4810
- newRemaining.push(object);
4811
- return;
4812
- }
5388
+ return promise.then(function() {
5389
+ return Parse.Promise._continueWhile(function() {
5390
+ return remaining.length > 0;
5391
+ }, function() {
5392
+
5393
+ // Gather up all the objects that can be saved in this batch.
5394
+ var batch = [];
5395
+ var newRemaining = [];
5396
+ Parse._arrayEach(remaining, function(object) {
5397
+ // Limit batches to 20 objects.
5398
+ if (batch.length > 20) {
5399
+ newRemaining.push(object);
5400
+ return;
5401
+ }
4813
5402
 
4814
- if (object._canBeSerialized()) {
4815
- batch.push(object);
4816
- } else {
4817
- newRemaining.push(object);
4818
- }
4819
- });
4820
- remaining = newRemaining;
5403
+ if (object._canBeSerialized()) {
5404
+ batch.push(object);
5405
+ } else {
5406
+ newRemaining.push(object);
5407
+ }
5408
+ });
5409
+ remaining = newRemaining;
4821
5410
 
4822
- // If we can't save any objects, there must be a circular reference.
4823
- if (batch.length === 0) {
4824
- return Parse.Promise.error(
4825
- new Parse.Error(Parse.Error.OTHER_CAUSE,
4826
- "Tried to save a batch with a cycle."));
4827
- }
5411
+ // If we can't save any objects, there must be a circular reference.
5412
+ if (batch.length === 0) {
5413
+ return Parse.Promise.error(
5414
+ new Parse.Error(Parse.Error.OTHER_CAUSE,
5415
+ "Tried to save a batch with a cycle."));
5416
+ }
4828
5417
 
4829
- // Reserve a spot in every object's save queue.
4830
- var readyToStart = Parse.Promise.when(_.map(batch, function(object) {
4831
- return object._allPreviousSaves || Parse.Promise.as();
4832
- }));
4833
- var batchFinished = new Parse.Promise();
4834
- _.each(batch, function(object) {
4835
- object._allPreviousSaves = batchFinished;
4836
- });
5418
+ // Reserve a spot in every object's save queue.
5419
+ var readyToStart = Parse.Promise.when(_.map(batch, function(object) {
5420
+ return object._allPreviousSaves || Parse.Promise.as();
5421
+ }));
5422
+ var batchFinished = new Parse.Promise();
5423
+ Parse._arrayEach(batch, function(object) {
5424
+ object._allPreviousSaves = batchFinished;
5425
+ });
4837
5426
 
4838
- // Save a single batch, whether previous saves succeeded or failed.
4839
- return readyToStart._continueWith(function() {
4840
- return Parse._request("batch", null, null, "POST", {
4841
- requests: _.map(batch, function(object) {
4842
- var json = object._getSaveJSON();
4843
- var method = "POST";
4844
-
4845
- var path = "/1/classes/" + object.className;
4846
- if (object.id) {
4847
- path = path + "/" + object.id;
4848
- method = "PUT";
5427
+ // Save a single batch, whether previous saves succeeded or failed.
5428
+ return readyToStart._continueWith(function() {
5429
+ return Parse._request("batch", null, null, "POST", {
5430
+ requests: _.map(batch, function(object) {
5431
+ var json = object._getSaveJSON();
5432
+ var method = "POST";
5433
+
5434
+ var path = "/1/classes/" + object.className;
5435
+ if (object.id) {
5436
+ path = path + "/" + object.id;
5437
+ method = "PUT";
5438
+ }
5439
+
5440
+ object._startSave();
5441
+
5442
+ return {
5443
+ method: method,
5444
+ path: path,
5445
+ body: json
5446
+ };
5447
+ })
5448
+
5449
+ }).then(function(response, status, xhr) {
5450
+ var error;
5451
+ Parse._arrayEach(batch, function(object, i) {
5452
+ if (response[i].success) {
5453
+ object._finishSave(
5454
+ object.parse(response[i].success, status, xhr));
5455
+ } else {
5456
+ error = error || response[i].error;
5457
+ object._cancelSave();
5458
+ }
5459
+ });
5460
+ if (error) {
5461
+ return Parse.Promise.error(
5462
+ new Parse.Error(error.code, error.error));
4849
5463
  }
4850
5464
 
4851
- object._startSave();
4852
-
4853
- return {
4854
- method: method,
4855
- path: path,
4856
- body: json
4857
- };
4858
- })
4859
-
4860
- }).then(function(response, status, xhr) {
4861
- var error;
4862
- _.each(batch, function(object, i) {
4863
- if (response[i].success) {
4864
- object._finishSave(
4865
- object.parse(response[i].success, status, xhr));
4866
- } else {
4867
- error = error || response[i].error;
4868
- object._cancelSave();
4869
- }
5465
+ }).then(function(results) {
5466
+ batchFinished.resolve(results);
5467
+ return results;
5468
+ }, function(error) {
5469
+ batchFinished.reject(error);
5470
+ return Parse.Promise.error(error);
4870
5471
  });
4871
- if (error) {
4872
- return Parse.Promise.error(
4873
- new Parse.Error(error.code, error.error));
4874
- }
4875
-
4876
- }).then(function(results) {
4877
- batchFinished.resolve(results);
4878
- return results;
4879
- }, function(error) {
4880
- batchFinished.reject(error);
4881
- return Parse.Promise.error(error);
4882
-
4883
5472
  });
4884
5473
  });
4885
5474
  }).then(function() {
4886
5475
  return object;
4887
-
4888
5476
  });
4889
5477
  };
4890
5478
 
@@ -5237,7 +5825,7 @@
5237
5825
  var self = this;
5238
5826
  models = models || [];
5239
5827
  options = options || {};
5240
- _.each(this.models, function(model) {
5828
+ Parse._arrayEach(this.models, function(model) {
5241
5829
  self._removeReference(model);
5242
5830
  });
5243
5831
  this._reset();
@@ -5254,26 +5842,20 @@
5254
5842
  * models to the collection instead of resetting.
5255
5843
  */
5256
5844
  fetch: function(options) {
5257
- options = options ? _.clone(options) : {};
5845
+ options = _.clone(options) || {};
5258
5846
  if (options.parse === undefined) {
5259
5847
  options.parse = true;
5260
5848
  }
5261
5849
  var collection = this;
5262
- var success = options.success;
5263
- options.success = function(results, resp) {
5850
+ var query = this.query || new Parse.Query(this.model);
5851
+ return query.find().then(function(results) {
5264
5852
  if (options.add) {
5265
5853
  collection.add(results, options);
5266
5854
  } else {
5267
5855
  collection.reset(results, options);
5268
5856
  }
5269
- if (success) {
5270
- success(collection, resp);
5271
- }
5272
- };
5273
- options.error = Parse.Object._wrapError(options.error, collection,
5274
- options);
5275
- var query = this.query || new Parse.Query(this.model);
5276
- query.find(options);
5857
+ return collection;
5858
+ })._thenRunCallbacks(options, this);
5277
5859
  },
5278
5860
 
5279
5861
  /**
@@ -5391,7 +5973,7 @@
5391
5973
  'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
5392
5974
 
5393
5975
  // Mix in each Underscore method as a proxy to `Collection#models`.
5394
- _.each(methods, function(method) {
5976
+ Parse._arrayEach(methods, function(method) {
5395
5977
  Parse.Collection.prototype[method] = function() {
5396
5978
  return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
5397
5979
  };
@@ -5556,7 +6138,7 @@
5556
6138
  }
5557
6139
  this.undelegateEvents();
5558
6140
  var self = this;
5559
- Parse._each(events, function(method, key) {
6141
+ Parse._objectEach(events, function(method, key) {
5560
6142
  if (!_.isFunction(method)) {
5561
6143
  method = self[events[key]];
5562
6144
  }
@@ -5679,7 +6261,7 @@
5679
6261
  if (!authData) {
5680
6262
  return;
5681
6263
  }
5682
- _.each(this.get('authData'), function(value, key) {
6264
+ Parse._objectEach(this.get('authData'), function(value, key) {
5683
6265
  if (!authData[key]) {
5684
6266
  delete authData[key];
5685
6267
  }
@@ -5696,7 +6278,7 @@
5696
6278
  }
5697
6279
 
5698
6280
  var self = this;
5699
- _.each(this.get('authData'), function(value, key) {
6281
+ Parse._objectEach(this.get('authData'), function(value, key) {
5700
6282
  self._synchronizeAuthData(key);
5701
6283
  });
5702
6284
  },
@@ -5760,7 +6342,7 @@
5760
6342
  this.set('authData', authData);
5761
6343
 
5762
6344
  // Overridden so that the user can be made the current user.
5763
- var newOptions = _.clone(options);
6345
+ var newOptions = _.clone(options) || {};
5764
6346
  newOptions.success = function(model) {
5765
6347
  model._handleSaveResult(true);
5766
6348
  if (options.success) {
@@ -5770,20 +6352,25 @@
5770
6352
  return this.save({'authData': authData}, newOptions);
5771
6353
  } else {
5772
6354
  var self = this;
5773
- return provider.authenticate({
6355
+ var promise = new Parse.Promise();
6356
+ provider.authenticate({
5774
6357
  success: function(provider, result) {
5775
6358
  self._linkWith(provider, {
5776
6359
  authData: result,
5777
6360
  success: options.success,
5778
6361
  error: options.error
6362
+ }).then(function() {
6363
+ promise.resolve(self);
5779
6364
  });
5780
6365
  },
5781
6366
  error: function(provider, error) {
5782
6367
  if (options.error) {
5783
6368
  options.error(self, error);
5784
6369
  }
6370
+ promise.reject(error);
5785
6371
  }
5786
6372
  });
6373
+ return promise;
5787
6374
  }
5788
6375
  },
5789
6376
 
@@ -5833,7 +6420,7 @@
5833
6420
  return;
5834
6421
  }
5835
6422
  var self = this;
5836
- _.each(this.get('authData'), function(value, key) {
6423
+ Parse._objectEach(this.get('authData'), function(value, key) {
5837
6424
  self._logOutWith(key);
5838
6425
  });
5839
6426
  },
@@ -5922,24 +6509,14 @@
5922
6509
  * the login is complete.
5923
6510
  */
5924
6511
  logIn: function(options) {
5925
- var promise = new Parse.Promise();
5926
6512
  var model = this;
5927
- var newOptions = options ? _.clone(options) : {};
5928
- newOptions.success = function(resp, status, xhr) {
6513
+ var request = Parse._request("login", null, null, "GET", this.toJSON());
6514
+ return request.then(function(resp, status, xhr) {
5929
6515
  var serverAttrs = model.parse(resp, status, xhr);
5930
6516
  model._finishFetch(serverAttrs);
5931
6517
  model._handleSaveResult(true);
5932
- if (options && options.success) {
5933
- options.success(model, resp);
5934
- } else {
5935
- model.trigger('sync', model, resp, newOptions);
5936
- }
5937
- promise.resolve(model);
5938
- };
5939
- newOptions.error = Parse.Object._wrapError(newOptions.error, model,
5940
- newOptions, promise);
5941
- Parse._request("login", null, null, "GET", this.toJSON(), newOptions);
5942
- return promise;
6518
+ return model;
6519
+ })._thenRunCallbacks(options, this);
5943
6520
  },
5944
6521
 
5945
6522
  /**
@@ -5971,10 +6548,10 @@
5971
6548
  * @see Parse.Object#fetch
5972
6549
  */
5973
6550
  fetch: function(options) {
5974
- var newOptions = _.clone(options);
6551
+ var newOptions = options ? _.clone(options) : {};
5975
6552
  newOptions.success = function(model) {
5976
6553
  model._handleSaveResult(false);
5977
- if (options.success) {
6554
+ if (options && options.success) {
5978
6555
  options.success.apply(this, arguments);
5979
6556
  }
5980
6557
  };
@@ -6142,8 +6719,9 @@
6142
6719
  */
6143
6720
  requestPasswordReset: function(email, options) {
6144
6721
  var json = { email: email };
6145
- options.error = Parse.Query._wrapError(options.error, options);
6146
- Parse._request("requestPasswordReset", null, null, "POST", json, options);
6722
+ var request = Parse._request("requestPasswordReset", null, null, "POST",
6723
+ json);
6724
+ return request._thenRunCallbacks(options);
6147
6725
  },
6148
6726
 
6149
6727
  /**
@@ -6313,7 +6891,7 @@
6313
6891
  Parse.Query.or = function() {
6314
6892
  var queries = _.toArray(arguments);
6315
6893
  var className = null;
6316
- _.each(queries, function(q) {
6894
+ Parse._arrayEach(queries, function(q) {
6317
6895
  if (_.isNull(className)) {
6318
6896
  className = q.className;
6319
6897
  }
@@ -6338,32 +6916,18 @@
6338
6916
  */
6339
6917
  get: function(objectId, options) {
6340
6918
  var self = this;
6341
- var success = options.success || function() {};
6342
- var error = options.error || function() {};
6343
- var promise = new Parse.Promise();
6919
+ self.equalTo('objectId', objectId);
6344
6920
 
6345
- /** ignore */
6346
- var ajaxOptions = {
6347
- error: function(errorObject) {
6348
- error(null, errorObject);
6349
- promise.reject(errorObject);
6350
- },
6351
- success: function(response) {
6352
- if (response) {
6353
- success(response);
6354
- promise.resolve(response);
6355
- } else {
6356
- var errorObject = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
6357
- "Object not found.");
6358
- error(null, errorObject);
6359
- promise.reject(errorObject);
6360
- }
6921
+ return self.first().then(function(response) {
6922
+ if (response) {
6923
+ return response;
6361
6924
  }
6362
- };
6363
6925
 
6364
- self.equalTo('objectId', objectId);
6365
- self.first(ajaxOptions);
6366
- return promise;
6926
+ var errorObject = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
6927
+ "Object not found.");
6928
+ return Parse.Promise.error(errorObject);
6929
+
6930
+ })._thenRunCallbacks(options, null);
6367
6931
  },
6368
6932
 
6369
6933
  /**
@@ -6378,6 +6942,9 @@
6378
6942
  if (this._include.length > 0) {
6379
6943
  params.include = this._include.join(",");
6380
6944
  }
6945
+ if (this._select) {
6946
+ params.keys = this._select.join(",");
6947
+ }
6381
6948
  if (this._limit >= 0) {
6382
6949
  params.limit = this._limit;
6383
6950
  }
@@ -6388,7 +6955,7 @@
6388
6955
  params.order = this._order;
6389
6956
  }
6390
6957
 
6391
- Parse._each(this._extraOptions, function(v, k) {
6958
+ Parse._objectEach(this._extraOptions, function(v, k) {
6392
6959
  params[k] = v;
6393
6960
  });
6394
6961
 
@@ -6406,35 +6973,22 @@
6406
6973
  */
6407
6974
  find: function(options) {
6408
6975
  var self = this;
6409
- options = options || {};
6410
- var success = options.success || function() {};
6411
- var promise = new Parse.Promise();
6412
6976
 
6413
- /** ignore */
6414
- var ajaxOptions = {
6415
- error: options.error,
6416
- success: function(response) {
6417
- var results = _.map(response.results, function(json) {
6418
- var obj;
6419
- if (response.className) {
6420
- obj = new Parse.Object(response.className);
6421
- } else {
6422
- obj = new self.objectClass();
6423
- }
6424
- obj._finishFetch(json, true);
6425
- return obj;
6426
- });
6427
- success(results);
6428
- promise.resolve(results);
6429
- }
6430
- };
6977
+ var request = Parse._request("classes", this.className, null, "GET",
6978
+ this.toJSON());
6431
6979
 
6432
- var params = this.toJSON();
6433
- ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions,
6434
- promise);
6435
- Parse._request("classes", this.className, null, "GET", params,
6436
- ajaxOptions);
6437
- return promise;
6980
+ return request.then(function(response) {
6981
+ return _.map(response.results, function(json) {
6982
+ var obj;
6983
+ if (response.className) {
6984
+ obj = new Parse.Object(response.className);
6985
+ } else {
6986
+ obj = new self.objectClass();
6987
+ }
6988
+ obj._finishFetch(json, true);
6989
+ return obj;
6990
+ });
6991
+ })._thenRunCallbacks(options);
6438
6992
  },
6439
6993
 
6440
6994
  /**
@@ -6447,28 +7001,15 @@
6447
7001
  * the query completes.
6448
7002
  */
6449
7003
  count: function(options) {
6450
- var self = this;
6451
- options = options || {};
6452
- var success = options.success || function() {};
6453
- var promise = new Parse.Promise();
6454
-
6455
- /** ignore */
6456
- var ajaxOptions = {
6457
- error: options.error,
6458
- success: function(response) {
6459
- success(response.count);
6460
- promise.resolve(response.count);
6461
- }
6462
- };
6463
-
6464
7004
  var params = this.toJSON();
6465
7005
  params.limit = 0;
6466
7006
  params.count = 1;
6467
- ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions,
6468
- promise);
6469
- Parse._request("classes", this.className, null, "GET", params,
6470
- ajaxOptions);
6471
- return promise;
7007
+ var request = Parse._request("classes", this.className, null, "GET",
7008
+ params);
7009
+
7010
+ return request.then(function(response) {
7011
+ return response.count;
7012
+ })._thenRunCallbacks(options);
6472
7013
  },
6473
7014
 
6474
7015
  /**
@@ -6483,31 +7024,19 @@
6483
7024
  */
6484
7025
  first: function(options) {
6485
7026
  var self = this;
6486
- options = options || {};
6487
- var success = options.success || function() {};
6488
- var promise = new Parse.Promise();
6489
-
6490
- /** ignore */
6491
- var ajaxOptions = {
6492
- error: options.error,
6493
- success: function(response) {
6494
- var result = _.map(response.results, function(json) {
6495
- var obj = new self.objectClass();
6496
- obj._finishFetch(json, true);
6497
- return obj;
6498
- })[0];
6499
- success(result);
6500
- promise.resolve(result);
6501
- }
6502
- };
6503
7027
 
6504
7028
  var params = this.toJSON();
6505
7029
  params.limit = 1;
6506
- ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions,
6507
- promise);
6508
- Parse._request("classes", this.className, null, "GET", params,
6509
- ajaxOptions);
6510
- return promise;
7030
+ var request = Parse._request("classes", this.className, null, "GET",
7031
+ params);
7032
+
7033
+ return request.then(function(response) {
7034
+ return _.map(response.results, function(json) {
7035
+ var obj = new self.objectClass();
7036
+ obj._finishFetch(json, true);
7037
+ return obj;
7038
+ })[0];
7039
+ })._thenRunCallbacks(options);
6511
7040
  },
6512
7041
 
6513
7042
  /**
@@ -6944,45 +7473,108 @@
6944
7473
  * @param {String} key The name of the key to include.
6945
7474
  * @return {Parse.Query} Returns the query, so you can chain this call.
6946
7475
  */
6947
- include: function(key) {
6948
- if (_.isArray(key)) {
6949
- this._include = this._include.concat(key);
6950
- } else {
6951
- this._include.push(key);
6952
- }
7476
+ include: function() {
7477
+ var self = this;
7478
+ Parse._arrayEach(arguments, function(key) {
7479
+ if (_.isArray(key)) {
7480
+ self._include = self._include.concat(key);
7481
+ } else {
7482
+ self._include.push(key);
7483
+ }
7484
+ });
6953
7485
  return this;
6954
- }
6955
- };
7486
+ },
6956
7487
 
6957
- // Wrap an optional error callback with a fallback error event.
6958
- Parse.Query._wrapError = function(onError, options, promise) {
6959
- return function(response) {
6960
- var error;
6961
- if (response.responseText) {
6962
- var errorJSON = JSON.parse(response.responseText);
6963
- if (errorJSON) {
6964
- error = new Parse.Error(errorJSON.code, errorJSON.error);
7488
+ /**
7489
+ * Restrict the fields of the returned Parse.Objects to include only the
7490
+ * provided keys. If this is called multiple times, then all of the keys
7491
+ * specified in each of the calls will be included.
7492
+ * @param {Array} keys The names of the keys to include.
7493
+ * @return {Parse.Query} Returns the query, so you can chain this call.
7494
+ */
7495
+ select: function() {
7496
+ var self = this;
7497
+ this._select = this._select || [];
7498
+ Parse._arrayEach(arguments, function(key) {
7499
+ if (_.isArray(key)) {
7500
+ self._select = self._select.concat(key);
7501
+ } else {
7502
+ self._select.push(key);
6965
7503
  }
7504
+ });
7505
+ return this;
7506
+ },
7507
+
7508
+ /**
7509
+ * Iterates over each result of a query, calling a callback for each one. If
7510
+ * the callback returns a promise, the iteration will not continue until
7511
+ * that promise has been fulfilled. If the callback returns a rejected
7512
+ * promise, then iteration will stop with that error. The items are
7513
+ * processed in an unspecified order. The query may not have any sort order,
7514
+ * and may not use limit or skip.
7515
+ * @param callback {Function} Callback that will be called with each result
7516
+ * of the query.
7517
+ * @param options {Object} An optional Backbone-like options object with
7518
+ * success and error callbacks that will be invoked once the iteration
7519
+ * has finished.
7520
+ * @return {Parse.Promise} A promise that will be fulfilled once the
7521
+ * iteration has completed.
7522
+ */
7523
+ each: function(callback, options) {
7524
+ options = options || {};
7525
+
7526
+ if (this._order || this._skip || (this._limit >= 0)) {
7527
+ var error =
7528
+ "Cannot iterate on a query with sort, skip, or limit.";
7529
+ return Parse.Promise.error(error)._thenRunCallbacks(options);
6966
7530
  }
6967
- error = error || new Parse.Error(-1, response.responseText);
6968
- if (onError) {
6969
- onError(error, options);
6970
- }
6971
- if (promise) {
6972
- promise.reject(error);
6973
- }
6974
- };
7531
+
7532
+ var promise = new Parse.Promise();
7533
+
7534
+ var query = new Parse.Query(this.objectClass);
7535
+ // We can override the batch size from the options.
7536
+ // This is undocumented, but useful for testing.
7537
+ query._limit = options.batchSize || 100;
7538
+ query._where = _.clone(this._where);
7539
+ query._include = _.clone(this._include);
7540
+
7541
+ query.ascending('objectId');
7542
+
7543
+ var finished = false;
7544
+ return Parse.Promise._continueWhile(function() {
7545
+ return !finished;
7546
+
7547
+ }, function() {
7548
+ return query.find().then(function(results) {
7549
+ var callbacksDone = Parse.Promise.as();
7550
+ Parse._.each(results, function(result) {
7551
+ callbacksDone = callbacksDone.then(function() {
7552
+ return callback(result);
7553
+ });
7554
+ });
7555
+
7556
+ return callbacksDone.then(function() {
7557
+ if (results.length >= query._limit) {
7558
+ query.greaterThan("objectId", results[results.length - 1].id);
7559
+ } else {
7560
+ finished = true;
7561
+ }
7562
+ });
7563
+ });
7564
+ })._thenRunCallbacks(options);
7565
+ }
6975
7566
  };
7567
+
6976
7568
  }(this));
6977
7569
 
6978
- /*global FB: false */
7570
+ /*global FB: false , console: false*/
6979
7571
  (function(root) {
6980
7572
  root.Parse = root.Parse || {};
6981
7573
  var Parse = root.Parse;
6982
7574
  var _ = Parse._;
6983
7575
 
6984
7576
  var PUBLIC_KEY = "*";
6985
-
7577
+
6986
7578
  var initialized = false;
6987
7579
  var requestedPermissions;
6988
7580
  var initOptions;
@@ -7018,6 +7610,9 @@
7018
7610
  };
7019
7611
  var newOptions = _.clone(initOptions);
7020
7612
  newOptions.authResponse = authResponse;
7613
+
7614
+ // Suppress checks for login status from the browser.
7615
+ newOptions.status = false;
7021
7616
  FB.init(newOptions);
7022
7617
  }
7023
7618
  return true;
@@ -7049,18 +7644,28 @@
7049
7644
  * @param {Object} options Facebook options argument as described here:
7050
7645
  * <a href=
7051
7646
  * "https://developers.facebook.com/docs/reference/javascript/FB.init/">
7052
- * FB.init()</a>
7647
+ * FB.init()</a>. The status flag will be coerced to 'false' because it
7648
+ * interferes with Parse Facebook integration. Call FB.getLoginStatus()
7649
+ * explicitly if this behavior is required by your application.
7053
7650
  */
7054
7651
  init: function(options) {
7055
7652
  if (typeof(FB) === 'undefined') {
7056
- throw "The Javascript Facebook SDK must be loaded before calling init.";
7653
+ throw "The Facebook JavaScript SDK must be loaded before calling init.";
7057
7654
  }
7058
- initOptions = _.clone(options);
7655
+ initOptions = _.clone(options) || {};
7656
+ if (initOptions.status && typeof(console) !== "undefined") {
7657
+ var warn = console.warn || console.log || function() {};
7658
+ warn.call(console, "The 'status' flag passed into" +
7659
+ " FB.init, when set to true, can interfere with Parse Facebook" +
7660
+ " integration, so it has been suppressed. Please call" +
7661
+ " FB.getLoginStatus() explicitly if you require this behavior.");
7662
+ }
7663
+ initOptions.status = false;
7059
7664
  FB.init(initOptions);
7060
7665
  Parse.User._registerAuthenticationProvider(provider);
7061
7666
  initialized = true;
7062
7667
  },
7063
-
7668
+
7064
7669
  /**
7065
7670
  * Gets whether the user has their account linked to Facebook.
7066
7671
  *
@@ -7072,7 +7677,7 @@
7072
7677
  isLinked: function(user) {
7073
7678
  return user._isLinked("facebook");
7074
7679
  },
7075
-
7680
+
7076
7681
  /**
7077
7682
  * Logs in a user using Facebook. This method delegates to the Facebook
7078
7683
  * SDK to authenticate the user, and then automatically logs in (or
@@ -7094,12 +7699,12 @@
7094
7699
  requestedPermissions = permissions;
7095
7700
  return Parse.User._logInWith("facebook", options);
7096
7701
  } else {
7097
- var newOptions = _.clone(options);
7702
+ var newOptions = _.clone(options) || {};
7098
7703
  newOptions.authData = permissions;
7099
7704
  return Parse.User._logInWith("facebook", newOptions);
7100
7705
  }
7101
7706
  },
7102
-
7707
+
7103
7708
  /**
7104
7709
  * Links Facebook to an existing PFUser. This method delegates to the
7105
7710
  * Facebook SDK to authenticate the user, and then automatically links
@@ -7123,12 +7728,12 @@
7123
7728
  requestedPermissions = permissions;
7124
7729
  return user._linkWith("facebook", options);
7125
7730
  } else {
7126
- var newOptions = _.clone(options);
7731
+ var newOptions = _.clone(options) || {};
7127
7732
  newOptions.authData = permissions;
7128
7733
  return user._linkWith("facebook", newOptions);
7129
7734
  }
7130
7735
  },
7131
-
7736
+
7132
7737
  /**
7133
7738
  * Unlinks the Parse.User from a Facebook account.
7134
7739
  *
@@ -7323,7 +7928,7 @@
7323
7928
  this.navigate(current);
7324
7929
  }
7325
7930
  if (!this.loadUrl()) {
7326
- this.loadUrl(this.getHash());
7931
+ this.loadUrl(this.getHash());
7327
7932
  }
7328
7933
  },
7329
7934
 
@@ -7537,15 +8142,15 @@
7537
8142
  var _ = Parse._;
7538
8143
 
7539
8144
  /**
7540
- *
7541
- *
7542
8145
  * @namespace Contains functions for calling and declaring
7543
8146
  * <a href="/docs/cloud_code_guide#functions">cloud functions</a>.
7544
8147
  * <p><strong><em>
7545
8148
  * Some functions are only available from Cloud Code.
7546
8149
  * </em></strong></p>
7547
8150
  */
7548
- Parse.Cloud = {
8151
+ Parse.Cloud = Parse.Cloud || {};
8152
+
8153
+ _.extend(Parse.Cloud, /** @lends Parse.Cloud */ {
7549
8154
  /**
7550
8155
  * Makes a call to a cloud function.
7551
8156
  * @param {String} name The function name.
@@ -7555,41 +8160,18 @@
7555
8160
  * call to a cloud function. options.error should be a function that
7556
8161
  * handles an error running the cloud function. Both functions are
7557
8162
  * optional. Both functions take a single argument.
8163
+ * @return {Parse.Promise} A promise that will be resolved with the result
8164
+ * of the function.
7558
8165
  */
7559
8166
  run: function(name, data, options) {
7560
- var oldOptions = options;
7561
- var newOptions = _.clone(options);
7562
- newOptions.success = function(resp) {
7563
- var results = Parse._decode(null, resp);
7564
- if (oldOptions.success) {
7565
- oldOptions.success(results.result);
7566
- }
7567
- };
8167
+ var request = Parse._request("functions", name, null, 'POST',
8168
+ Parse._encode(data, null, true));
7568
8169
 
7569
- newOptions.error = Parse.Cloud._wrapError(oldOptions.error, options);
7570
- Parse._request("functions",
7571
- name,
7572
- null,
7573
- 'POST',
7574
- Parse._encode(data, null, true),
7575
- newOptions);
7576
- },
7577
-
7578
- _wrapError: function(onError, options) {
7579
- return function(response) {
7580
- if (onError) {
7581
- var error = new Parse.Error(-1, response.responseText);
7582
- if (response.responseText) {
7583
- var errorJSON = JSON.parse(response.responseText);
7584
- if (errorJSON) {
7585
- error = new Parse.Error(errorJSON.code, errorJSON.error);
7586
- }
7587
- }
7588
- onError(error, options);
7589
- }
7590
- };
8170
+ return request.then(function(resp) {
8171
+ return Parse._decode(null, resp).result;
8172
+ })._thenRunCallbacks(options);
7591
8173
  }
7592
- };
8174
+ });
7593
8175
  }(this));
7594
8176
 
7595
8177
  (function(root) {
@@ -7640,11 +8222,8 @@
7640
8222
  if (data.expiration_time && data.expiration_time_interval) {
7641
8223
  throw "Both expiration_time and expiration_time_interval can't be set";
7642
8224
  }
7643
- var ajaxOptions = {
7644
- error: options.error,
7645
- success: options.success
7646
- };
7647
- ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions);
7648
- Parse._request('push', null, null, 'POST', data, ajaxOptions);
8225
+
8226
+ var request = Parse._request('push', null, null, 'POST', data);
8227
+ return request._thenRunCallbacks(options);
7649
8228
  };
7650
8229
  }(this));