From 898b22bc40bf168354ed2e54c492de3d51d13832 Mon Sep 17 00:00:00 2001
From: caromu
Date: Wed, 11 Mar 2015 11:55:39 -0700
Subject: [PATCH] Add parse.com library to our bower components
---
Web/bower.json | 3 +-
Web/public/vendor/parse/.bower.json | 27 +
Web/public/vendor/parse/README.md | 6 +
Web/public/vendor/parse/bower.json | 16 +
Web/public/vendor/parse/parse.js | 9368 ++++++++++++++++++++++++++
Web/public/vendor/parse/parse.min.js | 3 +
6 files changed, 9422 insertions(+), 1 deletion(-)
create mode 100644 Web/public/vendor/parse/.bower.json
create mode 100644 Web/public/vendor/parse/README.md
create mode 100644 Web/public/vendor/parse/bower.json
create mode 100644 Web/public/vendor/parse/parse.js
create mode 100644 Web/public/vendor/parse/parse.min.js
diff --git a/Web/bower.json b/Web/bower.json
index f77207a..3b8222b 100644
--- a/Web/bower.json
+++ b/Web/bower.json
@@ -17,6 +17,7 @@
"requirejs": "~2.1.16",
"bootstrap": "~3.3.2",
"knockout": "~3.3.0",
- "jquery": "~2.1.3"
+ "jquery": "~2.1.3",
+ "parse": "~1.3.5"
}
}
diff --git a/Web/public/vendor/parse/.bower.json b/Web/public/vendor/parse/.bower.json
new file mode 100644
index 0000000..36196ba
--- /dev/null
+++ b/Web/public/vendor/parse/.bower.json
@@ -0,0 +1,27 @@
+{
+ "name": "parse",
+ "version": "1.3.5",
+ "main": [
+ "parse.min.js"
+ ],
+ "keywords": [
+ "parse",
+ "parse.com",
+ "api",
+ "js"
+ ],
+ "ignore": [
+ "download_latest.sh"
+ ],
+ "homepage": "https://github.com/rabidaudio/bower-parse",
+ "_release": "1.3.5",
+ "_resolution": {
+ "type": "version",
+ "tag": "v1.3.5",
+ "commit": "063db61d6d4294294b04b2bf178a310326c19d58"
+ },
+ "_source": "git://github.com/rabidaudio/bower-parse.git",
+ "_target": "~1.3.5",
+ "_originalSource": "parse",
+ "_direct": true
+}
\ No newline at end of file
diff --git a/Web/public/vendor/parse/README.md b/Web/public/vendor/parse/README.md
new file mode 100644
index 0000000..6e013a5
--- /dev/null
+++ b/Web/public/vendor/parse/README.md
@@ -0,0 +1,6 @@
+bower-parse
+===========
+
+[Bower](http://bower.io) package for [Parse](http://parse.com)
+
+ bower install parse
diff --git a/Web/public/vendor/parse/bower.json b/Web/public/vendor/parse/bower.json
new file mode 100644
index 0000000..7a60f34
--- /dev/null
+++ b/Web/public/vendor/parse/bower.json
@@ -0,0 +1,16 @@
+{
+ "name": "parse",
+ "version": "1.3.5",
+ "main": [
+ "parse.min.js"
+ ],
+ "keywords": [
+ "parse",
+ "parse.com",
+ "api",
+ "js"
+ ],
+ "ignore": [
+ "download_latest.sh"
+ ]
+}
diff --git a/Web/public/vendor/parse/parse.js b/Web/public/vendor/parse/parse.js
new file mode 100644
index 0000000..4899ffd
--- /dev/null
+++ b/Web/public/vendor/parse/parse.js
@@ -0,0 +1,9368 @@
+/*!
+ * Parse JavaScript SDK
+ * Version: 1.3.5
+ * Built: Thu Feb 19 2015 14:12:57
+ * http://parse.com
+ *
+ * Copyright 2015 Parse, Inc.
+ * The Parse JavaScript SDK is freely distributable under the MIT license.
+ *
+ * Includes: Underscore.js
+ * Copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+ * Released under the MIT license.
+ */
+(function(root) {
+ root.Parse = root.Parse || {};
+ root.Parse.VERSION = "js1.3.5";
+}(this));
+// Underscore.js 1.4.4
+// http://underscorejs.org
+// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+ // Baseline setup
+ // --------------
+
+ // Establish the root object, `window` in the browser, or `global` on the server.
+ var root = this;
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Establish the object that gets returned to break out of a loop iteration.
+ var breaker = {};
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ concat = ArrayProto.concat,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var
+ nativeForEach = ArrayProto.forEach,
+ nativeMap = ArrayProto.map,
+ nativeReduce = ArrayProto.reduce,
+ nativeReduceRight = ArrayProto.reduceRight,
+ nativeFilter = ArrayProto.filter,
+ nativeEvery = ArrayProto.every,
+ nativeSome = ArrayProto.some,
+ nativeIndexOf = ArrayProto.indexOf,
+ nativeLastIndexOf = ArrayProto.lastIndexOf,
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeBind = FuncProto.bind;
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this._wrapped = obj;
+ };
+
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, add `_` as a global object via a string identifier,
+ // for Closure Compiler "advanced" mode.
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ exports = module.exports = _;
+ }
+ exports._ = _;
+ } else {
+ root._ = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.4.4';
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles objects with the built-in `forEach`, arrays, and raw objects.
+ // Delegates to **ECMAScript 5**'s native `forEach` if available.
+ var each = _.each = _.forEach = function(obj, iterator, context) {
+ if (obj == null) return;
+ if (nativeForEach && obj.forEach === nativeForEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, l = obj.length; i < l; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var key in obj) {
+ if (_.has(obj, key)) {
+ if (iterator.call(context, obj[key], key, obj) === breaker) return;
+ }
+ }
+ }
+ };
+
+ // Return the results of applying the iterator to each element.
+ // Delegates to **ECMAScript 5**'s native `map` if available.
+ _.map = _.collect = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+ each(obj, function(value, index, list) {
+ results[results.length] = iterator.call(context, value, index, list);
+ });
+ return results;
+ };
+
+ var reduceError = 'Reduce of empty array with no initial value';
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+ _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
+ if (obj == null) obj = [];
+ if (nativeReduce && obj.reduce === nativeReduce) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+ }
+ each(obj, function(value, index, list) {
+ if (!initial) {
+ memo = value;
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, value, index, list);
+ }
+ });
+ if (!initial) throw new TypeError(reduceError);
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as `foldr`.
+ // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+ _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
+ if (obj == null) obj = [];
+ if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ }
+ var length = obj.length;
+ if (length !== +length) {
+ var keys = _.keys(obj);
+ length = keys.length;
+ }
+ each(obj, function(value, index, list) {
+ index = keys ? keys[--length] : --length;
+ if (!initial) {
+ memo = obj[index];
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, obj[index], index, list);
+ }
+ });
+ if (!initial) throw new TypeError(reduceError);
+ return memo;
+ };
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, iterator, context) {
+ var result;
+ any(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ return true;
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test.
+ // Delegates to **ECMAScript 5**'s native `filter` if available.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+ each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ return _.filter(obj, function(value, index, list) {
+ return !iterator.call(context, value, index, list);
+ }, context);
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Delegates to **ECMAScript 5**'s native `every` if available.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
+ var result = true;
+ if (obj == null) return result;
+ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+ each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Delegates to **ECMAScript 5**'s native `some` if available.
+ // Aliased as `any`.
+ var any = _.some = _.any = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
+ var result = false;
+ if (obj == null) return result;
+ if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+ each(obj, function(value, index, list) {
+ if (result || (result = iterator.call(context, value, index, list))) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if the array or object contains a given value (using `===`).
+ // Aliased as `include`.
+ _.contains = _.include = function(obj, target) {
+ if (obj == null) return false;
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+ return any(obj, function(value) {
+ return value === target;
+ });
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = slice.call(arguments, 2);
+ var isFunc = _.isFunction(method);
+ return _.map(obj, function(value) {
+ return (isFunc ? method : value[method]).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Convenience version of a common use case of `filter`: selecting only objects
+ // containing specific `key:value` pairs.
+ _.where = function(obj, attrs, first) {
+ if (_.isEmpty(attrs)) return first ? null : [];
+ return _[first ? 'find' : 'filter'](obj, function(value) {
+ for (var key in attrs) {
+ if (attrs[key] !== value[key]) return false;
+ }
+ return true;
+ });
+ };
+
+ // Convenience version of a common use case of `find`: getting the first object
+ // containing specific `key:value` pairs.
+ _.findWhere = function(obj, attrs) {
+ return _.where(obj, attrs, true);
+ };
+
+ // Return the maximum element or (element-based computation).
+ // Can't optimize arrays of integers longer than 65,535 elements.
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.max.apply(Math, obj);
+ }
+ if (!iterator && _.isEmpty(obj)) return -Infinity;
+ var result = {computed : -Infinity, value: -Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.min.apply(Math, obj);
+ }
+ if (!iterator && _.isEmpty(obj)) return Infinity;
+ var result = {computed : Infinity, value: Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Shuffle an array.
+ _.shuffle = function(obj) {
+ var rand;
+ var index = 0;
+ var shuffled = [];
+ each(obj, function(value) {
+ rand = _.random(index++);
+ shuffled[index - 1] = shuffled[rand];
+ shuffled[rand] = value;
+ });
+ return shuffled;
+ };
+
+ // An internal function to generate lookup iterators.
+ var lookupIterator = function(value) {
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+ };
+
+ // Sort the object's values by a criterion produced by an iterator.
+ _.sortBy = function(obj, value, context) {
+ var iterator = lookupIterator(value);
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ index : index,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0) return 1;
+ if (a < b || b === void 0) return -1;
+ }
+ return left.index < right.index ? -1 : 1;
+ }), 'value');
+ };
+
+ // An internal function used for aggregate "group by" operations.
+ var group = function(obj, value, context, behavior) {
+ var result = {};
+ var iterator = lookupIterator(value || _.identity);
+ each(obj, function(value, index) {
+ var key = iterator.call(context, value, index, obj);
+ behavior(result, key, value);
+ });
+ return result;
+ };
+
+ // Groups the object's values by a criterion. Pass either a string attribute
+ // to group by, or a function that returns the criterion.
+ _.groupBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key, value) {
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+ });
+ };
+
+ // Counts instances of an object that group by a certain criterion. Pass
+ // either a string attribute to count by, or a function that returns the
+ // criterion.
+ _.countBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key) {
+ if (!_.has(result, key)) result[key] = 0;
+ result[key]++;
+ });
+ };
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator, context) {
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
+ var value = iterator.call(context, obj);
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Safely convert anything iterable into a real, live array.
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (obj.length === +obj.length) return _.map(obj, _.identity);
+ return _.values(obj);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ if (obj == null) return 0;
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+ };
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
+ if (array == null) return void 0;
+ return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the last entry of the array. Especially useful on
+ // the arguments object. Passing **n** will return all the values in
+ // the array, excluding the last N. The **guard** check allows it to work with
+ // `_.map`.
+ _.initial = function(array, n, guard) {
+ return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+ };
+
+ // Get the last element of an array. Passing **n** will return the last N
+ // values in the array. The **guard** check allows it to work with `_.map`.
+ _.last = function(array, n, guard) {
+ if (array == null) return void 0;
+ if ((n != null) && !guard) {
+ return slice.call(array, Math.max(array.length - n, 0));
+ } else {
+ return array[array.length - 1];
+ }
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+ // Especially useful on the arguments object. Passing an **n** will return
+ // the rest N values in the array. The **guard**
+ // check allows it to work with `_.map`.
+ _.rest = _.tail = _.drop = function(array, n, guard) {
+ return slice.call(array, (n == null) || guard ? 1 : n);
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, _.identity);
+ };
+
+ // Internal implementation of a recursive `flatten` function.
+ var flatten = function(input, shallow, output) {
+ each(input, function(value) {
+ if (_.isArray(value)) {
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
+ } else {
+ output.push(value);
+ }
+ });
+ return output;
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array, shallow) {
+ return flatten(array, shallow, []);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ return _.difference(array, slice.call(arguments, 1));
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
+ if (_.isFunction(isSorted)) {
+ context = iterator;
+ iterator = isSorted;
+ isSorted = false;
+ }
+ var initial = iterator ? _.map(array, iterator, context) : array;
+ var results = [];
+ var seen = [];
+ each(initial, function(value, index) {
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+ seen.push(value);
+ results.push(array[index]);
+ }
+ });
+ return results;
+ };
+
+ // Produce an array that contains the union: each distinct element from all of
+ // the passed-in arrays.
+ _.union = function() {
+ return _.uniq(concat.apply(ArrayProto, arguments));
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersection = function(array) {
+ var rest = slice.call(arguments, 1);
+ return _.filter(_.uniq(array), function(item) {
+ return _.every(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Take the difference between one array and a number of other arrays.
+ // Only the elements present in just the first array will remain.
+ _.difference = function(array) {
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = slice.call(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i = 0; i < length; i++) {
+ results[i] = _.pluck(args, "" + i);
+ }
+ return results;
+ };
+
+ // Converts lists into objects. Pass either a single array of `[key, value]`
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
+ // the corresponding values.
+ _.object = function(list, values) {
+ if (list == null) return {};
+ var result = {};
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0]] = list[i][1];
+ }
+ }
+ return result;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+ // we need this function. Return the position of the first occurrence of an
+ // item in an array, or -1 if the item is not included in the array.
+ // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = function(array, item, isSorted) {
+ if (array == null) return -1;
+ var i = 0, l = array.length;
+ if (isSorted) {
+ if (typeof isSorted == 'number') {
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
+ } else {
+ i = _.sortedIndex(array, item);
+ return array[i] === item ? i : -1;
+ }
+ }
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+ for (; i < l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+ _.lastIndexOf = function(array, item, from) {
+ if (array == null) return -1;
+ var hasIndex = from != null;
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+ }
+ var i = (hasIndex ? from : array.length);
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (arguments.length <= 1) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = arguments[2] || 1;
+
+ var len = Math.max(Math.ceil((stop - start) / step), 0);
+ var idx = 0;
+ var range = new Array(len);
+
+ while(idx < len) {
+ range[idx++] = start;
+ start += step;
+ }
+
+ return range;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+ // available.
+ _.bind = function(func, context) {
+ if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+ var args = slice.call(arguments, 2);
+ return function() {
+ return func.apply(context, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Partially apply a function by creating a version that has had some of its
+ // arguments pre-filled, without changing its dynamic `this` context.
+ _.partial = function(func) {
+ var args = slice.call(arguments, 1);
+ return function() {
+ return func.apply(this, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = slice.call(arguments, 1);
+ if (funcs.length === 0) funcs = _.functions(obj);
+ each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memo = {};
+ hasher || (hasher = _.identity);
+ return function() {
+ var key = hasher.apply(this, arguments);
+ return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ };
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+ };
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time.
+ _.throttle = function(func, wait) {
+ var context, args, timeout, result;
+ var previous = 0;
+ var later = function() {
+ previous = new Date;
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date;
+ var remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+ if (!immediate) result = func.apply(context, args);
+ };
+ var callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) result = func.apply(context, args);
+ return result;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = function(func) {
+ var ran = false, memo;
+ return function() {
+ if (ran) return memo;
+ ran = true;
+ memo = func.apply(this, arguments);
+ func = null;
+ return memo;
+ };
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func];
+ push.apply(args, arguments);
+ return wrapper.apply(this, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = arguments;
+ return function() {
+ var args = arguments;
+ for (var i = funcs.length - 1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // Returns a function that will only be executed after being called N times.
+ _.after = function(times, func) {
+ if (times <= 0) return func();
+ return function() {
+ if (--times < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ };
+
+ // Object Functions
+ // ----------------
+
+ // Retrieve the names of an object's properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`
+ _.keys = nativeKeys || function(obj) {
+ if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ var keys = [];
+ for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ var values = [];
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
+ return values;
+ };
+
+ // Convert an object into a list of `[key, value]` pairs.
+ _.pairs = function(obj) {
+ var pairs = [];
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
+ return pairs;
+ };
+
+ // Invert the keys and values of an object. The values must be serializable.
+ _.invert = function(obj) {
+ var result = {};
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
+ return result;
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`
+ _.functions = _.methods = function(obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ obj[prop] = source[prop];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ each(keys, function(key) {
+ if (key in obj) copy[key] = obj[key];
+ });
+ return copy;
+ };
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ for (var key in obj) {
+ if (!_.contains(keys, key)) copy[key] = obj[key];
+ }
+ return copy;
+ };
+
+ // Fill in a given object with default properties.
+ _.defaults = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ if (obj[prop] == null) obj[prop] = source[prop];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (!_.isObject(obj)) return obj;
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Internal recursive comparison function for `isEqual`.
+ var eq = function(a, b, aStack, bStack) {
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
+ if (a === b) return a !== 0 || 1 / a == 1 / b;
+ // A strict comparison is necessary because `null == undefined`.
+ if (a == null || b == null) return a === b;
+ // Unwrap any wrapped objects.
+ if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
+ // Compare `[[Class]]` names.
+ var className = toString.call(a);
+ if (className != toString.call(b)) return false;
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') return false;
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] == a) return bStack[length] == b;
+ }
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+ var size = 0, result = true;
+ // Recursively compare objects and arrays.
+ if (className == '[object Array]') {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ size = a.length;
+ result = size == b.length;
+ if (result) {
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (size--) {
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+ }
+ }
+ } else {
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+ return false;
+ }
+ // Deep compare objects.
+ for (var key in a) {
+ if (_.has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (_.has(b, key) && !(size--)) break;
+ }
+ result = !size;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+ return result;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ return eq(a, b, [], []);
+ };
+
+ // Is a given array, string, or object empty?
+ // An "empty" object has no enumerable own-properties.
+ _.isEmpty = function(obj) {
+ if (obj == null) return true;
+ if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ for (var key in obj) if (_.has(obj, key)) return false;
+ return true;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) == '[object Array]';
+ };
+
+ // Is a given variable an object?
+ _.isObject = function(obj) {
+ return obj === Object(obj);
+ };
+
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+ _['is' + name] = function(obj) {
+ return toString.call(obj) == '[object ' + name + ']';
+ };
+ });
+
+ // Define a fallback version of the method in browsers (ahem, IE), where
+ // there isn't any inspectable "Arguments" type.
+ if (!_.isArguments(arguments)) {
+ _.isArguments = function(obj) {
+ return !!(obj && _.has(obj, 'callee'));
+ };
+ }
+
+ // Optimize `isFunction` if appropriate.
+ if (typeof (/./) !== 'function') {
+ _.isFunction = function(obj) {
+ return typeof obj === 'function';
+ };
+ }
+
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return isFinite(obj) && !isNaN(parseFloat(obj));
+ };
+
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && obj != +obj;
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Shortcut function for checking if an object has a given property directly
+ // on itself (in other words, not on a prototype).
+ _.has = function(obj, key) {
+ return hasOwnProperty.call(obj, key);
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Run a function **n** times.
+ _.times = function(n, iterator, context) {
+ var accum = Array(n);
+ for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+ return accum;
+ };
+
+ // Return a random integer between min and max (inclusive).
+ _.random = function(min, max) {
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + Math.floor(Math.random() * (max - min + 1));
+ };
+
+ // List of HTML entities for escaping.
+ var entityMap = {
+ escape: {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '/': '/'
+ }
+ };
+ entityMap.unescape = _.invert(entityMap.escape);
+
+ // Regexes containing the keys and values listed immediately above.
+ var entityRegexes = {
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+ };
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ _.each(['escape', 'unescape'], function(method) {
+ _[method] = function(string) {
+ if (string == null) return '';
+ return ('' + string).replace(entityRegexes[method], function(match) {
+ return entityMap[method][match];
+ });
+ };
+ });
+
+ // If the value of the named property is a function then invoke it;
+ // otherwise, return it.
+ _.result = function(object, property) {
+ if (object == null) return null;
+ var value = object[property];
+ return _.isFunction(value) ? value.call(object) : value;
+ };
+
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ each(_.functions(obj), function(name){
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return result.call(this, func.apply(_, args));
+ };
+ });
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = ++idCounter + '';
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g,
+ escape : /<%-([\s\S]+?)%>/g
+ };
+
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /(.)^/;
+
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ _.template = function(text, data, settings) {
+ var render;
+ settings = _.defaults({}, settings, _.templateSettings);
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = new RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset)
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ }
+ if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ }
+ if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+ index = offset + match.length;
+ return match;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + "return __p;\n";
+
+ try {
+ render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ if (data) return render(data, _);
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled function source as a convenience for precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+ return template;
+ };
+
+ // Add a "chain" function, which will delegate to the wrapper.
+ _.chain = function(obj) {
+ return _(obj).chain();
+ };
+
+ // OOP
+ // ---------------
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj) {
+ return this._chain ? _(obj).chain() : obj;
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ var obj = this._wrapped;
+ method.apply(obj, arguments);
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+ return result.call(this, obj);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ return result.call(this, method.apply(this._wrapped, arguments));
+ };
+ });
+
+ _.extend(_.prototype, {
+
+ // Start chaining a wrapped Underscore object.
+ chain: function() {
+ this._chain = true;
+ return this;
+ },
+
+ // Extracts the result from a wrapped and chained object.
+ value: function() {
+ return this._wrapped;
+ }
+
+ });
+
+}).call(this);
+
+/*global _: false, $: false, localStorage: false, process: true,
+ XMLHttpRequest: false, XDomainRequest: false, exports: false,
+ require: false, setTimeout: true */
+(function(root) {
+ root.Parse = root.Parse || {};
+ /**
+ * Contains all Parse API classes and functions.
+ * @name Parse
+ * @namespace
+ *
+ * Contains all Parse API classes and functions.
+ */
+ var Parse = root.Parse;
+
+ // Load references to other dependencies
+ if (typeof(localStorage) !== 'undefined') {
+ Parse.localStorage = localStorage;
+ } else if (typeof(require) !== 'undefined') {
+ Parse.localStorage = require('localStorage');
+ }
+ if (typeof(XMLHttpRequest) !== 'undefined') {
+ Parse.XMLHttpRequest = XMLHttpRequest;
+ } else if (typeof(require) !== 'undefined') {
+ Parse.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
+ }
+ // Import Parse's local copy of underscore.
+ if (typeof(exports) !== 'undefined' && exports._) {
+ // We're running in a CommonJS environment
+ Parse._ = exports._.noConflict();
+ exports.Parse = Parse;
+ } else {
+ Parse._ = _.noConflict();
+ }
+
+ // If jQuery or Zepto has been included, grab a reference to it.
+ if (typeof($) !== "undefined") {
+ Parse.$ = $;
+ }
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var EmptyConstructor = function() {};
+
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ /** @ignore */
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ Parse._.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ EmptyConstructor.prototype = parent.prototype;
+ child.prototype = new EmptyConstructor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) {
+ Parse._.extend(child.prototype, protoProps);
+ }
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) {
+ Parse._.extend(child, staticProps);
+ }
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is
+ // needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Set the server for Parse to talk to.
+ Parse.serverURL = "https://api.parse.com";
+
+ // Check whether we are running in Node.js.
+ if (typeof(process) !== "undefined" &&
+ process.versions &&
+ process.versions.node) {
+ Parse._isNode = true;
+ }
+
+ /**
+ * Call this method first to set up your authentication tokens for Parse.
+ * You can get your keys from the Data Browser on parse.com.
+ * @param {String} applicationId Your Parse Application ID.
+ * @param {String} javaScriptKey Your Parse JavaScript Key.
+ * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
+ */
+ Parse.initialize = function(applicationId, javaScriptKey, masterKey) {
+ if (masterKey) {
+ throw "Parse.initialize() was passed a Master Key, which is only " +
+ "allowed from within Node.js.";
+ }
+ Parse._initialize(applicationId, javaScriptKey);
+ };
+
+ /**
+ * Call this method first to set up master authentication tokens for Parse.
+ * This method is for Parse's own private use.
+ * @param {String} applicationId Your Parse Application ID.
+ * @param {String} javaScriptKey Your Parse JavaScript Key.
+ * @param {String} masterKey Your Parse Master Key.
+ */
+ Parse._initialize = function(applicationId, javaScriptKey, masterKey) {
+ Parse.applicationId = applicationId;
+ Parse.javaScriptKey = javaScriptKey;
+ Parse.masterKey = masterKey;
+ Parse._useMasterKey = false;
+ };
+
+ // If we're running in node.js, allow using the master key.
+ if (Parse._isNode) {
+ Parse.initialize = Parse._initialize;
+
+ Parse.Cloud = Parse.Cloud || {};
+ /**
+ * Switches the Parse SDK to using the Master key. The Master key grants
+ * priveleged access to the data in Parse and can be used to bypass ACLs and
+ * other restrictions that are applied to the client SDKs.
+ * Available in Cloud Code and Node.js only.
+ *
+ */
+ Parse.Cloud.useMasterKey = function() {
+ Parse._useMasterKey = true;
+ };
+ }
+
+ /**
+ * Returns prefix for localStorage keys used by this instance of Parse.
+ * @param {String} path The relative suffix to append to it.
+ * null or undefined is treated as the empty string.
+ * @return {String} The full key name.
+ */
+ Parse._getParsePath = function(path) {
+ if (!Parse.applicationId) {
+ throw "You need to call Parse.initialize before using Parse.";
+ }
+ if (!path) {
+ path = "";
+ }
+ if (!Parse._.isString(path)) {
+ throw "Tried to get a localStorage path that wasn't a String.";
+ }
+ if (path[0] === "/") {
+ path = path.substring(1);
+ }
+ return "Parse/" + Parse.applicationId + "/" + path;
+ };
+
+ /**
+ * Returns the unique string for this app on this machine.
+ * Gets reset when localStorage is cleared.
+ */
+ Parse._installationId = null;
+ Parse._getInstallationId = function() {
+ // See if it's cached in RAM.
+ if (Parse._installationId) {
+ return Parse._installationId;
+ }
+
+ // Try to get it from localStorage.
+ var path = Parse._getParsePath("installationId");
+ Parse._installationId = Parse.localStorage.getItem(path);
+
+ if (!Parse._installationId || Parse._installationId === "") {
+ // It wasn't in localStorage, so create a new one.
+ var hexOctet = function() {
+ return Math.floor((1+Math.random())*0x10000).toString(16).substring(1);
+ };
+ Parse._installationId = (
+ hexOctet() + hexOctet() + "-" +
+ hexOctet() + "-" +
+ hexOctet() + "-" +
+ hexOctet() + "-" +
+ hexOctet() + hexOctet() + hexOctet());
+ Parse.localStorage.setItem(path, Parse._installationId);
+ }
+
+ return Parse._installationId;
+ };
+
+ Parse._parseDate = function(iso8601) {
+ var regexp = new RegExp(
+ "^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})" + "T" +
+ "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})" +
+ "(.([0-9]+))?" + "Z$");
+ var match = regexp.exec(iso8601);
+ if (!match) {
+ return null;
+ }
+
+ var year = match[1] || 0;
+ var month = (match[2] || 1) - 1;
+ var day = match[3] || 0;
+ var hour = match[4] || 0;
+ var minute = match[5] || 0;
+ var second = match[6] || 0;
+ var milli = match[8] || 0;
+
+ return new Date(Date.UTC(year, month, day, hour, minute, second, milli));
+ };
+
+ Parse._ajaxIE8 = function(method, url, data) {
+ var promise = new Parse.Promise();
+ var xdr = new XDomainRequest();
+ xdr.onload = function() {
+ var response;
+ try {
+ response = JSON.parse(xdr.responseText);
+ } catch (e) {
+ promise.reject(e);
+ }
+ if (response) {
+ promise.resolve(response);
+ }
+ };
+ xdr.onerror = xdr.ontimeout = function() {
+ // Let's fake a real error message.
+ var fakeResponse = {
+ responseText: JSON.stringify({
+ code: Parse.Error.X_DOMAIN_REQUEST,
+ error: "IE's XDomainRequest does not supply error info."
+ })
+ };
+ promise.reject(fakeResponse);
+ };
+ xdr.onprogress = function() {};
+ xdr.open(method, url);
+ xdr.send(data);
+ return promise;
+ };
+
+ Parse._useXDomainRequest = function() {
+ if (typeof(XDomainRequest) !== "undefined") {
+ // We're in IE 8+.
+ if ('withCredentials' in new XMLHttpRequest()) {
+ // We're in IE 10+.
+ return false;
+ }
+ return true;
+ }
+ return false;
+ };
+
+
+ Parse._ajax = function(method, url, data, success, error) {
+ var options = {
+ success: success,
+ error: error
+ };
+
+ if (Parse._useXDomainRequest()) {
+ return Parse._ajaxIE8(method, url, data)._thenRunCallbacks(options);
+ }
+
+ var promise = new Parse.Promise();
+ var attempts = 0;
+
+ var dispatch = function() {
+ var handled = false;
+ var xhr = new Parse.XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ if (handled) {
+ return;
+ }
+ handled = true;
+
+ if (xhr.status >= 200 && xhr.status < 300) {
+ var response;
+ try {
+ response = JSON.parse(xhr.responseText);
+ } catch (e) {
+ promise.reject(e);
+ }
+ if (response) {
+ promise.resolve(response, xhr.status, xhr);
+ }
+ } else if (xhr.status >= 500) { // Retry on 5XX
+ if (++attempts < 5) {
+ // Exponentially-growing delay
+ var delay = Math.round(
+ Math.random() * 125 * Math.pow(2, attempts)
+ );
+ setTimeout(dispatch, delay);
+ } else {
+ // After 5 retries, fail
+ promise.reject(xhr);
+ }
+ } else {
+ promise.reject(xhr);
+ }
+ }
+ };
+
+ xhr.open(method, url, true);
+ xhr.setRequestHeader('Content-Type', 'text/plain'); // avoid pre-flight.
+ if (Parse._isNode) {
+ // Add a special user agent just for request from node.js.
+ xhr.setRequestHeader("User-Agent",
+ "Parse/" + Parse.VERSION +
+ " (NodeJS " + process.versions.node + ")");
+ }
+ xhr.send(data);
+ };
+
+ dispatch();
+ return promise._thenRunCallbacks(options);
+ };
+
+ // A self-propagating extend function.
+ Parse._extend = function(protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ /**
+ * Options:
+ * route: is classes, users, login, etc.
+ * objectId: null if there is no associated objectId.
+ * method: the http method for the REST API.
+ * dataObject: the payload as an object, or null if there is none.
+ * useMasterKey: overrides whether to use the master key if set.
+ * @ignore
+ */
+ Parse._request = function(options) {
+ var route = options.route;
+ var className = options.className;
+ var objectId = options.objectId;
+ var method = options.method;
+ var useMasterKey = options.useMasterKey;
+ var sessionToken = options.sessionToken;
+ var dataObject = options.data;
+
+ if (!Parse.applicationId) {
+ throw "You must specify your applicationId using Parse.initialize.";
+ }
+
+ if (!Parse.javaScriptKey && !Parse.masterKey) {
+ throw "You must specify a key using Parse.initialize.";
+ }
+
+
+ if (!sessionToken) {
+ // Use the current user session token if none was provided.
+ var currentUser = Parse.User.current();
+ if (currentUser && currentUser._sessionToken) {
+ sessionToken = currentUser._sessionToken;
+ }
+ }
+
+
+ if (route !== "batch" &&
+ route !== "classes" &&
+ route !== "events" &&
+ route !== "files" &&
+ route !== "functions" &&
+ route !== "login" &&
+ route !== "push" &&
+ route !== "requestPasswordReset" &&
+ route !== "rest_verify_analytics" &&
+ route !== "users" &&
+ route !== "jobs" &&
+ route !== "config") {
+ throw "Bad route: '" + route + "'.";
+ }
+
+ var url = Parse.serverURL;
+ if (url.charAt(url.length - 1) !== "/") {
+ url += "/";
+ }
+ url += "1/" + route;
+ if (className) {
+ url += "/" + className;
+ }
+ if (objectId) {
+ url += "/" + objectId;
+ }
+
+ dataObject = Parse._.clone(dataObject || {});
+ if (method !== "POST") {
+ dataObject._method = method;
+ method = "POST";
+ }
+
+ if (Parse._.isUndefined(useMasterKey)) {
+ useMasterKey = Parse._useMasterKey;
+ }
+
+ dataObject._ApplicationId = Parse.applicationId;
+ if (!useMasterKey) {
+ dataObject._JavaScriptKey = Parse.javaScriptKey;
+ } else {
+ dataObject._MasterKey = Parse.masterKey;
+ }
+
+ dataObject._ClientVersion = Parse.VERSION;
+ dataObject._InstallationId = Parse._getInstallationId();
+ if (sessionToken) {
+ dataObject._SessionToken = sessionToken;
+ }
+ var data = JSON.stringify(dataObject);
+
+ return Parse._ajax(method, url, data).then(null, function(response) {
+ // Transform the error into an instance of Parse.Error by trying to parse
+ // the error string as JSON.
+ var error;
+ if (response && response.responseText) {
+ try {
+ var errorJSON = JSON.parse(response.responseText);
+ error = new Parse.Error(errorJSON.code, errorJSON.error);
+ } catch (e) {
+ // If we fail to parse the error text, that's okay.
+ error = new Parse.Error(
+ Parse.Error.INVALID_JSON,
+ "Received an error with invalid JSON from Parse: " +
+ response.responseText);
+ }
+ } else {
+ error = new Parse.Error(
+ Parse.Error.CONNECTION_FAILED,
+ "XMLHttpRequest failed: " + JSON.stringify(response));
+ }
+ // By explicitly returning a rejected Promise, this will work with
+ // either jQuery or Promises/A semantics.
+ return Parse.Promise.error(error);
+ });
+ };
+
+ // Helper function to get a value from a Backbone object as a property
+ // or as a function.
+ Parse._getValue = function(object, prop) {
+ if (!(object && object[prop])) {
+ return null;
+ }
+ return Parse._.isFunction(object[prop]) ? object[prop]() : object[prop];
+ };
+
+ /**
+ * Converts a value in a Parse Object into the appropriate representation.
+ * This is the JS equivalent of Java's Parse.maybeReferenceAndEncode(Object)
+ * if seenObjects is falsey. Otherwise any Parse.Objects not in
+ * seenObjects will be fully embedded rather than encoded
+ * as a pointer. This array will be used to prevent going into an infinite
+ * loop because we have circular references. If seenObjects
+ * is set, then none of the Parse Objects that are serialized can be dirty.
+ */
+ Parse._encode = function(value, seenObjects, disallowObjects) {
+ var _ = Parse._;
+ if (value instanceof Parse.Object) {
+ if (disallowObjects) {
+ throw "Parse.Objects not allowed here";
+ }
+ if (!seenObjects || _.include(seenObjects, value) || !value._hasData) {
+ return value._toPointer();
+ }
+ if (!value.dirty()) {
+ seenObjects = seenObjects.concat(value);
+ return Parse._encode(value._toFullJSON(seenObjects),
+ seenObjects,
+ disallowObjects);
+ }
+ throw "Tried to save an object with a pointer to a new, unsaved object.";
+ }
+ if (value instanceof Parse.ACL) {
+ return value.toJSON();
+ }
+ if (_.isDate(value)) {
+ return { "__type": "Date", "iso": value.toJSON() };
+ }
+ if (value instanceof Parse.GeoPoint) {
+ return value.toJSON();
+ }
+ if (_.isArray(value)) {
+ return _.map(value, function(x) {
+ return Parse._encode(x, seenObjects, disallowObjects);
+ });
+ }
+ if (_.isRegExp(value)) {
+ return value.source;
+ }
+ if (value instanceof Parse.Relation) {
+ return value.toJSON();
+ }
+ if (value instanceof Parse.Op) {
+ return value.toJSON();
+ }
+ if (value instanceof Parse.File) {
+ if (!value.url()) {
+ throw "Tried to save an object containing an unsaved file.";
+ }
+ return {
+ __type: "File",
+ name: value.name(),
+ url: value.url()
+ };
+ }
+ if (_.isObject(value)) {
+ var output = {};
+ Parse._objectEach(value, function(v, k) {
+ output[k] = Parse._encode(v, seenObjects, disallowObjects);
+ });
+ return output;
+ }
+ return value;
+ };
+
+ /**
+ * The inverse function of Parse._encode.
+ * TODO: make decode not mutate value.
+ */
+ Parse._decode = function(key, value) {
+ var _ = Parse._;
+ if (!_.isObject(value)) {
+ return value;
+ }
+ if (_.isArray(value)) {
+ Parse._arrayEach(value, function(v, k) {
+ value[k] = Parse._decode(k, v);
+ });
+ return value;
+ }
+ if (value instanceof Parse.Object) {
+ return value;
+ }
+ if (value instanceof Parse.File) {
+ return value;
+ }
+ if (value instanceof Parse.Op) {
+ return value;
+ }
+ if (value.__op) {
+ return Parse.Op._decode(value);
+ }
+ if (value.__type === "Pointer" && value.className) {
+ var pointer = Parse.Object._create(value.className);
+ pointer._finishFetch({ objectId: value.objectId }, false);
+ return pointer;
+ }
+ if (value.__type === "Object" && value.className) {
+ // It's an Object included in a query result.
+ var className = value.className;
+ delete value.__type;
+ delete value.className;
+ var object = Parse.Object._create(className);
+ object._finishFetch(value, true);
+ return object;
+ }
+ if (value.__type === "Date") {
+ return Parse._parseDate(value.iso);
+ }
+ if (value.__type === "GeoPoint") {
+ return new Parse.GeoPoint({
+ latitude: value.latitude,
+ longitude: value.longitude
+ });
+ }
+ if (key === "ACL") {
+ if (value instanceof Parse.ACL) {
+ return value;
+ }
+ return new Parse.ACL(value);
+ }
+ if (value.__type === "Relation") {
+ var relation = new Parse.Relation(null, key);
+ relation.targetClassName = value.className;
+ return relation;
+ }
+ if (value.__type === "File") {
+ var file = new Parse.File(value.name);
+ file._url = value.url;
+ return file;
+ }
+ Parse._objectEach(value, function(v, k) {
+ value[k] = Parse._decode(k, v);
+ });
+ return value;
+ };
+
+ Parse._arrayEach = Parse._.each;
+
+ /**
+ * Does a deep traversal of every item in object, calling func on every one.
+ * @param {Object} object The object or array to traverse deeply.
+ * @param {Function} func The function to call for every item. It will
+ * be passed the item as an argument. If it returns a truthy value, that
+ * value will replace the item in its parent container.
+ * @returns {} the result of calling func on the top-level object itself.
+ */
+ Parse._traverse = function(object, func, seen) {
+ if (object instanceof Parse.Object) {
+ seen = seen || [];
+ if (Parse._.indexOf(seen, object) >= 0) {
+ // We've already visited this object in this call.
+ return;
+ }
+ seen.push(object);
+ Parse._traverse(object.attributes, func, seen);
+ return func(object);
+ }
+ if (object instanceof Parse.Relation || object instanceof Parse.File) {
+ // Nothing needs to be done, but we don't want to recurse into the
+ // object's parent infinitely, so we catch this case.
+ return func(object);
+ }
+ if (Parse._.isArray(object)) {
+ Parse._.each(object, function(child, index) {
+ var newChild = Parse._traverse(child, func, seen);
+ if (newChild) {
+ object[index] = newChild;
+ }
+ });
+ return func(object);
+ }
+ if (Parse._.isObject(object)) {
+ Parse._each(object, function(child, key) {
+ var newChild = Parse._traverse(child, func, seen);
+ if (newChild) {
+ object[key] = newChild;
+ }
+ });
+ return func(object);
+ }
+ return func(object);
+ };
+
+ /**
+ * This is like _.each, except:
+ * * it doesn't work for so-called array-like objects,
+ * * it does work for dictionaries with a "length" attribute.
+ */
+ Parse._objectEach = Parse._each = function(obj, callback) {
+ var _ = Parse._;
+ if (_.isObject(obj)) {
+ _.each(_.keys(obj), function(key) {
+ callback(obj[key], key);
+ });
+ } else {
+ _.each(obj, callback);
+ }
+ };
+
+ // Helper function to check null or undefined.
+ Parse._isNullOrUndefined = function(x) {
+ return Parse._.isNull(x) || Parse._.isUndefined(x);
+ };
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * @namespace Provides an interface to Parse's logging and analytics backend.
+ */
+ Parse.Analytics = Parse.Analytics || {};
+
+ _.extend(Parse.Analytics, /** @lends Parse.Analytics */ {
+ /**
+ * Tracks the occurrence of a custom event with additional dimensions.
+ * Parse will store a data point at the time of invocation with the given
+ * event name.
+ *
+ * Dimensions will allow segmentation of the occurrences of this custom
+ * event. Keys and values should be {@code String}s, and will throw
+ * otherwise.
+ *
+ * To track a user signup along with additional metadata, consider the
+ * following:
+ *
+ * var dimensions = {
+ * gender: 'm',
+ * source: 'web',
+ * dayType: 'weekend'
+ * };
+ * Parse.Analytics.track('signup', dimensions);
+ *
+ *
+ * There is a default limit of 8 dimensions per event tracked.
+ *
+ * @param {String} name The name of the custom event to report to Parse as
+ * having happened.
+ * @param {Object} dimensions The dictionary of information by which to
+ * segment this event.
+ * @param {Object} options A Backbone-style callback object.
+ * @return {Parse.Promise} A promise that is resolved when the round-trip
+ * to the server completes.
+ */
+ track: function(name, dimensions, options) {
+ name = name || '';
+ name = name.replace(/^\s*/, '');
+ name = name.replace(/\s*$/, '');
+ if (name.length === 0) {
+ throw 'A name for the custom event must be provided';
+ }
+
+ _.each(dimensions, function(val, key) {
+ if (!_.isString(key) || !_.isString(val)) {
+ throw 'track() dimensions expects keys and values of type "string".';
+ }
+ });
+
+ options = options || {};
+ return Parse._request({
+ route: 'events',
+ className: name,
+ method: 'POST',
+ data: { dimensions: dimensions }
+ })._thenRunCallbacks(options);
+ }
+ });
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * @class Parse.Config is a local representation of configuration data that
+ * can be set from the Parse dashboard.
+ */
+ Parse.Config = function() {
+ this.attributes = {};
+ this._escapedAttributes = {};
+ };
+
+ /**
+ * Retrieves the most recently-fetched configuration object, either from
+ * memory or from local storage if necessary.
+ *
+ * @return {Parse.Config} The most recently-fetched Parse.Config if it
+ * exists, else an empty Parse.Config.
+ */
+ Parse.Config.current = function() {
+ if (Parse.Config._currentConfig) {
+ return Parse.Config._currentConfig;
+ }
+
+ var configData = Parse.localStorage.getItem(Parse._getParsePath(
+ Parse.Config._CURRENT_CONFIG_KEY));
+
+ var config = new Parse.Config();
+ if (configData) {
+ config._finishFetch(JSON.parse(configData));
+ Parse.Config._currentConfig = config;
+ }
+ return config;
+ };
+
+ /**
+ * Gets a new configuration object from the server.
+ * @param {Object} options A Backbone-style options object.
+ * Valid options are:
+ * - success: Function to call when the get completes successfully.
+ *
- error: Function to call when the get fails.
+ *
+ * @return {Parse.Promise} A promise that is resolved with a newly-created
+ * configuration object when the get completes.
+ */
+ Parse.Config.get = function(options) {
+ options = options || {};
+
+ var request = Parse._request({
+ route: "config",
+ method: "GET",
+ });
+
+ return request.then(function(response) {
+ if (!response || !response.params) {
+ var errorObject = new Parse.Error(
+ Parse.Error.INVALID_JSON,
+ "Config JSON response invalid.");
+ return Parse.Promise.error(errorObject);
+ }
+
+ var config = new Parse.Config();
+ config._finishFetch(response);
+ Parse.Config._currentConfig = config;
+ return config;
+ })._thenRunCallbacks(options);
+ };
+
+ Parse.Config.prototype = {
+
+ /**
+ * Gets the HTML-escaped value of an attribute.
+ */
+ escape: function(attr) {
+ var html = this._escapedAttributes[attr];
+ if (html) {
+ return html;
+ }
+ var val = this.attributes[attr];
+ var escaped;
+ if (Parse._isNullOrUndefined(val)) {
+ escaped = '';
+ } else {
+ escaped = _.escape(val.toString());
+ }
+ this._escapedAttributes[attr] = escaped;
+ return escaped;
+ },
+
+ /**
+ * Gets the value of an attribute.
+ * @param {String} attr The name of an attribute.
+ */
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ _finishFetch: function(serverData) {
+ this.attributes = Parse._decode(null, _.clone(serverData.params));
+ Parse.localStorage.setItem(
+ Parse._getParsePath(Parse.Config._CURRENT_CONFIG_KEY),
+ JSON.stringify(serverData));
+ }
+ };
+
+ Parse.Config._currentConfig = null;
+
+ Parse.Config._CURRENT_CONFIG_KEY = "currentConfig";
+
+}(this));
+
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Constructs a new Parse.Error object with the given code and message.
+ * @param {Number} code An error code constant from Parse.Error.
+ * @param {String} message A detailed description of the error.
+ * @class
+ *
+ * Class used for all objects passed to error callbacks.
+ */
+ Parse.Error = function(code, message) {
+ this.code = code;
+ this.message = message;
+ };
+
+ _.extend(Parse.Error, /** @lends Parse.Error */ {
+ /**
+ * Error code indicating some error other than those enumerated here.
+ * @constant
+ */
+ OTHER_CAUSE: -1,
+
+ /**
+ * Error code indicating that something has gone wrong with the server.
+ * If you get this error code, it is Parse's fault. Contact us at
+ * https://parse.com/help
+ * @constant
+ */
+ INTERNAL_SERVER_ERROR: 1,
+
+ /**
+ * Error code indicating the connection to the Parse servers failed.
+ * @constant
+ */
+ CONNECTION_FAILED: 100,
+
+ /**
+ * Error code indicating the specified object doesn't exist.
+ * @constant
+ */
+ OBJECT_NOT_FOUND: 101,
+
+ /**
+ * Error code indicating you tried to query with a datatype that doesn't
+ * support it, like exact matching an array or object.
+ * @constant
+ */
+ INVALID_QUERY: 102,
+
+ /**
+ * Error code indicating a missing or invalid classname. Classnames are
+ * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
+ * only valid characters.
+ * @constant
+ */
+ INVALID_CLASS_NAME: 103,
+
+ /**
+ * Error code indicating an unspecified object id.
+ * @constant
+ */
+ MISSING_OBJECT_ID: 104,
+
+ /**
+ * Error code indicating an invalid key name. Keys are case-sensitive. They
+ * must start with a letter, and a-zA-Z0-9_ are the only valid characters.
+ * @constant
+ */
+ INVALID_KEY_NAME: 105,
+
+ /**
+ * Error code indicating a malformed pointer. You should not see this unless
+ * you have been mucking about changing internal Parse code.
+ * @constant
+ */
+ INVALID_POINTER: 106,
+
+ /**
+ * Error code indicating that badly formed JSON was received upstream. This
+ * either indicates you have done something unusual with modifying how
+ * things encode to JSON, or the network is failing badly.
+ * @constant
+ */
+ INVALID_JSON: 107,
+
+ /**
+ * Error code indicating that the feature you tried to access is only
+ * available internally for testing purposes.
+ * @constant
+ */
+ COMMAND_UNAVAILABLE: 108,
+
+ /**
+ * You must call Parse.initialize before using the Parse library.
+ * @constant
+ */
+ NOT_INITIALIZED: 109,
+
+ /**
+ * Error code indicating that a field was set to an inconsistent type.
+ * @constant
+ */
+ INCORRECT_TYPE: 111,
+
+ /**
+ * Error code indicating an invalid channel name. A channel name is either
+ * an empty string (the broadcast channel) or contains only a-zA-Z0-9_
+ * characters and starts with a letter.
+ * @constant
+ */
+ INVALID_CHANNEL_NAME: 112,
+
+ /**
+ * Error code indicating that push is misconfigured.
+ * @constant
+ */
+ PUSH_MISCONFIGURED: 115,
+
+ /**
+ * Error code indicating that the object is too large.
+ * @constant
+ */
+ OBJECT_TOO_LARGE: 116,
+
+ /**
+ * Error code indicating that the operation isn't allowed for clients.
+ * @constant
+ */
+ OPERATION_FORBIDDEN: 119,
+
+ /**
+ * Error code indicating the result was not found in the cache.
+ * @constant
+ */
+ CACHE_MISS: 120,
+
+ /**
+ * Error code indicating that an invalid key was used in a nested
+ * JSONObject.
+ * @constant
+ */
+ INVALID_NESTED_KEY: 121,
+
+ /**
+ * Error code indicating that an invalid filename was used for ParseFile.
+ * A valid file name contains only a-zA-Z0-9_. characters and is between 1
+ * and 128 characters.
+ * @constant
+ */
+ INVALID_FILE_NAME: 122,
+
+ /**
+ * Error code indicating an invalid ACL was provided.
+ * @constant
+ */
+ INVALID_ACL: 123,
+
+ /**
+ * Error code indicating that the request timed out on the server. Typically
+ * this indicates that the request is too expensive to run.
+ * @constant
+ */
+ TIMEOUT: 124,
+
+ /**
+ * Error code indicating that the email address was invalid.
+ * @constant
+ */
+ INVALID_EMAIL_ADDRESS: 125,
+
+ /**
+ * Error code indicating a missing content type.
+ * @constant
+ */
+ MISSING_CONTENT_TYPE: 126,
+
+ /**
+ * Error code indicating a missing content length.
+ * @constant
+ */
+ MISSING_CONTENT_LENGTH: 127,
+
+ /**
+ * Error code indicating an invalid content length.
+ * @constant
+ */
+ INVALID_CONTENT_LENGTH: 128,
+
+ /**
+ * Error code indicating a file that was too large.
+ * @constant
+ */
+ FILE_TOO_LARGE: 129,
+
+ /**
+ * Error code indicating an error saving a file.
+ * @constant
+ */
+ FILE_SAVE_ERROR: 130,
+
+ /**
+ * Error code indicating that a unique field was given a value that is
+ * already taken.
+ * @constant
+ */
+ DUPLICATE_VALUE: 137,
+
+ /**
+ * Error code indicating that a role's name is invalid.
+ * @constant
+ */
+ INVALID_ROLE_NAME: 139,
+
+ /**
+ * Error code indicating that an application quota was exceeded. Upgrade to
+ * resolve.
+ * @constant
+ */
+ EXCEEDED_QUOTA: 140,
+
+ /**
+ * Error code indicating that a Cloud Code script failed.
+ * @constant
+ */
+ SCRIPT_FAILED: 141,
+
+ /**
+ * Error code indicating that a Cloud Code validation failed.
+ * @constant
+ */
+ VALIDATION_ERROR: 142,
+
+ /**
+ * Error code indicating that invalid image data was provided.
+ * @constant
+ */
+ INVALID_IMAGE_DATA: 150,
+
+ /**
+ * Error code indicating an unsaved file.
+ * @constant
+ */
+ UNSAVED_FILE_ERROR: 151,
+
+ /**
+ * Error code indicating an invalid push time.
+ */
+ INVALID_PUSH_TIME_ERROR: 152,
+
+ /**
+ * Error code indicating an error deleting a file.
+ * @constant
+ */
+ FILE_DELETE_ERROR: 153,
+
+ /**
+ * Error code indicating that the application has exceeded its request
+ * limit.
+ * @constant
+ */
+ REQUEST_LIMIT_EXCEEDED: 155,
+
+ /**
+ * Error code indicating an invalid event name.
+ */
+ INVALID_EVENT_NAME: 160,
+
+ /**
+ * Error code indicating that the username is missing or empty.
+ * @constant
+ */
+ USERNAME_MISSING: 200,
+
+ /**
+ * Error code indicating that the password is missing or empty.
+ * @constant
+ */
+ PASSWORD_MISSING: 201,
+
+ /**
+ * Error code indicating that the username has already been taken.
+ * @constant
+ */
+ USERNAME_TAKEN: 202,
+
+ /**
+ * Error code indicating that the email has already been taken.
+ * @constant
+ */
+ EMAIL_TAKEN: 203,
+
+ /**
+ * Error code indicating that the email is missing, but must be specified.
+ * @constant
+ */
+ EMAIL_MISSING: 204,
+
+ /**
+ * Error code indicating that a user with the specified email was not found.
+ * @constant
+ */
+ EMAIL_NOT_FOUND: 205,
+
+ /**
+ * Error code indicating that a user object without a valid session could
+ * not be altered.
+ * @constant
+ */
+ SESSION_MISSING: 206,
+
+ /**
+ * Error code indicating that a user can only be created through signup.
+ * @constant
+ */
+ MUST_CREATE_USER_THROUGH_SIGNUP: 207,
+
+ /**
+ * Error code indicating that an an account being linked is already linked
+ * to another user.
+ * @constant
+ */
+ ACCOUNT_ALREADY_LINKED: 208,
+
+ /**
+ * Error code indicating that a user cannot be linked to an account because
+ * that account's id could not be found.
+ * @constant
+ */
+ LINKED_ID_MISSING: 250,
+
+ /**
+ * Error code indicating that a user with a linked (e.g. Facebook) account
+ * has an invalid session.
+ * @constant
+ */
+ INVALID_LINKED_SESSION: 251,
+
+ /**
+ * Error code indicating that a service being linked (e.g. Facebook or
+ * Twitter) is unsupported.
+ * @constant
+ */
+ UNSUPPORTED_SERVICE: 252,
+
+ /**
+ * Error code indicating that there were multiple errors. Aggregate errors
+ * have an "errors" property, which is an array of error objects with more
+ * detail about each error that occurred.
+ * @constant
+ */
+ AGGREGATE_ERROR: 600,
+
+ /**
+ * Error code indicating the client was unable to read an input file.
+ * @constant
+ */
+ FILE_READ_ERROR: 601,
+
+ /**
+ * Error code indicating a real error code is unavailable because
+ * we had to use an XDomainRequest object to allow CORS requests in
+ * Internet Explorer, which strips the body from HTTP responses that have
+ * a non-2XX status code.
+ * @constant
+ */
+ X_DOMAIN_REQUEST: 602
+ });
+
+}(this));
+
+/*global _: false */
+(function() {
+ var root = this;
+ var Parse = (root.Parse || (root.Parse = {}));
+ var eventSplitter = /\s+/;
+ var slice = Array.prototype.slice;
+
+ /**
+ * @class
+ *
+ * Parse.Events is a fork of Backbone's Events module, provided for your
+ * convenience.
+ *
+ * A module that can be mixed in to any object in order to provide
+ * it with custom events. You may bind callback functions to an event
+ * with `on`, or remove these functions with `off`.
+ * Triggering an event fires all callbacks in the order that `on` was
+ * called.
+ *
+ *
+ * var object = {};
+ * _.extend(object, Parse.Events);
+ * object.on('expand', function(){ alert('expanded'); });
+ * object.trigger('expand');
+ *
+ * For more information, see the
+ * Backbone
+ * documentation.
+ */
+ Parse.Events = {
+ /**
+ * Bind one or more space separated events, `events`, to a `callback`
+ * function. Passing `"all"` will bind the callback to all events fired.
+ */
+ on: function(events, callback, context) {
+
+ var calls, event, node, tail, list;
+ if (!callback) {
+ return this;
+ }
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ event = events.shift();
+ while (event) {
+ list = calls[event];
+ node = list ? list.tail : {};
+ node.next = tail = {};
+ node.context = context;
+ node.callback = callback;
+ calls[event] = {tail: tail, next: list ? list.next : node};
+ event = events.shift();
+ }
+
+ return this;
+ },
+
+ /**
+ * Remove one or many callbacks. If `context` is null, removes all callbacks
+ * with that function. If `callback` is null, removes all callbacks for the
+ * event. If `events` is null, removes all bound callbacks for all events.
+ */
+ off: function(events, callback, context) {
+ var event, calls, node, tail, cb, ctx;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) {
+ return;
+ }
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ // Loop through the listed events and contexts, splicing them out of the
+ // linked list of callbacks if appropriate.
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+ event = events.shift();
+ while (event) {
+ node = calls[event];
+ delete calls[event];
+ if (!node || !(callback || context)) {
+ event = events.shift();
+ continue;
+ }
+ // Create a new list, omitting the indicated callbacks.
+ tail = node.tail;
+ node = node.next;
+ while (node !== tail) {
+ cb = node.callback;
+ ctx = node.context;
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
+ this.on(event, cb, ctx);
+ }
+ node = node.next;
+ }
+ event = events.shift();
+ }
+
+ return this;
+ },
+
+ /**
+ * Trigger one or many events, firing all bound callbacks. Callbacks are
+ * passed the same arguments as `trigger` is, apart from the event name
+ * (unless you're listening on `"all"`, which will cause your callback to
+ * receive the true name of the event as the first argument).
+ */
+ trigger: function(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks)) {
+ return this;
+ }
+ all = calls.all;
+ events = events.split(eventSplitter);
+ rest = slice.call(arguments, 1);
+
+ // For each event, walk through the linked list of callbacks twice,
+ // first to trigger the event, then to trigger any `"all"` callbacks.
+ event = events.shift();
+ while (event) {
+ node = calls[event];
+ if (node) {
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, rest);
+ }
+ }
+ node = all;
+ if (node) {
+ tail = node.tail;
+ args = [event].concat(rest);
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ event = events.shift();
+ }
+
+ return this;
+ }
+ };
+
+ /**
+ * @function
+ */
+ Parse.Events.bind = Parse.Events.on;
+
+ /**
+ * @function
+ */
+ Parse.Events.unbind = Parse.Events.off;
+}.call(this));
+
+
+/*global navigator: false */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Creates a new GeoPoint with any of the following forms:
+ *
+ * new GeoPoint(otherGeoPoint)
+ * new GeoPoint(30, 30)
+ * new GeoPoint([30, 30])
+ * new GeoPoint({latitude: 30, longitude: 30})
+ * new GeoPoint() // defaults to (0, 0)
+ *
+ * @class
+ *
+ * Represents a latitude / longitude point that may be associated
+ * with a key in a ParseObject or used as a reference point for geo queries.
+ * This allows proximity-based queries on the key.
+ *
+ * Only one key in a class may contain a GeoPoint.
+ *
+ * Example:
+ * var point = new Parse.GeoPoint(30.0, -20.0);
+ * var object = new Parse.Object("PlaceObject");
+ * object.set("location", point);
+ * object.save();
+ */
+ Parse.GeoPoint = function(arg1, arg2) {
+ if (_.isArray(arg1)) {
+ Parse.GeoPoint._validate(arg1[0], arg1[1]);
+ this.latitude = arg1[0];
+ this.longitude = arg1[1];
+ } else if (_.isObject(arg1)) {
+ Parse.GeoPoint._validate(arg1.latitude, arg1.longitude);
+ this.latitude = arg1.latitude;
+ this.longitude = arg1.longitude;
+ } else if (_.isNumber(arg1) && _.isNumber(arg2)) {
+ Parse.GeoPoint._validate(arg1, arg2);
+ this.latitude = arg1;
+ this.longitude = arg2;
+ } else {
+ this.latitude = 0;
+ this.longitude = 0;
+ }
+
+ // Add properties so that anyone using Webkit or Mozilla will get an error
+ // if they try to set values that are out of bounds.
+ var self = this;
+ if (this.__defineGetter__ && this.__defineSetter__) {
+ // Use _latitude and _longitude to actually store the values, and add
+ // getters and setters for latitude and longitude.
+ this._latitude = this.latitude;
+ this._longitude = this.longitude;
+ this.__defineGetter__("latitude", function() {
+ return self._latitude;
+ });
+ this.__defineGetter__("longitude", function() {
+ return self._longitude;
+ });
+ this.__defineSetter__("latitude", function(val) {
+ Parse.GeoPoint._validate(val, self.longitude);
+ self._latitude = val;
+ });
+ this.__defineSetter__("longitude", function(val) {
+ Parse.GeoPoint._validate(self.latitude, val);
+ self._longitude = val;
+ });
+ }
+ };
+
+ /**
+ * @lends Parse.GeoPoint.prototype
+ * @property {float} latitude North-south portion of the coordinate, in range
+ * [-90, 90]. Throws an exception if set out of range in a modern browser.
+ * @property {float} longitude East-west portion of the coordinate, in range
+ * [-180, 180]. Throws if set out of range in a modern browser.
+ */
+
+ /**
+ * Throws an exception if the given lat-long is out of bounds.
+ */
+ Parse.GeoPoint._validate = function(latitude, longitude) {
+ if (latitude < -90.0) {
+ throw "Parse.GeoPoint latitude " + latitude + " < -90.0.";
+ }
+ if (latitude > 90.0) {
+ throw "Parse.GeoPoint latitude " + latitude + " > 90.0.";
+ }
+ if (longitude < -180.0) {
+ throw "Parse.GeoPoint longitude " + longitude + " < -180.0.";
+ }
+ if (longitude > 180.0) {
+ throw "Parse.GeoPoint longitude " + longitude + " > 180.0.";
+ }
+ };
+
+ /**
+ * Creates a GeoPoint with the user's current location, if available.
+ * Calls options.success with a new GeoPoint instance or calls options.error.
+ * @param {Object} options An object with success and error callbacks.
+ */
+ Parse.GeoPoint.current = function(options) {
+ var promise = new Parse.Promise();
+ navigator.geolocation.getCurrentPosition(function(location) {
+ promise.resolve(new Parse.GeoPoint({
+ latitude: location.coords.latitude,
+ longitude: location.coords.longitude
+ }));
+
+ }, function(error) {
+ promise.reject(error);
+ });
+
+ return promise._thenRunCallbacks(options);
+ };
+
+ Parse.GeoPoint.prototype = {
+ /**
+ * Returns a JSON representation of the GeoPoint, suitable for Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ Parse.GeoPoint._validate(this.latitude, this.longitude);
+ return {
+ "__type": "GeoPoint",
+ latitude: this.latitude,
+ longitude: this.longitude
+ };
+ },
+
+ /**
+ * Returns the distance from this GeoPoint to another in radians.
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+ radiansTo: function(point) {
+ var d2r = Math.PI / 180.0;
+ var lat1rad = this.latitude * d2r;
+ var long1rad = this.longitude * d2r;
+ var lat2rad = point.latitude * d2r;
+ var long2rad = point.longitude * d2r;
+ var deltaLat = lat1rad - lat2rad;
+ var deltaLong = long1rad - long2rad;
+ var sinDeltaLatDiv2 = Math.sin(deltaLat / 2);
+ var sinDeltaLongDiv2 = Math.sin(deltaLong / 2);
+ // Square of half the straight line chord distance between both points.
+ var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) +
+ (Math.cos(lat1rad) * Math.cos(lat2rad) *
+ sinDeltaLongDiv2 * sinDeltaLongDiv2));
+ a = Math.min(1.0, a);
+ return 2 * Math.asin(Math.sqrt(a));
+ },
+
+ /**
+ * Returns the distance from this GeoPoint to another in kilometers.
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+ kilometersTo: function(point) {
+ return this.radiansTo(point) * 6371.0;
+ },
+
+ /**
+ * Returns the distance from this GeoPoint to another in miles.
+ * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
+ * @return {Number}
+ */
+ milesTo: function(point) {
+ return this.radiansTo(point) * 3958.8;
+ }
+ };
+}(this));
+
+/*global navigator: false */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ var PUBLIC_KEY = "*";
+
+ /**
+ * Creates a new ACL.
+ * If no argument is given, the ACL has no permissions for anyone.
+ * If the argument is a Parse.User, the ACL will have read and write
+ * permission for only that user.
+ * If the argument is any other JSON object, that object will be interpretted
+ * as a serialized ACL created with toJSON().
+ * @see Parse.Object#setACL
+ * @class
+ *
+ * An ACL, or Access Control List can be added to any
+ * Parse.Object to restrict access to only a subset of users
+ * of your application.
+ */
+ Parse.ACL = function(arg1) {
+ var self = this;
+ self.permissionsById = {};
+ if (_.isObject(arg1)) {
+ if (arg1 instanceof Parse.User) {
+ self.setReadAccess(arg1, true);
+ self.setWriteAccess(arg1, true);
+ } else {
+ if (_.isFunction(arg1)) {
+ throw "Parse.ACL() called with a function. Did you forget ()?";
+ }
+ Parse._objectEach(arg1, function(accessList, userId) {
+ if (!_.isString(userId)) {
+ throw "Tried to create an ACL with an invalid userId.";
+ }
+ self.permissionsById[userId] = {};
+ Parse._objectEach(accessList, function(allowed, permission) {
+ if (permission !== "read" && permission !== "write") {
+ throw "Tried to create an ACL with an invalid permission type.";
+ }
+ if (!_.isBoolean(allowed)) {
+ throw "Tried to create an ACL with an invalid permission value.";
+ }
+ self.permissionsById[userId][permission] = allowed;
+ });
+ });
+ }
+ }
+ };
+
+ /**
+ * Returns a JSON-encoded version of the ACL.
+ * @return {Object}
+ */
+ Parse.ACL.prototype.toJSON = function() {
+ return _.clone(this.permissionsById);
+ };
+
+ Parse.ACL.prototype._setAccess = function(accessType, userId, allowed) {
+ if (userId instanceof Parse.User) {
+ userId = userId.id;
+ } else if (userId instanceof Parse.Role) {
+ userId = "role:" + userId.getName();
+ }
+ if (!_.isString(userId)) {
+ throw "userId must be a string.";
+ }
+ if (!_.isBoolean(allowed)) {
+ throw "allowed must be either true or false.";
+ }
+ var permissions = this.permissionsById[userId];
+ if (!permissions) {
+ if (!allowed) {
+ // The user already doesn't have this permission, so no action needed.
+ return;
+ } else {
+ permissions = {};
+ this.permissionsById[userId] = permissions;
+ }
+ }
+
+ if (allowed) {
+ this.permissionsById[userId][accessType] = true;
+ } else {
+ delete permissions[accessType];
+ if (_.isEmpty(permissions)) {
+ delete permissions[userId];
+ }
+ }
+ };
+
+ Parse.ACL.prototype._getAccess = function(accessType, userId) {
+ if (userId instanceof Parse.User) {
+ userId = userId.id;
+ } else if (userId instanceof Parse.Role) {
+ userId = "role:" + userId.getName();
+ }
+ var permissions = this.permissionsById[userId];
+ if (!permissions) {
+ return false;
+ }
+ return permissions[accessType] ? true : false;
+ };
+
+ /**
+ * Set whether the given user is allowed to read this object.
+ * @param userId An instance of Parse.User or its objectId.
+ * @param {Boolean} allowed Whether that user should have read access.
+ */
+ Parse.ACL.prototype.setReadAccess = function(userId, allowed) {
+ this._setAccess("read", userId, allowed);
+ };
+
+ /**
+ * Get whether the given user id is *explicitly* allowed to read this object.
+ * Even if this returns false, the user may still be able to access it if
+ * getPublicReadAccess returns true or a role that the user belongs to has
+ * write access.
+ * @param userId An instance of Parse.User or its objectId, or a Parse.Role.
+ * @return {Boolean}
+ */
+ Parse.ACL.prototype.getReadAccess = function(userId) {
+ return this._getAccess("read", userId);
+ };
+
+ /**
+ * Set whether the given user id is allowed to write this object.
+ * @param userId An instance of Parse.User or its objectId, or a Parse.Role..
+ * @param {Boolean} allowed Whether that user should have write access.
+ */
+ Parse.ACL.prototype.setWriteAccess = function(userId, allowed) {
+ this._setAccess("write", userId, allowed);
+ };
+
+ /**
+ * Get whether the given user id is *explicitly* allowed to write this object.
+ * Even if this returns false, the user may still be able to write it if
+ * getPublicWriteAccess returns true or a role that the user belongs to has
+ * write access.
+ * @param userId An instance of Parse.User or its objectId, or a Parse.Role.
+ * @return {Boolean}
+ */
+ Parse.ACL.prototype.getWriteAccess = function(userId) {
+ return this._getAccess("write", userId);
+ };
+
+ /**
+ * Set whether the public is allowed to read this object.
+ * @param {Boolean} allowed
+ */
+ Parse.ACL.prototype.setPublicReadAccess = function(allowed) {
+ this.setReadAccess(PUBLIC_KEY, allowed);
+ };
+
+ /**
+ * Get whether the public is allowed to read this object.
+ * @return {Boolean}
+ */
+ Parse.ACL.prototype.getPublicReadAccess = function() {
+ return this.getReadAccess(PUBLIC_KEY);
+ };
+
+ /**
+ * Set whether the public is allowed to write this object.
+ * @param {Boolean} allowed
+ */
+ Parse.ACL.prototype.setPublicWriteAccess = function(allowed) {
+ this.setWriteAccess(PUBLIC_KEY, allowed);
+ };
+
+ /**
+ * Get whether the public is allowed to write this object.
+ * @return {Boolean}
+ */
+ Parse.ACL.prototype.getPublicWriteAccess = function() {
+ return this.getWriteAccess(PUBLIC_KEY);
+ };
+
+ /**
+ * Get whether users belonging to the given role are allowed
+ * to read this object. Even if this returns false, the role may
+ * still be able to write it if a parent role has read access.
+ *
+ * @param role The name of the role, or a Parse.Role object.
+ * @return {Boolean} true if the role has read access. false otherwise.
+ * @throws {String} If role is neither a Parse.Role nor a String.
+ */
+ Parse.ACL.prototype.getRoleReadAccess = function(role) {
+ if (role instanceof Parse.Role) {
+ // Normalize to the String name
+ role = role.getName();
+ }
+ if (_.isString(role)) {
+ return this.getReadAccess("role:" + role);
+ }
+ throw "role must be a Parse.Role or a String";
+ };
+
+ /**
+ * Get whether users belonging to the given role are allowed
+ * to write this object. Even if this returns false, the role may
+ * still be able to write it if a parent role has write access.
+ *
+ * @param role The name of the role, or a Parse.Role object.
+ * @return {Boolean} true if the role has write access. false otherwise.
+ * @throws {String} If role is neither a Parse.Role nor a String.
+ */
+ Parse.ACL.prototype.getRoleWriteAccess = function(role) {
+ if (role instanceof Parse.Role) {
+ // Normalize to the String name
+ role = role.getName();
+ }
+ if (_.isString(role)) {
+ return this.getWriteAccess("role:" + role);
+ }
+ throw "role must be a Parse.Role or a String";
+ };
+
+ /**
+ * Set whether users belonging to the given role are allowed
+ * to read this object.
+ *
+ * @param role The name of the role, or a Parse.Role object.
+ * @param {Boolean} allowed Whether the given role can read this object.
+ * @throws {String} If role is neither a Parse.Role nor a String.
+ */
+ Parse.ACL.prototype.setRoleReadAccess = function(role, allowed) {
+ if (role instanceof Parse.Role) {
+ // Normalize to the String name
+ role = role.getName();
+ }
+ if (_.isString(role)) {
+ this.setReadAccess("role:" + role, allowed);
+ return;
+ }
+ throw "role must be a Parse.Role or a String";
+ };
+
+ /**
+ * Set whether users belonging to the given role are allowed
+ * to write this object.
+ *
+ * @param role The name of the role, or a Parse.Role object.
+ * @param {Boolean} allowed Whether the given role can write this object.
+ * @throws {String} If role is neither a Parse.Role nor a String.
+ */
+ Parse.ACL.prototype.setRoleWriteAccess = function(role, allowed) {
+ if (role instanceof Parse.Role) {
+ // Normalize to the String name
+ role = role.getName();
+ }
+ if (_.isString(role)) {
+ this.setWriteAccess("role:" + role, allowed);
+ return;
+ }
+ throw "role must be a Parse.Role or a String";
+ };
+
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * @class
+ * A Parse.Op is an atomic operation that can be applied to a field in a
+ * Parse.Object. For example, calling object.set("foo", "bar")
+ * is an example of a Parse.Op.Set. Calling object.unset("foo")
+ * is a Parse.Op.Unset. These operations are stored in a Parse.Object and
+ * sent to the server as part of object.save() operations.
+ * Instances of Parse.Op should be immutable.
+ *
+ * You should not create subclasses of Parse.Op or instantiate Parse.Op
+ * directly.
+ */
+ Parse.Op = function() {
+ this._initialize.apply(this, arguments);
+ };
+
+ Parse.Op.prototype = {
+ _initialize: function() {}
+ };
+
+ _.extend(Parse.Op, {
+ /**
+ * To create a new Op, call Parse.Op._extend();
+ */
+ _extend: Parse._extend,
+
+ // A map of __op string to decoder function.
+ _opDecoderMap: {},
+
+ /**
+ * Registers a function to convert a json object with an __op field into an
+ * instance of a subclass of Parse.Op.
+ */
+ _registerDecoder: function(opName, decoder) {
+ Parse.Op._opDecoderMap[opName] = decoder;
+ },
+
+ /**
+ * Converts a json object into an instance of a subclass of Parse.Op.
+ */
+ _decode: function(json) {
+ var decoder = Parse.Op._opDecoderMap[json.__op];
+ if (decoder) {
+ return decoder(json);
+ } else {
+ return undefined;
+ }
+ }
+ });
+
+ /*
+ * Add a handler for Batch ops.
+ */
+ Parse.Op._registerDecoder("Batch", function(json) {
+ var op = null;
+ Parse._arrayEach(json.ops, function(nextOp) {
+ nextOp = Parse.Op._decode(nextOp);
+ op = nextOp._mergeWithPrevious(op);
+ });
+ return op;
+ });
+
+ /**
+ * @class
+ * A Set operation indicates that either the field was changed using
+ * Parse.Object.set, or it is a mutable container that was detected as being
+ * changed.
+ */
+ Parse.Op.Set = Parse.Op._extend(/** @lends Parse.Op.Set.prototype */ {
+ _initialize: function(value) {
+ this._value = value;
+ },
+
+ /**
+ * Returns the new value of this field after the set.
+ */
+ value: function() {
+ return this._value;
+ },
+
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return Parse._encode(this.value());
+ },
+
+ _mergeWithPrevious: function(previous) {
+ return this;
+ },
+
+ _estimate: function(oldValue) {
+ return this.value();
+ }
+ });
+
+ /**
+ * A sentinel value that is returned by Parse.Op.Unset._estimate to
+ * indicate the field should be deleted. Basically, if you find _UNSET as a
+ * value in your object, you should remove that key.
+ */
+ Parse.Op._UNSET = {};
+
+ /**
+ * @class
+ * An Unset operation indicates that this field has been deleted from the
+ * object.
+ */
+ Parse.Op.Unset = Parse.Op._extend(/** @lends Parse.Op.Unset.prototype */ {
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return { __op: "Delete" };
+ },
+
+ _mergeWithPrevious: function(previous) {
+ return this;
+ },
+
+ _estimate: function(oldValue) {
+ return Parse.Op._UNSET;
+ }
+ });
+
+ Parse.Op._registerDecoder("Delete", function(json) {
+ return new Parse.Op.Unset();
+ });
+
+ /**
+ * @class
+ * An Increment is an atomic operation where the numeric value for the field
+ * will be increased by a given amount.
+ */
+ Parse.Op.Increment = Parse.Op._extend(
+ /** @lends Parse.Op.Increment.prototype */ {
+
+ _initialize: function(amount) {
+ this._amount = amount;
+ },
+
+ /**
+ * Returns the amount to increment by.
+ * @return {Number} the amount to increment by.
+ */
+ amount: function() {
+ return this._amount;
+ },
+
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return { __op: "Increment", amount: this._amount };
+ },
+
+ _mergeWithPrevious: function(previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof Parse.Op.Unset) {
+ return new Parse.Op.Set(this.amount());
+ } else if (previous instanceof Parse.Op.Set) {
+ return new Parse.Op.Set(previous.value() + this.amount());
+ } else if (previous instanceof Parse.Op.Increment) {
+ return new Parse.Op.Increment(this.amount() + previous.amount());
+ } else {
+ throw "Op is invalid after previous op.";
+ }
+ },
+
+ _estimate: function(oldValue) {
+ if (!oldValue) {
+ return this.amount();
+ }
+ return oldValue + this.amount();
+ }
+ });
+
+ Parse.Op._registerDecoder("Increment", function(json) {
+ return new Parse.Op.Increment(json.amount);
+ });
+
+ /**
+ * @class
+ * Add is an atomic operation where the given objects will be appended to the
+ * array that is stored in this field.
+ */
+ Parse.Op.Add = Parse.Op._extend(/** @lends Parse.Op.Add.prototype */ {
+ _initialize: function(objects) {
+ this._objects = objects;
+ },
+
+ /**
+ * Returns the objects to be added to the array.
+ * @return {Array} The objects to be added to the array.
+ */
+ objects: function() {
+ return this._objects;
+ },
+
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return { __op: "Add", objects: Parse._encode(this.objects()) };
+ },
+
+ _mergeWithPrevious: function(previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof Parse.Op.Unset) {
+ return new Parse.Op.Set(this.objects());
+ } else if (previous instanceof Parse.Op.Set) {
+ return new Parse.Op.Set(this._estimate(previous.value()));
+ } else if (previous instanceof Parse.Op.Add) {
+ return new Parse.Op.Add(previous.objects().concat(this.objects()));
+ } else {
+ throw "Op is invalid after previous op.";
+ }
+ },
+
+ _estimate: function(oldValue) {
+ if (!oldValue) {
+ return _.clone(this.objects());
+ } else {
+ return oldValue.concat(this.objects());
+ }
+ }
+ });
+
+ Parse.Op._registerDecoder("Add", function(json) {
+ return new Parse.Op.Add(Parse._decode(undefined, json.objects));
+ });
+
+ /**
+ * @class
+ * AddUnique is an atomic operation where the given items will be appended to
+ * the array that is stored in this field only if they were not already
+ * present in the array.
+ */
+ Parse.Op.AddUnique = Parse.Op._extend(
+ /** @lends Parse.Op.AddUnique.prototype */ {
+
+ _initialize: function(objects) {
+ this._objects = _.uniq(objects);
+ },
+
+ /**
+ * Returns the objects to be added to the array.
+ * @return {Array} The objects to be added to the array.
+ */
+ objects: function() {
+ return this._objects;
+ },
+
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return { __op: "AddUnique", objects: Parse._encode(this.objects()) };
+ },
+
+ _mergeWithPrevious: function(previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof Parse.Op.Unset) {
+ return new Parse.Op.Set(this.objects());
+ } else if (previous instanceof Parse.Op.Set) {
+ return new Parse.Op.Set(this._estimate(previous.value()));
+ } else if (previous instanceof Parse.Op.AddUnique) {
+ return new Parse.Op.AddUnique(this._estimate(previous.objects()));
+ } else {
+ throw "Op is invalid after previous op.";
+ }
+ },
+
+ _estimate: function(oldValue) {
+ if (!oldValue) {
+ return _.clone(this.objects());
+ } else {
+ // We can't just take the _.uniq(_.union(...)) of oldValue and
+ // this.objects, because the uniqueness may not apply to oldValue
+ // (especially if the oldValue was set via .set())
+ var newValue = _.clone(oldValue);
+ Parse._arrayEach(this.objects(), function(obj) {
+ if (obj instanceof Parse.Object && obj.id) {
+ var matchingObj = _.find(newValue, function(anObj) {
+ return (anObj instanceof Parse.Object) && (anObj.id === obj.id);
+ });
+ if (!matchingObj) {
+ newValue.push(obj);
+ } else {
+ var index = _.indexOf(newValue, matchingObj);
+ newValue[index] = obj;
+ }
+ } else if (!_.contains(newValue, obj)) {
+ newValue.push(obj);
+ }
+ });
+ return newValue;
+ }
+ }
+ });
+
+ Parse.Op._registerDecoder("AddUnique", function(json) {
+ return new Parse.Op.AddUnique(Parse._decode(undefined, json.objects));
+ });
+
+ /**
+ * @class
+ * Remove is an atomic operation where the given objects will be removed from
+ * the array that is stored in this field.
+ */
+ Parse.Op.Remove = Parse.Op._extend(/** @lends Parse.Op.Remove.prototype */ {
+ _initialize: function(objects) {
+ this._objects = _.uniq(objects);
+ },
+
+ /**
+ * Returns the objects to be removed from the array.
+ * @return {Array} The objects to be removed from the array.
+ */
+ objects: function() {
+ return this._objects;
+ },
+
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return { __op: "Remove", objects: Parse._encode(this.objects()) };
+ },
+
+ _mergeWithPrevious: function(previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof Parse.Op.Unset) {
+ return previous;
+ } else if (previous instanceof Parse.Op.Set) {
+ return new Parse.Op.Set(this._estimate(previous.value()));
+ } else if (previous instanceof Parse.Op.Remove) {
+ return new Parse.Op.Remove(_.union(previous.objects(), this.objects()));
+ } else {
+ throw "Op is invalid after previous op.";
+ }
+ },
+
+ _estimate: function(oldValue) {
+ if (!oldValue) {
+ return [];
+ } else {
+ var newValue = _.difference(oldValue, this.objects());
+ // If there are saved Parse Objects being removed, also remove them.
+ Parse._arrayEach(this.objects(), function(obj) {
+ if (obj instanceof Parse.Object && obj.id) {
+ newValue = _.reject(newValue, function(other) {
+ return (other instanceof Parse.Object) && (other.id === obj.id);
+ });
+ }
+ });
+ return newValue;
+ }
+ }
+ });
+
+ Parse.Op._registerDecoder("Remove", function(json) {
+ return new Parse.Op.Remove(Parse._decode(undefined, json.objects));
+ });
+
+ /**
+ * @class
+ * A Relation operation indicates that the field is an instance of
+ * Parse.Relation, and objects are being added to, or removed from, that
+ * relation.
+ */
+ Parse.Op.Relation = Parse.Op._extend(
+ /** @lends Parse.Op.Relation.prototype */ {
+
+ _initialize: function(adds, removes) {
+ this._targetClassName = null;
+
+ var self = this;
+
+ var pointerToId = function(object) {
+ if (object instanceof Parse.Object) {
+ if (!object.id) {
+ throw "You can't add an unsaved Parse.Object to a relation.";
+ }
+ if (!self._targetClassName) {
+ self._targetClassName = object.className;
+ }
+ if (self._targetClassName !== object.className) {
+ throw "Tried to create a Parse.Relation with 2 different types: " +
+ self._targetClassName + " and " + object.className + ".";
+ }
+ return object.id;
+ }
+ return object;
+ };
+
+ this.relationsToAdd = _.uniq(_.map(adds, pointerToId));
+ this.relationsToRemove = _.uniq(_.map(removes, pointerToId));
+ },
+
+ /**
+ * Returns an array of unfetched Parse.Object that are being added to the
+ * relation.
+ * @return {Array}
+ */
+ added: function() {
+ var self = this;
+ return _.map(this.relationsToAdd, function(objectId) {
+ var object = Parse.Object._create(self._targetClassName);
+ object.id = objectId;
+ return object;
+ });
+ },
+
+ /**
+ * Returns an array of unfetched Parse.Object that are being removed from
+ * the relation.
+ * @return {Array}
+ */
+ removed: function() {
+ var self = this;
+ return _.map(this.relationsToRemove, function(objectId) {
+ var object = Parse.Object._create(self._targetClassName);
+ object.id = objectId;
+ return object;
+ });
+ },
+
+ /**
+ * Returns a JSON version of the operation suitable for sending to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ var adds = null;
+ var removes = null;
+ var self = this;
+ var idToPointer = function(id) {
+ return { __type: 'Pointer',
+ className: self._targetClassName,
+ objectId: id };
+ };
+ var pointers = null;
+ if (this.relationsToAdd.length > 0) {
+ pointers = _.map(this.relationsToAdd, idToPointer);
+ adds = { "__op": "AddRelation", "objects": pointers };
+ }
+
+ if (this.relationsToRemove.length > 0) {
+ pointers = _.map(this.relationsToRemove, idToPointer);
+ removes = { "__op": "RemoveRelation", "objects": pointers };
+ }
+
+ if (adds && removes) {
+ return { "__op": "Batch", "ops": [adds, removes]};
+ }
+
+ return adds || removes || {};
+ },
+
+ _mergeWithPrevious: function(previous) {
+ if (!previous) {
+ return this;
+ } else if (previous instanceof Parse.Op.Unset) {
+ throw "You can't modify a relation after deleting it.";
+ } else if (previous instanceof Parse.Op.Relation) {
+ if (previous._targetClassName &&
+ previous._targetClassName !== this._targetClassName) {
+ throw "Related object must be of class " + previous._targetClassName +
+ ", but " + this._targetClassName + " was passed in.";
+ }
+ var newAdd = _.union(_.difference(previous.relationsToAdd,
+ this.relationsToRemove),
+ this.relationsToAdd);
+ var newRemove = _.union(_.difference(previous.relationsToRemove,
+ this.relationsToAdd),
+ this.relationsToRemove);
+
+ var newRelation = new Parse.Op.Relation(newAdd, newRemove);
+ newRelation._targetClassName = this._targetClassName;
+ return newRelation;
+ } else {
+ throw "Op is invalid after previous op.";
+ }
+ },
+
+ _estimate: function(oldValue, object, key) {
+ if (!oldValue) {
+ var relation = new Parse.Relation(object, key);
+ relation.targetClassName = this._targetClassName;
+ } else if (oldValue instanceof Parse.Relation) {
+ if (this._targetClassName) {
+ if (oldValue.targetClassName) {
+ if (oldValue.targetClassName !== this._targetClassName) {
+ throw "Related object must be a " + oldValue.targetClassName +
+ ", but a " + this._targetClassName + " was passed in.";
+ }
+ } else {
+ oldValue.targetClassName = this._targetClassName;
+ }
+ }
+ return oldValue;
+ } else {
+ throw "Op is invalid after previous op.";
+ }
+ }
+ });
+
+ Parse.Op._registerDecoder("AddRelation", function(json) {
+ return new Parse.Op.Relation(Parse._decode(undefined, json.objects), []);
+ });
+ Parse.Op._registerDecoder("RemoveRelation", function(json) {
+ return new Parse.Op.Relation([], Parse._decode(undefined, json.objects));
+ });
+
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Creates a new Relation for the given parent object and key. This
+ * constructor should rarely be used directly, but rather created by
+ * Parse.Object.relation.
+ * @param {Parse.Object} parent The parent of this relation.
+ * @param {String} key The key for this relation on the parent.
+ * @see Parse.Object#relation
+ * @class
+ *
+ *
+ * A class that is used to access all of the children of a many-to-many
+ * relationship. Each instance of Parse.Relation is associated with a
+ * particular parent object and key.
+ *
+ */
+ Parse.Relation = function(parent, key) {
+ this.parent = parent;
+ this.key = key;
+ this.targetClassName = null;
+ };
+
+ Parse.Relation.prototype = {
+ /**
+ * Makes sure that this relation has the right parent and key.
+ */
+ _ensureParentAndKey: function(parent, key) {
+ this.parent = this.parent || parent;
+ this.key = this.key || key;
+ if (this.parent !== parent) {
+ throw "Internal Error. Relation retrieved from two different Objects.";
+ }
+ if (this.key !== key) {
+ throw "Internal Error. Relation retrieved from two different keys.";
+ }
+ },
+
+ /**
+ * Adds a Parse.Object or an array of Parse.Objects to the relation.
+ * @param {} objects The item or items to add.
+ */
+ add: function(objects) {
+ if (!_.isArray(objects)) {
+ objects = [objects];
+ }
+
+ var change = new Parse.Op.Relation(objects, []);
+ this.parent.set(this.key, change);
+ this.targetClassName = change._targetClassName;
+ },
+
+ /**
+ * Removes a Parse.Object or an array of Parse.Objects from this relation.
+ * @param {} objects The item or items to remove.
+ */
+ remove: function(objects) {
+ if (!_.isArray(objects)) {
+ objects = [objects];
+ }
+
+ var change = new Parse.Op.Relation([], objects);
+ this.parent.set(this.key, change);
+ this.targetClassName = change._targetClassName;
+ },
+
+ /**
+ * Returns a JSON version of the object suitable for saving to disk.
+ * @return {Object}
+ */
+ toJSON: function() {
+ return { "__type": "Relation", "className": this.targetClassName };
+ },
+
+ /**
+ * Returns a Parse.Query that is limited to objects in this
+ * relation.
+ * @return {Parse.Query}
+ */
+ query: function() {
+ var targetClass;
+ var query;
+ if (!this.targetClassName) {
+ targetClass = Parse.Object._getSubclass(this.parent.className);
+ query = new Parse.Query(targetClass);
+ query._extraOptions.redirectClassNameForKey = this.key;
+ } else {
+ targetClass = Parse.Object._getSubclass(this.targetClassName);
+ query = new Parse.Query(targetClass);
+ }
+ query._addCondition("$relatedTo", "object", this.parent._toPointer());
+ query._addCondition("$relatedTo", "key", this.key);
+
+ return query;
+ }
+ };
+}(this));
+
+/*global window: false, process: false */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * A Promise is returned by async methods as a hook to provide callbacks to be
+ * called when the async task is fulfilled.
+ *
+ * Typical usage would be like:
+ * query.find().then(function(results) {
+ * results[0].set("foo", "bar");
+ * return results[0].saveAsync();
+ * }).then(function(result) {
+ * console.log("Updated " + result.id);
+ * });
+ *
+ *
+ * @see Parse.Promise.prototype.then
+ * @class
+ */
+ Parse.Promise = function() {
+ this._resolved = false;
+ this._rejected = false;
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ };
+
+ _.extend(Parse.Promise, /** @lends Parse.Promise */ {
+
+ _isPromisesAPlusCompliant: false,
+
+ /**
+ * Returns true iff the given object fulfils the Promise interface.
+ * @return {Boolean}
+ */
+ is: function(promise) {
+ return promise && promise.then && _.isFunction(promise.then);
+ },
+
+ /**
+ * Returns a new promise that is resolved with a given value.
+ * @return {Parse.Promise} the new promise.
+ */
+ as: function() {
+ var promise = new Parse.Promise();
+ promise.resolve.apply(promise, arguments);
+ return promise;
+ },
+
+ /**
+ * Returns a new promise that is rejected with a given error.
+ * @return {Parse.Promise} the new promise.
+ */
+ error: function() {
+ var promise = new Parse.Promise();
+ promise.reject.apply(promise, arguments);
+ return promise;
+ },
+
+ /**
+ * Returns a new promise that is fulfilled when all of the input promises
+ * are resolved. If any promise in the list fails, then the returned promise
+ * will fail with the last error. If they all succeed, then the returned
+ * promise will succeed, with the results being the results of all the input
+ * promises. For example:
+ * var p1 = Parse.Promise.as(1);
+ * var p2 = Parse.Promise.as(2);
+ * var p3 = Parse.Promise.as(3);
+ *
+ * Parse.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * The input promises can also be specified as an array:
+ * var promises = [p1, p2, p3];
+ * Parse.Promise.when(promises).then(function(r1, r2, r3) {
+ * console.log(r1); // prints 1
+ * console.log(r2); // prints 2
+ * console.log(r3); // prints 3
+ * });
+ *
+ * @param {Array} promises a list of promises to wait for.
+ * @return {Parse.Promise} the new promise.
+ */
+ when: function(promises) {
+ // Allow passing in Promises as separate arguments instead of an Array.
+ var objects;
+ if (promises && Parse._isNullOrUndefined(promises.length)) {
+ objects = arguments;
+ } else {
+ objects = promises;
+ }
+
+ var total = objects.length;
+ var hadError = false;
+ var results = [];
+ var errors = [];
+ results.length = objects.length;
+ errors.length = objects.length;
+
+ if (total === 0) {
+ return Parse.Promise.as.apply(this, results);
+ }
+
+ var promise = new Parse.Promise();
+
+ var resolveOne = function() {
+ total = total - 1;
+ if (total === 0) {
+ if (hadError) {
+ promise.reject(errors);
+ } else {
+ promise.resolve.apply(promise, results);
+ }
+ }
+ };
+
+ Parse._arrayEach(objects, function(object, i) {
+ if (Parse.Promise.is(object)) {
+ object.then(function(result) {
+ results[i] = result;
+ resolveOne();
+ }, function(error) {
+ errors[i] = error;
+ hadError = true;
+ resolveOne();
+ });
+ } else {
+ results[i] = object;
+ resolveOne();
+ }
+ });
+
+ return promise;
+ },
+
+ /**
+ * Runs the given asyncFunction repeatedly, as long as the predicate
+ * function returns a truthy value. Stops repeating if asyncFunction returns
+ * a rejected promise.
+ * @param {Function} predicate should return false when ready to stop.
+ * @param {Function} asyncFunction should return a Promise.
+ */
+ _continueWhile: function(predicate, asyncFunction) {
+ if (predicate()) {
+ return asyncFunction().then(function() {
+ return Parse.Promise._continueWhile(predicate, asyncFunction);
+ });
+ }
+ return Parse.Promise.as();
+ }
+ });
+
+ _.extend(Parse.Promise.prototype, /** @lends Parse.Promise.prototype */ {
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @param {Object} result the result to pass to the callbacks.
+ */
+ resolve: function(result) {
+ if (this._resolved || this._rejected) {
+ throw "A promise was resolved even though it had already been " +
+ (this._resolved ? "resolved" : "rejected") + ".";
+ }
+ this._resolved = true;
+ this._result = arguments;
+ var results = arguments;
+ Parse._arrayEach(this._resolvedCallbacks, function(resolvedCallback) {
+ resolvedCallback.apply(this, results);
+ });
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ },
+
+ /**
+ * Marks this promise as fulfilled, firing any callbacks waiting on it.
+ * @param {Object} error the error to pass to the callbacks.
+ */
+ reject: function(error) {
+ if (this._resolved || this._rejected) {
+ throw "A promise was rejected even though it had already been " +
+ (this._resolved ? "resolved" : "rejected") + ".";
+ }
+ this._rejected = true;
+ this._error = error;
+ Parse._arrayEach(this._rejectedCallbacks, function(rejectedCallback) {
+ rejectedCallback(error);
+ });
+ this._resolvedCallbacks = [];
+ this._rejectedCallbacks = [];
+ },
+
+ /**
+ * Adds callbacks to be called when this promise is fulfilled. Returns a new
+ * Promise that will be fulfilled when the callback is complete. It allows
+ * chaining. If the callback itself returns a Promise, then the one returned
+ * by "then" will not be fulfilled until that one returned by the callback
+ * is fulfilled.
+ * @param {Function} resolvedCallback Function that is called when this
+ * Promise is resolved. Once the callback is complete, then the Promise
+ * returned by "then" will also be fulfilled.
+ * @param {Function} rejectedCallback Function that is called when this
+ * Promise is rejected with an error. Once the callback is complete, then
+ * the promise returned by "then" with be resolved successfully. If
+ * rejectedCallback is null, or it returns a rejected Promise, then the
+ * Promise returned by "then" will be rejected with that error.
+ * @return {Parse.Promise} A new Promise that will be fulfilled after this
+ * Promise is fulfilled and either callback has completed. If the callback
+ * returned a Promise, then this Promise will not be fulfilled until that
+ * one is.
+ */
+ then: function(resolvedCallback, rejectedCallback) {
+ var promise = new Parse.Promise();
+
+ var wrappedResolvedCallback = function() {
+ var result = arguments;
+ if (resolvedCallback) {
+ if (Parse.Promise._isPromisesAPlusCompliant) {
+ try {
+ result = [resolvedCallback.apply(this, result)];
+ } catch (e) {
+ result = [Parse.Promise.error(e)];
+ }
+ } else {
+ result = [resolvedCallback.apply(this, result)];
+ }
+ }
+ if (result.length === 1 && Parse.Promise.is(result[0])) {
+ result[0].then(function() {
+ promise.resolve.apply(promise, arguments);
+ }, function(error) {
+ promise.reject(error);
+ });
+ } else {
+ promise.resolve.apply(promise, result);
+ }
+ };
+
+ var wrappedRejectedCallback = function(error) {
+ var result = [];
+ if (rejectedCallback) {
+ if (Parse.Promise._isPromisesAPlusCompliant) {
+ try {
+ result = [rejectedCallback(error)];
+ } catch (e) {
+ result = [Parse.Promise.error(e)];
+ }
+ } else {
+ result = [rejectedCallback(error)];
+ }
+ if (result.length === 1 && Parse.Promise.is(result[0])) {
+ result[0].then(function() {
+ promise.resolve.apply(promise, arguments);
+ }, function(error) {
+ promise.reject(error);
+ });
+ } else {
+ if (Parse.Promise._isPromisesAPlusCompliant) {
+ promise.resolve.apply(promise, result);
+ } else {
+ promise.reject(result[0]);
+ }
+ }
+ } else {
+ promise.reject(error);
+ }
+ };
+
+ var runLater = function(func) {
+ func.call();
+ };
+ if (Parse.Promise._isPromisesAPlusCompliant) {
+ if (typeof(window) !== 'undefined' && window.setTimeout) {
+ runLater = function(func) {
+ window.setTimeout(func, 0);
+ };
+ } else if (typeof(process) !== 'undefined' && process.nextTick) {
+ runLater = function(func) {
+ process.nextTick(func);
+ };
+ }
+ }
+
+ var self = this;
+ if (this._resolved) {
+ runLater(function() {
+ wrappedResolvedCallback.apply(self, self._result);
+ });
+ } else if (this._rejected) {
+ runLater(function() {
+ wrappedRejectedCallback(self._error);
+ });
+ } else {
+ this._resolvedCallbacks.push(wrappedResolvedCallback);
+ this._rejectedCallbacks.push(wrappedRejectedCallback);
+ }
+
+ return promise;
+ },
+
+ /**
+ * Add handlers to be called when the promise
+ * is either resolved or rejected
+ */
+ always: function(callback) {
+ return this.then(callback, callback);
+ },
+
+ /**
+ * Add handlers to be called when the Promise object is resolved
+ */
+ done: function(callback) {
+ return this.then(callback);
+ },
+
+ /**
+ * Add handlers to be called when the Promise object is rejected
+ */
+ fail: function(callback) {
+ return this.then(null, callback);
+ },
+
+ /**
+ * Run the given callbacks after this promise is fulfilled.
+ * @param optionsOrCallback {} A Backbone-style options callback, or a
+ * callback function. If this is an options object and contains a "model"
+ * attributes, that will be passed to error callbacks as the first argument.
+ * @param model {} If truthy, this will be passed as the first result of
+ * error callbacks. This is for Backbone-compatability.
+ * @return {Parse.Promise} A promise that will be resolved after the
+ * callbacks are run, with the same result as this.
+ */
+ _thenRunCallbacks: function(optionsOrCallback, model) {
+ var options;
+ if (_.isFunction(optionsOrCallback)) {
+ var callback = optionsOrCallback;
+ options = {
+ success: function(result) {
+ callback(result, null);
+ },
+ error: function(error) {
+ callback(null, error);
+ }
+ };
+ } else {
+ options = _.clone(optionsOrCallback);
+ }
+ options = options || {};
+
+ return this.then(function(result) {
+ if (options.success) {
+ options.success.apply(this, arguments);
+ } else if (model) {
+ // When there's no callback, a sync event should be triggered.
+ model.trigger('sync', model, result, options);
+ }
+ return Parse.Promise.as.apply(Parse.Promise, arguments);
+ }, function(error) {
+ if (options.error) {
+ if (!_.isUndefined(model)) {
+ options.error(model, error);
+ } else {
+ options.error(error);
+ }
+ } else if (model) {
+ // When there's no error callback, an error event should be triggered.
+ model.trigger('error', model, error, options);
+ }
+ // By explicitly returning a rejected Promise, this will work with
+ // either jQuery or Promises/A semantics.
+ return Parse.Promise.error(error);
+ });
+ },
+
+ /**
+ * Adds a callback function that should be called regardless of whether
+ * this promise failed or succeeded. The callback will be given either the
+ * array of results for its first argument, or the error as its second,
+ * depending on whether this Promise was rejected or resolved. Returns a
+ * new Promise, like "then" would.
+ * @param {Function} continuation the callback.
+ */
+ _continueWith: function(continuation) {
+ return this.then(function() {
+ return continuation(arguments, null);
+ }, function(error) {
+ return continuation(null, error);
+ });
+ }
+
+ });
+
+}(this));
+
+/*jshint bitwise:false *//*global FileReader: true, File: true */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ var b64Digit = function(number) {
+ if (number < 26) {
+ return String.fromCharCode(65 + number);
+ }
+ if (number < 52) {
+ return String.fromCharCode(97 + (number - 26));
+ }
+ if (number < 62) {
+ return String.fromCharCode(48 + (number - 52));
+ }
+ if (number === 62) {
+ return "+";
+ }
+ if (number === 63) {
+ return "/";
+ }
+ throw "Tried to encode large digit " + number + " in base64.";
+ };
+
+ var encodeBase64 = function(array) {
+ var chunks = [];
+ chunks.length = Math.ceil(array.length / 3);
+ _.times(chunks.length, function(i) {
+ var b1 = array[i * 3];
+ var b2 = array[i * 3 + 1] || 0;
+ var b3 = array[i * 3 + 2] || 0;
+
+ var has2 = (i * 3 + 1) < array.length;
+ var has3 = (i * 3 + 2) < array.length;
+
+ chunks[i] = [
+ b64Digit((b1 >> 2) & 0x3F),
+ b64Digit(((b1 << 4) & 0x30) | ((b2 >> 4) & 0x0F)),
+ has2 ? b64Digit(((b2 << 2) & 0x3C) | ((b3 >> 6) & 0x03)) : "=",
+ has3 ? b64Digit(b3 & 0x3F) : "="
+ ].join("");
+ });
+ return chunks.join("");
+ };
+
+
+ // A list of file extensions to mime types as found here:
+ // http://stackoverflow.com/questions/58510/using-net-how-can-you-find-the-
+ // mime-type-of-a-file-based-on-the-file-signature
+ var mimeTypes = {
+ ai: "application/postscript",
+ aif: "audio/x-aiff",
+ aifc: "audio/x-aiff",
+ aiff: "audio/x-aiff",
+ asc: "text/plain",
+ atom: "application/atom+xml",
+ au: "audio/basic",
+ avi: "video/x-msvideo",
+ bcpio: "application/x-bcpio",
+ bin: "application/octet-stream",
+ bmp: "image/bmp",
+ cdf: "application/x-netcdf",
+ cgm: "image/cgm",
+ "class": "application/octet-stream",
+ cpio: "application/x-cpio",
+ cpt: "application/mac-compactpro",
+ csh: "application/x-csh",
+ css: "text/css",
+ dcr: "application/x-director",
+ dif: "video/x-dv",
+ dir: "application/x-director",
+ djv: "image/vnd.djvu",
+ djvu: "image/vnd.djvu",
+ dll: "application/octet-stream",
+ dmg: "application/octet-stream",
+ dms: "application/octet-stream",
+ doc: "application/msword",
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml." +
+ "document",
+ dotx: "application/vnd.openxmlformats-officedocument.wordprocessingml." +
+ "template",
+ docm: "application/vnd.ms-word.document.macroEnabled.12",
+ dotm: "application/vnd.ms-word.template.macroEnabled.12",
+ dtd: "application/xml-dtd",
+ dv: "video/x-dv",
+ dvi: "application/x-dvi",
+ dxr: "application/x-director",
+ eps: "application/postscript",
+ etx: "text/x-setext",
+ exe: "application/octet-stream",
+ ez: "application/andrew-inset",
+ gif: "image/gif",
+ gram: "application/srgs",
+ grxml: "application/srgs+xml",
+ gtar: "application/x-gtar",
+ hdf: "application/x-hdf",
+ hqx: "application/mac-binhex40",
+ htm: "text/html",
+ html: "text/html",
+ ice: "x-conference/x-cooltalk",
+ ico: "image/x-icon",
+ ics: "text/calendar",
+ ief: "image/ief",
+ ifb: "text/calendar",
+ iges: "model/iges",
+ igs: "model/iges",
+ jnlp: "application/x-java-jnlp-file",
+ jp2: "image/jp2",
+ jpe: "image/jpeg",
+ jpeg: "image/jpeg",
+ jpg: "image/jpeg",
+ js: "application/x-javascript",
+ kar: "audio/midi",
+ latex: "application/x-latex",
+ lha: "application/octet-stream",
+ lzh: "application/octet-stream",
+ m3u: "audio/x-mpegurl",
+ m4a: "audio/mp4a-latm",
+ m4b: "audio/mp4a-latm",
+ m4p: "audio/mp4a-latm",
+ m4u: "video/vnd.mpegurl",
+ m4v: "video/x-m4v",
+ mac: "image/x-macpaint",
+ man: "application/x-troff-man",
+ mathml: "application/mathml+xml",
+ me: "application/x-troff-me",
+ mesh: "model/mesh",
+ mid: "audio/midi",
+ midi: "audio/midi",
+ mif: "application/vnd.mif",
+ mov: "video/quicktime",
+ movie: "video/x-sgi-movie",
+ mp2: "audio/mpeg",
+ mp3: "audio/mpeg",
+ mp4: "video/mp4",
+ mpe: "video/mpeg",
+ mpeg: "video/mpeg",
+ mpg: "video/mpeg",
+ mpga: "audio/mpeg",
+ ms: "application/x-troff-ms",
+ msh: "model/mesh",
+ mxu: "video/vnd.mpegurl",
+ nc: "application/x-netcdf",
+ oda: "application/oda",
+ ogg: "application/ogg",
+ pbm: "image/x-portable-bitmap",
+ pct: "image/pict",
+ pdb: "chemical/x-pdb",
+ pdf: "application/pdf",
+ pgm: "image/x-portable-graymap",
+ pgn: "application/x-chess-pgn",
+ pic: "image/pict",
+ pict: "image/pict",
+ png: "image/png",
+ pnm: "image/x-portable-anymap",
+ pnt: "image/x-macpaint",
+ pntg: "image/x-macpaint",
+ ppm: "image/x-portable-pixmap",
+ ppt: "application/vnd.ms-powerpoint",
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml." +
+ "presentation",
+ potx: "application/vnd.openxmlformats-officedocument.presentationml." +
+ "template",
+ ppsx: "application/vnd.openxmlformats-officedocument.presentationml." +
+ "slideshow",
+ ppam: "application/vnd.ms-powerpoint.addin.macroEnabled.12",
+ pptm: "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
+ potm: "application/vnd.ms-powerpoint.template.macroEnabled.12",
+ ppsm: "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
+ ps: "application/postscript",
+ qt: "video/quicktime",
+ qti: "image/x-quicktime",
+ qtif: "image/x-quicktime",
+ ra: "audio/x-pn-realaudio",
+ ram: "audio/x-pn-realaudio",
+ ras: "image/x-cmu-raster",
+ rdf: "application/rdf+xml",
+ rgb: "image/x-rgb",
+ rm: "application/vnd.rn-realmedia",
+ roff: "application/x-troff",
+ rtf: "text/rtf",
+ rtx: "text/richtext",
+ sgm: "text/sgml",
+ sgml: "text/sgml",
+ sh: "application/x-sh",
+ shar: "application/x-shar",
+ silo: "model/mesh",
+ sit: "application/x-stuffit",
+ skd: "application/x-koan",
+ skm: "application/x-koan",
+ skp: "application/x-koan",
+ skt: "application/x-koan",
+ smi: "application/smil",
+ smil: "application/smil",
+ snd: "audio/basic",
+ so: "application/octet-stream",
+ spl: "application/x-futuresplash",
+ src: "application/x-wais-source",
+ sv4cpio: "application/x-sv4cpio",
+ sv4crc: "application/x-sv4crc",
+ svg: "image/svg+xml",
+ swf: "application/x-shockwave-flash",
+ t: "application/x-troff",
+ tar: "application/x-tar",
+ tcl: "application/x-tcl",
+ tex: "application/x-tex",
+ texi: "application/x-texinfo",
+ texinfo: "application/x-texinfo",
+ tif: "image/tiff",
+ tiff: "image/tiff",
+ tr: "application/x-troff",
+ tsv: "text/tab-separated-values",
+ txt: "text/plain",
+ ustar: "application/x-ustar",
+ vcd: "application/x-cdlink",
+ vrml: "model/vrml",
+ vxml: "application/voicexml+xml",
+ wav: "audio/x-wav",
+ wbmp: "image/vnd.wap.wbmp",
+ wbmxl: "application/vnd.wap.wbxml",
+ wml: "text/vnd.wap.wml",
+ wmlc: "application/vnd.wap.wmlc",
+ wmls: "text/vnd.wap.wmlscript",
+ wmlsc: "application/vnd.wap.wmlscriptc",
+ wrl: "model/vrml",
+ xbm: "image/x-xbitmap",
+ xht: "application/xhtml+xml",
+ xhtml: "application/xhtml+xml",
+ xls: "application/vnd.ms-excel",
+ xml: "application/xml",
+ xpm: "image/x-xpixmap",
+ xsl: "application/xml",
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml." +
+ "template",
+ xlsm: "application/vnd.ms-excel.sheet.macroEnabled.12",
+ xltm: "application/vnd.ms-excel.template.macroEnabled.12",
+ xlam: "application/vnd.ms-excel.addin.macroEnabled.12",
+ xlsb: "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
+ xslt: "application/xslt+xml",
+ xul: "application/vnd.mozilla.xul+xml",
+ xwd: "image/x-xwindowdump",
+ xyz: "chemical/x-xyz",
+ zip: "application/zip"
+ };
+
+ /**
+ * Reads a File using a FileReader.
+ * @param file {File} the File to read.
+ * @param type {String} (optional) the mimetype to override with.
+ * @return {Parse.Promise} A Promise that will be fulfilled with a
+ * base64-encoded string of the data and its mime type.
+ */
+ var readAsync = function(file, type) {
+ var promise = new Parse.Promise();
+
+ if (typeof(FileReader) === "undefined") {
+ return Parse.Promise.error(new Parse.Error(
+ Parse.Error.FILE_READ_ERROR,
+ "Attempted to use a FileReader on an unsupported browser."));
+ }
+
+ var reader = new FileReader();
+ reader.onloadend = function() {
+ if (reader.readyState !== 2) {
+ promise.reject(new Parse.Error(
+ Parse.Error.FILE_READ_ERROR,
+ "Error reading file."));
+ return;
+ }
+
+ var dataURL = reader.result;
+ var matches = /^data:([^;]*);base64,(.*)$/.exec(dataURL);
+ if (!matches) {
+ promise.reject(new Parse.Error(
+ Parse.Error.FILE_READ_ERROR,
+ "Unable to interpret data URL: " + dataURL));
+ return;
+ }
+
+ promise.resolve(matches[2], type || matches[1]);
+ };
+ reader.readAsDataURL(file);
+ return promise;
+ };
+
+ /**
+ * A Parse.File is a local representation of a file that is saved to the Parse
+ * cloud.
+ * @class
+ * @param name {String} The file's name. This will be prefixed by a unique
+ * value once the file has finished saving. The file name must begin with
+ * an alphanumeric character, and consist of alphanumeric characters,
+ * periods, spaces, underscores, or dashes.
+ * @param data {Array} The data for the file, as either:
+ * 1. an Array of byte value Numbers, or
+ * 2. an Object like { base64: "..." } with a base64-encoded String.
+ * 3. a File object selected with a file upload control. (3) only works
+ * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
+ * For example:
+ * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+ * if (fileUploadControl.files.length > 0) {
+ * var file = fileUploadControl.files[0];
+ * var name = "photo.jpg";
+ * var parseFile = new Parse.File(name, file);
+ * parseFile.save().then(function() {
+ * // The file has been saved to Parse.
+ * }, function(error) {
+ * // The file either could not be read, or could not be saved to Parse.
+ * });
+ * }
+ * @param type {String} Optional Content-Type header to use for the file. If
+ * this is omitted, the content type will be inferred from the name's
+ * extension.
+ */
+ Parse.File = function(name, data, type) {
+ this._name = name;
+
+ // Guess the content type from the extension if we need to.
+ var extension = /\.([^.]*)$/.exec(name);
+ if (extension) {
+ extension = extension[1].toLowerCase();
+ }
+ var guessedType = type || mimeTypes[extension] || "text/plain";
+
+ if (_.isArray(data)) {
+ this._source = Parse.Promise.as(encodeBase64(data), guessedType);
+ } else if (data && data.base64) {
+ // if it contains data uri, extract based64 and the type out of it.
+ /*jslint maxlen: 1000*/
+ var dataUriRegexp = /^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,(\S+)/;
+ /*jslint maxlen: 80*/
+
+ var matches = dataUriRegexp.exec(data.base64);
+ if (matches && matches.length > 0) {
+ // if data URI with charset, there will have 4 matches.
+ this._source = Parse.Promise.as(
+ (matches.length === 4 ? matches[3] : matches[2]), matches[1]
+ );
+ } else {
+ this._source = Parse.Promise.as(data.base64, guessedType);
+ }
+ } else if (typeof(File) !== "undefined" && data instanceof File) {
+ this._source = readAsync(data, type);
+ } else if (_.isString(data)) {
+ throw "Creating a Parse.File from a String is not yet supported.";
+ }
+ };
+
+ Parse.File.prototype = {
+
+ /**
+ * Gets the name of the file. Before save is called, this is the filename
+ * given by the user. After save is called, that name gets prefixed with a
+ * unique identifier.
+ */
+ name: function() {
+ return this._name;
+ },
+
+ /**
+ * Gets the url of the file. It is only available after you save the file or
+ * after you get the file from a Parse.Object.
+ * @return {String}
+ */
+ url: function() {
+ return this._url;
+ },
+
+ /**
+ * Saves the file to the Parse cloud.
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} Promise that is resolved when the save finishes.
+ */
+ save: function(options) {
+ options= options || {};
+
+ var self = this;
+ if (!self._previousSave) {
+ self._previousSave = self._source.then(function(base64, type) {
+ var data = {
+ base64: base64,
+ _ContentType: type
+ };
+ return Parse._request({
+ route: "files",
+ className: self._name,
+ method: 'POST',
+ data: data,
+ useMasterKey: options.useMasterKey
+ });
+
+ }).then(function(response) {
+ self._name = response.name;
+ self._url = response.url;
+ return self;
+ });
+ }
+ return self._previousSave._thenRunCallbacks(options);
+ }
+ };
+
+}(this));
+
+// Parse.Object is analogous to the Java ParseObject.
+// It also implements the same interface as a Backbone model.
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Creates a new model with defined attributes. A client id (cid) is
+ * automatically generated and assigned for you.
+ *
+ * You won't normally call this method directly. It is recommended that
+ * you use a subclass of Parse.Object instead, created by calling
+ * extend.
+ *
+ * However, if you don't want to use a subclass, or aren't sure which
+ * subclass is appropriate, you can use this form:
+ * var object = new Parse.Object("ClassName");
+ *
+ * That is basically equivalent to:
+ * var MyClass = Parse.Object.extend("ClassName");
+ * var object = new MyClass();
+ *
+ *
+ * @param {Object} attributes The initial set of data to store in the object.
+ * @param {Object} options A set of Backbone-like options for creating the
+ * object. The only option currently supported is "collection".
+ * @see Parse.Object.extend
+ *
+ * @class
+ *
+ * The fundamental unit of Parse data, which implements the Backbone Model
+ * interface.
+ */
+ Parse.Object = function(attributes, options) {
+ // Allow new Parse.Object("ClassName") as a shortcut to _create.
+ if (_.isString(attributes)) {
+ return Parse.Object._create.apply(this, arguments);
+ }
+
+ attributes = attributes || {};
+ if (options && options.parse) {
+ attributes = this.parse(attributes);
+ }
+ var defaults = Parse._getValue(this, 'defaults');
+ if (defaults) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ if (options && options.collection) {
+ this.collection = options.collection;
+ }
+
+ this._serverData = {}; // The last known data for this object from cloud.
+ this._opSetQueue = [{}]; // List of sets of changes to the data.
+ this.attributes = {}; // The best estimate of this's current data.
+
+ this._hashedJSON = {}; // Hash of values of containers at last save.
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ if (!this.set(attributes, {silent: true})) {
+ throw new Error("Can't create an invalid Parse.Object");
+ }
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._hasData = true;
+ this._previousAttributes = _.clone(this.attributes);
+ this.initialize.apply(this, arguments);
+ };
+
+ /**
+ * The ID of this object, unique within its class.
+ * @name id
+ * @type String
+ * @field
+ * @memberOf Parse.Object.prototype
+ */
+
+ /**
+ * The first time this object was saved on the server.
+ * @name createdAt
+ * @type Date
+ * @field
+ * @memberOf Parse.Object.prototype
+ */
+
+ /**
+ * The last time this object was updated on the server.
+ * @name updatedAt
+ * @type Date
+ * @field
+ * @memberOf Parse.Object.prototype
+ */
+
+ /**
+ * Saves the given list of Parse.Object.
+ * If any error is encountered, stops and calls the error handler.
+ *
+ *
+ * Parse.Object.saveAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were saved.
+ * },
+ * error: function(error) {
+ * // An error occurred while saving one of the objects.
+ * },
+ * });
+ *
+ *
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ */
+ Parse.Object.saveAll = function(list, options) {
+ options = options || {};
+ return Parse.Object._deepSaveAsync(list, {
+ useMasterKey: options.useMasterKey
+ })._thenRunCallbacks(options);
+ };
+
+ /**
+ * Destroy the given list of models on the server if it was already persisted.
+ * Optimistically removes each model from its collection, if it has one.
+ * If `wait: true` is passed, waits for the server to respond before removal.
+ *
+ * Unlike saveAll, if an error occurs while deleting an individual model,
+ * this method will continue trying to delete the rest of the models if
+ * possible, except in the case of a fatal error like a connection error.
+ *
+ *
In particular, the Parse.Error object returned in the case of error may
+ * be one of two types:
+ *
+ *
+ * - A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an
+ * array of other Parse.Error objects. Each error object in this array
+ * has an "object" property that references the object that could not be
+ * deleted (for instance, because that object could not be found).
+ * - A non-aggregate Parse.Error. This indicates a serious error that
+ * caused the delete operation to be aborted partway through (for
+ * instance, a connection failure in the middle of the delete).
+ *
+ *
+ *
+ * Parse.Object.destroyAll([object1, object2, ...], {
+ * success: function() {
+ * // All the objects were deleted.
+ * },
+ * error: function(error) {
+ * // An error occurred while deleting one or more of the objects.
+ * // If this is an aggregate error, then we can inspect each error
+ * // object individually to determine the reason why a particular
+ * // object was not deleted.
+ * if (error.code == Parse.Error.AGGREGATE_ERROR) {
+ * for (var i = 0; i < error.errors.length; i++) {
+ * console.log("Couldn't delete " + error.errors[i].object.id +
+ * "due to " + error.errors[i].message);
+ * }
+ * } else {
+ * console.log("Delete aborted because of " + error.message);
+ * }
+ * },
+ * });
+ *
+ *
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ * @return {Parse.Promise} A promise that is fulfilled when the destroyAll
+ * completes.
+ */
+ Parse.Object.destroyAll = function(list, options) {
+ options = options || {};
+
+ var triggerDestroy = function(object) {
+ object.trigger('destroy', object, object.collection, options);
+ };
+
+ var errors = [];
+ var destroyBatch = function(batch) {
+ var promise = Parse.Promise.as();
+
+ if (batch.length > 0) {
+ promise = promise.then(function() {
+ return Parse._request({
+ route: "batch",
+ method: "POST",
+ useMasterKey: options.useMasterKey,
+ data: {
+ requests: _.map(batch, function(object) {
+ return {
+ method: "DELETE",
+ path: "/1/classes/" + object.className + "/" + object.id
+ };
+ })
+ }
+ });
+ }).then(function(responses, status, xhr) {
+ Parse._arrayEach(batch, function(object, i) {
+ if (responses[i].success && options.wait) {
+ triggerDestroy(object);
+ } else if (responses[i].error) {
+ var error = new Parse.Error(responses[i].error.code,
+ responses[i].error.error);
+ error.object = object;
+
+ errors.push(error);
+ }
+ });
+ });
+ }
+
+ return promise;
+ };
+
+ var promise = Parse.Promise.as();
+ var batch = [];
+ Parse._arrayEach(list, function(object, i) {
+ if (!object.id || !options.wait) {
+ triggerDestroy(object);
+ }
+
+ if (object.id) {
+ batch.push(object);
+ }
+
+ if (batch.length === 20 || i+1 === list.length) {
+ var thisBatch = batch;
+ batch = [];
+
+ promise = promise.then(function() {
+ return destroyBatch(thisBatch);
+ });
+ }
+ });
+
+ return promise.then(function() {
+ if (errors.length === 0) {
+ return true;
+ } else {
+ var error = new Parse.Error(Parse.Error.AGGREGATE_ERROR,
+ "Error deleting an object in destroyAll");
+ error.errors = errors;
+
+ return Parse.Promise.error(error);
+ }
+ })._thenRunCallbacks(options);
+ };
+
+ /**
+ * Fetches the given list of Parse.Object.
+ * If any error is encountered, stops and calls the error handler.
+ *
+ *
+ * Parse.Object.fetchAll([object1, object2, ...], {
+ * success: function(list) {
+ * // All the objects were fetched.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - success: A Backbone-style success callback.
+ *
- error: An Backbone-style error callback.
+ *
+ */
+ Parse.Object.fetchAll = function(list, options) {
+ return Parse.Object._fetchAll(
+ list,
+ true
+ )._thenRunCallbacks(options);
+ };
+
+ /**
+ * Fetches the given list of Parse.Object if needed.
+ * If any error is encountered, stops and calls the error handler.
+ *
+ *
+ * Parse.Object.fetchAllIfNeeded([object1, ...], {
+ * success: function(list) {
+ * // Objects were fetched and updated.
+ * },
+ * error: function(error) {
+ * // An error occurred while fetching one of the objects.
+ * },
+ * });
+ *
+ *
+ * @param {Array} list A list of Parse.Object.
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - success: A Backbone-style success callback.
+ *
- error: An Backbone-style error callback.
+ *
+ */
+ Parse.Object.fetchAllIfNeeded = function(list, options) {
+ return Parse.Object._fetchAll(
+ list,
+ false
+ )._thenRunCallbacks(options);
+ };
+
+ // Attach all inheritable methods to the Parse.Object prototype.
+ _.extend(Parse.Object.prototype, Parse.Events,
+ /** @lends Parse.Object.prototype */ {
+ _existed: false,
+
+ /**
+ * Initialize is an empty function by default. Override it with your own
+ * initialization logic.
+ */
+ initialize: function(){},
+
+ /**
+ * Returns a JSON version of the object suitable for saving to Parse.
+ * @return {Object}
+ */
+ toJSON: function() {
+ var json = this._toFullJSON();
+ Parse._arrayEach(["__type", "className"],
+ function(key) { delete json[key]; });
+ return json;
+ },
+
+ _toFullJSON: function(seenObjects) {
+ var json = _.clone(this.attributes);
+ Parse._objectEach(json, function(val, key) {
+ json[key] = Parse._encode(val, seenObjects);
+ });
+ Parse._objectEach(this._operations, function(val, key) {
+ json[key] = val;
+ });
+
+ if (_.has(this, "id")) {
+ json.objectId = this.id;
+ }
+ if (_.has(this, "createdAt")) {
+ if (_.isDate(this.createdAt)) {
+ json.createdAt = this.createdAt.toJSON();
+ } else {
+ json.createdAt = this.createdAt;
+ }
+ }
+
+ if (_.has(this, "updatedAt")) {
+ if (_.isDate(this.updatedAt)) {
+ json.updatedAt = this.updatedAt.toJSON();
+ } else {
+ json.updatedAt = this.updatedAt;
+ }
+ }
+ json.__type = "Object";
+ json.className = this.className;
+ return json;
+ },
+
+ /**
+ * Updates _hashedJSON to reflect the current state of this object.
+ * Adds any changed hash values to the set of pending changes.
+ */
+ _refreshCache: function() {
+ var self = this;
+ if (self._refreshingCache) {
+ return;
+ }
+ self._refreshingCache = true;
+ Parse._objectEach(this.attributes, function(value, key) {
+ if (value instanceof Parse.Object) {
+ value._refreshCache();
+ } else if (_.isObject(value)) {
+ var objectArray = false;
+ if (_.isArray(value)) {
+ // We don't cache arrays of Parse.Objects
+ _.each(value, function(arrVal) {
+ if (arrVal instanceof Parse.Object) {
+ objectArray = true;
+ arrVal._refreshCache();
+ }
+ });
+ }
+ if (!objectArray && self._resetCacheForKey(key)) {
+ self.set(key, new Parse.Op.Set(value), { silent: true });
+ }
+ }
+ });
+ delete self._refreshingCache;
+ },
+
+ /**
+ * Returns true if this object has been modified since its last
+ * save/refresh. If an attribute is specified, it returns true only if that
+ * particular attribute has been modified since the last save/refresh.
+ * @param {String} attr An attribute name (optional).
+ * @return {Boolean}
+ */
+ dirty: function(attr) {
+ this._refreshCache();
+
+ var currentChanges = _.last(this._opSetQueue);
+
+ if (attr) {
+ return (currentChanges[attr] ? true : false);
+ }
+ if (!this.id) {
+ return true;
+ }
+ if (_.keys(currentChanges).length > 0) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Returns an array of keys that have been modified since last save/refresh
+ * @return {Array of string}
+ */
+ dirtyKeys: function() {
+ return _.keys(_.last(this._opSetQueue));
+ },
+
+ /**
+ * Gets a Pointer referencing this Object.
+ */
+ _toPointer: function() {
+ if (!this.id) {
+ throw new Error("Can't serialize an unsaved Parse.Object");
+ }
+ return { __type: "Pointer",
+ className: this.className,
+ objectId: this.id };
+ },
+
+ /**
+ * Gets the value of an attribute.
+ * @param {String} attr The string name of an attribute.
+ */
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ /**
+ * Gets a relation on the given class for the attribute.
+ * @param String attr The attribute to get the relation for.
+ */
+ relation: function(attr) {
+ var value = this.get(attr);
+ if (value) {
+ if (!(value instanceof Parse.Relation)) {
+ throw "Called relation() on non-relation field " + attr;
+ }
+ value._ensureParentAndKey(this, attr);
+ return value;
+ } else {
+ return new Parse.Relation(this, attr);
+ }
+ },
+
+ /**
+ * Gets the HTML-escaped value of an attribute.
+ */
+ escape: function(attr) {
+ var html = this._escapedAttributes[attr];
+ if (html) {
+ return html;
+ }
+ var val = this.attributes[attr];
+ var escaped;
+ if (Parse._isNullOrUndefined(val)) {
+ escaped = '';
+ } else {
+ escaped = _.escape(val.toString());
+ }
+ this._escapedAttributes[attr] = escaped;
+ return escaped;
+ },
+
+ /**
+ * Returns true if the attribute contains a value that is not
+ * null or undefined.
+ * @param {String} attr The string name of the attribute.
+ * @return {Boolean}
+ */
+ has: function(attr) {
+ return !Parse._isNullOrUndefined(this.attributes[attr]);
+ },
+
+ /**
+ * Pulls "special" fields like objectId, createdAt, etc. out of attrs
+ * and puts them on "this" directly. Removes them from attrs.
+ * @param attrs - A dictionary with the data for this Parse.Object.
+ */
+ _mergeMagicFields: function(attrs) {
+ // Check for changes of magic fields.
+ var model = this;
+ var specialFields = ["id", "objectId", "createdAt", "updatedAt"];
+ Parse._arrayEach(specialFields, function(attr) {
+ if (attrs[attr]) {
+ if (attr === "objectId") {
+ model.id = attrs[attr];
+ } else if ((attr === "createdAt" || attr === "updatedAt") &&
+ !_.isDate(attrs[attr])) {
+ model[attr] = Parse._parseDate(attrs[attr]);
+ } else {
+ model[attr] = attrs[attr];
+ }
+ delete attrs[attr];
+ }
+ });
+ },
+
+ /**
+ * Copies the given serverData to "this", refreshes attributes, and
+ * clears pending changes;
+ */
+ _copyServerData: function(serverData) {
+ // Copy server data
+ var tempServerData = {};
+ Parse._objectEach(serverData, function(value, key) {
+ tempServerData[key] = Parse._decode(key, value);
+ });
+ this._serverData = tempServerData;
+
+ // Refresh the attributes.
+ this._rebuildAllEstimatedData();
+
+
+ // Clear out any changes the user might have made previously.
+ this._refreshCache();
+ this._opSetQueue = [{}];
+
+ // Refresh the attributes again.
+ this._rebuildAllEstimatedData();
+ },
+
+ /**
+ * Merges another object's attributes into this object.
+ */
+ _mergeFromObject: function(other) {
+ if (!other) {
+ return;
+ }
+
+ // This does the inverse of _mergeMagicFields.
+ this.id = other.id;
+ this.createdAt = other.createdAt;
+ this.updatedAt = other.updatedAt;
+
+ this._copyServerData(other._serverData);
+
+ this._hasData = true;
+ },
+
+ /**
+ * Returns the json to be sent to the server.
+ */
+ _startSave: function() {
+ this._opSetQueue.push({});
+ },
+
+ /**
+ * Called when a save fails because of an error. Any changes that were part
+ * of the save need to be merged with changes made after the save. This
+ * might throw an exception is you do conflicting operations. For example,
+ * if you do:
+ * object.set("foo", "bar");
+ * object.set("invalid field name", "baz");
+ * object.save();
+ * object.increment("foo");
+ * then this will throw when the save fails and the client tries to merge
+ * "bar" with the +1.
+ */
+ _cancelSave: function() {
+ var self = this;
+ var failedChanges = _.first(this._opSetQueue);
+ this._opSetQueue = _.rest(this._opSetQueue);
+ var nextChanges = _.first(this._opSetQueue);
+ Parse._objectEach(failedChanges, function(op, key) {
+ var op1 = failedChanges[key];
+ var op2 = nextChanges[key];
+ if (op1 && op2) {
+ nextChanges[key] = op2._mergeWithPrevious(op1);
+ } else if (op1) {
+ nextChanges[key] = op1;
+ }
+ });
+ this._saving = this._saving - 1;
+ },
+
+ /**
+ * Called when a save completes successfully. This merges the changes that
+ * were saved into the known server data, and overrides it with any data
+ * sent directly from the server.
+ */
+ _finishSave: function(serverData) {
+ // Grab a copy of any object referenced by this object. These instances
+ // may have already been fetched, and we don't want to lose their data.
+ // Note that doing it like this means we will unify separate copies of the
+ // same object, but that's a risk we have to take.
+ var fetchedObjects = {};
+ Parse._traverse(this.attributes, function(object) {
+ if (object instanceof Parse.Object && object.id && object._hasData) {
+ fetchedObjects[object.id] = object;
+ }
+ });
+
+ var savedChanges = _.first(this._opSetQueue);
+ this._opSetQueue = _.rest(this._opSetQueue);
+ this._applyOpSet(savedChanges, this._serverData);
+ this._mergeMagicFields(serverData);
+ var self = this;
+ Parse._objectEach(serverData, function(value, key) {
+ self._serverData[key] = Parse._decode(key, value);
+
+ // Look for any objects that might have become unfetched and fix them
+ // by replacing their values with the previously observed values.
+ var fetched = Parse._traverse(self._serverData[key], function(object) {
+ if (object instanceof Parse.Object && fetchedObjects[object.id]) {
+ return fetchedObjects[object.id];
+ }
+ });
+ if (fetched) {
+ self._serverData[key] = fetched;
+ }
+ });
+ this._rebuildAllEstimatedData();
+ this._saving = this._saving - 1;
+ },
+
+ /**
+ * Called when a fetch or login is complete to set the known server data to
+ * the given object.
+ */
+ _finishFetch: function(serverData, hasData) {
+
+ this._opSetQueue = [{}];
+
+ // Bring in all the new server data.
+ this._mergeMagicFields(serverData);
+ this._copyServerData(serverData);
+
+ this._hasData = hasData;
+ },
+
+ /**
+ * Applies the set of Parse.Op in opSet to the object target.
+ */
+ _applyOpSet: function(opSet, target) {
+ var self = this;
+ Parse._objectEach(opSet, function(change, key) {
+ target[key] = change._estimate(target[key], self, key);
+ if (target[key] === Parse.Op._UNSET) {
+ delete target[key];
+ }
+ });
+ },
+
+ /**
+ * Replaces the cached value for key with the current value.
+ * Returns true if the new value is different than the old value.
+ */
+ _resetCacheForKey: function(key) {
+ var value = this.attributes[key];
+ if (_.isObject(value) &&
+ !(value instanceof Parse.Object) &&
+ !(value instanceof Parse.File)) {
+ value = value.toJSON ? value.toJSON() : value;
+ var json = JSON.stringify(value);
+ if (this._hashedJSON[key] !== json) {
+ var wasSet = !!this._hashedJSON[key];
+ this._hashedJSON[key] = json;
+ return wasSet;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Populates attributes[key] by starting with the last known data from the
+ * server, and applying all of the local changes that have been made to that
+ * key since then.
+ */
+ _rebuildEstimatedDataForKey: function(key) {
+ var self = this;
+ delete this.attributes[key];
+ if (this._serverData[key]) {
+ this.attributes[key] = this._serverData[key];
+ }
+ Parse._arrayEach(this._opSetQueue, function(opSet) {
+ var op = opSet[key];
+ if (op) {
+ self.attributes[key] = op._estimate(self.attributes[key], self, key);
+ if (self.attributes[key] === Parse.Op._UNSET) {
+ delete self.attributes[key];
+ } else {
+ self._resetCacheForKey(key);
+ }
+ }
+ });
+ },
+
+ /**
+ * Populates attributes by starting with the last known data from the
+ * server, and applying all of the local changes that have been made since
+ * then.
+ */
+ _rebuildAllEstimatedData: function() {
+ var self = this;
+
+ var previousAttributes = _.clone(this.attributes);
+
+ this.attributes = _.clone(this._serverData);
+ Parse._arrayEach(this._opSetQueue, function(opSet) {
+ self._applyOpSet(opSet, self.attributes);
+ Parse._objectEach(opSet, function(op, key) {
+ self._resetCacheForKey(key);
+ });
+ });
+
+ // Trigger change events for anything that changed because of the fetch.
+ Parse._objectEach(previousAttributes, function(oldValue, key) {
+ if (self.attributes[key] !== oldValue) {
+ self.trigger('change:' + key, self, self.attributes[key], {});
+ }
+ });
+ Parse._objectEach(this.attributes, function(newValue, key) {
+ if (!_.has(previousAttributes, key)) {
+ self.trigger('change:' + key, self, newValue, {});
+ }
+ });
+ },
+
+ /**
+ * Sets a hash of model attributes on the object, firing
+ * "change" unless you choose to silence it.
+ *
+ * You can call it with an object containing keys and values, or with one
+ * key and value. For example:
+ * gameTurn.set({
+ * player: player1,
+ * diceRoll: 2
+ * }, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("currentPlayer", player2, {
+ * error: function(gameTurnAgain, error) {
+ * // The set failed validation.
+ * }
+ * });
+ *
+ * game.set("finished", true);
+ *
+ * @param {String} key The key to set.
+ * @param {} value The value to give it.
+ * @param {Object} options A set of Backbone-like options for the set.
+ * The only supported options are silent,
+ * error, and promise.
+ * @return {Boolean} true if the set succeeded.
+ * @see Parse.Object#validate
+ * @see Parse.Error
+ */
+ set: function(key, value, options) {
+ var attrs, attr;
+ if (_.isObject(key) || Parse._isNullOrUndefined(key)) {
+ attrs = key;
+ Parse._objectEach(attrs, function(v, k) {
+ attrs[k] = Parse._decode(k, v);
+ });
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = Parse._decode(key, value);
+ }
+
+ // Extract attributes and options.
+ options = options || {};
+ if (!attrs) {
+ return this;
+ }
+ if (attrs instanceof Parse.Object) {
+ attrs = attrs.attributes;
+ }
+
+ // If the unset option is used, every attribute should be a Unset.
+ if (options.unset) {
+ Parse._objectEach(attrs, function(unused_value, key) {
+ attrs[key] = new Parse.Op.Unset();
+ });
+ }
+
+ // Apply all the attributes to get the estimated values.
+ var dataToValidate = _.clone(attrs);
+ var self = this;
+ Parse._objectEach(dataToValidate, function(value, key) {
+ if (value instanceof Parse.Op) {
+ dataToValidate[key] = value._estimate(self.attributes[key],
+ self, key);
+ if (dataToValidate[key] === Parse.Op._UNSET) {
+ delete dataToValidate[key];
+ }
+ }
+ });
+
+ // Run validation.
+ if (!this._validate(attrs, options)) {
+ return false;
+ }
+
+ this._mergeMagicFields(attrs);
+
+ options.changes = {};
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // Update attributes.
+ Parse._arrayEach(_.keys(attrs), function(attr) {
+ var val = attrs[attr];
+
+ // If this is a relation object we need to set the parent correctly,
+ // since the location where it was parsed does not have access to
+ // this object.
+ if (val instanceof Parse.Relation) {
+ val.parent = self;
+ }
+
+ if (!(val instanceof Parse.Op)) {
+ val = new Parse.Op.Set(val);
+ }
+
+ // See if this change will actually have any effect.
+ var isRealChange = true;
+ if (val instanceof Parse.Op.Set &&
+ _.isEqual(self.attributes[attr], val.value)) {
+ isRealChange = false;
+ }
+
+ if (isRealChange) {
+ delete escaped[attr];
+ if (options.silent) {
+ self._silent[attr] = true;
+ } else {
+ options.changes[attr] = true;
+ }
+ }
+
+ var currentChanges = _.last(self._opSetQueue);
+ currentChanges[attr] = val._mergeWithPrevious(currentChanges[attr]);
+ self._rebuildEstimatedDataForKey(attr);
+
+ if (isRealChange) {
+ self.changed[attr] = self.attributes[attr];
+ if (!options.silent) {
+ self._pending[attr] = true;
+ }
+ } else {
+ delete self.changed[attr];
+ delete self._pending[attr];
+ }
+ });
+
+ if (!options.silent) {
+ this.change(options);
+ }
+ return this;
+ },
+
+ /**
+ * Remove an attribute from the model, firing "change" unless
+ * you choose to silence it. This is a noop if the attribute doesn't
+ * exist.
+ */
+ unset: function(attr, options) {
+ options = options || {};
+ options.unset = true;
+ return this.set(attr, null, options);
+ },
+
+ /**
+ * Atomically increments the value of the given attribute the next time the
+ * object is saved. If no amount is specified, 1 is used by default.
+ *
+ * @param attr {String} The key.
+ * @param amount {Number} The amount to increment by.
+ */
+ increment: function(attr, amount) {
+ if (_.isUndefined(amount) || _.isNull(amount)) {
+ amount = 1;
+ }
+ return this.set(attr, new Parse.Op.Increment(amount));
+ },
+
+ /**
+ * Atomically add an object to the end of the array associated with a given
+ * key.
+ * @param attr {String} The key.
+ * @param item {} The item to add.
+ */
+ add: function(attr, item) {
+ return this.set(attr, new Parse.Op.Add([item]));
+ },
+
+ /**
+ * Atomically add an object to the array associated with a given key, only
+ * if it is not already present in the array. The position of the insert is
+ * not guaranteed.
+ *
+ * @param attr {String} The key.
+ * @param item {} The object to add.
+ */
+ addUnique: function(attr, item) {
+ return this.set(attr, new Parse.Op.AddUnique([item]));
+ },
+
+ /**
+ * Atomically remove all instances of an object from the array associated
+ * with a given key.
+ *
+ * @param attr {String} The key.
+ * @param item {} The object to remove.
+ */
+ remove: function(attr, item) {
+ return this.set(attr, new Parse.Op.Remove([item]));
+ },
+
+ /**
+ * Returns an instance of a subclass of Parse.Op describing what kind of
+ * modification has been performed on this field since the last time it was
+ * saved. For example, after calling object.increment("x"), calling
+ * object.op("x") would return an instance of Parse.Op.Increment.
+ *
+ * @param attr {String} The key.
+ * @returns {Parse.Op} The operation, or undefined if none.
+ */
+ op: function(attr) {
+ return _.last(this._opSetQueue)[attr];
+ },
+
+ /**
+ * Clear all attributes on the model, firing "change" unless
+ * you choose to silence it.
+ */
+ clear: function(options) {
+ options = options || {};
+ options.unset = true;
+ var keysToClear = _.extend(this.attributes, this._operations);
+ return this.set(keysToClear, options);
+ },
+
+ /**
+ * Returns a JSON-encoded set of operations to be sent with the next save
+ * request.
+ */
+ _getSaveJSON: function() {
+ var json = _.clone(_.first(this._opSetQueue));
+ Parse._objectEach(json, function(op, key) {
+ json[key] = op.toJSON();
+ });
+ return json;
+ },
+
+ /**
+ * Returns true if this object can be serialized for saving.
+ */
+ _canBeSerialized: function() {
+ return Parse.Object._canBeSerializedAsValue(this.attributes);
+ },
+
+ /**
+ * Fetch the model from the server. If the server's representation of the
+ * model differs from its current attributes, they will be overriden,
+ * triggering a "change" event.
+ *
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - success: A Backbone-style success callback.
+ *
- error: An Backbone-style error callback.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ * @return {Parse.Promise} A promise that is fulfilled when the fetch
+ * completes.
+ */
+ fetch: function(options) {
+ var self = this;
+ options = options || {};
+ var request = Parse._request({
+ method: 'GET',
+ route: "classes",
+ className: this.className,
+ objectId: this.id,
+ useMasterKey: options.useMasterKey
+ });
+ return request.then(function(response, status, xhr) {
+ self._finishFetch(self.parse(response, status, xhr), true);
+ return self;
+ })._thenRunCallbacks(options, this);
+ },
+
+ /**
+ * Set a hash of model attributes, and save the model to the server.
+ * updatedAt will be updated when the request returns.
+ * You can either call it as:
+ * object.save();
+ * or
+ * object.save(null, options);
+ * or
+ * object.save(attrs, options);
+ * or
+ * object.save(key, value, options);
+ *
+ * For example,
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }, {
+ * success: function(gameTurnAgain) {
+ * // The save was successful.
+ * },
+ * error: function(gameTurnAgain, error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * }
+ * });
+ * or with promises:
+ * gameTurn.save({
+ * player: "Jake Cutter",
+ * diceRoll: 2
+ * }).then(function(gameTurnAgain) {
+ * // The save was successful.
+ * }, function(error) {
+ * // The save failed. Error is an instance of Parse.Error.
+ * });
+ *
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - wait: Set to true to wait for the server to confirm a successful
+ * save before modifying the attributes on the object.
+ *
- silent: Set to true to avoid firing the `set` event.
+ *
- success: A Backbone-style success callback.
+ *
- error: An Backbone-style error callback.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ * @return {Parse.Promise} A promise that is fulfilled when the save
+ * completes.
+ * @see Parse.Error
+ */
+ save: function(arg1, arg2, arg3) {
+ var i, attrs, current, options, saved;
+ if (_.isObject(arg1) || Parse._isNullOrUndefined(arg1)) {
+ attrs = arg1;
+ options = arg2;
+ } else {
+ attrs = {};
+ attrs[arg1] = arg2;
+ options = arg3;
+ }
+
+ // Make save({ success: function() {} }) work.
+ if (!options && attrs) {
+ var extra_keys = _.reject(attrs, function(value, key) {
+ return _.include(["success", "error", "wait"], key);
+ });
+ if (extra_keys.length === 0) {
+ var all_functions = true;
+ if (_.has(attrs, "success") && !_.isFunction(attrs.success)) {
+ all_functions = false;
+ }
+ if (_.has(attrs, "error") && !_.isFunction(attrs.error)) {
+ all_functions = false;
+ }
+ if (all_functions) {
+ // This attrs object looks like it's really an options object,
+ // and there's no other options object, so let's just use it.
+ return this.save(null, attrs);
+ }
+ }
+ }
+
+ options = _.clone(options) || {};
+ if (options.wait) {
+ current = _.clone(this.attributes);
+ }
+
+ var setOptions = _.clone(options) || {};
+ if (setOptions.wait) {
+ setOptions.silent = true;
+ }
+ var setError;
+ setOptions.error = function(model, error) {
+ setError = error;
+ };
+ if (attrs && !this.set(attrs, setOptions)) {
+ return Parse.Promise.error(setError)._thenRunCallbacks(options, this);
+ }
+
+ var model = this;
+
+ // If there is any unsaved child, save it first.
+ model._refreshCache();
+
+
+
+ var unsavedChildren = [];
+ var unsavedFiles = [];
+ Parse.Object._findUnsavedChildren(model.attributes,
+ unsavedChildren,
+ unsavedFiles);
+ if (unsavedChildren.length + unsavedFiles.length > 0) {
+ return Parse.Object._deepSaveAsync(this.attributes, {
+ useMasterKey: options.useMasterKey
+ }).then(function() {
+ return model.save(null, options);
+ }, function(error) {
+ return Parse.Promise.error(error)._thenRunCallbacks(options, model);
+ });
+ }
+
+ this._startSave();
+ this._saving = (this._saving || 0) + 1;
+
+ this._allPreviousSaves = this._allPreviousSaves || Parse.Promise.as();
+ this._allPreviousSaves = this._allPreviousSaves._continueWith(function() {
+ var method = model.id ? 'PUT' : 'POST';
+
+ var json = model._getSaveJSON();
+
+ var route = "classes";
+ var className = model.className;
+ if (model.className === "_User" && !model.id) {
+ // Special-case user sign-up.
+ route = "users";
+ className = null;
+ }
+ var request = Parse._request({
+ route: route,
+ className: className,
+ objectId: model.id,
+ method: method,
+ useMasterKey: options.useMasterKey,
+ data: json
+ });
+
+ request = request.then(function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, status, xhr);
+ if (options.wait) {
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
+ }
+ model._finishSave(serverAttrs);
+ if (options.wait) {
+ model.set(current, setOptions);
+ }
+ return model;
+
+ }, function(error) {
+ model._cancelSave();
+ return Parse.Promise.error(error);
+
+ })._thenRunCallbacks(options, model);
+
+ return request;
+ });
+ return this._allPreviousSaves;
+ },
+
+ /**
+ * Destroy this model on the server if it was already persisted.
+ * Optimistically removes the model from its collection, if it has one.
+ * If `wait: true` is passed, waits for the server to respond
+ * before removal.
+ *
+ * @param {Object} options A Backbone-style callback object.
+ * Valid options are:
+ * - wait: Set to true to wait for the server to confirm successful
+ * deletion of the object before triggering the `destroy` event.
+ *
- success: A Backbone-style success callback
+ *
- error: An Backbone-style error callback.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ * @return {Parse.Promise} A promise that is fulfilled when the destroy
+ * completes.
+ */
+ destroy: function(options) {
+ options = options || {};
+ var model = this;
+
+ var triggerDestroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ if (!this.id) {
+ return triggerDestroy();
+ }
+
+ if (!options.wait) {
+ triggerDestroy();
+ }
+
+ var request = Parse._request({
+ route: "classes",
+ className: this.className,
+ objectId: this.id,
+ method: 'DELETE',
+ useMasterKey: options.useMasterKey
+ });
+ return request.then(function() {
+ if (options.wait) {
+ triggerDestroy();
+ }
+ return model;
+ })._thenRunCallbacks(options, this);
+ },
+
+ /**
+ * Converts a response into the hash of attributes to be set on the model.
+ * @ignore
+ */
+ parse: function(resp, status, xhr) {
+ var output = _.clone(resp);
+ _(["createdAt", "updatedAt"]).each(function(key) {
+ if (output[key]) {
+ output[key] = Parse._parseDate(output[key]);
+ }
+ });
+ if (!output.updatedAt) {
+ output.updatedAt = output.createdAt;
+ }
+ if (status) {
+ this._existed = (status !== 201);
+ }
+ return output;
+ },
+
+ /**
+ * Creates a new model with identical attributes to this one.
+ * @return {Parse.Object}
+ */
+ clone: function() {
+ return new this.constructor(this.attributes);
+ },
+
+ /**
+ * Returns true if this object has never been saved to Parse.
+ * @return {Boolean}
+ */
+ isNew: function() {
+ return !this.id;
+ },
+
+ /**
+ * Call this method to manually fire a `"change"` event for this model and
+ * a `"change:attribute"` event for each changed attribute.
+ * Calling this will cause all objects observing the model to update.
+ */
+ change: function(options) {
+ options = options || {};
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ var self = this;
+ Parse._objectEach(this._silent, function(attr) {
+ self._pending[attr] = true;
+ });
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ Parse._objectEach(changes, function(unused_value, attr) {
+ self.trigger('change:' + attr, self, self.get(attr), options);
+ });
+ if (changing) {
+ return this;
+ }
+
+ // This is to get around lint not letting us make a function in a loop.
+ var deleteChanged = function(value, attr) {
+ if (!self._pending[attr] && !self._silent[attr]) {
+ delete self.changed[attr];
+ }
+ };
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ Parse._objectEach(this.changed, deleteChanged);
+ self._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ /**
+ * Returns true if this object was created by the Parse server when the
+ * object might have already been there (e.g. in the case of a Facebook
+ * login)
+ */
+ existed: function() {
+ return this._existed;
+ },
+
+ /**
+ * Determine if the model has changed since the last "change"
+ * event. If you specify an attribute name, determine if that attribute
+ * has changed.
+ * @param {String} attr Optional attribute name
+ * @return {Boolean}
+ */
+ hasChanged: function(attr) {
+ if (!arguments.length) {
+ return !_.isEmpty(this.changed);
+ }
+ return this.changed && _.has(this.changed, attr);
+ },
+
+ /**
+ * Returns an object containing all the attributes that have changed, or
+ * false if there are no changed attributes. Useful for determining what
+ * parts of a view need to be updated and/or what attributes need to be
+ * persisted to the server. Unset attributes will be set to undefined.
+ * You can also pass an attributes object to diff against the model,
+ * determining if there *would be* a change.
+ */
+ changedAttributes: function(diff) {
+ if (!diff) {
+ return this.hasChanged() ? _.clone(this.changed) : false;
+ }
+ var changed = {};
+ var old = this._previousAttributes;
+ Parse._objectEach(diff, function(diffVal, attr) {
+ if (!_.isEqual(old[attr], diffVal)) {
+ changed[attr] = diffVal;
+ }
+ });
+ return changed;
+ },
+
+ /**
+ * Gets the previous value of an attribute, recorded at the time the last
+ * "change" event was fired.
+ * @param {String} attr Name of the attribute to get.
+ */
+ previous: function(attr) {
+ if (!arguments.length || !this._previousAttributes) {
+ return null;
+ }
+ return this._previousAttributes[attr];
+ },
+
+ /**
+ * Gets all of the attributes of the model at the time of the previous
+ * "change" event.
+ * @return {Object}
+ */
+ previousAttributes: function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ /**
+ * Checks if the model is currently in a valid state. It's only possible to
+ * get into an *invalid* state if you're using silent changes.
+ * @return {Boolean}
+ */
+ isValid: function() {
+ return !this.validate(this.attributes);
+ },
+
+ /**
+ * You should not call this function directly unless you subclass
+ * Parse.Object, in which case you can override this method
+ * to provide additional validation on set and
+ * save. Your implementation should return
+ *
+ * @param {Object} attrs The current data to validate.
+ * @param {Object} options A Backbone-like options object.
+ * @return {} False if the data is valid. An error object otherwise.
+ * @see Parse.Object#set
+ */
+ validate: function(attrs, options) {
+ if (_.has(attrs, "ACL") && !(attrs.ACL instanceof Parse.ACL)) {
+ return new Parse.Error(Parse.Error.OTHER_CAUSE,
+ "ACL must be a Parse.ACL.");
+ }
+ var correct = true;
+ Parse._objectEach(attrs, function(unused_value, key) {
+ if (!(/^[A-Za-z][0-9A-Za-z_]*$/).test(key)) {
+ correct = false;
+ }
+ });
+ if (!correct) {
+ return new Parse.Error(Parse.Error.INVALID_KEY_NAME);
+ }
+ return false;
+ },
+
+ /**
+ * Run validation against a set of incoming attributes, returning `true`
+ * if all is well. If a specific `error` callback has been passed,
+ * call that instead of firing the general `"error"` event.
+ */
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) {
+ return true;
+ }
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) {
+ return true;
+ }
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ },
+
+ /**
+ * Returns the ACL for this object.
+ * @returns {Parse.ACL} An instance of Parse.ACL.
+ * @see Parse.Object#get
+ */
+ getACL: function() {
+ return this.get("ACL");
+ },
+
+ /**
+ * Sets the ACL to be used for this object.
+ * @param {Parse.ACL} acl An instance of Parse.ACL.
+ * @param {Object} options Optional Backbone-like options object to be
+ * passed in to set.
+ * @return {Boolean} Whether the set passed validation.
+ * @see Parse.Object#set
+ */
+ setACL: function(acl, options) {
+ return this.set("ACL", acl, options);
+ }
+
+ });
+
+ /**
+ * Returns the appropriate subclass for making new instances of the given
+ * className string.
+ */
+ Parse.Object._getSubclass = function(className) {
+ if (!_.isString(className)) {
+ throw "Parse.Object._getSubclass requires a string argument.";
+ }
+ var ObjectClass = Parse.Object._classMap[className];
+ if (!ObjectClass) {
+ ObjectClass = Parse.Object.extend(className);
+ Parse.Object._classMap[className] = ObjectClass;
+ }
+ return ObjectClass;
+ };
+
+ /**
+ * Creates an instance of a subclass of Parse.Object for the given classname.
+ */
+ Parse.Object._create = function(className, attributes, options) {
+ var ObjectClass = Parse.Object._getSubclass(className);
+ return new ObjectClass(attributes, options);
+ };
+
+ /**
+ * Returns a list of object ids given a list of objects.
+ */
+ Parse.Object._toObjectIdArray = function(list, omitObjectsWithData) {
+ if (list.length === 0) {
+ return Parse.Promise.as(list);
+ }
+
+ var error;
+ var className = list[0].className;
+ var objectIds = [];
+ for (var i = 0; i < list.length; i++) {
+ var object = list[i];
+ if (className !== object.className) {
+ error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME,
+ "All objects should be of the same class");
+ return Parse.Promise.error(error);
+ } else if (!object.id) {
+ error = new Parse.Error(Parse.Error.MISSING_OBJECT_ID,
+ "All objects must have an ID");
+ return Parse.Promise.error(error);
+ } else if (omitObjectsWithData && object._hasData) {
+ continue;
+ }
+ objectIds.push(object.id);
+ }
+
+ return Parse.Promise.as(objectIds);
+ };
+
+ /**
+ * Updates a list of objects with fetched results.
+ */
+ Parse.Object._updateWithFetchedResults = function(list, fetched, forceFetch) {
+ var fetchedObjectsById = {};
+ Parse._arrayEach(fetched, function(object, i) {
+ fetchedObjectsById[object.id] = object;
+ });
+
+ for (var i = 0; i < list.length; i++) {
+ var object = list[i];
+ var fetchedObject = fetchedObjectsById[object.id];
+ if (!fetchedObject && forceFetch) {
+ var error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
+ "All objects must exist on the server");
+ return Parse.Promise.error(error);
+ }
+
+ object._mergeFromObject(fetchedObject);
+ }
+
+ return Parse.Promise.as(list);
+ };
+
+ /**
+ * Fetches the objects given in list. The forceFetch option will fetch all
+ * objects if true and ignore objects with data if false.
+ */
+ Parse.Object._fetchAll = function(list, forceFetch) {
+ if (list.length === 0) {
+ return Parse.Promise.as(list);
+ }
+
+ var omitObjectsWithData = !forceFetch;
+ return Parse.Object._toObjectIdArray(
+ list,
+ omitObjectsWithData
+ ).then(function(objectIds) {
+ var className = list[0].className;
+ var query = new Parse.Query(className);
+ query.containedIn("objectId", objectIds);
+ query.limit = objectIds.length;
+ return query.find();
+ }).then(function(results) {
+ return Parse.Object._updateWithFetchedResults(
+ list,
+ results,
+ forceFetch
+ );
+ });
+ };
+
+ // Set up a map of className to class so that we can create new instances of
+ // Parse Objects from JSON automatically.
+ Parse.Object._classMap = {};
+
+ Parse.Object._extend = Parse._extend;
+
+ /**
+ * Creates a new subclass of Parse.Object for the given Parse class name.
+ *
+ * Every extension of a Parse class will inherit from the most recent
+ * previous extension of that class. When a Parse.Object is automatically
+ * created by parsing JSON, it will use the most recent extension of that
+ * class.
+ *
+ * You should call either:
+ * var MyClass = Parse.Object.extend("MyClass", {
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ * or, for Backbone compatibility:
+ * var MyClass = Parse.Object.extend({
+ * className: "MyClass",
+ * Instance methods,
+ * initialize: function(attrs, options) {
+ * this.someInstanceProperty = [],
+ * Other instance properties
+ * }
+ * }, {
+ * Class properties
+ * });
+ *
+ * @param {String} className The name of the Parse class backing this model.
+ * @param {Object} protoProps Instance properties to add to instances of the
+ * class returned from this method.
+ * @param {Object} classProps Class properties to add the class returned from
+ * this method.
+ * @return {Class} A new subclass of Parse.Object.
+ */
+ Parse.Object.extend = function(className, protoProps, classProps) {
+ // Handle the case with only two args.
+ if (!_.isString(className)) {
+ if (className && _.has(className, "className")) {
+ return Parse.Object.extend(className.className, className, protoProps);
+ } else {
+ throw new Error(
+ "Parse.Object.extend's first argument should be the className.");
+ }
+ }
+
+ // If someone tries to subclass "User", coerce it to the right type.
+ if (className === "User" && Parse.User._performUserRewrite) {
+ className = "_User";
+ }
+ protoProps = protoProps || {};
+ protoProps.className = className;
+
+ var NewClassObject = null;
+ if (_.has(Parse.Object._classMap, className)) {
+ var OldClassObject = Parse.Object._classMap[className];
+ // This new subclass has been told to extend both from "this" and from
+ // OldClassObject. This is multiple inheritance, which isn't supported.
+ // For now, let's just pick one.
+ NewClassObject = OldClassObject._extend(protoProps, classProps);
+ } else {
+ NewClassObject = this._extend(protoProps, classProps);
+ }
+ // Extending a subclass should reuse the classname automatically.
+ NewClassObject.extend = function(arg0) {
+ if (_.isString(arg0) || (arg0 && _.has(arg0, "className"))) {
+ return Parse.Object.extend.apply(NewClassObject, arguments);
+ }
+ var newArguments = [className].concat(Parse._.toArray(arguments));
+ return Parse.Object.extend.apply(NewClassObject, newArguments);
+ };
+
+ /**
+ * Creates a reference to a subclass of Parse.Object with the given id. This
+ * does not exist on Parse.Object, only on subclasses.
+ *
+ * A shortcut for:
+ * var Foo = Parse.Object.extend("Foo");
+ * var pointerToFoo = new Foo();
+ * pointerToFoo.id = "myObjectId";
+ *
+ *
+ * @name createWithoutData
+ * @param {String} id The ID of the object to create a reference to.
+ * @return {Parse.Object} A Parse.Object reference.
+ * @function
+ * @memberOf Parse.Object
+ */
+ NewClassObject.createWithoutData = function(id) {
+ var obj = new NewClassObject();
+ obj.id = id;
+ return obj;
+ };
+
+ Parse.Object._classMap[className] = NewClassObject;
+ return NewClassObject;
+ };
+
+ Parse.Object._findUnsavedChildren = function(object, children, files) {
+ Parse._traverse(object, function(object) {
+ if (object instanceof Parse.Object) {
+ object._refreshCache();
+ if (object.dirty()) {
+ children.push(object);
+ }
+ return;
+ }
+
+ if (object instanceof Parse.File) {
+ if (!object.url()) {
+ files.push(object);
+ }
+ return;
+ }
+ });
+ };
+
+ Parse.Object._canBeSerializedAsValue = function(object) {
+
+ if (object instanceof Parse.Object) {
+ return !!object.id;
+ }
+ if (object instanceof Parse.File) {
+ // Don't recurse indefinitely into files.
+ return true;
+ }
+
+ var canBeSerializedAsValue = true;
+
+ if (_.isArray(object)) {
+ Parse._arrayEach(object, function(child) {
+ if (!Parse.Object._canBeSerializedAsValue(child)) {
+ canBeSerializedAsValue = false;
+ }
+ });
+ } else if (_.isObject(object)) {
+ Parse._objectEach(object, function(child) {
+ if (!Parse.Object._canBeSerializedAsValue(child)) {
+ canBeSerializedAsValue = false;
+ }
+ });
+ }
+ return canBeSerializedAsValue;
+ };
+
+ /**
+ * @param {Object} object The root object.
+ * @param {Object} options: The only valid option is useMasterKey.
+ */
+ Parse.Object._deepSaveAsync = function(object, options) {
+ var unsavedChildren = [];
+ var unsavedFiles = [];
+ Parse.Object._findUnsavedChildren(object, unsavedChildren, unsavedFiles);
+
+ var promise = Parse.Promise.as();
+ _.each(unsavedFiles, function(file) {
+ promise = promise.then(function() {
+ return file.save(options);
+ });
+ });
+
+ var objects = _.uniq(unsavedChildren);
+ var remaining = _.uniq(objects);
+
+ return promise.then(function() {
+ return Parse.Promise._continueWhile(function() {
+ return remaining.length > 0;
+ }, function() {
+
+ // Gather up all the objects that can be saved in this batch.
+ var batch = [];
+ var newRemaining = [];
+ Parse._arrayEach(remaining, function(object) {
+ // Limit batches to 20 objects.
+ if (batch.length > 20) {
+ newRemaining.push(object);
+ return;
+ }
+
+ if (object._canBeSerialized()) {
+ batch.push(object);
+ } else {
+ newRemaining.push(object);
+ }
+ });
+ remaining = newRemaining;
+
+ // If we can't save any objects, there must be a circular reference.
+ if (batch.length === 0) {
+ return Parse.Promise.error(
+ new Parse.Error(Parse.Error.OTHER_CAUSE,
+ "Tried to save a batch with a cycle."));
+ }
+
+ // Reserve a spot in every object's save queue.
+ var readyToStart = Parse.Promise.when(_.map(batch, function(object) {
+ return object._allPreviousSaves || Parse.Promise.as();
+ }));
+ var batchFinished = new Parse.Promise();
+ Parse._arrayEach(batch, function(object) {
+ object._allPreviousSaves = batchFinished;
+ });
+
+ // Save a single batch, whether previous saves succeeded or failed.
+ return readyToStart._continueWith(function() {
+ return Parse._request({
+ route: "batch",
+ method: "POST",
+ useMasterKey: options.useMasterKey,
+ data: {
+ requests: _.map(batch, function(object) {
+ var json = object._getSaveJSON();
+ var method = "POST";
+
+ var path = "/1/classes/" + object.className;
+ if (object.id) {
+ path = path + "/" + object.id;
+ method = "PUT";
+ }
+
+ object._startSave();
+
+ return {
+ method: method,
+ path: path,
+ body: json
+ };
+ })
+ }
+ }).then(function(response, status, xhr) {
+ var error;
+ Parse._arrayEach(batch, function(object, i) {
+ if (response[i].success) {
+ object._finishSave(
+ object.parse(response[i].success, status, xhr));
+ } else {
+ error = error || response[i].error;
+ object._cancelSave();
+ }
+ });
+ if (error) {
+ return Parse.Promise.error(
+ new Parse.Error(error.code, error.error));
+ }
+
+ }).then(function(results) {
+ batchFinished.resolve(results);
+ return results;
+ }, function(error) {
+ batchFinished.reject(error);
+ return Parse.Promise.error(error);
+ });
+ });
+ });
+ }).then(function() {
+ return object;
+ });
+ };
+
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Represents a Role on the Parse server. Roles represent groupings of
+ * Users for the purposes of granting permissions (e.g. specifying an ACL
+ * for an Object). Roles are specified by their sets of child users and
+ * child roles, all of which are granted any permissions that the parent
+ * role has.
+ *
+ * Roles must have a name (which cannot be changed after creation of the
+ * role), and must specify an ACL.
+ * @class
+ * A Parse.Role is a local representation of a role persisted to the Parse
+ * cloud.
+ */
+ Parse.Role = Parse.Object.extend("_Role", /** @lends Parse.Role.prototype */ {
+ // Instance Methods
+
+ /**
+ * Constructs a new ParseRole with the given name and ACL.
+ *
+ * @param {String} name The name of the Role to create.
+ * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL.
+ */
+ constructor: function(name, acl) {
+ if (_.isString(name) && (acl instanceof Parse.ACL)) {
+ Parse.Object.prototype.constructor.call(this, null, null);
+ this.setName(name);
+ this.setACL(acl);
+ } else {
+ Parse.Object.prototype.constructor.call(this, name, acl);
+ }
+ },
+
+ /**
+ * Gets the name of the role. You can alternatively call role.get("name")
+ *
+ * @return {String} the name of the role.
+ */
+ getName: function() {
+ return this.get("name");
+ },
+
+ /**
+ * Sets the name for a role. This value must be set before the role has
+ * been saved to the server, and cannot be set once the role has been
+ * saved.
+ *
+ *
+ * A role's name can only contain alphanumeric characters, _, -, and
+ * spaces.
+ *
+ *
+ * This is equivalent to calling role.set("name", name)
+ *
+ * @param {String} name The name of the role.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ setName: function(name, options) {
+ return this.set("name", name, options);
+ },
+
+ /**
+ * Gets the Parse.Relation for the Parse.Users that are direct
+ * children of this role. These users are granted any privileges that this
+ * role has been granted (e.g. read or write access through ACLs). You can
+ * add or remove users from the role through this relation.
+ *
+ * This is equivalent to calling role.relation("users")
+ *
+ * @return {Parse.Relation} the relation for the users belonging to this
+ * role.
+ */
+ getUsers: function() {
+ return this.relation("users");
+ },
+
+ /**
+ * Gets the Parse.Relation for the Parse.Roles that are direct
+ * children of this role. These roles' users are granted any privileges that
+ * this role has been granted (e.g. read or write access through ACLs). You
+ * can add or remove child roles from this role through this relation.
+ *
+ * This is equivalent to calling role.relation("roles")
+ *
+ * @return {Parse.Relation} the relation for the roles belonging to this
+ * role.
+ */
+ getRoles: function() {
+ return this.relation("roles");
+ },
+
+ /**
+ * @ignore
+ */
+ validate: function(attrs, options) {
+ if ("name" in attrs && attrs.name !== this.getName()) {
+ var newName = attrs.name;
+ if (this.id && this.id !== attrs.objectId) {
+ // Check to see if the objectId being set matches this.id.
+ // This happens during a fetch -- the id is set before calling fetch.
+ // Let the name be set in this case.
+ return new Parse.Error(Parse.Error.OTHER_CAUSE,
+ "A role's name can only be set before it has been saved.");
+ }
+ if (!_.isString(newName)) {
+ return new Parse.Error(Parse.Error.OTHER_CAUSE,
+ "A role's name must be a String.");
+ }
+ if (!(/^[0-9a-zA-Z\-_ ]+$/).test(newName)) {
+ return new Parse.Error(Parse.Error.OTHER_CAUSE,
+ "A role's name can only contain alphanumeric characters, _," +
+ " -, and spaces.");
+ }
+ }
+ if (Parse.Object.prototype.validate) {
+ return Parse.Object.prototype.validate.call(this, attrs, options);
+ }
+ return false;
+ }
+ });
+}(this));
+
+
+/*global _: false */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Creates a new instance with the given models and options. Typically, you
+ * will not call this method directly, but will instead make a subclass using
+ * Parse.Collection.extend.
+ *
+ * @param {Array} models An array of instances of Parse.Object.
+ *
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - model: The Parse.Object subclass that this collection contains.
+ *
- query: An instance of Parse.Query to use when fetching items.
+ *
- comparator: A string property name or function to sort by.
+ *
+ *
+ * @see Parse.Collection.extend
+ *
+ * @class
+ *
+ * Provides a standard collection class for our sets of models, ordered
+ * or unordered. For more information, see the
+ * Backbone
+ * documentation.
+ */
+ Parse.Collection = function(models, options) {
+ options = options || {};
+ if (options.comparator) {
+ this.comparator = options.comparator;
+ }
+ if (options.model) {
+ this.model = options.model;
+ }
+ if (options.query) {
+ this.query = options.query;
+ }
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) {
+ this.reset(models, {silent: true, parse: options.parse});
+ }
+ };
+
+ // Define the Collection's inheritable methods.
+ _.extend(Parse.Collection.prototype, Parse.Events,
+ /** @lends Parse.Collection.prototype */ {
+
+ // The default model for a collection is just a Parse.Object.
+ // This should be overridden in most cases.
+
+ model: Parse.Object,
+
+ /**
+ * Initialize is an empty function by default. Override it with your own
+ * initialization logic.
+ */
+ initialize: function(){},
+
+ /**
+ * The JSON representation of a Collection is an array of the
+ * models' attributes.
+ */
+ toJSON: function() {
+ return this.map(function(model){ return model.toJSON(); });
+ },
+
+ /**
+ * Add a model, or list of models to the set. Pass **silent** to avoid
+ * firing the `add` event for every new model.
+ *
+ * @param {Array} models An array of instances of Parse.Object.
+ *
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - at: The index at which to add the models.
+ *
- silent: Set to true to avoid firing the `add` event for every new
+ * model.
+ *
+ */
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {};
+ options = options || {};
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ models[i] = this._prepareModel(models[i], options);
+ model = models[i];
+ if (!model) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ if (cids[cid] || this._byCid[cid]) {
+ throw new Error("Duplicate cid: can't add the same model " +
+ "to a collection twice");
+ }
+ id = model.id;
+ if (!Parse._isNullOrUndefined(id) && (ids[id] || this._byId[id])) {
+ throw new Error("Duplicate id: can't add the same model " +
+ "to a collection twice");
+ }
+ ids[id] = model;
+ cids[cid] = model;
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id) {
+ this._byId[model.id] = model;
+ }
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = Parse._isNullOrUndefined(options.at) ?
+ this.models.length : options.at;
+ this.models.splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) {
+ this.sort({silent: true});
+ }
+ if (options.silent) {
+ return this;
+ }
+ for (i = 0, length = this.models.length; i < length; i++) {
+ model = this.models[i];
+ if (cids[model.cid]) {
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Remove a model, or a list of models from the set. Pass silent to avoid
+ * firing the remove event for every model removed.
+ *
+ * @param {Array} models The model or list of models to remove from the
+ * collection.
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - silent: Set to true to avoid firing the `remove` event.
+ *
+ */
+ remove: function(models, options) {
+ var i, l, index, model;
+ options = options || {};
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) {
+ continue;
+ }
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+ this._removeReference(model);
+ }
+ return this;
+ },
+
+ /**
+ * Gets a model from the set by id.
+ * @param {String} id The Parse objectId identifying the Parse.Object to
+ * fetch from this collection.
+ */
+ get: function(id) {
+ return id && this._byId[id.id || id];
+ },
+
+ /**
+ * Gets a model from the set by client id.
+ * @param {} cid The Backbone collection id identifying the Parse.Object to
+ * fetch from this collection.
+ */
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || cid];
+ },
+
+ /**
+ * Gets the model at the given index.
+ *
+ * @param {Number} index The index of the model to return.
+ */
+ at: function(index) {
+ return this.models[index];
+ },
+
+ /**
+ * Forces the collection to re-sort itself. You don't need to call this
+ * under normal circumstances, as the set will maintain sort order as each
+ * item is added.
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - silent: Set to true to avoid firing the `reset` event.
+ *
+ */
+ sort: function(options) {
+ options = options || {};
+ if (!this.comparator) {
+ throw new Error('Cannot sort a set without a comparator');
+ }
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length === 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) {
+ this.trigger('reset', this, options);
+ }
+ return this;
+ },
+
+ /**
+ * Plucks an attribute from each model in the collection.
+ * @param {String} attr The attribute to return from each model in the
+ * collection.
+ */
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ /**
+ * When you have more items than you want to add or remove individually,
+ * you can reset the entire set with a new list of models, without firing
+ * any `add` or `remove` events. Fires `reset` when finished.
+ *
+ * @param {Array} models The model or list of models to remove from the
+ * collection.
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - silent: Set to true to avoid firing the `reset` event.
+ *
+ */
+ reset: function(models, options) {
+ var self = this;
+ models = models || [];
+ options = options || {};
+ Parse._arrayEach(this.models, function(model) {
+ self._removeReference(model);
+ });
+ this._reset();
+ this.add(models, {silent: true, parse: options.parse});
+ if (!options.silent) {
+ this.trigger('reset', this, options);
+ }
+ return this;
+ },
+
+ /**
+ * Fetches the default set of models for this collection, resetting the
+ * collection when they arrive. If `add: true` is passed, appends the
+ * models to the collection instead of resetting.
+ *
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - silent: Set to true to avoid firing `add` or `reset` events for
+ * models fetched by this fetch.
+ *
- success: A Backbone-style success callback.
+ *
- error: An Backbone-style error callback.
+ *
- useMasterKey: In Cloud Code and Node only, uses the Master Key for
+ * this request.
+ *
+ */
+ fetch: function(options) {
+ options = _.clone(options) || {};
+ if (options.parse === undefined) {
+ options.parse = true;
+ }
+ var collection = this;
+ var query = this.query || new Parse.Query(this.model);
+ return query.find({
+ useMasterKey: options.useMasterKey
+ }).then(function(results) {
+ if (options.add) {
+ collection.add(results, options);
+ } else {
+ collection.reset(results, options);
+ }
+ return collection;
+ })._thenRunCallbacks(options, this);
+ },
+
+ /**
+ * Creates a new instance of a model in this collection. Add the model to
+ * the collection immediately, unless `wait: true` is passed, in which case
+ * we wait for the server to agree.
+ *
+ * @param {Parse.Object} model The new model to create and add to the
+ * collection.
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - wait: Set to true to wait for the server to confirm creation of the
+ * model before adding it to the collection.
+ *
- silent: Set to true to avoid firing an `add` event.
+ *
- success: A Backbone-style success callback.
+ *
- error: An Backbone-style error callback.
+ *
- useMasterKey: In Cloud Code and Node only, uses the Master Key for
+ * this request.
+ *
+ */
+ create: function(model, options) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) {
+ return false;
+ }
+ if (!options.wait) {
+ coll.add(model, options);
+ }
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ if (options.wait) {
+ coll.add(nextModel, options);
+ }
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', model, resp, options);
+ }
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ /**
+ * Converts a response into a list of models to be added to the collection.
+ * The default implementation is just to pass it through.
+ * @ignore
+ */
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ /**
+ * Proxy to _'s chain. Can't be proxied the same way the rest of the
+ * underscore methods are proxied because it relies on the underscore
+ * constructor.
+ */
+ chain: function() {
+ return _(this.models).chain();
+ },
+
+ /**
+ * Reset all internal state. Called when the collection is reset.
+ */
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ /**
+ * Prepare a model or hash of attributes to be added to this collection.
+ */
+ _prepareModel: function(model, options) {
+ if (!(model instanceof Parse.Object)) {
+ var attrs = model;
+ options.collection = this;
+ model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) {
+ model = false;
+ }
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ /**
+ * Internal method to remove a model's ties to a collection.
+ */
+ _removeReference: function(model) {
+ if (this === model.collection) {
+ delete model.collection;
+ }
+ model.off('all', this._onModelEvent, this);
+ },
+
+ /**
+ * Internal method called every time a model in the set fires an event.
+ * Sets need to update their indexes when models change ids. All other
+ * events simply proxy through. "add" and "remove" events that originate
+ * in other collections are ignored.
+ */
+ _onModelEvent: function(ev, model, collection, options) {
+ if ((ev === 'add' || ev === 'remove') && collection !== this) {
+ return;
+ }
+ if (ev === 'destroy') {
+ this.remove(model, options);
+ }
+ if (model && ev === 'change:objectId') {
+ delete this._byId[model.previous("objectId")];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ Parse._arrayEach(methods, function(method) {
+ Parse.Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+ };
+ });
+
+ /**
+ * Creates a new subclass of Parse.Collection. For example,
+ * var MyCollection = Parse.Collection.extend({
+ * // Instance properties
+ *
+ * model: MyClass,
+ * query: MyQuery,
+ *
+ * getFirst: function() {
+ * return this.at(0);
+ * }
+ * }, {
+ * // Class properties
+ *
+ * makeOne: function() {
+ * return new MyCollection();
+ * }
+ * });
+ *
+ * var collection = new MyCollection();
+ *
+ *
+ * @function
+ * @param {Object} instanceProps Instance properties for the collection.
+ * @param {Object} classProps Class properies for the collection.
+ * @return {Class} A new subclass of Parse.Collection.
+ */
+ Parse.Collection.extend = Parse._extend;
+
+}(this));
+
+/*global _: false, document: false */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Creating a Parse.View creates its initial element outside of the DOM,
+ * if an existing element is not provided...
+ * @class
+ *
+ * A fork of Backbone.View, provided for your convenience. If you use this
+ * class, you must also include jQuery, or another library that provides a
+ * jQuery-compatible $ function. For more information, see the
+ * Backbone
+ * documentation.
+ * Available in the client SDK only.
+ */
+ Parse.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ this.delegateEvents();
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var eventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be merged as properties.
+
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes',
+ 'className', 'tagName'];
+
+ // Set up all inheritable **Parse.View** properties and methods.
+ _.extend(Parse.View.prototype, Parse.Events,
+ /** @lends Parse.View.prototype */ {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName: 'div',
+
+ /**
+ * jQuery delegate for element lookup, scoped to DOM elements within the
+ * current view. This should be prefered to global lookups where possible.
+ */
+ $: function(selector) {
+ return this.$el.find(selector);
+ },
+
+ /**
+ * Initialize is an empty function by default. Override it with your own
+ * initialization logic.
+ */
+ initialize: function(){},
+
+ /**
+ * The core function that your view should override, in order
+ * to populate its element (`this.el`), with the appropriate HTML. The
+ * convention is for **render** to always return `this`.
+ */
+ render: function() {
+ return this;
+ },
+
+ /**
+ * Remove this view from the DOM. Note that the view isn't present in the
+ * DOM by default, so calling this method may be a no-op.
+ */
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ /**
+ * For small amounts of DOM Elements, where a full-blown template isn't
+ * needed, use **make** to manufacture elements, one at a time.
+ *
+ * var el = this.make('li', {'class': 'row'},
+ * this.model.escape('title'));
+ */
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) {
+ Parse.$(el).attr(attributes);
+ }
+ if (content) {
+ Parse.$(el).html(content);
+ }
+ return el;
+ },
+
+ /**
+ * Changes the view's element (`this.el` property), including event
+ * re-delegation.
+ */
+ setElement: function(element, delegate) {
+ this.$el = Parse.$(element);
+ this.el = this.$el[0];
+ if (delegate !== false) {
+ this.delegateEvents();
+ }
+ return this;
+ },
+
+ /**
+ * Set callbacks. this.events is a hash of
+ *
+ * *{"event selector": "callback"}*
+ *
+ * {
+ * 'mousedown .title': 'edit',
+ * 'click .button': 'save'
+ * 'click .open': function(e) { ... }
+ * }
+ *
+ * pairs. Callbacks will be bound to the view, with `this` set properly.
+ * Uses event delegation for efficiency.
+ * Omitting the selector binds the event to `this.el`.
+ * This only works for delegate-able events: not `focus`, `blur`, and
+ * not `change`, `submit`, and `reset` in Internet Explorer.
+ */
+ delegateEvents: function(events) {
+ events = events || Parse._getValue(this, 'events');
+ if (!events) {
+ return;
+ }
+ this.undelegateEvents();
+ var self = this;
+ Parse._objectEach(events, function(method, key) {
+ if (!_.isFunction(method)) {
+ method = self[events[key]];
+ }
+ if (!method) {
+ throw new Error('Event "' + events[key] + '" does not exist');
+ }
+ var match = key.match(eventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, self);
+ eventName += '.delegateEvents' + self.cid;
+ if (selector === '') {
+ self.$el.bind(eventName, method);
+ } else {
+ self.$el.delegate(selector, eventName, method);
+ }
+ });
+ },
+
+ /**
+ * Clears all callbacks previously bound to the view with `delegateEvents`.
+ * You usually don't need to use this, but may wish to if you have multiple
+ * Backbone views attached to the same DOM element.
+ */
+ undelegateEvents: function() {
+ this.$el.unbind('.delegateEvents' + this.cid);
+ },
+
+ /**
+ * Performs the initial configuration of a View with a set of options.
+ * Keys with special meaning *(model, collection, id, className)*, are
+ * attached directly to the view.
+ */
+ _configure: function(options) {
+ if (this.options) {
+ options = _.extend({}, this.options, options);
+ }
+ var self = this;
+ _.each(viewOptions, function(attr) {
+ if (options[attr]) {
+ self[attr] = options[attr];
+ }
+ });
+ this.options = options;
+ },
+
+ /**
+ * Ensure that the View has a DOM element to render into.
+ * If `this.el` is a string, pass it through `$()`, take the first
+ * matching element, and re-assign it to `el`. Otherwise, create
+ * an element from the `id`, `className` and `tagName` properties.
+ */
+ _ensureElement: function() {
+ if (!this.el) {
+ var attrs = Parse._getValue(this, 'attributes') || {};
+ if (this.id) {
+ attrs.id = this.id;
+ }
+ if (this.className) {
+ attrs['class'] = this.className;
+ }
+ this.setElement(this.make(this.tagName, attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+ });
+
+ /**
+ * @function
+ * @param {Object} instanceProps Instance properties for the view.
+ * @param {Object} classProps Class properies for the view.
+ * @return {Class} A new subclass of Parse.View.
+ */
+ Parse.View.extend = Parse._extend;
+
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * @class
+ *
+ * A Parse.User object is a local representation of a user persisted to the
+ * Parse cloud. This class is a subclass of a Parse.Object, and retains the
+ * same functionality of a Parse.Object, but also extends it with various
+ * user specific methods, like authentication, signing up, and validation of
+ * uniqueness.
+ */
+ Parse.User = Parse.Object.extend("_User", /** @lends Parse.User.prototype */ {
+ // Instance Variables
+ _isCurrentUser: false,
+
+
+ // Instance Methods
+
+ /**
+ * Merges another object's attributes into this object.
+ */
+ _mergeFromObject: function(other) {
+ if (other.getSessionToken()) {
+ this._sessionToken = other.getSessionToken();
+ }
+ Parse.User.__super__._mergeFromObject.call(this, other);
+ },
+
+ /**
+ * Internal method to handle special fields in a _User response.
+ */
+ _mergeMagicFields: function(attrs) {
+ if (attrs.sessionToken) {
+ this._sessionToken = attrs.sessionToken;
+ delete attrs.sessionToken;
+ }
+ Parse.User.__super__._mergeMagicFields.call(this, attrs);
+ },
+
+ /**
+ * Removes null values from authData (which exist temporarily for
+ * unlinking)
+ */
+ _cleanupAuthData: function() {
+ if (!this.isCurrent()) {
+ return;
+ }
+ var authData = this.get('authData');
+ if (!authData) {
+ return;
+ }
+ Parse._objectEach(this.get('authData'), function(value, key) {
+ if (!authData[key]) {
+ delete authData[key];
+ }
+ });
+ },
+
+ /**
+ * Synchronizes authData for all providers.
+ */
+ _synchronizeAllAuthData: function() {
+ var authData = this.get('authData');
+ if (!authData) {
+ return;
+ }
+
+ var self = this;
+ Parse._objectEach(this.get('authData'), function(value, key) {
+ self._synchronizeAuthData(key);
+ });
+ },
+
+ /**
+ * Synchronizes auth data for a provider (e.g. puts the access token in the
+ * right place to be used by the Facebook SDK).
+ */
+ _synchronizeAuthData: function(provider) {
+ if (!this.isCurrent()) {
+ return;
+ }
+ var authType;
+ if (_.isString(provider)) {
+ authType = provider;
+ provider = Parse.User._authProviders[authType];
+ } else {
+ authType = provider.getAuthType();
+ }
+ var authData = this.get('authData');
+ if (!authData || !provider) {
+ return;
+ }
+ var success = provider.restoreAuthentication(authData[authType]);
+ if (!success) {
+ this._unlinkFrom(provider);
+ }
+ },
+
+ _handleSaveResult: function(makeCurrent) {
+ // Clean up and synchronize the authData object, removing any unset values
+ if (makeCurrent) {
+ this._isCurrentUser = true;
+ }
+ this._cleanupAuthData();
+ this._synchronizeAllAuthData();
+ // Don't keep the password around.
+ delete this._serverData.password;
+ this._rebuildEstimatedDataForKey("password");
+ this._refreshCache();
+ if (makeCurrent || this.isCurrent()) {
+ Parse.User._saveCurrentUser(this);
+ }
+ },
+
+ /**
+ * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can
+ * call linkWith on the user (even if it doesn't exist yet on the server).
+ */
+ _linkWith: function(provider, options) {
+ var authType;
+ if (_.isString(provider)) {
+ authType = provider;
+ provider = Parse.User._authProviders[provider];
+ } else {
+ authType = provider.getAuthType();
+ }
+ if (_.has(options, 'authData')) {
+ var authData = this.get('authData') || {};
+ authData[authType] = options.authData;
+ this.set('authData', authData);
+
+ // Overridden so that the user can be made the current user.
+ var newOptions = _.clone(options) || {};
+ newOptions.success = function(model) {
+ model._handleSaveResult(true);
+ if (options.success) {
+ options.success.apply(this, arguments);
+ }
+ };
+ return this.save({'authData': authData}, newOptions);
+ } else {
+ var self = this;
+ var promise = new Parse.Promise();
+ provider.authenticate({
+ success: function(provider, result) {
+ self._linkWith(provider, {
+ authData: result,
+ success: options.success,
+ error: options.error
+ }).then(function() {
+ promise.resolve(self);
+ });
+ },
+ error: function(provider, error) {
+ if (options.error) {
+ options.error(self, error);
+ }
+ promise.reject(error);
+ }
+ });
+ return promise;
+ }
+ },
+
+ /**
+ * Unlinks a user from a service.
+ */
+ _unlinkFrom: function(provider, options) {
+ var authType;
+ if (_.isString(provider)) {
+ authType = provider;
+ provider = Parse.User._authProviders[provider];
+ } else {
+ authType = provider.getAuthType();
+ }
+ var newOptions = _.clone(options);
+ var self = this;
+ newOptions.authData = null;
+ newOptions.success = function(model) {
+ self._synchronizeAuthData(provider);
+ if (options.success) {
+ options.success.apply(this, arguments);
+ }
+ };
+ return this._linkWith(provider, newOptions);
+ },
+
+ /**
+ * Checks whether a user is linked to a service.
+ */
+ _isLinked: function(provider) {
+ var authType;
+ if (_.isString(provider)) {
+ authType = provider;
+ } else {
+ authType = provider.getAuthType();
+ }
+ var authData = this.get('authData') || {};
+ return !!authData[authType];
+ },
+
+ /**
+ * Deauthenticates all providers.
+ */
+ _logOutWithAll: function() {
+ var authData = this.get('authData');
+ if (!authData) {
+ return;
+ }
+ var self = this;
+ Parse._objectEach(this.get('authData'), function(value, key) {
+ self._logOutWith(key);
+ });
+ },
+
+ /**
+ * Deauthenticates a single provider (e.g. removing access tokens from the
+ * Facebook SDK).
+ */
+ _logOutWith: function(provider) {
+ if (!this.isCurrent()) {
+ return;
+ }
+ if (_.isString(provider)) {
+ provider = Parse.User._authProviders[provider];
+ }
+ if (provider && provider.deauthenticate) {
+ provider.deauthenticate();
+ }
+ },
+
+ /**
+ * Signs up a new user. You should call this instead of save for
+ * new Parse.Users. This will create a new Parse.User on the server, and
+ * also persist the session on disk so that you can access the user using
+ * current.
+ *
+ * A username and password must be set before calling signUp.
+ *
+ * Calls options.success or options.error on completion.
+ *
+ * @param {Object} attrs Extra fields to set on the new user, or null.
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} A promise that is fulfilled when the signup
+ * finishes.
+ * @see Parse.User.signUp
+ */
+ signUp: function(attrs, options) {
+ var error;
+ options = options || {};
+
+ var username = (attrs && attrs.username) || this.get("username");
+ if (!username || (username === "")) {
+ error = new Parse.Error(
+ Parse.Error.OTHER_CAUSE,
+ "Cannot sign up user with an empty name.");
+ if (options && options.error) {
+ options.error(this, error);
+ }
+ return Parse.Promise.error(error);
+ }
+
+ var password = (attrs && attrs.password) || this.get("password");
+ if (!password || (password === "")) {
+ error = new Parse.Error(
+ Parse.Error.OTHER_CAUSE,
+ "Cannot sign up user with an empty password.");
+ if (options && options.error) {
+ options.error(this, error);
+ }
+ return Parse.Promise.error(error);
+ }
+
+ // Overridden so that the user can be made the current user.
+ var newOptions = _.clone(options);
+ newOptions.success = function(model) {
+ model._handleSaveResult(true);
+ if (options.success) {
+ options.success.apply(this, arguments);
+ }
+ };
+ return this.save(attrs, newOptions);
+ },
+
+ /**
+ * Logs in a Parse.User. On success, this saves the session to localStorage,
+ * so you can retrieve the currently logged in user using
+ * current.
+ *
+ * A username and password must be set before calling logIn.
+ *
+ * Calls options.success or options.error on completion.
+ *
+ * @param {Object} options A Backbone-style options object.
+ * @see Parse.User.logIn
+ * @return {Parse.Promise} A promise that is fulfilled with the user when
+ * the login is complete.
+ */
+ logIn: function(options) {
+ var model = this;
+ options = options || {};
+ var request = Parse._request({
+ route: "login",
+ method: "GET",
+ useMasterKey: options.useMasterKey,
+ data: this.toJSON()
+ });
+ return request.then(function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, status, xhr);
+ model._finishFetch(serverAttrs);
+ model._handleSaveResult(true);
+ return model;
+ })._thenRunCallbacks(options, this);
+ },
+
+ /**
+ * @see Parse.Object#save
+ */
+ save: function(arg1, arg2, arg3) {
+ var i, attrs, current, options, saved;
+ if (_.isObject(arg1) || _.isNull(arg1) || _.isUndefined(arg1)) {
+ attrs = arg1;
+ options = arg2;
+ } else {
+ attrs = {};
+ attrs[arg1] = arg2;
+ options = arg3;
+ }
+ options = options || {};
+
+ var newOptions = _.clone(options);
+ newOptions.success = function(model) {
+ model._handleSaveResult(false);
+ if (options.success) {
+ options.success.apply(this, arguments);
+ }
+ };
+ return Parse.Object.prototype.save.call(this, attrs, newOptions);
+ },
+
+ /**
+ * @see Parse.Object#fetch
+ */
+ fetch: function(options) {
+ var newOptions = options ? _.clone(options) : {};
+ newOptions.success = function(model) {
+ model._handleSaveResult(false);
+ if (options && options.success) {
+ options.success.apply(this, arguments);
+ }
+ };
+ return Parse.Object.prototype.fetch.call(this, newOptions);
+ },
+
+ /**
+ * Returns true if current would return this user.
+ * @see Parse.User#current
+ */
+ isCurrent: function() {
+ return this._isCurrentUser;
+ },
+
+ /**
+ * Returns get("username").
+ * @return {String}
+ * @see Parse.Object#get
+ */
+ getUsername: function() {
+ return this.get("username");
+ },
+
+ /**
+ * Calls set("username", username, options) and returns the result.
+ * @param {String} username
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ * @see Parse.Object.set
+ */
+ setUsername: function(username, options) {
+ return this.set("username", username, options);
+ },
+
+ /**
+ * Calls set("password", password, options) and returns the result.
+ * @param {String} password
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ * @see Parse.Object.set
+ */
+ setPassword: function(password, options) {
+ return this.set("password", password, options);
+ },
+
+ /**
+ * Returns get("email").
+ * @return {String}
+ * @see Parse.Object#get
+ */
+ getEmail: function() {
+ return this.get("email");
+ },
+
+ /**
+ * Calls set("email", email, options) and returns the result.
+ * @param {String} email
+ * @param {Object} options A Backbone-style options object.
+ * @return {Boolean}
+ * @see Parse.Object.set
+ */
+ setEmail: function(email, options) {
+ return this.set("email", email, options);
+ },
+
+ /**
+ * Checks whether this user is the current user and has been authenticated.
+ * @return (Boolean) whether this user is the current user and is logged in.
+ */
+ authenticated: function() {
+ return !!this._sessionToken &&
+ (Parse.User.current() && Parse.User.current().id === this.id);
+ },
+
+ /**
+ * Returns the session token for this user, if the user has been logged in,
+ * or if it is the result of a query with the master key. Otherwise, returns
+ * undefined.
+ * @return {String} the session token, or undefined
+ */
+ getSessionToken: function() {
+ return this._sessionToken;
+ }
+
+ }, /** @lends Parse.User */ {
+ // Class Variables
+
+ // The currently logged-in user.
+ _currentUser: null,
+
+ // Whether currentUser is known to match the serialized version on disk.
+ // This is useful for saving a localstorage check if you try to load
+ // _currentUser frequently while there is none stored.
+ _currentUserMatchesDisk: false,
+
+ // The localStorage key suffix that the current user is stored under.
+ _CURRENT_USER_KEY: "currentUser",
+
+ // The mapping of auth provider names to actual providers
+ _authProviders: {},
+
+ // Whether to rewrite className User to _User
+ _performUserRewrite: true,
+
+
+ // Class Methods
+
+ /**
+ * Signs up a new user with a username (or email) and password.
+ * This will create a new Parse.User on the server, and also persist the
+ * session in localStorage so that you can access the user using
+ * {@link #current}.
+ *
+ * Calls options.success or options.error on completion.
+ *
+ * @param {String} username The username (or email) to sign up with.
+ * @param {String} password The password to sign up with.
+ * @param {Object} attrs Extra fields to set on the new user.
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} A promise that is fulfilled with the user when
+ * the signup completes.
+ * @see Parse.User#signUp
+ */
+ signUp: function(username, password, attrs, options) {
+ attrs = attrs || {};
+ attrs.username = username;
+ attrs.password = password;
+ var user = Parse.Object._create("_User");
+ return user.signUp(attrs, options);
+ },
+
+ /**
+ * Logs in a user with a username (or email) and password. On success, this
+ * saves the session to disk, so you can retrieve the currently logged in
+ * user using current.
+ *
+ * Calls options.success or options.error on completion.
+ *
+ * @param {String} username The username (or email) to log in with.
+ * @param {String} password The password to log in with.
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} A promise that is fulfilled with the user when
+ * the login completes.
+ * @see Parse.User#logIn
+ */
+ logIn: function(username, password, options) {
+ var user = Parse.Object._create("_User");
+ user._finishFetch({ username: username, password: password });
+ return user.logIn(options);
+ },
+
+ /**
+ * Logs in a user with a session token. On success, this saves the session
+ * to disk, so you can retrieve the currently logged in user using
+ * current.
+ *
+ * Calls options.success or options.error on completion.
+ *
+ * @param {String} sessionToken The sessionToken to log in with.
+ * @param {Object} options A Backbone-style options object.
+ * @return {Parse.Promise} A promise that is fulfilled with the user when
+ * the login completes.
+ */
+ become: function(sessionToken, options) {
+ options = options || {};
+
+ var user = Parse.Object._create("_User");
+ return Parse._request({
+ route: "users",
+ className: "me",
+ method: "GET",
+ useMasterKey: options.useMasterKey,
+ sessionToken: sessionToken
+ }).then(function(resp, status, xhr) {
+ var serverAttrs = user.parse(resp, status, xhr);
+ user._finishFetch(serverAttrs);
+ user._handleSaveResult(true);
+ return user;
+
+ })._thenRunCallbacks(options, user);
+ },
+
+ /**
+ * Logs out the currently logged in user session. This will remove the
+ * session from disk, log out of linked services, and future calls to
+ * current will return null.
+ */
+ logOut: function() {
+ if (Parse.User._currentUser !== null) {
+ Parse.User._currentUser._logOutWithAll();
+ Parse.User._currentUser._isCurrentUser = false;
+ }
+ Parse.User._currentUserMatchesDisk = true;
+ Parse.User._currentUser = null;
+ Parse.localStorage.removeItem(
+ Parse._getParsePath(Parse.User._CURRENT_USER_KEY));
+ },
+
+ /**
+ * Requests a password reset email to be sent to the specified email address
+ * associated with the user account. This email allows the user to securely
+ * reset their password on the Parse site.
+ *
+ * Calls options.success or options.error on completion.
+ *
+ * @param {String} email The email address associated with the user that
+ * forgot their password.
+ * @param {Object} options A Backbone-style options object.
+ */
+ requestPasswordReset: function(email, options) {
+ options = options || {};
+ var request = Parse._request({
+ route: "requestPasswordReset",
+ method: "POST",
+ useMasterKey: options.useMasterKey,
+ data: { email: email }
+ });
+ return request._thenRunCallbacks(options);
+ },
+
+ /**
+ * Retrieves the currently logged in ParseUser with a valid session,
+ * either from memory or localStorage, if necessary.
+ * @return {Parse.Object} The currently logged in Parse.User.
+ */
+ current: function() {
+ if (Parse.User._currentUser) {
+ return Parse.User._currentUser;
+ }
+
+ if (Parse.User._currentUserMatchesDisk) {
+
+ return Parse.User._currentUser;
+ }
+
+ // Load the user from local storage.
+ Parse.User._currentUserMatchesDisk = true;
+
+ var userData = Parse.localStorage.getItem(Parse._getParsePath(
+ Parse.User._CURRENT_USER_KEY));
+ if (!userData) {
+
+ return null;
+ }
+ Parse.User._currentUser = Parse.Object._create("_User");
+ Parse.User._currentUser._isCurrentUser = true;
+
+ var json = JSON.parse(userData);
+ Parse.User._currentUser.id = json._id;
+ delete json._id;
+ Parse.User._currentUser._sessionToken = json._sessionToken;
+ delete json._sessionToken;
+ Parse.User._currentUser._finishFetch(json);
+
+ Parse.User._currentUser._synchronizeAllAuthData();
+ Parse.User._currentUser._refreshCache();
+ Parse.User._currentUser._opSetQueue = [{}];
+ return Parse.User._currentUser;
+ },
+
+ /**
+ * Allow someone to define a custom User class without className
+ * being rewritten to _User. The default behavior is to rewrite
+ * User to _User for legacy reasons. This allows developers to
+ * override that behavior.
+ *
+ * @param {Boolean} isAllowed Whether or not to allow custom User class
+ */
+ allowCustomUserClass: function(isAllowed) {
+ this._performUserRewrite = !isAllowed;
+ },
+
+ /**
+ * Persists a user as currentUser to localStorage, and into the singleton.
+ */
+ _saveCurrentUser: function(user) {
+ if (Parse.User._currentUser !== user) {
+ Parse.User.logOut();
+ }
+ user._isCurrentUser = true;
+ Parse.User._currentUser = user;
+ Parse.User._currentUserMatchesDisk = true;
+
+ var json = user.toJSON();
+ json._id = user.id;
+ json._sessionToken = user._sessionToken;
+ Parse.localStorage.setItem(
+ Parse._getParsePath(Parse.User._CURRENT_USER_KEY),
+ JSON.stringify(json));
+ },
+
+ _registerAuthenticationProvider: function(provider) {
+ Parse.User._authProviders[provider.getAuthType()] = provider;
+ // Synchronize the current user with the auth provider.
+ if (Parse.User.current()) {
+ Parse.User.current()._synchronizeAuthData(provider.getAuthType());
+ }
+ },
+
+ _logInWith: function(provider, options) {
+ var user = Parse.Object._create("_User");
+ return user._linkWith(provider, options);
+ }
+
+ });
+}(this));
+
+
+// Parse.Query is a way to create a list of Parse.Objects.
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Creates a new parse Parse.Query for the given Parse.Object subclass.
+ * @param objectClass -
+ * An instance of a subclass of Parse.Object, or a Parse className string.
+ * @class
+ *
+ * Parse.Query defines a query that is used to fetch Parse.Objects. The
+ * most common use case is finding all objects that match a query through the
+ * find method. For example, this sample code fetches all objects
+ * of class MyClass. It calls a different function depending on
+ * whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.find({
+ * success: function(results) {
+ * // results is an array of Parse.Object.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to retrieve a single object whose id is
+ * known, through the get method. For example, this sample code fetches an
+ * object of class MyClass and id myId. It calls a
+ * different function depending on whether the fetch succeeded or not.
+ *
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.get(myId, {
+ * success: function(object) {
+ * // object is an instance of Parse.Object.
+ * },
+ *
+ * error: function(object, error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ *
+ * A Parse.Query can also be used to count the number of objects that match
+ * the query without retrieving all of those objects. For example, this
+ * sample code counts the number of objects of the class MyClass
+ *
+ * var query = new Parse.Query(MyClass);
+ * query.count({
+ * success: function(number) {
+ * // There are number instances of MyClass.
+ * },
+ *
+ * error: function(error) {
+ * // error is an instance of Parse.Error.
+ * }
+ * });
+ */
+ Parse.Query = function(objectClass) {
+ if (_.isString(objectClass)) {
+ objectClass = Parse.Object._getSubclass(objectClass);
+ }
+
+ this.objectClass = objectClass;
+
+ this.className = objectClass.prototype.className;
+
+ this._where = {};
+ this._include = [];
+ this._limit = -1; // negative limit means, do not send a limit
+ this._skip = 0;
+ this._extraOptions = {};
+ };
+
+ /**
+ * Constructs a Parse.Query that is the OR of the passed in queries. For
+ * example:
+ * var compoundQuery = Parse.Query.or(query1, query2, query3);
+ *
+ * will create a compoundQuery that is an or of the query1, query2, and
+ * query3.
+ * @param {...Parse.Query} var_args The list of queries to OR.
+ * @return {Parse.Query} The query that is the OR of the passed in queries.
+ */
+ Parse.Query.or = function() {
+ var queries = _.toArray(arguments);
+ var className = null;
+ Parse._arrayEach(queries, function(q) {
+ if (_.isNull(className)) {
+ className = q.className;
+ }
+
+ if (className !== q.className) {
+ throw "All queries must be for the same class";
+ }
+ });
+ var query = new Parse.Query(className);
+ query._orQuery(queries);
+ return query;
+ };
+
+ Parse.Query.prototype = {
+ /**
+ * Constructs a Parse.Object whose id is already known by fetching data from
+ * the server. Either options.success or options.error is called when the
+ * find completes.
+ *
+ * @param {String} objectId The id of the object to be fetched.
+ * @param {Object} options A Backbone-style options object.
+ * Valid options are:
+ * - success: A Backbone-style success callback
+ *
- error: An Backbone-style error callback.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ */
+ get: function(objectId, options) {
+ var self = this;
+ self.equalTo('objectId', objectId);
+
+ var firstOptions = {};
+ if (options && _.has(options, 'useMasterKey')) {
+ firstOptions = { useMasterKey: options.useMasterKey };
+ }
+
+ return self.first(firstOptions).then(function(response) {
+ if (response) {
+ return response;
+ }
+
+ var errorObject = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
+ "Object not found.");
+ return Parse.Promise.error(errorObject);
+
+ })._thenRunCallbacks(options, null);
+ },
+
+ /**
+ * Returns a JSON representation of this query.
+ * @return {Object} The JSON representation of the query.
+ */
+ toJSON: function() {
+ var params = {
+ where: this._where
+ };
+
+ if (this._include.length > 0) {
+ params.include = this._include.join(",");
+ }
+ if (this._select) {
+ params.keys = this._select.join(",");
+ }
+ if (this._limit >= 0) {
+ params.limit = this._limit;
+ }
+ if (this._skip > 0) {
+ params.skip = this._skip;
+ }
+ if (this._order !== undefined) {
+ params.order = this._order.join(",");
+ }
+
+ Parse._objectEach(this._extraOptions, function(v, k) {
+ params[k] = v;
+ });
+
+ return params;
+ },
+
+ /**
+ * Retrieves a list of ParseObjects that satisfy this query.
+ * Either options.success or options.error is called when the find
+ * completes.
+ *
+ * @param {Object} options A Backbone-style options object. Valid options
+ * are:
+ * - success: Function to call when the find completes successfully.
+ *
- error: Function to call when the find fails.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ *
+ * @return {Parse.Promise} A promise that is resolved with the results when
+ * the query completes.
+ */
+ find: function(options) {
+ var self = this;
+ options = options || {};
+
+ var request = Parse._request({
+ route: "classes",
+ className: this.className,
+ method: "GET",
+ useMasterKey: options.useMasterKey,
+ data: this.toJSON()
+ });
+
+ return request.then(function(response) {
+ return _.map(response.results, function(json) {
+ var obj;
+ if (response.className) {
+ obj = new Parse.Object(response.className);
+ } else {
+ obj = new self.objectClass();
+ }
+ obj._finishFetch(json, true);
+ return obj;
+ });
+ })._thenRunCallbacks(options);
+ },
+
+ /**
+ * Counts the number of objects that match this query.
+ * Either options.success or options.error is called when the count
+ * completes.
+ *
+ * @param {Object} options A Backbone-style options object. Valid options
+ * are:
+ * - success: Function to call when the count completes successfully.
+ *
- error: Function to call when the find fails.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ *
+ * @return {Parse.Promise} A promise that is resolved with the count when
+ * the query completes.
+ */
+ count: function(options) {
+ var self = this;
+ options = options || {};
+
+ var params = this.toJSON();
+ params.limit = 0;
+ params.count = 1;
+ var request = Parse._request({
+ route: "classes",
+ className: self.className,
+ method: "GET",
+ useMasterKey: options.useMasterKey,
+ data: params
+ });
+
+ return request.then(function(response) {
+ return response.count;
+ })._thenRunCallbacks(options);
+ },
+
+ /**
+ * Retrieves at most one Parse.Object that satisfies this query.
+ *
+ * Either options.success or options.error is called when it completes.
+ * success is passed the object if there is one. otherwise, undefined.
+ *
+ * @param {Object} options A Backbone-style options object. Valid options
+ * are:
+ * - success: Function to call when the find completes successfully.
+ *
- error: Function to call when the find fails.
+ *
- useMasterKey: In Cloud Code and Node only, causes the Master Key to
+ * be used for this request.
+ *
+ *
+ * @return {Parse.Promise} A promise that is resolved with the object when
+ * the query completes.
+ */
+ first: function(options) {
+ var self = this;
+ options = options || {};
+
+ var params = this.toJSON();
+ params.limit = 1;
+ var request = Parse._request({
+ route: "classes",
+ className: this.className,
+ method: "GET",
+ useMasterKey: options.useMasterKey,
+ data: params
+ });
+
+ return request.then(function(response) {
+ return _.map(response.results, function(json) {
+ var obj;
+ if (response.className) {
+ obj = new Parse.Object(response.className);
+ } else {
+ obj = new self.objectClass();
+ }
+ obj._finishFetch(json, true);
+ return obj;
+ })[0];
+ })._thenRunCallbacks(options);
+ },
+
+ /**
+ * Returns a new instance of Parse.Collection backed by this query.
+ * @param {Array} items An array of instances of Parse.Object
+ * with which to start this Collection.
+ * @param {Object} options An optional object with Backbone-style options.
+ * Valid options are:
+ * - model: The Parse.Object subclass that this collection contains.
+ *
- query: An instance of Parse.Query to use when fetching items.
+ *
- comparator: A string property name or function to sort by.
+ *
+ * @return {Parse.Collection}
+ */
+ collection: function(items, options) {
+ options = options || {};
+ return new Parse.Collection(items, _.extend(options, {
+ model: this.objectClass,
+ query: this
+ }));
+ },
+
+ /**
+ * Sets the number of results to skip before returning any results.
+ * This is useful for pagination.
+ * Default is to skip zero results.
+ * @param {Number} n the number of results to skip.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ skip: function(n) {
+ this._skip = n;
+ return this;
+ },
+
+ /**
+ * Sets the limit of the number of results to return. The default limit is
+ * 100, with a maximum of 1000 results being returned at a time.
+ * @param {Number} n the number of results to limit to.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ limit: function(n) {
+ this._limit = n;
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be equal to the provided value.
+ * @param {String} key The key to check.
+ * @param value The value that the Parse.Object must contain.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ equalTo: function(key, value) {
+ if (_.isUndefined(value)) {
+ return this.doesNotExist(key);
+ }
+
+ this._where[key] = Parse._encode(value);
+ return this;
+ },
+
+ /**
+ * Helper for condition queries
+ */
+ _addCondition: function(key, condition, value) {
+ // Check if we already have a condition
+ if (!this._where[key]) {
+ this._where[key] = {};
+ }
+ this._where[key][condition] = Parse._encode(value);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be not equal to the provided value.
+ * @param {String} key The key to check.
+ * @param value The value that must not be equalled.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ notEqualTo: function(key, value) {
+ this._addCondition(key, "$ne", value);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be less than the provided value.
+ * @param {String} key The key to check.
+ * @param value The value that provides an upper bound.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ lessThan: function(key, value) {
+ this._addCondition(key, "$lt", value);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be greater than the provided value.
+ * @param {String} key The key to check.
+ * @param value The value that provides an lower bound.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ greaterThan: function(key, value) {
+ this._addCondition(key, "$gt", value);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be less than or equal to the provided value.
+ * @param {String} key The key to check.
+ * @param value The value that provides an upper bound.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ lessThanOrEqualTo: function(key, value) {
+ this._addCondition(key, "$lte", value);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be greater than or equal to the provided value.
+ * @param {String} key The key to check.
+ * @param value The value that provides an lower bound.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ greaterThanOrEqualTo: function(key, value) {
+ this._addCondition(key, "$gte", value);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * be contained in the provided list of values.
+ * @param {String} key The key to check.
+ * @param {Array} values The values that will match.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ containedIn: function(key, values) {
+ this._addCondition(key, "$in", values);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * not be contained in the provided list of values.
+ * @param {String} key The key to check.
+ * @param {Array} values The values that will not match.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ notContainedIn: function(key, values) {
+ this._addCondition(key, "$nin", values);
+ return this;
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's value to
+ * contain each one of the provided list of values.
+ * @param {String} key The key to check. This key's value must be an array.
+ * @param {Array} values The values that will match.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ containsAll: function(key, values) {
+ this._addCondition(key, "$all", values);
+ return this;
+ },
+
+
+ /**
+ * Add a constraint for finding objects that contain the given key.
+ * @param {String} key The key that should exist.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ exists: function(key) {
+ this._addCondition(key, "$exists", true);
+ return this;
+ },
+
+ /**
+ * Add a constraint for finding objects that do not contain a given key.
+ * @param {String} key The key that should not exist
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ doesNotExist: function(key) {
+ this._addCondition(key, "$exists", false);
+ return this;
+ },
+
+ /**
+ * Add a regular expression constraint for finding string values that match
+ * the provided regular expression.
+ * This may be slow for large datasets.
+ * @param {String} key The key that the string to match is stored in.
+ * @param {RegExp} regex The regular expression pattern to match.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ matches: function(key, regex, modifiers) {
+ this._addCondition(key, "$regex", regex);
+ if (!modifiers) { modifiers = ""; }
+ // Javascript regex options support mig as inline options but store them
+ // as properties of the object. We support mi & should migrate them to
+ // modifiers
+ if (regex.ignoreCase) { modifiers += 'i'; }
+ if (regex.multiline) { modifiers += 'm'; }
+
+ if (modifiers && modifiers.length) {
+ this._addCondition(key, "$options", modifiers);
+ }
+ return this;
+ },
+
+ /**
+ * Add a constraint that requires that a key's value matches a Parse.Query
+ * constraint.
+ * @param {String} key The key that the contains the object to match the
+ * query.
+ * @param {Parse.Query} query The query that should match.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ matchesQuery: function(key, query) {
+ var queryJSON = query.toJSON();
+ queryJSON.className = query.className;
+ this._addCondition(key, "$inQuery", queryJSON);
+ return this;
+ },
+
+ /**
+ * Add a constraint that requires that a key's value not matches a
+ * Parse.Query constraint.
+ * @param {String} key The key that the contains the object to match the
+ * query.
+ * @param {Parse.Query} query The query that should not match.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ doesNotMatchQuery: function(key, query) {
+ var queryJSON = query.toJSON();
+ queryJSON.className = query.className;
+ this._addCondition(key, "$notInQuery", queryJSON);
+ return this;
+ },
+
+
+ /**
+ * Add a constraint that requires that a key's value matches a value in
+ * an object returned by a different Parse.Query.
+ * @param {String} key The key that contains the value that is being
+ * matched.
+ * @param {String} queryKey The key in the objects returned by the query to
+ * match against.
+ * @param {Parse.Query} query The query to run.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ matchesKeyInQuery: function(key, queryKey, query) {
+ var queryJSON = query.toJSON();
+ queryJSON.className = query.className;
+ this._addCondition(key, "$select",
+ { key: queryKey, query: queryJSON });
+ return this;
+ },
+
+ /**
+ * Add a constraint that requires that a key's value not match a value in
+ * an object returned by a different Parse.Query.
+ * @param {String} key The key that contains the value that is being
+ * excluded.
+ * @param {String} queryKey The key in the objects returned by the query to
+ * match against.
+ * @param {Parse.Query} query The query to run.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ doesNotMatchKeyInQuery: function(key, queryKey, query) {
+ var queryJSON = query.toJSON();
+ queryJSON.className = query.className;
+ this._addCondition(key, "$dontSelect",
+ { key: queryKey, query: queryJSON });
+ return this;
+ },
+
+ /**
+ * Add constraint that at least one of the passed in queries matches.
+ * @param {Array} queries
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ _orQuery: function(queries) {
+ var queryJSON = _.map(queries, function(q) {
+ return q.toJSON().where;
+ });
+
+ this._where.$or = queryJSON;
+ return this;
+ },
+
+ /**
+ * Converts a string into a regex that matches it.
+ * Surrounding with \Q .. \E does this, we just need to escape \E's in
+ * the text separately.
+ */
+ _quote: function(s) {
+ return "\\Q" + s.replace("\\E", "\\E\\\\E\\Q") + "\\E";
+ },
+
+ /**
+ * Add a constraint for finding string values that contain a provided
+ * string. This may be slow for large datasets.
+ * @param {String} key The key that the string to match is stored in.
+ * @param {String} substring The substring that the value must contain.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ contains: function(key, value) {
+ this._addCondition(key, "$regex", this._quote(value));
+ return this;
+ },
+
+ /**
+ * Add a constraint for finding string values that start with a provided
+ * string. This query will use the backend index, so it will be fast even
+ * for large datasets.
+ * @param {String} key The key that the string to match is stored in.
+ * @param {String} prefix The substring that the value must start with.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ startsWith: function(key, value) {
+ this._addCondition(key, "$regex", "^" + this._quote(value));
+ return this;
+ },
+
+ /**
+ * Add a constraint for finding string values that end with a provided
+ * string. This will be slow for large datasets.
+ * @param {String} key The key that the string to match is stored in.
+ * @param {String} suffix The substring that the value must end with.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ endsWith: function(key, value) {
+ this._addCondition(key, "$regex", this._quote(value) + "$");
+ return this;
+ },
+
+ /**
+ * Sorts the results in ascending order by the given key.
+ *
+ * @param {(String|String[]|...String} key The key to order by, which is a
+ * string of comma separated values, or an Array of keys, or multiple keys.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ ascending: function() {
+ this._order = [];
+ return this.addAscending.apply(this, arguments);
+ },
+
+ /**
+ * Sorts the results in ascending order by the given key,
+ * but can also add secondary sort descriptors without overwriting _order.
+ *
+ * @param {(String|String[]|...String} key The key to order by, which is a
+ * string of comma separated values, or an Array of keys, or multiple keys.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ addAscending: function(key) {
+ var self = this;
+ if (!this._order) {
+ this._order = [];
+ }
+ Parse._arrayEach(arguments, function(key) {
+ if (Array.isArray(key)) {
+ key = key.join();
+ }
+ self._order = self._order.concat(key.replace(/\s/g, "").split(","));
+ });
+ return this;
+ },
+
+ /**
+ * Sorts the results in descending order by the given key.
+ *
+ * @param {(String|String[]|...String} key The key to order by, which is a
+ * string of comma separated values, or an Array of keys, or multiple keys.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ descending: function(key) {
+ this._order = [];
+ return this.addDescending.apply(this, arguments);
+ },
+
+ /**
+ * Sorts the results in descending order by the given key,
+ * but can also add secondary sort descriptors without overwriting _order.
+ *
+ * @param {(String|String[]|...String} key The key to order by, which is a
+ * string of comma separated values, or an Array of keys, or multiple keys.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ addDescending: function(key) {
+ var self = this;
+ if (!this._order) {
+ this._order = [];
+ }
+ Parse._arrayEach(arguments, function(key) {
+ if (Array.isArray(key)) {
+ key = key.join();
+ }
+ self._order = self._order.concat(
+ _.map(key.replace(/\s/g, "").split(","),
+ function(k) { return "-" + k; }));
+ });
+ return this;
+ },
+
+ /**
+ * Add a proximity based constraint for finding objects with key point
+ * values near the point given.
+ * @param {String} key The key that the Parse.GeoPoint is stored in.
+ * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ near: function(key, point) {
+ if (!(point instanceof Parse.GeoPoint)) {
+ // Try to cast it to a GeoPoint, so that near("loc", [20,30]) works.
+ point = new Parse.GeoPoint(point);
+ }
+ this._addCondition(key, "$nearSphere", point);
+ return this;
+ },
+
+ /**
+ * Add a proximity based constraint for finding objects with key point
+ * values near the point given and within the maximum distance given.
+ * @param {String} key The key that the Parse.GeoPoint is stored in.
+ * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
+ * @param {Number} maxDistance Maximum distance (in radians) of results to
+ * return.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ withinRadians: function(key, point, distance) {
+ this.near(key, point);
+ this._addCondition(key, "$maxDistance", distance);
+ return this;
+ },
+
+ /**
+ * Add a proximity based constraint for finding objects with key point
+ * values near the point given and within the maximum distance given.
+ * Radius of earth used is 3958.8 miles.
+ * @param {String} key The key that the Parse.GeoPoint is stored in.
+ * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
+ * @param {Number} maxDistance Maximum distance (in miles) of results to
+ * return.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ withinMiles: function(key, point, distance) {
+ return this.withinRadians(key, point, distance / 3958.8);
+ },
+
+ /**
+ * Add a proximity based constraint for finding objects with key point
+ * values near the point given and within the maximum distance given.
+ * Radius of earth used is 6371.0 kilometers.
+ * @param {String} key The key that the Parse.GeoPoint is stored in.
+ * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
+ * @param {Number} maxDistance Maximum distance (in kilometers) of results
+ * to return.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ withinKilometers: function(key, point, distance) {
+ return this.withinRadians(key, point, distance / 6371.0);
+ },
+
+ /**
+ * Add a constraint to the query that requires a particular key's
+ * coordinates be contained within a given rectangular geographic bounding
+ * box.
+ * @param {String} key The key to be constrained.
+ * @param {Parse.GeoPoint} southwest
+ * The lower-left inclusive corner of the box.
+ * @param {Parse.GeoPoint} northeast
+ * The upper-right inclusive corner of the box.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ withinGeoBox: function(key, southwest, northeast) {
+ if (!(southwest instanceof Parse.GeoPoint)) {
+ southwest = new Parse.GeoPoint(southwest);
+ }
+ if (!(northeast instanceof Parse.GeoPoint)) {
+ northeast = new Parse.GeoPoint(northeast);
+ }
+ this._addCondition(key, '$within', { '$box': [southwest, northeast] });
+ return this;
+ },
+
+ /**
+ * Include nested Parse.Objects for the provided key. You can use dot
+ * notation to specify which fields in the included object are also fetch.
+ * @param {String} key The name of the key to include.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ include: function() {
+ var self = this;
+ Parse._arrayEach(arguments, function(key) {
+ if (_.isArray(key)) {
+ self._include = self._include.concat(key);
+ } else {
+ self._include.push(key);
+ }
+ });
+ return this;
+ },
+
+ /**
+ * Restrict the fields of the returned Parse.Objects to include only the
+ * provided keys. If this is called multiple times, then all of the keys
+ * specified in each of the calls will be included.
+ * @param {Array} keys The names of the keys to include.
+ * @return {Parse.Query} Returns the query, so you can chain this call.
+ */
+ select: function() {
+ var self = this;
+ this._select = this._select || [];
+ Parse._arrayEach(arguments, function(key) {
+ if (_.isArray(key)) {
+ self._select = self._select.concat(key);
+ } else {
+ self._select.push(key);
+ }
+ });
+ return this;
+ },
+
+ /**
+ * Iterates over each result of a query, calling a callback for each one. If
+ * the callback returns a promise, the iteration will not continue until
+ * that promise has been fulfilled. If the callback returns a rejected
+ * promise, then iteration will stop with that error. The items are
+ * processed in an unspecified order. The query may not have any sort order,
+ * and may not use limit or skip.
+ * @param {Function} callback Callback that will be called with each result
+ * of the query.
+ * @param {Object} options An optional Backbone-like options object with
+ * success and error callbacks that will be invoked once the iteration
+ * has finished.
+ * @return {Parse.Promise} A promise that will be fulfilled once the
+ * iteration has completed.
+ */
+ each: function(callback, options) {
+ options = options || {};
+
+ if (this._order || this._skip || (this._limit >= 0)) {
+ var error =
+ "Cannot iterate on a query with sort, skip, or limit.";
+ return Parse.Promise.error(error)._thenRunCallbacks(options);
+ }
+
+ var promise = new Parse.Promise();
+
+ var query = new Parse.Query(this.objectClass);
+ // We can override the batch size from the options.
+ // This is undocumented, but useful for testing.
+ query._limit = options.batchSize || 100;
+ query._where = _.clone(this._where);
+ query._include = _.clone(this._include);
+ if (this._select) {
+ query._select = _.clone(this._select);
+ }
+
+ query.ascending('objectId');
+
+ var findOptions = {};
+ if (_.has(options, "useMasterKey")) {
+ findOptions.useMasterKey = options.useMasterKey;
+ }
+
+ var finished = false;
+ return Parse.Promise._continueWhile(function() {
+ return !finished;
+
+ }, function() {
+ return query.find(findOptions).then(function(results) {
+ var callbacksDone = Parse.Promise.as();
+ Parse._.each(results, function(result) {
+ callbacksDone = callbacksDone.then(function() {
+ return callback(result);
+ });
+ });
+
+ return callbacksDone.then(function() {
+ if (results.length >= query._limit) {
+ query.greaterThan("objectId", results[results.length - 1].id);
+ } else {
+ finished = true;
+ }
+ });
+ });
+ })._thenRunCallbacks(options);
+ }
+ };
+
+}(this));
+
+/*global FB: false , console: false*/
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ var PUBLIC_KEY = "*";
+
+ var initialized = false;
+ var requestedPermissions;
+ var initOptions;
+ var provider = {
+ authenticate: function(options) {
+ var self = this;
+ FB.login(function(response) {
+ if (response.authResponse) {
+ if (options.success) {
+ options.success(self, {
+ id: response.authResponse.userID,
+ access_token: response.authResponse.accessToken,
+ expiration_date: new Date(response.authResponse.expiresIn * 1000 +
+ (new Date()).getTime()).toJSON()
+ });
+ }
+ } else {
+ if (options.error) {
+ options.error(self, response);
+ }
+ }
+ }, {
+ scope: requestedPermissions
+ });
+ },
+ restoreAuthentication: function(authData) {
+ if (authData) {
+ var authResponse = {
+ userID: authData.id,
+ accessToken: authData.access_token,
+ expiresIn: (Parse._parseDate(authData.expiration_date).getTime() -
+ (new Date()).getTime()) / 1000
+ };
+ var newOptions = _.clone(initOptions);
+ newOptions.authResponse = authResponse;
+
+ // Suppress checks for login status from the browser.
+ newOptions.status = false;
+
+ // If the user doesn't match the one known by the FB SDK, log out.
+ // Most of the time, the users will match -- it's only in cases where
+ // the FB SDK knows of a different user than the one being restored
+ // from a Parse User that logged in with username/password.
+ var existingResponse = FB.getAuthResponse();
+ if (existingResponse &&
+ existingResponse.userID !== authResponse.userID) {
+ FB.logout();
+ }
+
+ FB.init(newOptions);
+ }
+ return true;
+ },
+ getAuthType: function() {
+ return "facebook";
+ },
+ deauthenticate: function() {
+ this.restoreAuthentication(null);
+ }
+ };
+
+ /**
+ * Provides a set of utilities for using Parse with Facebook.
+ * @namespace
+ * Provides a set of utilities for using Parse with Facebook.
+ */
+ Parse.FacebookUtils = {
+ /**
+ * Initializes Parse Facebook integration. Call this function after you
+ * have loaded the Facebook Javascript SDK with the same parameters
+ * as you would pass to
+ *
+ * FB.init(). Parse.FacebookUtils will invoke FB.init() for you
+ * with these arguments.
+ *
+ * @param {Object} options Facebook options argument as described here:
+ *
+ * FB.init(). The status flag will be coerced to 'false' because it
+ * interferes with Parse Facebook integration. Call FB.getLoginStatus()
+ * explicitly if this behavior is required by your application.
+ */
+ init: function(options) {
+ if (typeof(FB) === 'undefined') {
+ throw "The Facebook JavaScript SDK must be loaded before calling init.";
+ }
+ initOptions = _.clone(options) || {};
+ if (initOptions.status && typeof(console) !== "undefined") {
+ var warn = console.warn || console.log || function() {};
+ warn.call(console, "The 'status' flag passed into" +
+ " FB.init, when set to true, can interfere with Parse Facebook" +
+ " integration, so it has been suppressed. Please call" +
+ " FB.getLoginStatus() explicitly if you require this behavior.");
+ }
+ initOptions.status = false;
+ FB.init(initOptions);
+ Parse.User._registerAuthenticationProvider(provider);
+ initialized = true;
+ },
+
+ /**
+ * Gets whether the user has their account linked to Facebook.
+ *
+ * @param {Parse.User} user User to check for a facebook link.
+ * The user must be logged in on this device.
+ * @return {Boolean} true if the user has their account
+ * linked to Facebook.
+ */
+ isLinked: function(user) {
+ return user._isLinked("facebook");
+ },
+
+ /**
+ * Logs in a user using Facebook. This method delegates to the Facebook
+ * SDK to authenticate the user, and then automatically logs in (or
+ * creates, in the case where it is a new user) a Parse.User.
+ *
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ logIn: function(permissions, options) {
+ if (!permissions || _.isString(permissions)) {
+ if (!initialized) {
+ throw "You must initialize FacebookUtils before calling logIn.";
+ }
+ requestedPermissions = permissions;
+ return Parse.User._logInWith("facebook", options);
+ } else {
+ var newOptions = _.clone(options) || {};
+ newOptions.authData = permissions;
+ return Parse.User._logInWith("facebook", newOptions);
+ }
+ },
+
+ /**
+ * Links Facebook to an existing PFUser. This method delegates to the
+ * Facebook SDK to authenticate the user, and then automatically links
+ * the account to the Parse.User.
+ *
+ * @param {Parse.User} user User to link to Facebook. This must be the
+ * current user.
+ * @param {String, Object} permissions The permissions required for Facebook
+ * log in. This is a comma-separated string of permissions.
+ * Alternatively, supply a Facebook authData object as described in our
+ * REST API docs if you want to handle getting facebook auth tokens
+ * yourself.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ link: function(user, permissions, options) {
+ if (!permissions || _.isString(permissions)) {
+ if (!initialized) {
+ throw "You must initialize FacebookUtils before calling link.";
+ }
+ requestedPermissions = permissions;
+ return user._linkWith("facebook", options);
+ } else {
+ var newOptions = _.clone(options) || {};
+ newOptions.authData = permissions;
+ return user._linkWith("facebook", newOptions);
+ }
+ },
+
+ /**
+ * Unlinks the Parse.User from a Facebook account.
+ *
+ * @param {Parse.User} user User to unlink from Facebook. This must be the
+ * current user.
+ * @param {Object} options Standard options object with success and error
+ * callbacks.
+ */
+ unlink: function(user, options) {
+ if (!initialized) {
+ throw "You must initialize FacebookUtils before calling unlink.";
+ }
+ return user._unlinkFrom("facebook", options);
+ }
+ };
+
+}(this));
+
+/*global _: false, document: false, window: false, navigator: false */
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * History serves as a global router (per frame) to handle hashchange
+ * events or pushState, match the appropriate route, and trigger
+ * callbacks. You shouldn't ever have to create one of these yourself
+ * — you should use the reference to Parse.history
+ * that will be created for you automatically if you make use of
+ * Routers with routes.
+ * @class
+ *
+ * A fork of Backbone.History, provided for your convenience. If you
+ * use this class, you must also include jQuery, or another library
+ * that provides a jQuery-compatible $ function. For more information,
+ * see the
+ * Backbone documentation.
+ * Available in the client SDK only.
+ */
+ Parse.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ };
+
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // Has the history handling already been started?
+ Parse.History.started = false;
+
+ // Set up all inheritable **Parse.History** properties and methods.
+ _.extend(Parse.History.prototype, Parse.Events,
+ /** @lends Parse.History.prototype */ {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(windowOverride) {
+ var loc = windowOverride ? windowOverride.location : window.location;
+ var match = loc.href.match(/#(.*)$/);
+ return match ? match[1] : '';
+ },
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment: function(fragment, forcePushState) {
+ if (Parse._isNullOrUndefined(fragment)) {
+ if (this._hasPushState || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) {
+ fragment += search;
+ }
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ if (!fragment.indexOf(this.options.root)) {
+ fragment = fragment.substr(this.options.root.length);
+ }
+ return fragment.replace(routeStripper, '');
+ },
+
+ /**
+ * Start the hash change handling, returning `true` if the current
+ * URL matches an existing route, and `false` otherwise.
+ */
+ start: function(options) {
+ if (Parse.History.started) {
+ throw new Error("Parse.history has already been started");
+ }
+ Parse.History.started = true;
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState &&
+ window.history &&
+ window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) &&
+ (!docMode || docMode <= 7));
+
+ if (oldIE) {
+ this.iframe = Parse.$('')
+ .hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ Parse.$(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange &&
+ ('onhashchange' in window) &&
+ !oldIE) {
+ Parse.$(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = window.setInterval(this.checkUrl,
+ this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = window.location;
+ var atRoot = loc.pathname === this.options.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange &&
+ this._wantsPushState &&
+ !this._hasPushState &&
+ !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ window.location.replace(this.options.root + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState &&
+ this._hasPushState &&
+ atRoot &&
+ loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ window.history.replaceState({}, document.title,
+ loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Parse.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ Parse.$(window).unbind('popstate', this.checkUrl)
+ .unbind('hashchange', this.checkUrl);
+ window.clearInterval(this._checkUrlInterval);
+ Parse.History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current === this.fragment && this.iframe) {
+ current = this.getFragment(this.getHash(this.iframe));
+ }
+ if (current === this.fragment) {
+ return false;
+ }
+ if (this.iframe) {
+ this.navigate(current);
+ }
+ if (!this.loadUrl()) {
+ this.loadUrl(this.getHash());
+ }
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the
+ // history.
+ navigate: function(fragment, options) {
+ if (!Parse.History.started) {
+ return false;
+ }
+ if (!options || options === true) {
+ options = {trigger: options};
+ }
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment === frag) {
+ return;
+ }
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ if (frag.indexOf(this.options.root) !== 0) {
+ frag = this.options.root + frag;
+ }
+ this.fragment = frag;
+ var replaceOrPush = options.replace ? 'replaceState' : 'pushState';
+ window.history[replaceOrPush]({}, document.title, frag);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this.fragment = frag;
+ this._updateHash(window.location, frag, options.replace);
+ if (this.iframe &&
+ (frag !== this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier
+ // to push a history entry on hash-tag change.
+ // When replace is true, we don't want this.
+ if (!options.replace) {
+ this.iframe.document.open().close();
+ }
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ window.location.assign(this.options.root + fragment);
+ }
+ if (options.trigger) {
+ this.loadUrl(fragment);
+ }
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ var s = location.toString().replace(/(javascript:|#).*$/, '');
+ location.replace(s + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+ });
+}(this));
+
+/*global _: false*/
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * Routers map faux-URLs to actions, and fire events when routes are
+ * matched. Creating a new one sets its `routes` hash, if not set statically.
+ * @class
+ *
+ * A fork of Backbone.Router, provided for your convenience.
+ * For more information, see the
+ * Backbone
+ * documentation.
+ * Available in the client SDK only.
+ */
+ Parse.Router = function(options) {
+ options = options || {};
+ if (options.routes) {
+ this.routes = options.routes;
+ }
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regular expressions for matching named param parts and splatted
+ // parts of route strings.
+ var namedParam = /:\w+/g;
+ var splatParam = /\*\w+/g;
+ var escapeRegExp = /[\-\[\]{}()+?.,\\\^\$\|#\s]/g;
+
+ // Set up all inheritable **Parse.Router** properties and methods.
+ _.extend(Parse.Router.prototype, Parse.Events,
+ /** @lends Parse.Router.prototype */ {
+
+ /**
+ * Initialize is an empty function by default. Override it with your own
+ * initialization logic.
+ */
+ initialize: function(){},
+
+ /**
+ * Manually bind a single named route to a callback. For example:
+ *
+ * this.route('search/:query/p:num', 'search', function(query, num) {
+ * ...
+ * });
+ */
+ route: function(route, name, callback) {
+ Parse.history = Parse.history || new Parse.History();
+ if (!_.isRegExp(route)) {
+ route = this._routeToRegExp(route);
+ }
+ if (!callback) {
+ callback = this[name];
+ }
+ Parse.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ if (callback) {
+ callback.apply(this, args);
+ }
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Parse.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ /**
+ * Whenever you reach a point in your application that you'd
+ * like to save as a URL, call navigate in order to update the
+ * URL. If you wish to also call the route function, set the
+ * trigger option to true. To update the URL without creating
+ * an entry in the browser's history, set the replace option
+ * to true.
+ */
+ navigate: function(fragment, options) {
+ Parse.history.navigate(fragment, options);
+ },
+
+ // Bind all defined routes to `Parse.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes: function() {
+ if (!this.routes) {
+ return;
+ }
+ var routes = [];
+ for (var route in this.routes) {
+ if (this.routes.hasOwnProperty(route)) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+ });
+
+ /**
+ * @function
+ * @param {Object} instanceProps Instance properties for the router.
+ * @param {Object} classProps Class properies for the router.
+ * @return {Class} A new subclass of Parse.Router.
+ */
+ Parse.Router.extend = Parse._extend;
+}(this));
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+ var _ = Parse._;
+
+ /**
+ * @namespace Contains functions for calling and declaring
+ * cloud functions.
+ *
+ * Some functions are only available from Cloud Code.
+ *
+ */
+ Parse.Cloud = Parse.Cloud || {};
+
+ _.extend(Parse.Cloud, /** @lends Parse.Cloud */ {
+ /**
+ * Makes a call to a cloud function.
+ * @param {String} name The function name.
+ * @param {Object} data The parameters to send to the cloud function.
+ * @param {Object} options A Backbone-style options object
+ * options.success, if set, should be a function to handle a successful
+ * call to a cloud function. options.error should be a function that
+ * handles an error running the cloud function. Both functions are
+ * optional. Both functions take a single argument.
+ * @return {Parse.Promise} A promise that will be resolved with the result
+ * of the function.
+ */
+ run: function(name, data, options) {
+ options = options || {};
+
+ var request = Parse._request({
+ route: "functions",
+ className: name,
+ method: 'POST',
+ useMasterKey: options.useMasterKey,
+ data: Parse._encode(data, null, true)
+ });
+
+ return request.then(function(resp) {
+ return Parse._decode(null, resp).result;
+ })._thenRunCallbacks(options);
+ }
+ });
+}(this));
+
+(function(root) {
+ root.Parse = root.Parse || {};
+ var Parse = root.Parse;
+
+ Parse.Installation = Parse.Object.extend("_Installation");
+
+ /**
+ * Contains functions to deal with Push in Parse
+ * @name Parse.Push
+ * @namespace
+ */
+ Parse.Push = Parse.Push || {};
+
+ /**
+ * Sends a push notification.
+ * @param {Object} data - The data of the push notification. Valid fields
+ * are:
+ *
+ * - channels - An Array of channels to push to.
+ * - push_time - A Date object for when to send the push.
+ * - expiration_time - A Date object for when to expire
+ * the push.
+ * - expiration_interval - The seconds from now to expire the push.
+ * - where - A Parse.Query over Parse.Installation that is used to match
+ * a set of installations to push to.
+ * - data - The data to send as part of the push
+ *
+ * @param {Object} options An object that has an optional success function,
+ * that takes no arguments and will be called on a successful push, and
+ * an error function that takes a Parse.Error and will be called if the push
+ * failed.
+ * @return {Parse.Promise} A promise that is fulfilled when the push request
+ * completes.
+ */
+ Parse.Push.send = function(data, options) {
+ options = options || {};
+
+ if (data.where) {
+ data.where = data.where.toJSON().where;
+ }
+
+ if (data.push_time) {
+ data.push_time = data.push_time.toJSON();
+ }
+
+ if (data.expiration_time) {
+ data.expiration_time = data.expiration_time.toJSON();
+ }
+
+ if (data.expiration_time && data.expiration_interval) {
+ throw "Both expiration_time and expiration_interval can't be set";
+ }
+
+ var request = Parse._request({
+ route: 'push',
+ method: 'POST',
+ data: data,
+ useMasterKey: options.useMasterKey
+ });
+ return request._thenRunCallbacks(options);
+ };
+}(this));
diff --git a/Web/public/vendor/parse/parse.min.js b/Web/public/vendor/parse/parse.min.js
new file mode 100644
index 0000000..209d965
--- /dev/null
+++ b/Web/public/vendor/parse/parse.min.js
@@ -0,0 +1,3 @@
+!function(a){a.Parse=a.Parse||{},a.Parse.VERSION="js1.3.5"}(this),function(){var a=this,b=a._,c={},d=Array.prototype,e=Object.prototype,f=Function.prototype,g=d.push,h=d.slice,i=d.concat,j=e.toString,k=e.hasOwnProperty,l=d.forEach,m=d.map,n=d.reduce,o=d.reduceRight,p=d.filter,q=d.every,r=d.some,s=d.indexOf,t=d.lastIndexOf,u=Array.isArray,v=Object.keys,w=f.bind,x=function(a){return a instanceof x?a:this instanceof x?void(this._wrapped=a):new x(a)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=x),exports._=x):a._=x,x.VERSION="1.4.4";var y=x.each=x.forEach=function(a,b,d){if(null!=a)if(l&&a.forEach===l)a.forEach(b,d);else if(a.length===+a.length){for(var e=0,f=a.length;f>e;e++)if(b.call(d,a[e],e,a)===c)return}else for(var g in a)if(x.has(a,g)&&b.call(d,a[g],g,a)===c)return};x.map=x.collect=function(a,b,c){var d=[];return null==a?d:m&&a.map===m?a.map(b,c):(y(a,function(a,e,f){d[d.length]=b.call(c,a,e,f)}),d)};var z="Reduce of empty array with no initial value";x.reduce=x.foldl=x.inject=function(a,b,c,d){var e=arguments.length>2;if(null==a&&(a=[]),n&&a.reduce===n)return d&&(b=x.bind(b,d)),e?a.reduce(b,c):a.reduce(b);if(y(a,function(a,f,g){e?c=b.call(d,c,a,f,g):(c=a,e=!0)}),!e)throw new TypeError(z);return c},x.reduceRight=x.foldr=function(a,b,c,d){var e=arguments.length>2;if(null==a&&(a=[]),o&&a.reduceRight===o)return d&&(b=x.bind(b,d)),e?a.reduceRight(b,c):a.reduceRight(b);var f=a.length;if(f!==+f){var g=x.keys(a);f=g.length}if(y(a,function(h,i,j){i=g?g[--f]:--f,e?c=b.call(d,c,a[i],i,j):(c=a[i],e=!0)}),!e)throw new TypeError(z);return c},x.find=x.detect=function(a,b,c){var d;return A(a,function(a,e,f){return b.call(c,a,e,f)?(d=a,!0):void 0}),d},x.filter=x.select=function(a,b,c){var d=[];return null==a?d:p&&a.filter===p?a.filter(b,c):(y(a,function(a,e,f){b.call(c,a,e,f)&&(d[d.length]=a)}),d)},x.reject=function(a,b,c){return x.filter(a,function(a,d,e){return!b.call(c,a,d,e)},c)},x.every=x.all=function(a,b,d){b||(b=x.identity);var e=!0;return null==a?e:q&&a.every===q?a.every(b,d):(y(a,function(a,f,g){return(e=e&&b.call(d,a,f,g))?void 0:c}),!!e)};var A=x.some=x.any=function(a,b,d){b||(b=x.identity);var e=!1;return null==a?e:r&&a.some===r?a.some(b,d):(y(a,function(a,f,g){return e||(e=b.call(d,a,f,g))?c:void 0}),!!e)};x.contains=x.include=function(a,b){return null==a?!1:s&&a.indexOf===s?-1!=a.indexOf(b):A(a,function(a){return a===b})},x.invoke=function(a,b){var c=h.call(arguments,2),d=x.isFunction(b);return x.map(a,function(a){return(d?b:a[b]).apply(a,c)})},x.pluck=function(a,b){return x.map(a,function(a){return a[b]})},x.where=function(a,b,c){return x.isEmpty(b)?c?null:[]:x[c?"find":"filter"](a,function(a){for(var c in b)if(b[c]!==a[c])return!1;return!0})},x.findWhere=function(a,b){return x.where(a,b,!0)},x.max=function(a,b,c){if(!b&&x.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.max.apply(Math,a);if(!b&&x.isEmpty(a))return-1/0;var d={computed:-1/0,value:-1/0};return y(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g>=d.computed&&(d={value:a,computed:g})}),d.value},x.min=function(a,b,c){if(!b&&x.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.min.apply(Math,a);if(!b&&x.isEmpty(a))return 1/0;var d={computed:1/0,value:1/0};return y(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;gd||void 0===c)return 1;if(d>c||void 0===d)return-1}return a.indexf;){var h=f+g>>>1;c.call(d,a[h])=0})})},x.difference=function(a){var b=i.apply(d,h.call(arguments,1));return x.filter(a,function(a){return!x.contains(b,a)})},x.zip=function(){for(var a=h.call(arguments),b=x.max(x.pluck(a,"length")),c=new Array(b),d=0;b>d;d++)c[d]=x.pluck(a,""+d);return c},x.object=function(a,b){if(null==a)return{};for(var c={},d=0,e=a.length;e>d;d++)b?c[a[d]]=b[d]:c[a[d][0]]=a[d][1];return c},x.indexOf=function(a,b,c){if(null==a)return-1;var d=0,e=a.length;if(c){if("number"!=typeof c)return d=x.sortedIndex(a,b),a[d]===b?d:-1;d=0>c?Math.max(0,e+c):c}if(s&&a.indexOf===s)return a.indexOf(b,c);for(;e>d;d++)if(a[d]===b)return d;return-1},x.lastIndexOf=function(a,b,c){if(null==a)return-1;var d=null!=c;if(t&&a.lastIndexOf===t)return d?a.lastIndexOf(b,c):a.lastIndexOf(b);for(var e=d?c:a.length;e--;)if(a[e]===b)return e;return-1},x.range=function(a,b,c){arguments.length<=1&&(b=a||0,a=0),c=arguments[2]||1;for(var d=Math.max(Math.ceil((b-a)/c),0),e=0,f=new Array(d);d>e;)f[e++]=a,a+=c;return f},x.bind=function(a,b){if(a.bind===w&&w)return w.apply(a,h.call(arguments,1));var c=h.call(arguments,2);return function(){return a.apply(b,c.concat(h.call(arguments)))}},x.partial=function(a){var b=h.call(arguments,1);return function(){return a.apply(this,b.concat(h.call(arguments)))}},x.bindAll=function(a){var b=h.call(arguments,1);return 0===b.length&&(b=x.functions(a)),y(b,function(b){a[b]=x.bind(a[b],a)}),a},x.memoize=function(a,b){var c={};return b||(b=x.identity),function(){var d=b.apply(this,arguments);return x.has(c,d)?c[d]:c[d]=a.apply(this,arguments)}},x.delay=function(a,b){var c=h.call(arguments,2);return setTimeout(function(){return a.apply(null,c)},b)},x.defer=function(a){return x.delay.apply(x,[a,1].concat(h.call(arguments,1)))},x.throttle=function(a,b){var c,d,e,f,g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)};return function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},x.debounce=function(a,b,c){var d,e;return function(){var f=this,g=arguments,h=function(){d=null,c||(e=a.apply(f,g))},i=c&&!d;return clearTimeout(d),d=setTimeout(h,b),i&&(e=a.apply(f,g)),e}},x.once=function(a){var b,c=!1;return function(){return c?b:(c=!0,b=a.apply(this,arguments),a=null,b)}},x.wrap=function(a,b){return function(){var c=[a];return g.apply(c,arguments),b.apply(this,c)}},x.compose=function(){var a=arguments;return function(){for(var b=arguments,c=a.length-1;c>=0;c--)b=[a[c].apply(this,b)];return b[0]}},x.after=function(a,b){return 0>=a?b():function(){return--a<1?b.apply(this,arguments):void 0}},x.keys=v||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)x.has(a,c)&&(b[b.length]=c);return b},x.values=function(a){var b=[];for(var c in a)x.has(a,c)&&b.push(a[c]);return b},x.pairs=function(a){var b=[];for(var c in a)x.has(a,c)&&b.push([c,a[c]]);return b},x.invert=function(a){var b={};for(var c in a)x.has(a,c)&&(b[a[c]]=c);return b},x.functions=x.methods=function(a){var b=[];for(var c in a)x.isFunction(a[c])&&b.push(c);return b.sort()},x.extend=function(a){return y(h.call(arguments,1),function(b){if(b)for(var c in b)a[c]=b[c]}),a},x.pick=function(a){var b={},c=i.apply(d,h.call(arguments,1));return y(c,function(c){c in a&&(b[c]=a[c])}),b},x.omit=function(a){var b={},c=i.apply(d,h.call(arguments,1));for(var e in a)x.contains(c,e)||(b[e]=a[e]);return b},x.defaults=function(a){return y(h.call(arguments,1),function(b){if(b)for(var c in b)null==a[c]&&(a[c]=b[c])}),a},x.clone=function(a){return x.isObject(a)?x.isArray(a)?a.slice():x.extend({},a):a},x.tap=function(a,b){return b(a),a};var E=function(a,b,c,d){if(a===b)return 0!==a||1/a==1/b;if(null==a||null==b)return a===b;a instanceof x&&(a=a._wrapped),b instanceof x&&(b=b._wrapped);var e=j.call(a);if(e!=j.call(b))return!1;switch(e){case"[object String]":return a==String(b);case"[object Number]":return a!=+a?b!=+b:0==a?1/a==1/b:a==+b;case"[object Date]":case"[object Boolean]":return+a==+b;case"[object RegExp]":return a.source==b.source&&a.global==b.global&&a.multiline==b.multiline&&a.ignoreCase==b.ignoreCase}if("object"!=typeof a||"object"!=typeof b)return!1;for(var f=c.length;f--;)if(c[f]==a)return d[f]==b;c.push(a),d.push(b);var g=0,h=!0;if("[object Array]"==e){if(g=a.length,h=g==b.length)for(;g--&&(h=E(a[g],b[g],c,d)););}else{var i=a.constructor,k=b.constructor;if(i!==k&&!(x.isFunction(i)&&i instanceof i&&x.isFunction(k)&&k instanceof k))return!1;for(var l in a)if(x.has(a,l)&&(g++,!(h=x.has(b,l)&&E(a[l],b[l],c,d))))break;if(h){for(l in b)if(x.has(b,l)&&!g--)break;h=!g}}return c.pop(),d.pop(),h};x.isEqual=function(a,b){return E(a,b,[],[])},x.isEmpty=function(a){if(null==a)return!0;if(x.isArray(a)||x.isString(a))return 0===a.length;for(var b in a)if(x.has(a,b))return!1;return!0},x.isElement=function(a){return!(!a||1!==a.nodeType)},x.isArray=u||function(a){return"[object Array]"==j.call(a)},x.isObject=function(a){return a===Object(a)},y(["Arguments","Function","String","Number","Date","RegExp"],function(a){x["is"+a]=function(b){return j.call(b)=="[object "+a+"]"}}),x.isArguments(arguments)||(x.isArguments=function(a){return!(!a||!x.has(a,"callee"))}),"function"!=typeof/./&&(x.isFunction=function(a){return"function"==typeof a}),x.isFinite=function(a){return isFinite(a)&&!isNaN(parseFloat(a))},x.isNaN=function(a){return x.isNumber(a)&&a!=+a},x.isBoolean=function(a){return a===!0||a===!1||"[object Boolean]"==j.call(a)},x.isNull=function(a){return null===a},x.isUndefined=function(a){return void 0===a},x.has=function(a,b){return k.call(a,b)},x.noConflict=function(){return a._=b,this},x.identity=function(a){return a},x.times=function(a,b,c){for(var d=Array(a),e=0;a>e;e++)d[e]=b.call(c,e);return d},x.random=function(a,b){return null==b&&(b=a,a=0),a+Math.floor(Math.random()*(b-a+1))};var F={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};F.unescape=x.invert(F.escape);var G={escape:new RegExp("["+x.keys(F.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(F.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(a){x[a]=function(b){return null==b?"":(""+b).replace(G[a],function(b){return F[a][b]})}}),x.result=function(a,b){if(null==a)return null;var c=a[b];return x.isFunction(c)?c.call(a):c},x.mixin=function(a){y(x.functions(a),function(b){var c=x[b]=a[b];x.prototype[b]=function(){var a=[this._wrapped];return g.apply(a,arguments),L.call(this,c.apply(x,a))}})};var H=0;x.uniqueId=function(a){var b=++H+"";return a?a+b:b},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var I=/(.)^/,J={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},K=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(a,b,c){var d;c=x.defaults({},c,x.templateSettings);var e=new RegExp([(c.escape||I).source,(c.interpolate||I).source,(c.evaluate||I).source].join("|")+"|$","g"),f=0,g="__p+='";a.replace(e,function(b,c,d,e,h){return g+=a.slice(f,h).replace(K,function(a){return"\\"+J[a]}),c&&(g+="'+\n((__t=("+c+"))==null?'':_.escape(__t))+\n'"),d&&(g+="'+\n((__t=("+d+"))==null?'':__t)+\n'"),e&&(g+="';\n"+e+"\n__p+='"),f=h+b.length,b}),g+="';\n",c.variable||(g="with(obj||{}){\n"+g+"}\n"),g="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+g+"return __p;\n";try{d=new Function(c.variable||"obj","_",g)}catch(h){throw h.source=g,h}if(b)return d(b,x);var i=function(a){return d.call(this,a,x)};return i.source="function("+(c.variable||"obj")+"){\n"+g+"}",i},x.chain=function(a){return x(a).chain()};var L=function(a){return this._chain?x(a).chain():a};x.mixin(x),y(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];x.prototype[a]=function(){var c=this._wrapped;return b.apply(c,arguments),"shift"!=a&&"splice"!=a||0!==c.length||delete c[0],L.call(this,c)}}),y(["concat","join","slice"],function(a){var b=d[a];x.prototype[a]=function(){return L.call(this,b.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}.call(this),function(a){a.Parse=a.Parse||{};var b=a.Parse;"undefined"!=typeof localStorage?b.localStorage=localStorage:"undefined"!=typeof require&&(b.localStorage=require("localStorage")),"undefined"!=typeof XMLHttpRequest?b.XMLHttpRequest=XMLHttpRequest:"undefined"!=typeof require&&(b.XMLHttpRequest=require("xmlhttprequest").XMLHttpRequest),"undefined"!=typeof exports&&exports._?(b._=exports._.noConflict(),exports.Parse=b):b._=_.noConflict(),"undefined"!=typeof $&&(b.$=$);var c=function(){},d=function(a,d,e){var f;return f=d&&d.hasOwnProperty("constructor")?d.constructor:function(){a.apply(this,arguments)},b._.extend(f,a),c.prototype=a.prototype,f.prototype=new c,d&&b._.extend(f.prototype,d),e&&b._.extend(f,e),f.prototype.constructor=f,f.__super__=a.prototype,f};b.serverURL="https://api.parse.com","undefined"!=typeof process&&process.versions&&process.versions.node&&(b._isNode=!0),b.initialize=function(a,c,d){if(d)throw"Parse.initialize() was passed a Master Key, which is only allowed from within Node.js.";b._initialize(a,c)},b._initialize=function(a,c,d){b.applicationId=a,b.javaScriptKey=c,b.masterKey=d,b._useMasterKey=!1},b._isNode&&(b.initialize=b._initialize,b.Cloud=b.Cloud||{},b.Cloud.useMasterKey=function(){b._useMasterKey=!0}),b._getParsePath=function(a){if(!b.applicationId)throw"You need to call Parse.initialize before using Parse.";if(a||(a=""),!b._.isString(a))throw"Tried to get a localStorage path that wasn't a String.";return"/"===a[0]&&(a=a.substring(1)),"Parse/"+b.applicationId+"/"+a},b._installationId=null,b._getInstallationId=function(){if(b._installationId)return b._installationId;var a=b._getParsePath("installationId");if(b._installationId=b.localStorage.getItem(a),!b._installationId||""===b._installationId){var c=function(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)};b._installationId=c()+c()+"-"+c()+"-"+c()+"-"+c()+"-"+c()+c()+c(),b.localStorage.setItem(a,b._installationId)}return b._installationId},b._parseDate=function(a){var b=new RegExp("^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})T([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(.([0-9]+))?Z$"),c=b.exec(a);if(!c)return null;var d=c[1]||0,e=(c[2]||1)-1,f=c[3]||0,g=c[4]||0,h=c[5]||0,i=c[6]||0,j=c[8]||0;return new Date(Date.UTC(d,e,f,g,h,i,j))},b._ajaxIE8=function(a,c,d){var e=new b.Promise,f=new XDomainRequest;return f.onload=function(){var a;try{a=JSON.parse(f.responseText)}catch(b){e.reject(b)}a&&e.resolve(a)},f.onerror=f.ontimeout=function(){var a={responseText:JSON.stringify({code:b.Error.X_DOMAIN_REQUEST,error:"IE's XDomainRequest does not supply error info."})};e.reject(a)},f.onprogress=function(){},f.open(a,c),f.send(d),e},b._useXDomainRequest=function(){return"undefined"!=typeof XDomainRequest?"withCredentials"in new XMLHttpRequest?!1:!0:!1},b._ajax=function(a,c,d,e,f){var g={success:e,error:f};if(b._useXDomainRequest())return b._ajaxIE8(a,c,d)._thenRunCallbacks(g);var h=new b.Promise,i=0,j=function(){var e=!1,f=new b.XMLHttpRequest;f.onreadystatechange=function(){if(4===f.readyState){if(e)return;if(e=!0,f.status>=200&&f.status<300){var a;try{a=JSON.parse(f.responseText)}catch(b){h.reject(b)}a&&h.resolve(a,f.status,f)}else if(f.status>=500)if(++i<5){var c=Math.round(125*Math.random()*Math.pow(2,i));setTimeout(j,c)}else h.reject(f);else h.reject(f)}},f.open(a,c,!0),f.setRequestHeader("Content-Type","text/plain"),b._isNode&&f.setRequestHeader("User-Agent","Parse/"+b.VERSION+" (NodeJS "+process.versions.node+")"),f.send(d)};return j(),h._thenRunCallbacks(g)},b._extend=function(a,b){var c=d(this,a,b);return c.extend=this.extend,c},b._request=function(a){var c=a.route,d=a.className,e=a.objectId,f=a.method,g=a.useMasterKey,h=a.sessionToken,i=a.data;if(!b.applicationId)throw"You must specify your applicationId using Parse.initialize.";if(!b.javaScriptKey&&!b.masterKey)throw"You must specify a key using Parse.initialize.";if(!h){var j=b.User.current();j&&j._sessionToken&&(h=j._sessionToken)}if("batch"!==c&&"classes"!==c&&"events"!==c&&"files"!==c&&"functions"!==c&&"login"!==c&&"push"!==c&&"requestPasswordReset"!==c&&"rest_verify_analytics"!==c&&"users"!==c&&"jobs"!==c&&"config"!==c)throw"Bad route: '"+c+"'.";var k=b.serverURL;"/"!==k.charAt(k.length-1)&&(k+="/"),k+="1/"+c,d&&(k+="/"+d),e&&(k+="/"+e),i=b._.clone(i||{}),"POST"!==f&&(i._method=f,f="POST"),b._.isUndefined(g)&&(g=b._useMasterKey),i._ApplicationId=b.applicationId,g?i._MasterKey=b.masterKey:i._JavaScriptKey=b.javaScriptKey,i._ClientVersion=b.VERSION,i._InstallationId=b._getInstallationId(),h&&(i._SessionToken=h);var l=JSON.stringify(i);return b._ajax(f,k,l).then(null,function(a){var c;if(a&&a.responseText)try{var d=JSON.parse(a.responseText);c=new b.Error(d.code,d.error)}catch(e){c=new b.Error(b.Error.INVALID_JSON,"Received an error with invalid JSON from Parse: "+a.responseText)}else c=new b.Error(b.Error.CONNECTION_FAILED,"XMLHttpRequest failed: "+JSON.stringify(a));return b.Promise.error(c)})},b._getValue=function(a,c){return a&&a[c]?b._.isFunction(a[c])?a[c]():a[c]:null},b._encode=function(a,c,d){var e=b._;if(a instanceof b.Object){if(d)throw"Parse.Objects not allowed here";if(!c||e.include(c,a)||!a._hasData)return a._toPointer();if(!a.dirty())return c=c.concat(a),b._encode(a._toFullJSON(c),c,d);throw"Tried to save an object with a pointer to a new, unsaved object."}if(a instanceof b.ACL)return a.toJSON();if(e.isDate(a))return{__type:"Date",iso:a.toJSON()};if(a instanceof b.GeoPoint)return a.toJSON();if(e.isArray(a))return e.map(a,function(a){return b._encode(a,c,d)});if(e.isRegExp(a))return a.source;if(a instanceof b.Relation)return a.toJSON();if(a instanceof b.Op)return a.toJSON();if(a instanceof b.File){if(!a.url())throw"Tried to save an object containing an unsaved file.";return{__type:"File",name:a.name(),url:a.url()}}if(e.isObject(a)){var f={};return b._objectEach(a,function(a,e){f[e]=b._encode(a,c,d)}),f}return a},b._decode=function(a,c){var d=b._;if(!d.isObject(c))return c;if(d.isArray(c))return b._arrayEach(c,function(a,d){c[d]=b._decode(d,a)}),c;if(c instanceof b.Object)return c;if(c instanceof b.File)return c;if(c instanceof b.Op)return c;if(c.__op)return b.Op._decode(c);if("Pointer"===c.__type&&c.className){var e=b.Object._create(c.className);return e._finishFetch({objectId:c.objectId},!1),e}if("Object"===c.__type&&c.className){var f=c.className;delete c.__type,delete c.className;var g=b.Object._create(f);return g._finishFetch(c,!0),g}if("Date"===c.__type)return b._parseDate(c.iso);if("GeoPoint"===c.__type)return new b.GeoPoint({latitude:c.latitude,longitude:c.longitude});if("ACL"===a)return c instanceof b.ACL?c:new b.ACL(c);if("Relation"===c.__type){var h=new b.Relation(null,a);return h.targetClassName=c.className,h}if("File"===c.__type){var i=new b.File(c.name);return i._url=c.url,i}return b._objectEach(c,function(a,d){c[d]=b._decode(d,a)}),c},b._arrayEach=b._.each,b._traverse=function(a,c,d){if(a instanceof b.Object){if(d=d||[],b._.indexOf(d,a)>=0)return;return d.push(a),b._traverse(a.attributes,c,d),c(a)}return a instanceof b.Relation||a instanceof b.File?c(a):b._.isArray(a)?(b._.each(a,function(e,f){var g=b._traverse(e,c,d);g&&(a[f]=g)}),c(a)):b._.isObject(a)?(b._each(a,function(e,f){var g=b._traverse(e,c,d);g&&(a[f]=g)}),c(a)):c(a)},b._objectEach=b._each=function(a,c){var d=b._;d.isObject(a)?d.each(d.keys(a),function(b){c(a[b],b)}):d.each(a,c)},b._isNullOrUndefined=function(a){return b._.isNull(a)||b._.isUndefined(a)}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Analytics=b.Analytics||{},c.extend(b.Analytics,{track:function(a,d,e){if(a=a||"",a=a.replace(/^\s*/,""),a=a.replace(/\s*$/,""),0===a.length)throw"A name for the custom event must be provided";return c.each(d,function(a,b){if(!c.isString(b)||!c.isString(a))throw'track() dimensions expects keys and values of type "string".'}),e=e||{},b._request({route:"events",className:a,method:"POST",data:{dimensions:d}})._thenRunCallbacks(e)}})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Config=function(){this.attributes={},this._escapedAttributes={}},b.Config.current=function(){if(b.Config._currentConfig)return b.Config._currentConfig;var a=b.localStorage.getItem(b._getParsePath(b.Config._CURRENT_CONFIG_KEY)),c=new b.Config;return a&&(c._finishFetch(JSON.parse(a)),b.Config._currentConfig=c),c},b.Config.get=function(a){a=a||{};var c=b._request({route:"config",method:"GET"});return c.then(function(a){if(!a||!a.params){var c=new b.Error(b.Error.INVALID_JSON,"Config JSON response invalid.");return b.Promise.error(c)}var d=new b.Config;return d._finishFetch(a),b.Config._currentConfig=d,d})._thenRunCallbacks(a)},b.Config.prototype={escape:function(a){var d=this._escapedAttributes[a];if(d)return d;var e,f=this.attributes[a];return e=b._isNullOrUndefined(f)?"":c.escape(f.toString()),this._escapedAttributes[a]=e,e},get:function(a){return this.attributes[a]},_finishFetch:function(a){this.attributes=b._decode(null,c.clone(a.params)),b.localStorage.setItem(b._getParsePath(b.Config._CURRENT_CONFIG_KEY),JSON.stringify(a))}},b.Config._currentConfig=null,b.Config._CURRENT_CONFIG_KEY="currentConfig"}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Error=function(a,b){this.code=a,this.message=b},c.extend(b.Error,{OTHER_CAUSE:-1,INTERNAL_SERVER_ERROR:1,CONNECTION_FAILED:100,OBJECT_NOT_FOUND:101,INVALID_QUERY:102,INVALID_CLASS_NAME:103,MISSING_OBJECT_ID:104,INVALID_KEY_NAME:105,INVALID_POINTER:106,INVALID_JSON:107,COMMAND_UNAVAILABLE:108,NOT_INITIALIZED:109,INCORRECT_TYPE:111,INVALID_CHANNEL_NAME:112,PUSH_MISCONFIGURED:115,OBJECT_TOO_LARGE:116,OPERATION_FORBIDDEN:119,CACHE_MISS:120,INVALID_NESTED_KEY:121,INVALID_FILE_NAME:122,INVALID_ACL:123,TIMEOUT:124,INVALID_EMAIL_ADDRESS:125,MISSING_CONTENT_TYPE:126,MISSING_CONTENT_LENGTH:127,INVALID_CONTENT_LENGTH:128,FILE_TOO_LARGE:129,FILE_SAVE_ERROR:130,DUPLICATE_VALUE:137,INVALID_ROLE_NAME:139,EXCEEDED_QUOTA:140,SCRIPT_FAILED:141,VALIDATION_ERROR:142,INVALID_IMAGE_DATA:150,UNSAVED_FILE_ERROR:151,INVALID_PUSH_TIME_ERROR:152,FILE_DELETE_ERROR:153,REQUEST_LIMIT_EXCEEDED:155,INVALID_EVENT_NAME:160,USERNAME_MISSING:200,PASSWORD_MISSING:201,USERNAME_TAKEN:202,EMAIL_TAKEN:203,EMAIL_MISSING:204,EMAIL_NOT_FOUND:205,SESSION_MISSING:206,MUST_CREATE_USER_THROUGH_SIGNUP:207,ACCOUNT_ALREADY_LINKED:208,LINKED_ID_MISSING:250,INVALID_LINKED_SESSION:251,UNSUPPORTED_SERVICE:252,AGGREGATE_ERROR:600,FILE_READ_ERROR:601,X_DOMAIN_REQUEST:602})}(this),function(){var a=this,b=a.Parse||(a.Parse={}),c=/\s+/,d=Array.prototype.slice;b.Events={on:function(a,b,d){var e,f,g,h,i;if(!b)return this;for(a=a.split(c),e=this._callbacks||(this._callbacks={}),f=a.shift();f;)i=e[f],g=i?i.tail:{},g.next=h={},g.context=d,g.callback=b,e[f]={tail:h,next:i?i.next:g},f=a.shift();return this},off:function(a,b,d){var e,f,g,h,i,j;if(f=this._callbacks){if(!(a||b||d))return delete this._callbacks,this;for(a=a?a.split(c):_.keys(f),e=a.shift();e;)if(g=f[e],delete f[e],g&&(b||d)){for(h=g.tail,g=g.next;g!==h;)i=g.callback,j=g.context,(b&&i!==b||d&&j!==d)&&this.on(e,i,j),g=g.next;e=a.shift()}else e=a.shift();return this}},trigger:function(a){var b,e,f,g,h,i,j;if(!(f=this._callbacks))return this;for(i=f.all,a=a.split(c),j=d.call(arguments,1),b=a.shift();b;){if(e=f[b])for(g=e.tail;(e=e.next)!==g;)e.callback.apply(e.context||this,j);if(e=i)for(g=e.tail,h=[b].concat(j);(e=e.next)!==g;)e.callback.apply(e.context||this,h);b=a.shift()}return this}},b.Events.bind=b.Events.on,b.Events.unbind=b.Events.off}.call(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.GeoPoint=function(a,d){c.isArray(a)?(b.GeoPoint._validate(a[0],a[1]),this.latitude=a[0],this.longitude=a[1]):c.isObject(a)?(b.GeoPoint._validate(a.latitude,a.longitude),this.latitude=a.latitude,this.longitude=a.longitude):c.isNumber(a)&&c.isNumber(d)?(b.GeoPoint._validate(a,d),this.latitude=a,this.longitude=d):(this.latitude=0,this.longitude=0);var e=this;this.__defineGetter__&&this.__defineSetter__&&(this._latitude=this.latitude,this._longitude=this.longitude,this.__defineGetter__("latitude",function(){return e._latitude}),this.__defineGetter__("longitude",function(){return e._longitude}),this.__defineSetter__("latitude",function(a){b.GeoPoint._validate(a,e.longitude),e._latitude=a}),this.__defineSetter__("longitude",function(a){b.GeoPoint._validate(e.latitude,a),e._longitude=a}))},b.GeoPoint._validate=function(a,b){if(-90>a)throw"Parse.GeoPoint latitude "+a+" < -90.0.";if(a>90)throw"Parse.GeoPoint latitude "+a+" > 90.0.";if(-180>b)throw"Parse.GeoPoint longitude "+b+" < -180.0.";if(b>180)throw"Parse.GeoPoint longitude "+b+" > 180.0."},b.GeoPoint.current=function(a){var c=new b.Promise;return navigator.geolocation.getCurrentPosition(function(a){c.resolve(new b.GeoPoint({latitude:a.coords.latitude,longitude:a.coords.longitude}))},function(a){c.reject(a)}),c._thenRunCallbacks(a)},b.GeoPoint.prototype={toJSON:function(){return b.GeoPoint._validate(this.latitude,this.longitude),{__type:"GeoPoint",latitude:this.latitude,longitude:this.longitude}},radiansTo:function(a){var b=Math.PI/180,c=this.latitude*b,d=this.longitude*b,e=a.latitude*b,f=a.longitude*b,g=c-e,h=d-f,i=Math.sin(g/2),j=Math.sin(h/2),k=i*i+Math.cos(c)*Math.cos(e)*j*j;return k=Math.min(1,k),2*Math.asin(Math.sqrt(k))},kilometersTo:function(a){return 6371*this.radiansTo(a)},milesTo:function(a){return 3958.8*this.radiansTo(a)}}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._,d="*";b.ACL=function(a){var d=this;if(d.permissionsById={},c.isObject(a))if(a instanceof b.User)d.setReadAccess(a,!0),d.setWriteAccess(a,!0);else{if(c.isFunction(a))throw"Parse.ACL() called with a function. Did you forget ()?";b._objectEach(a,function(a,e){if(!c.isString(e))throw"Tried to create an ACL with an invalid userId.";d.permissionsById[e]={},b._objectEach(a,function(a,b){if("read"!==b&&"write"!==b)throw"Tried to create an ACL with an invalid permission type.";if(!c.isBoolean(a))throw"Tried to create an ACL with an invalid permission value.";d.permissionsById[e][b]=a})})}},b.ACL.prototype.toJSON=function(){return c.clone(this.permissionsById)},b.ACL.prototype._setAccess=function(a,d,e){if(d instanceof b.User?d=d.id:d instanceof b.Role&&(d="role:"+d.getName()),!c.isString(d))throw"userId must be a string.";if(!c.isBoolean(e))throw"allowed must be either true or false.";var f=this.permissionsById[d];if(!f){if(!e)return;f={},this.permissionsById[d]=f}e?this.permissionsById[d][a]=!0:(delete f[a],c.isEmpty(f)&&delete f[d])},b.ACL.prototype._getAccess=function(a,c){c instanceof b.User?c=c.id:c instanceof b.Role&&(c="role:"+c.getName());var d=this.permissionsById[c];return d&&d[a]?!0:!1},b.ACL.prototype.setReadAccess=function(a,b){this._setAccess("read",a,b)},b.ACL.prototype.getReadAccess=function(a){return this._getAccess("read",a)},b.ACL.prototype.setWriteAccess=function(a,b){this._setAccess("write",a,b)},b.ACL.prototype.getWriteAccess=function(a){return this._getAccess("write",a)},b.ACL.prototype.setPublicReadAccess=function(a){this.setReadAccess(d,a)},b.ACL.prototype.getPublicReadAccess=function(){return this.getReadAccess(d)},b.ACL.prototype.setPublicWriteAccess=function(a){this.setWriteAccess(d,a)},b.ACL.prototype.getPublicWriteAccess=function(){return this.getWriteAccess(d)},b.ACL.prototype.getRoleReadAccess=function(a){if(a instanceof b.Role&&(a=a.getName()),c.isString(a))return this.getReadAccess("role:"+a);throw"role must be a Parse.Role or a String"},b.ACL.prototype.getRoleWriteAccess=function(a){if(a instanceof b.Role&&(a=a.getName()),c.isString(a))return this.getWriteAccess("role:"+a);throw"role must be a Parse.Role or a String"},b.ACL.prototype.setRoleReadAccess=function(a,d){if(a instanceof b.Role&&(a=a.getName()),c.isString(a))return void this.setReadAccess("role:"+a,d);throw"role must be a Parse.Role or a String"},b.ACL.prototype.setRoleWriteAccess=function(a,d){if(a instanceof b.Role&&(a=a.getName()),c.isString(a))return void this.setWriteAccess("role:"+a,d);throw"role must be a Parse.Role or a String"}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Op=function(){this._initialize.apply(this,arguments)},b.Op.prototype={_initialize:function(){}},c.extend(b.Op,{_extend:b._extend,_opDecoderMap:{},_registerDecoder:function(a,c){b.Op._opDecoderMap[a]=c},_decode:function(a){var c=b.Op._opDecoderMap[a.__op];return c?c(a):void 0}}),b.Op._registerDecoder("Batch",function(a){var c=null;return b._arrayEach(a.ops,function(a){a=b.Op._decode(a),c=a._mergeWithPrevious(c)}),c}),b.Op.Set=b.Op._extend({_initialize:function(a){this._value=a},value:function(){return this._value},toJSON:function(){return b._encode(this.value())},_mergeWithPrevious:function(){return this},_estimate:function(){return this.value()}}),b.Op._UNSET={},b.Op.Unset=b.Op._extend({toJSON:function(){return{__op:"Delete"}},_mergeWithPrevious:function(){return this},_estimate:function(){return b.Op._UNSET}}),b.Op._registerDecoder("Delete",function(){return new b.Op.Unset}),b.Op.Increment=b.Op._extend({_initialize:function(a){this._amount=a},amount:function(){return this._amount},toJSON:function(){return{__op:"Increment",amount:this._amount}},_mergeWithPrevious:function(a){if(a){if(a instanceof b.Op.Unset)return new b.Op.Set(this.amount());if(a instanceof b.Op.Set)return new b.Op.Set(a.value()+this.amount());if(a instanceof b.Op.Increment)return new b.Op.Increment(this.amount()+a.amount());throw"Op is invalid after previous op."}return this},_estimate:function(a){return a?a+this.amount():this.amount()}}),b.Op._registerDecoder("Increment",function(a){return new b.Op.Increment(a.amount)}),b.Op.Add=b.Op._extend({_initialize:function(a){this._objects=a},objects:function(){return this._objects},toJSON:function(){return{__op:"Add",objects:b._encode(this.objects())}},_mergeWithPrevious:function(a){if(a){if(a instanceof b.Op.Unset)return new b.Op.Set(this.objects());if(a instanceof b.Op.Set)return new b.Op.Set(this._estimate(a.value()));if(a instanceof b.Op.Add)return new b.Op.Add(a.objects().concat(this.objects()));throw"Op is invalid after previous op."}return this},_estimate:function(a){return a?a.concat(this.objects()):c.clone(this.objects())}}),b.Op._registerDecoder("Add",function(a){return new b.Op.Add(b._decode(void 0,a.objects))}),b.Op.AddUnique=b.Op._extend({_initialize:function(a){this._objects=c.uniq(a)},objects:function(){return this._objects},toJSON:function(){return{__op:"AddUnique",objects:b._encode(this.objects())}},_mergeWithPrevious:function(a){if(a){if(a instanceof b.Op.Unset)return new b.Op.Set(this.objects());if(a instanceof b.Op.Set)return new b.Op.Set(this._estimate(a.value()));
+if(a instanceof b.Op.AddUnique)return new b.Op.AddUnique(this._estimate(a.objects()));throw"Op is invalid after previous op."}return this},_estimate:function(a){if(a){var d=c.clone(a);return b._arrayEach(this.objects(),function(a){if(a instanceof b.Object&&a.id){var e=c.find(d,function(c){return c instanceof b.Object&&c.id===a.id});if(e){var f=c.indexOf(d,e);d[f]=a}else d.push(a)}else c.contains(d,a)||d.push(a)}),d}return c.clone(this.objects())}}),b.Op._registerDecoder("AddUnique",function(a){return new b.Op.AddUnique(b._decode(void 0,a.objects))}),b.Op.Remove=b.Op._extend({_initialize:function(a){this._objects=c.uniq(a)},objects:function(){return this._objects},toJSON:function(){return{__op:"Remove",objects:b._encode(this.objects())}},_mergeWithPrevious:function(a){if(a){if(a instanceof b.Op.Unset)return a;if(a instanceof b.Op.Set)return new b.Op.Set(this._estimate(a.value()));if(a instanceof b.Op.Remove)return new b.Op.Remove(c.union(a.objects(),this.objects()));throw"Op is invalid after previous op."}return this},_estimate:function(a){if(a){var d=c.difference(a,this.objects());return b._arrayEach(this.objects(),function(a){a instanceof b.Object&&a.id&&(d=c.reject(d,function(c){return c instanceof b.Object&&c.id===a.id}))}),d}return[]}}),b.Op._registerDecoder("Remove",function(a){return new b.Op.Remove(b._decode(void 0,a.objects))}),b.Op.Relation=b.Op._extend({_initialize:function(a,d){this._targetClassName=null;var e=this,f=function(a){if(a instanceof b.Object){if(!a.id)throw"You can't add an unsaved Parse.Object to a relation.";if(e._targetClassName||(e._targetClassName=a.className),e._targetClassName!==a.className)throw"Tried to create a Parse.Relation with 2 different types: "+e._targetClassName+" and "+a.className+".";return a.id}return a};this.relationsToAdd=c.uniq(c.map(a,f)),this.relationsToRemove=c.uniq(c.map(d,f))},added:function(){var a=this;return c.map(this.relationsToAdd,function(c){var d=b.Object._create(a._targetClassName);return d.id=c,d})},removed:function(){var a=this;return c.map(this.relationsToRemove,function(c){var d=b.Object._create(a._targetClassName);return d.id=c,d})},toJSON:function(){var a=null,b=null,d=this,e=function(a){return{__type:"Pointer",className:d._targetClassName,objectId:a}},f=null;return this.relationsToAdd.length>0&&(f=c.map(this.relationsToAdd,e),a={__op:"AddRelation",objects:f}),this.relationsToRemove.length>0&&(f=c.map(this.relationsToRemove,e),b={__op:"RemoveRelation",objects:f}),a&&b?{__op:"Batch",ops:[a,b]}:a||b||{}},_mergeWithPrevious:function(a){if(a){if(a instanceof b.Op.Unset)throw"You can't modify a relation after deleting it.";if(a instanceof b.Op.Relation){if(a._targetClassName&&a._targetClassName!==this._targetClassName)throw"Related object must be of class "+a._targetClassName+", but "+this._targetClassName+" was passed in.";var d=c.union(c.difference(a.relationsToAdd,this.relationsToRemove),this.relationsToAdd),e=c.union(c.difference(a.relationsToRemove,this.relationsToAdd),this.relationsToRemove),f=new b.Op.Relation(d,e);return f._targetClassName=this._targetClassName,f}throw"Op is invalid after previous op."}return this},_estimate:function(a,c,d){if(a){if(a instanceof b.Relation){if(this._targetClassName)if(a.targetClassName){if(a.targetClassName!==this._targetClassName)throw"Related object must be a "+a.targetClassName+", but a "+this._targetClassName+" was passed in."}else a.targetClassName=this._targetClassName;return a}throw"Op is invalid after previous op."}var e=new b.Relation(c,d);e.targetClassName=this._targetClassName}}),b.Op._registerDecoder("AddRelation",function(a){return new b.Op.Relation(b._decode(void 0,a.objects),[])}),b.Op._registerDecoder("RemoveRelation",function(a){return new b.Op.Relation([],b._decode(void 0,a.objects))})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Relation=function(a,b){this.parent=a,this.key=b,this.targetClassName=null},b.Relation.prototype={_ensureParentAndKey:function(a,b){if(this.parent=this.parent||a,this.key=this.key||b,this.parent!==a)throw"Internal Error. Relation retrieved from two different Objects.";if(this.key!==b)throw"Internal Error. Relation retrieved from two different keys."},add:function(a){c.isArray(a)||(a=[a]);var d=new b.Op.Relation(a,[]);this.parent.set(this.key,d),this.targetClassName=d._targetClassName},remove:function(a){c.isArray(a)||(a=[a]);var d=new b.Op.Relation([],a);this.parent.set(this.key,d),this.targetClassName=d._targetClassName},toJSON:function(){return{__type:"Relation",className:this.targetClassName}},query:function(){var a,c;return this.targetClassName?(a=b.Object._getSubclass(this.targetClassName),c=new b.Query(a)):(a=b.Object._getSubclass(this.parent.className),c=new b.Query(a),c._extraOptions.redirectClassNameForKey=this.key),c._addCondition("$relatedTo","object",this.parent._toPointer()),c._addCondition("$relatedTo","key",this.key),c}}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Promise=function(){this._resolved=!1,this._rejected=!1,this._resolvedCallbacks=[],this._rejectedCallbacks=[]},c.extend(b.Promise,{_isPromisesAPlusCompliant:!1,is:function(a){return a&&a.then&&c.isFunction(a.then)},as:function(){var a=new b.Promise;return a.resolve.apply(a,arguments),a},error:function(){var a=new b.Promise;return a.reject.apply(a,arguments),a},when:function(a){var c;c=a&&b._isNullOrUndefined(a.length)?arguments:a;var d=c.length,e=!1,f=[],g=[];if(f.length=c.length,g.length=c.length,0===d)return b.Promise.as.apply(this,f);var h=new b.Promise,i=function(){d-=1,0===d&&(e?h.reject(g):h.resolve.apply(h,f))};return b._arrayEach(c,function(a,c){b.Promise.is(a)?a.then(function(a){f[c]=a,i()},function(a){g[c]=a,e=!0,i()}):(f[c]=a,i())}),h},_continueWhile:function(a,c){return a()?c().then(function(){return b.Promise._continueWhile(a,c)}):b.Promise.as()}}),c.extend(b.Promise.prototype,{resolve:function(){if(this._resolved||this._rejected)throw"A promise was resolved even though it had already been "+(this._resolved?"resolved":"rejected")+".";this._resolved=!0,this._result=arguments;var a=arguments;b._arrayEach(this._resolvedCallbacks,function(b){b.apply(this,a)}),this._resolvedCallbacks=[],this._rejectedCallbacks=[]},reject:function(a){if(this._resolved||this._rejected)throw"A promise was rejected even though it had already been "+(this._resolved?"resolved":"rejected")+".";this._rejected=!0,this._error=a,b._arrayEach(this._rejectedCallbacks,function(b){b(a)}),this._resolvedCallbacks=[],this._rejectedCallbacks=[]},then:function(a,c){var d=new b.Promise,e=function(){var c=arguments;if(a)if(b.Promise._isPromisesAPlusCompliant)try{c=[a.apply(this,c)]}catch(e){c=[b.Promise.error(e)]}else c=[a.apply(this,c)];1===c.length&&b.Promise.is(c[0])?c[0].then(function(){d.resolve.apply(d,arguments)},function(a){d.reject(a)}):d.resolve.apply(d,c)},f=function(a){var e=[];if(c){if(b.Promise._isPromisesAPlusCompliant)try{e=[c(a)]}catch(f){e=[b.Promise.error(f)]}else e=[c(a)];1===e.length&&b.Promise.is(e[0])?e[0].then(function(){d.resolve.apply(d,arguments)},function(a){d.reject(a)}):b.Promise._isPromisesAPlusCompliant?d.resolve.apply(d,e):d.reject(e[0])}else d.reject(a)},g=function(a){a.call()};b.Promise._isPromisesAPlusCompliant&&("undefined"!=typeof window&&window.setTimeout?g=function(a){window.setTimeout(a,0)}:"undefined"!=typeof process&&process.nextTick&&(g=function(a){process.nextTick(a)}));var h=this;return this._resolved?g(function(){e.apply(h,h._result)}):this._rejected?g(function(){f(h._error)}):(this._resolvedCallbacks.push(e),this._rejectedCallbacks.push(f)),d},always:function(a){return this.then(a,a)},done:function(a){return this.then(a)},fail:function(a){return this.then(null,a)},_thenRunCallbacks:function(a,d){var e;if(c.isFunction(a)){var f=a;e={success:function(a){f(a,null)},error:function(a){f(null,a)}}}else e=c.clone(a);return e=e||{},this.then(function(a){return e.success?e.success.apply(this,arguments):d&&d.trigger("sync",d,a,e),b.Promise.as.apply(b.Promise,arguments)},function(a){return e.error?c.isUndefined(d)?e.error(a):e.error(d,a):d&&d.trigger("error",d,a,e),b.Promise.error(a)})},_continueWith:function(a){return this.then(function(){return a(arguments,null)},function(b){return a(null,b)})}})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._,d=function(a){if(26>a)return String.fromCharCode(65+a);if(52>a)return String.fromCharCode(97+(a-26));if(62>a)return String.fromCharCode(48+(a-52));if(62===a)return"+";if(63===a)return"/";throw"Tried to encode large digit "+a+" in base64."},e=function(a){var b=[];return b.length=Math.ceil(a.length/3),c.times(b.length,function(c){var e=a[3*c],f=a[3*c+1]||0,g=a[3*c+2]||0,h=3*c+1>2&63),d(e<<4&48|f>>4&15),h?d(f<<2&60|g>>6&3):"=",i?d(63&g):"="].join("")}),b.join("")},f={ai:"application/postscript",aif:"audio/x-aiff",aifc:"audio/x-aiff",aiff:"audio/x-aiff",asc:"text/plain",atom:"application/atom+xml",au:"audio/basic",avi:"video/x-msvideo",bcpio:"application/x-bcpio",bin:"application/octet-stream",bmp:"image/bmp",cdf:"application/x-netcdf",cgm:"image/cgm","class":"application/octet-stream",cpio:"application/x-cpio",cpt:"application/mac-compactpro",csh:"application/x-csh",css:"text/css",dcr:"application/x-director",dif:"video/x-dv",dir:"application/x-director",djv:"image/vnd.djvu",djvu:"image/vnd.djvu",dll:"application/octet-stream",dmg:"application/octet-stream",dms:"application/octet-stream",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",dotx:"application/vnd.openxmlformats-officedocument.wordprocessingml.template",docm:"application/vnd.ms-word.document.macroEnabled.12",dotm:"application/vnd.ms-word.template.macroEnabled.12",dtd:"application/xml-dtd",dv:"video/x-dv",dvi:"application/x-dvi",dxr:"application/x-director",eps:"application/postscript",etx:"text/x-setext",exe:"application/octet-stream",ez:"application/andrew-inset",gif:"image/gif",gram:"application/srgs",grxml:"application/srgs+xml",gtar:"application/x-gtar",hdf:"application/x-hdf",hqx:"application/mac-binhex40",htm:"text/html",html:"text/html",ice:"x-conference/x-cooltalk",ico:"image/x-icon",ics:"text/calendar",ief:"image/ief",ifb:"text/calendar",iges:"model/iges",igs:"model/iges",jnlp:"application/x-java-jnlp-file",jp2:"image/jp2",jpe:"image/jpeg",jpeg:"image/jpeg",jpg:"image/jpeg",js:"application/x-javascript",kar:"audio/midi",latex:"application/x-latex",lha:"application/octet-stream",lzh:"application/octet-stream",m3u:"audio/x-mpegurl",m4a:"audio/mp4a-latm",m4b:"audio/mp4a-latm",m4p:"audio/mp4a-latm",m4u:"video/vnd.mpegurl",m4v:"video/x-m4v",mac:"image/x-macpaint",man:"application/x-troff-man",mathml:"application/mathml+xml",me:"application/x-troff-me",mesh:"model/mesh",mid:"audio/midi",midi:"audio/midi",mif:"application/vnd.mif",mov:"video/quicktime",movie:"video/x-sgi-movie",mp2:"audio/mpeg",mp3:"audio/mpeg",mp4:"video/mp4",mpe:"video/mpeg",mpeg:"video/mpeg",mpg:"video/mpeg",mpga:"audio/mpeg",ms:"application/x-troff-ms",msh:"model/mesh",mxu:"video/vnd.mpegurl",nc:"application/x-netcdf",oda:"application/oda",ogg:"application/ogg",pbm:"image/x-portable-bitmap",pct:"image/pict",pdb:"chemical/x-pdb",pdf:"application/pdf",pgm:"image/x-portable-graymap",pgn:"application/x-chess-pgn",pic:"image/pict",pict:"image/pict",png:"image/png",pnm:"image/x-portable-anymap",pnt:"image/x-macpaint",pntg:"image/x-macpaint",ppm:"image/x-portable-pixmap",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",potx:"application/vnd.openxmlformats-officedocument.presentationml.template",ppsx:"application/vnd.openxmlformats-officedocument.presentationml.slideshow",ppam:"application/vnd.ms-powerpoint.addin.macroEnabled.12",pptm:"application/vnd.ms-powerpoint.presentation.macroEnabled.12",potm:"application/vnd.ms-powerpoint.template.macroEnabled.12",ppsm:"application/vnd.ms-powerpoint.slideshow.macroEnabled.12",ps:"application/postscript",qt:"video/quicktime",qti:"image/x-quicktime",qtif:"image/x-quicktime",ra:"audio/x-pn-realaudio",ram:"audio/x-pn-realaudio",ras:"image/x-cmu-raster",rdf:"application/rdf+xml",rgb:"image/x-rgb",rm:"application/vnd.rn-realmedia",roff:"application/x-troff",rtf:"text/rtf",rtx:"text/richtext",sgm:"text/sgml",sgml:"text/sgml",sh:"application/x-sh",shar:"application/x-shar",silo:"model/mesh",sit:"application/x-stuffit",skd:"application/x-koan",skm:"application/x-koan",skp:"application/x-koan",skt:"application/x-koan",smi:"application/smil",smil:"application/smil",snd:"audio/basic",so:"application/octet-stream",spl:"application/x-futuresplash",src:"application/x-wais-source",sv4cpio:"application/x-sv4cpio",sv4crc:"application/x-sv4crc",svg:"image/svg+xml",swf:"application/x-shockwave-flash",t:"application/x-troff",tar:"application/x-tar",tcl:"application/x-tcl",tex:"application/x-tex",texi:"application/x-texinfo",texinfo:"application/x-texinfo",tif:"image/tiff",tiff:"image/tiff",tr:"application/x-troff",tsv:"text/tab-separated-values",txt:"text/plain",ustar:"application/x-ustar",vcd:"application/x-cdlink",vrml:"model/vrml",vxml:"application/voicexml+xml",wav:"audio/x-wav",wbmp:"image/vnd.wap.wbmp",wbmxl:"application/vnd.wap.wbxml",wml:"text/vnd.wap.wml",wmlc:"application/vnd.wap.wmlc",wmls:"text/vnd.wap.wmlscript",wmlsc:"application/vnd.wap.wmlscriptc",wrl:"model/vrml",xbm:"image/x-xbitmap",xht:"application/xhtml+xml",xhtml:"application/xhtml+xml",xls:"application/vnd.ms-excel",xml:"application/xml",xpm:"image/x-xpixmap",xsl:"application/xml",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",xltx:"application/vnd.openxmlformats-officedocument.spreadsheetml.template",xlsm:"application/vnd.ms-excel.sheet.macroEnabled.12",xltm:"application/vnd.ms-excel.template.macroEnabled.12",xlam:"application/vnd.ms-excel.addin.macroEnabled.12",xlsb:"application/vnd.ms-excel.sheet.binary.macroEnabled.12",xslt:"application/xslt+xml",xul:"application/vnd.mozilla.xul+xml",xwd:"image/x-xwindowdump",xyz:"chemical/x-xyz",zip:"application/zip"},g=function(a,c){var d=new b.Promise;if("undefined"==typeof FileReader)return b.Promise.error(new b.Error(b.Error.FILE_READ_ERROR,"Attempted to use a FileReader on an unsupported browser."));var e=new FileReader;return e.onloadend=function(){if(2!==e.readyState)return void d.reject(new b.Error(b.Error.FILE_READ_ERROR,"Error reading file."));var a=e.result,f=/^data:([^;]*);base64,(.*)$/.exec(a);return f?void d.resolve(f[2],c||f[1]):void d.reject(new b.Error(b.Error.FILE_READ_ERROR,"Unable to interpret data URL: "+a))},e.readAsDataURL(a),d};b.File=function(a,d,h){this._name=a;var i=/\.([^.]*)$/.exec(a);i&&(i=i[1].toLowerCase());var j=h||f[i]||"text/plain";if(c.isArray(d))this._source=b.Promise.as(e(d),j);else if(d&&d.base64){var k=/^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,(\S+)/,l=k.exec(d.base64);this._source=l&&l.length>0?b.Promise.as(4===l.length?l[3]:l[2],l[1]):b.Promise.as(d.base64,j)}else if("undefined"!=typeof File&&d instanceof File)this._source=g(d,h);else if(c.isString(d))throw"Creating a Parse.File from a String is not yet supported."},b.File.prototype={name:function(){return this._name},url:function(){return this._url},save:function(a){a=a||{};var c=this;return c._previousSave||(c._previousSave=c._source.then(function(d,e){var f={base64:d,_ContentType:e};return b._request({route:"files",className:c._name,method:"POST",data:f,useMasterKey:a.useMasterKey})}).then(function(a){return c._name=a.name,c._url=a.url,c})),c._previousSave._thenRunCallbacks(a)}}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Object=function(a,d){if(c.isString(a))return b.Object._create.apply(this,arguments);a=a||{},d&&d.parse&&(a=this.parse(a));var e=b._getValue(this,"defaults");if(e&&(a=c.extend({},e,a)),d&&d.collection&&(this.collection=d.collection),this._serverData={},this._opSetQueue=[{}],this.attributes={},this._hashedJSON={},this._escapedAttributes={},this.cid=c.uniqueId("c"),this.changed={},this._silent={},this._pending={},!this.set(a,{silent:!0}))throw new Error("Can't create an invalid Parse.Object");this.changed={},this._silent={},this._pending={},this._hasData=!0,this._previousAttributes=c.clone(this.attributes),this.initialize.apply(this,arguments)},b.Object.saveAll=function(a,c){return c=c||{},b.Object._deepSaveAsync(a,{useMasterKey:c.useMasterKey})._thenRunCallbacks(c)},b.Object.destroyAll=function(a,d){d=d||{};var e=function(a){a.trigger("destroy",a,a.collection,d)},f=[],g=function(a){var g=b.Promise.as();return a.length>0&&(g=g.then(function(){return b._request({route:"batch",method:"POST",useMasterKey:d.useMasterKey,data:{requests:c.map(a,function(a){return{method:"DELETE",path:"/1/classes/"+a.className+"/"+a.id}})}})}).then(function(c){b._arrayEach(a,function(a,g){if(c[g].success&&d.wait)e(a);else if(c[g].error){var h=new b.Error(c[g].error.code,c[g].error.error);h.object=a,f.push(h)}})})),g},h=b.Promise.as(),i=[];return b._arrayEach(a,function(b,c){if(b.id&&d.wait||e(b),b.id&&i.push(b),20===i.length||c+1===a.length){var f=i;i=[],h=h.then(function(){return g(f)})}}),h.then(function(){if(0===f.length)return!0;var a=new b.Error(b.Error.AGGREGATE_ERROR,"Error deleting an object in destroyAll");return a.errors=f,b.Promise.error(a)})._thenRunCallbacks(d)},b.Object.fetchAll=function(a,c){return b.Object._fetchAll(a,!0)._thenRunCallbacks(c)},b.Object.fetchAllIfNeeded=function(a,c){return b.Object._fetchAll(a,!1)._thenRunCallbacks(c)},c.extend(b.Object.prototype,b.Events,{_existed:!1,initialize:function(){},toJSON:function(){var a=this._toFullJSON();return b._arrayEach(["__type","className"],function(b){delete a[b]}),a},_toFullJSON:function(a){var d=c.clone(this.attributes);return b._objectEach(d,function(c,e){d[e]=b._encode(c,a)}),b._objectEach(this._operations,function(a,b){d[b]=a}),c.has(this,"id")&&(d.objectId=this.id),c.has(this,"createdAt")&&(d.createdAt=c.isDate(this.createdAt)?this.createdAt.toJSON():this.createdAt),c.has(this,"updatedAt")&&(d.updatedAt=c.isDate(this.updatedAt)?this.updatedAt.toJSON():this.updatedAt),d.__type="Object",d.className=this.className,d},_refreshCache:function(){var a=this;a._refreshingCache||(a._refreshingCache=!0,b._objectEach(this.attributes,function(d,e){if(d instanceof b.Object)d._refreshCache();else if(c.isObject(d)){var f=!1;c.isArray(d)&&c.each(d,function(a){a instanceof b.Object&&(f=!0,a._refreshCache())}),!f&&a._resetCacheForKey(e)&&a.set(e,new b.Op.Set(d),{silent:!0})}}),delete a._refreshingCache)},dirty:function(a){this._refreshCache();var b=c.last(this._opSetQueue);return a?b[a]?!0:!1:this.id?c.keys(b).length>0?!0:!1:!0},dirtyKeys:function(){return c.keys(c.last(this._opSetQueue))},_toPointer:function(){if(!this.id)throw new Error("Can't serialize an unsaved Parse.Object");return{__type:"Pointer",className:this.className,objectId:this.id}},get:function(a){return this.attributes[a]},relation:function(a){var c=this.get(a);if(c){if(!(c instanceof b.Relation))throw"Called relation() on non-relation field "+a;return c._ensureParentAndKey(this,a),c}return new b.Relation(this,a)},escape:function(a){var d=this._escapedAttributes[a];if(d)return d;var e,f=this.attributes[a];return e=b._isNullOrUndefined(f)?"":c.escape(f.toString()),this._escapedAttributes[a]=e,e},has:function(a){return!b._isNullOrUndefined(this.attributes[a])},_mergeMagicFields:function(a){var d=this,e=["id","objectId","createdAt","updatedAt"];b._arrayEach(e,function(e){a[e]&&("objectId"===e?d.id=a[e]:d[e]="createdAt"!==e&&"updatedAt"!==e||c.isDate(a[e])?a[e]:b._parseDate(a[e]),delete a[e])})},_copyServerData:function(a){var c={};b._objectEach(a,function(a,d){c[d]=b._decode(d,a)}),this._serverData=c,this._rebuildAllEstimatedData(),this._refreshCache(),this._opSetQueue=[{}],this._rebuildAllEstimatedData()},_mergeFromObject:function(a){a&&(this.id=a.id,this.createdAt=a.createdAt,this.updatedAt=a.updatedAt,this._copyServerData(a._serverData),this._hasData=!0)},_startSave:function(){this._opSetQueue.push({})},_cancelSave:function(){var a=c.first(this._opSetQueue);this._opSetQueue=c.rest(this._opSetQueue);var d=c.first(this._opSetQueue);b._objectEach(a,function(b,c){var e=a[c],f=d[c];e&&f?d[c]=f._mergeWithPrevious(e):e&&(d[c]=e)}),this._saving=this._saving-1},_finishSave:function(a){var d={};b._traverse(this.attributes,function(a){a instanceof b.Object&&a.id&&a._hasData&&(d[a.id]=a)});var e=c.first(this._opSetQueue);this._opSetQueue=c.rest(this._opSetQueue),this._applyOpSet(e,this._serverData),this._mergeMagicFields(a);var f=this;b._objectEach(a,function(a,c){f._serverData[c]=b._decode(c,a);var e=b._traverse(f._serverData[c],function(a){return a instanceof b.Object&&d[a.id]?d[a.id]:void 0});e&&(f._serverData[c]=e)}),this._rebuildAllEstimatedData(),this._saving=this._saving-1},_finishFetch:function(a,b){this._opSetQueue=[{}],this._mergeMagicFields(a),this._copyServerData(a),this._hasData=b},_applyOpSet:function(a,c){var d=this;b._objectEach(a,function(a,e){c[e]=a._estimate(c[e],d,e),c[e]===b.Op._UNSET&&delete c[e]})},_resetCacheForKey:function(a){var d=this.attributes[a];if(!(!c.isObject(d)||d instanceof b.Object||d instanceof b.File)){d=d.toJSON?d.toJSON():d;var e=JSON.stringify(d);if(this._hashedJSON[a]!==e){var f=!!this._hashedJSON[a];return this._hashedJSON[a]=e,f}}return!1},_rebuildEstimatedDataForKey:function(a){var c=this;delete this.attributes[a],this._serverData[a]&&(this.attributes[a]=this._serverData[a]),b._arrayEach(this._opSetQueue,function(d){var e=d[a];e&&(c.attributes[a]=e._estimate(c.attributes[a],c,a),c.attributes[a]===b.Op._UNSET?delete c.attributes[a]:c._resetCacheForKey(a))})},_rebuildAllEstimatedData:function(){var a=this,d=c.clone(this.attributes);this.attributes=c.clone(this._serverData),b._arrayEach(this._opSetQueue,function(c){a._applyOpSet(c,a.attributes),b._objectEach(c,function(b,c){a._resetCacheForKey(c)})}),b._objectEach(d,function(b,c){a.attributes[c]!==b&&a.trigger("change:"+c,a,a.attributes[c],{})}),b._objectEach(this.attributes,function(b,e){c.has(d,e)||a.trigger("change:"+e,a,b,{})})},set:function(a,d,e){var f;if(c.isObject(a)||b._isNullOrUndefined(a)?(f=a,b._objectEach(f,function(a,c){f[c]=b._decode(c,a)}),e=d):(f={},f[a]=b._decode(a,d)),e=e||{},!f)return this;f instanceof b.Object&&(f=f.attributes),e.unset&&b._objectEach(f,function(a,c){f[c]=new b.Op.Unset});var g=c.clone(f),h=this;if(b._objectEach(g,function(a,c){a instanceof b.Op&&(g[c]=a._estimate(h.attributes[c],h,c),g[c]===b.Op._UNSET&&delete g[c])}),!this._validate(f,e))return!1;this._mergeMagicFields(f),e.changes={};{var i=this._escapedAttributes;this._previousAttributes||{}}return b._arrayEach(c.keys(f),function(a){var d=f[a];d instanceof b.Relation&&(d.parent=h),d instanceof b.Op||(d=new b.Op.Set(d));var g=!0;d instanceof b.Op.Set&&c.isEqual(h.attributes[a],d.value)&&(g=!1),g&&(delete i[a],e.silent?h._silent[a]=!0:e.changes[a]=!0);var j=c.last(h._opSetQueue);j[a]=d._mergeWithPrevious(j[a]),h._rebuildEstimatedDataForKey(a),g?(h.changed[a]=h.attributes[a],e.silent||(h._pending[a]=!0)):(delete h.changed[a],delete h._pending[a])}),e.silent||this.change(e),this},unset:function(a,b){return b=b||{},b.unset=!0,this.set(a,null,b)},increment:function(a,d){return(c.isUndefined(d)||c.isNull(d))&&(d=1),this.set(a,new b.Op.Increment(d))},add:function(a,c){return this.set(a,new b.Op.Add([c]))},addUnique:function(a,c){return this.set(a,new b.Op.AddUnique([c]))},remove:function(a,c){return this.set(a,new b.Op.Remove([c]))},op:function(a){return c.last(this._opSetQueue)[a]},clear:function(a){a=a||{},a.unset=!0;var b=c.extend(this.attributes,this._operations);return this.set(b,a)},_getSaveJSON:function(){var a=c.clone(c.first(this._opSetQueue));return b._objectEach(a,function(b,c){a[c]=b.toJSON()}),a},_canBeSerialized:function(){return b.Object._canBeSerializedAsValue(this.attributes)},fetch:function(a){var c=this;a=a||{};var d=b._request({method:"GET",route:"classes",className:this.className,objectId:this.id,useMasterKey:a.useMasterKey});return d.then(function(a,b,d){return c._finishFetch(c.parse(a,b,d),!0),c})._thenRunCallbacks(a,this)},save:function(a,d,e){var f,g,h;if(c.isObject(a)||b._isNullOrUndefined(a)?(f=a,h=d):(f={},f[a]=d,h=e),!h&&f){var i=c.reject(f,function(a,b){return c.include(["success","error","wait"],b)});if(0===i.length){var j=!0;if(c.has(f,"success")&&!c.isFunction(f.success)&&(j=!1),c.has(f,"error")&&!c.isFunction(f.error)&&(j=!1),j)return this.save(null,f)}}h=c.clone(h)||{},h.wait&&(g=c.clone(this.attributes));var k=c.clone(h)||{};k.wait&&(k.silent=!0);var l;if(k.error=function(a,b){l=b},f&&!this.set(f,k))return b.Promise.error(l)._thenRunCallbacks(h,this);var m=this;m._refreshCache();var n=[],o=[];return b.Object._findUnsavedChildren(m.attributes,n,o),n.length+o.length>0?b.Object._deepSaveAsync(this.attributes,{useMasterKey:h.useMasterKey}).then(function(){return m.save(null,h)},function(a){return b.Promise.error(a)._thenRunCallbacks(h,m)}):(this._startSave(),this._saving=(this._saving||0)+1,this._allPreviousSaves=this._allPreviousSaves||b.Promise.as(),this._allPreviousSaves=this._allPreviousSaves._continueWith(function(){var a=m.id?"PUT":"POST",d=m._getSaveJSON(),e="classes",i=m.className;"_User"!==m.className||m.id||(e="users",i=null);var j=b._request({route:e,className:i,objectId:m.id,method:a,useMasterKey:h.useMasterKey,data:d});return j=j.then(function(a,b,d){var e=m.parse(a,b,d);return h.wait&&(e=c.extend(f||{},e)),m._finishSave(e),h.wait&&m.set(g,k),m},function(a){return m._cancelSave(),b.Promise.error(a)})._thenRunCallbacks(h,m)}),this._allPreviousSaves)},destroy:function(a){a=a||{};var c=this,d=function(){c.trigger("destroy",c,c.collection,a)};if(!this.id)return d();a.wait||d();var e=b._request({route:"classes",className:this.className,objectId:this.id,method:"DELETE",useMasterKey:a.useMasterKey});return e.then(function(){return a.wait&&d(),c})._thenRunCallbacks(a,this)},parse:function(a,d){var e=c.clone(a);return c(["createdAt","updatedAt"]).each(function(a){e[a]&&(e[a]=b._parseDate(e[a]))}),e.updatedAt||(e.updatedAt=e.createdAt),d&&(this._existed=201!==d),e},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return!this.id},change:function(a){a=a||{};var d=this._changing;this._changing=!0;var e=this;b._objectEach(this._silent,function(a){e._pending[a]=!0});var f=c.extend({},a.changes,this._silent);if(this._silent={},b._objectEach(f,function(b,c){e.trigger("change:"+c,e,e.get(c),a)}),d)return this;for(var g=function(a,b){e._pending[b]||e._silent[b]||delete e.changed[b]};!c.isEmpty(this._pending);)this._pending={},this.trigger("change",this,a),b._objectEach(this.changed,g),e._previousAttributes=c.clone(this.attributes);return this._changing=!1,this},existed:function(){return this._existed},hasChanged:function(a){return arguments.length?this.changed&&c.has(this.changed,a):!c.isEmpty(this.changed)},changedAttributes:function(a){if(!a)return this.hasChanged()?c.clone(this.changed):!1;var d={},e=this._previousAttributes;return b._objectEach(a,function(a,b){c.isEqual(e[b],a)||(d[b]=a)}),d},previous:function(a){return arguments.length&&this._previousAttributes?this._previousAttributes[a]:null},previousAttributes:function(){return c.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},validate:function(a){if(c.has(a,"ACL")&&!(a.ACL instanceof b.ACL))return new b.Error(b.Error.OTHER_CAUSE,"ACL must be a Parse.ACL.");var d=!0;return b._objectEach(a,function(a,b){/^[A-Za-z][0-9A-Za-z_]*$/.test(b)||(d=!1)}),d?!1:new b.Error(b.Error.INVALID_KEY_NAME)},_validate:function(a,b){if(b.silent||!this.validate)return!0;a=c.extend({},this.attributes,a);var d=this.validate(a,b);return d?(b&&b.error?b.error(this,d,b):this.trigger("error",this,d,b),!1):!0},getACL:function(){return this.get("ACL")},setACL:function(a,b){return this.set("ACL",a,b)}}),b.Object._getSubclass=function(a){if(!c.isString(a))throw"Parse.Object._getSubclass requires a string argument.";var d=b.Object._classMap[a];return d||(d=b.Object.extend(a),b.Object._classMap[a]=d),d},b.Object._create=function(a,c,d){var e=b.Object._getSubclass(a);return new e(c,d)},b.Object._toObjectIdArray=function(a,c){if(0===a.length)return b.Promise.as(a);for(var d,e=a[0].className,f=[],g=0;g0},function(){var a=[],e=[];if(b._arrayEach(i,function(b){return a.length>20?void e.push(b):void(b._canBeSerialized()?a.push(b):e.push(b))}),i=e,0===a.length)return b.Promise.error(new b.Error(b.Error.OTHER_CAUSE,"Tried to save a batch with a cycle."));var f=b.Promise.when(c.map(a,function(a){return a._allPreviousSaves||b.Promise.as()})),g=new b.Promise;return b._arrayEach(a,function(a){a._allPreviousSaves=g}),f._continueWith(function(){return b._request({route:"batch",method:"POST",useMasterKey:d.useMasterKey,data:{requests:c.map(a,function(a){var b=a._getSaveJSON(),c="POST",d="/1/classes/"+a.className;return a.id&&(d=d+"/"+a.id,c="PUT"),a._startSave(),{method:c,path:d,body:b}})}}).then(function(c,d,e){var f;return b._arrayEach(a,function(a,b){c[b].success?a._finishSave(a.parse(c[b].success,d,e)):(f=f||c[b].error,a._cancelSave())}),f?b.Promise.error(new b.Error(f.code,f.error)):void 0}).then(function(a){return g.resolve(a),a},function(a){return g.reject(a),b.Promise.error(a)})})})}).then(function(){return a})}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Role=b.Object.extend("_Role",{constructor:function(a,d){c.isString(a)&&d instanceof b.ACL?(b.Object.prototype.constructor.call(this,null,null),this.setName(a),this.setACL(d)):b.Object.prototype.constructor.call(this,a,d)},getName:function(){return this.get("name")
+},setName:function(a,b){return this.set("name",a,b)},getUsers:function(){return this.relation("users")},getRoles:function(){return this.relation("roles")},validate:function(a,d){if("name"in a&&a.name!==this.getName()){var e=a.name;if(this.id&&this.id!==a.objectId)return new b.Error(b.Error.OTHER_CAUSE,"A role's name can only be set before it has been saved.");if(!c.isString(e))return new b.Error(b.Error.OTHER_CAUSE,"A role's name must be a String.");if(!/^[0-9a-zA-Z\-_ ]+$/.test(e))return new b.Error(b.Error.OTHER_CAUSE,"A role's name can only contain alphanumeric characters, _, -, and spaces.")}return b.Object.prototype.validate?b.Object.prototype.validate.call(this,a,d):!1}})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Collection=function(a,b){b=b||{},b.comparator&&(this.comparator=b.comparator),b.model&&(this.model=b.model),b.query&&(this.query=b.query),this._reset(),this.initialize.apply(this,arguments),a&&this.reset(a,{silent:!0,parse:b.parse})},c.extend(b.Collection.prototype,b.Events,{model:b.Object,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,d){var e,f,g,h,i,j,k={},l={};for(d=d||{},a=c.isArray(a)?a.slice():[a],e=0,g=a.length;g>e;e++){if(a[e]=this._prepareModel(a[e],d),h=a[e],!h)throw new Error("Can't add an invalid model to a collection");if(i=h.cid,k[i]||this._byCid[i])throw new Error("Duplicate cid: can't add the same model to a collection twice");if(j=h.id,!b._isNullOrUndefined(j)&&(l[j]||this._byId[j]))throw new Error("Duplicate id: can't add the same model to a collection twice");l[j]=h,k[i]=h}for(e=0;g>e;e++)(h=a[e]).on("all",this._onModelEvent,this),this._byCid[h.cid]=h,h.id&&(this._byId[h.id]=h);if(this.length+=g,f=b._isNullOrUndefined(d.at)?this.models.length:d.at,this.models.splice.apply(this.models,[f,0].concat(a)),this.comparator&&this.sort({silent:!0}),d.silent)return this;for(e=0,g=this.models.length;g>e;e++)h=this.models[e],k[h.cid]&&(d.index=e,h.trigger("add",h,this,d));return this},remove:function(a,b){var d,e,f,g;for(b=b||{},a=c.isArray(a)?a.slice():[a],d=0,e=a.length;e>d;d++)g=this.getByCid(a[d])||this.get(a[d]),g&&(delete this._byId[g.id],delete this._byCid[g.cid],f=this.indexOf(g),this.models.splice(f,1),this.length--,b.silent||(b.index=f,g.trigger("remove",g,this,b)),this._removeReference(g));return this},get:function(a){return a&&this._byId[a.id||a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},sort:function(a){if(a=a||{},!this.comparator)throw new Error("Cannot sort a set without a comparator");var b=c.bind(this.comparator,this);return 1===this.comparator.length?this.models=this.sortBy(b):this.models.sort(b),a.silent||this.trigger("reset",this,a),this},pluck:function(a){return c.map(this.models,function(b){return b.get(a)})},reset:function(a,c){var d=this;return a=a||[],c=c||{},b._arrayEach(this.models,function(a){d._removeReference(a)}),this._reset(),this.add(a,{silent:!0,parse:c.parse}),c.silent||this.trigger("reset",this,c),this},fetch:function(a){a=c.clone(a)||{},void 0===a.parse&&(a.parse=!0);var d=this,e=this.query||new b.Query(this.model);return e.find({useMasterKey:a.useMasterKey}).then(function(b){return a.add?d.add(b,a):d.reset(b,a),d})._thenRunCallbacks(a,this)},create:function(a,b){var d=this;if(b=b?c.clone(b):{},a=this._prepareModel(a,b),!a)return!1;b.wait||d.add(a,b);var e=b.success;return b.success=function(c,f){b.wait&&d.add(c,b),e?e(c,f):c.trigger("sync",a,f,b)},a.save(null,b),a},parse:function(a){return a},chain:function(){return c(this.models).chain()},_reset:function(){this.length=0,this.models=[],this._byId={},this._byCid={}},_prepareModel:function(a,c){if(a instanceof b.Object)a.collection||(a.collection=this);else{var d=a;c.collection=this,a=new this.model(d,c),a._validate(a.attributes,c)||(a=!1)}return a},_removeReference:function(a){this===a.collection&&delete a.collection,a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"!==a&&"remove"!==a||c===this)&&("destroy"===a&&this.remove(b,d),b&&"change:objectId"===a&&(delete this._byId[b.previous("objectId")],this._byId[b.id]=b),this.trigger.apply(this,arguments))}});var d=["forEach","each","map","reduce","reduceRight","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","sortBy","sortedIndex","toArray","size","first","initial","rest","last","without","indexOf","shuffle","lastIndexOf","isEmpty","groupBy"];b._arrayEach(d,function(a){b.Collection.prototype[a]=function(){return c[a].apply(c,[this.models].concat(c.toArray(arguments)))}}),b.Collection.extend=b._extend}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.View=function(a){this.cid=c.uniqueId("view"),this._configure(a||{}),this._ensureElement(),this.initialize.apply(this,arguments),this.delegateEvents()};var d=/^(\S+)\s*(.*)$/,e=["model","collection","el","id","attributes","className","tagName"];c.extend(b.View.prototype,b.Events,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){return this.$el.remove(),this},make:function(a,c,d){var e=document.createElement(a);return c&&b.$(e).attr(c),d&&b.$(e).html(d),e},setElement:function(a,c){return this.$el=b.$(a),this.el=this.$el[0],c!==!1&&this.delegateEvents(),this},delegateEvents:function(a){if(a=a||b._getValue(this,"events")){this.undelegateEvents();var e=this;b._objectEach(a,function(b,f){if(c.isFunction(b)||(b=e[a[f]]),!b)throw new Error('Event "'+a[f]+'" does not exist');var g=f.match(d),h=g[1],i=g[2];b=c.bind(b,e),h+=".delegateEvents"+e.cid,""===i?e.$el.bind(h,b):e.$el.delegate(i,h,b)})}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=c.extend({},this.options,a));var b=this;c.each(e,function(c){a[c]&&(b[c]=a[c])}),this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,!1);else{var a=b._getValue(this,"attributes")||{};this.id&&(a.id=this.id),this.className&&(a["class"]=this.className),this.setElement(this.make(this.tagName,a),!1)}}}),b.View.extend=b._extend}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.User=b.Object.extend("_User",{_isCurrentUser:!1,_mergeFromObject:function(a){a.getSessionToken()&&(this._sessionToken=a.getSessionToken()),b.User.__super__._mergeFromObject.call(this,a)},_mergeMagicFields:function(a){a.sessionToken&&(this._sessionToken=a.sessionToken,delete a.sessionToken),b.User.__super__._mergeMagicFields.call(this,a)},_cleanupAuthData:function(){if(this.isCurrent()){var a=this.get("authData");a&&b._objectEach(this.get("authData"),function(b,c){a[c]||delete a[c]})}},_synchronizeAllAuthData:function(){var a=this.get("authData");if(a){var c=this;b._objectEach(this.get("authData"),function(a,b){c._synchronizeAuthData(b)})}},_synchronizeAuthData:function(a){if(this.isCurrent()){var d;c.isString(a)?(d=a,a=b.User._authProviders[d]):d=a.getAuthType();var e=this.get("authData");if(e&&a){var f=a.restoreAuthentication(e[d]);f||this._unlinkFrom(a)}}},_handleSaveResult:function(a){a&&(this._isCurrentUser=!0),this._cleanupAuthData(),this._synchronizeAllAuthData(),delete this._serverData.password,this._rebuildEstimatedDataForKey("password"),this._refreshCache(),(a||this.isCurrent())&&b.User._saveCurrentUser(this)},_linkWith:function(a,d){var e;if(c.isString(a)?(e=a,a=b.User._authProviders[a]):e=a.getAuthType(),c.has(d,"authData")){var f=this.get("authData")||{};f[e]=d.authData,this.set("authData",f);var g=c.clone(d)||{};return g.success=function(a){a._handleSaveResult(!0),d.success&&d.success.apply(this,arguments)},this.save({authData:f},g)}var h=this,i=new b.Promise;return a.authenticate({success:function(a,b){h._linkWith(a,{authData:b,success:d.success,error:d.error}).then(function(){i.resolve(h)})},error:function(a,b){d.error&&d.error(h,b),i.reject(b)}}),i},_unlinkFrom:function(a,d){var e;c.isString(a)?(e=a,a=b.User._authProviders[a]):e=a.getAuthType();var f=c.clone(d),g=this;return f.authData=null,f.success=function(){g._synchronizeAuthData(a),d.success&&d.success.apply(this,arguments)},this._linkWith(a,f)},_isLinked:function(a){var b;b=c.isString(a)?a:a.getAuthType();var d=this.get("authData")||{};return!!d[b]},_logOutWithAll:function(){var a=this.get("authData");if(a){var c=this;b._objectEach(this.get("authData"),function(a,b){c._logOutWith(b)})}},_logOutWith:function(a){this.isCurrent()&&(c.isString(a)&&(a=b.User._authProviders[a]),a&&a.deauthenticate&&a.deauthenticate())},signUp:function(a,d){var e;d=d||{};var f=a&&a.username||this.get("username");if(!f||""===f)return e=new b.Error(b.Error.OTHER_CAUSE,"Cannot sign up user with an empty name."),d&&d.error&&d.error(this,e),b.Promise.error(e);var g=a&&a.password||this.get("password");if(!g||""===g)return e=new b.Error(b.Error.OTHER_CAUSE,"Cannot sign up user with an empty password."),d&&d.error&&d.error(this,e),b.Promise.error(e);var h=c.clone(d);return h.success=function(a){a._handleSaveResult(!0),d.success&&d.success.apply(this,arguments)},this.save(a,h)},logIn:function(a){var c=this;a=a||{};var d=b._request({route:"login",method:"GET",useMasterKey:a.useMasterKey,data:this.toJSON()});return d.then(function(a,b,d){var e=c.parse(a,b,d);return c._finishFetch(e),c._handleSaveResult(!0),c})._thenRunCallbacks(a,this)},save:function(a,d,e){var f,g;c.isObject(a)||c.isNull(a)||c.isUndefined(a)?(f=a,g=d):(f={},f[a]=d,g=e),g=g||{};var h=c.clone(g);return h.success=function(a){a._handleSaveResult(!1),g.success&&g.success.apply(this,arguments)},b.Object.prototype.save.call(this,f,h)},fetch:function(a){var d=a?c.clone(a):{};return d.success=function(b){b._handleSaveResult(!1),a&&a.success&&a.success.apply(this,arguments)},b.Object.prototype.fetch.call(this,d)},isCurrent:function(){return this._isCurrentUser},getUsername:function(){return this.get("username")},setUsername:function(a,b){return this.set("username",a,b)},setPassword:function(a,b){return this.set("password",a,b)},getEmail:function(){return this.get("email")},setEmail:function(a,b){return this.set("email",a,b)},authenticated:function(){return!!this._sessionToken&&b.User.current()&&b.User.current().id===this.id},getSessionToken:function(){return this._sessionToken}},{_currentUser:null,_currentUserMatchesDisk:!1,_CURRENT_USER_KEY:"currentUser",_authProviders:{},_performUserRewrite:!0,signUp:function(a,c,d,e){d=d||{},d.username=a,d.password=c;var f=b.Object._create("_User");return f.signUp(d,e)},logIn:function(a,c,d){var e=b.Object._create("_User");return e._finishFetch({username:a,password:c}),e.logIn(d)},become:function(a,c){c=c||{};var d=b.Object._create("_User");return b._request({route:"users",className:"me",method:"GET",useMasterKey:c.useMasterKey,sessionToken:a}).then(function(a,b,c){var e=d.parse(a,b,c);return d._finishFetch(e),d._handleSaveResult(!0),d})._thenRunCallbacks(c,d)},logOut:function(){null!==b.User._currentUser&&(b.User._currentUser._logOutWithAll(),b.User._currentUser._isCurrentUser=!1),b.User._currentUserMatchesDisk=!0,b.User._currentUser=null,b.localStorage.removeItem(b._getParsePath(b.User._CURRENT_USER_KEY))},requestPasswordReset:function(a,c){c=c||{};var d=b._request({route:"requestPasswordReset",method:"POST",useMasterKey:c.useMasterKey,data:{email:a}});return d._thenRunCallbacks(c)},current:function(){if(b.User._currentUser)return b.User._currentUser;if(b.User._currentUserMatchesDisk)return b.User._currentUser;b.User._currentUserMatchesDisk=!0;var a=b.localStorage.getItem(b._getParsePath(b.User._CURRENT_USER_KEY));if(!a)return null;b.User._currentUser=b.Object._create("_User"),b.User._currentUser._isCurrentUser=!0;var c=JSON.parse(a);return b.User._currentUser.id=c._id,delete c._id,b.User._currentUser._sessionToken=c._sessionToken,delete c._sessionToken,b.User._currentUser._finishFetch(c),b.User._currentUser._synchronizeAllAuthData(),b.User._currentUser._refreshCache(),b.User._currentUser._opSetQueue=[{}],b.User._currentUser},allowCustomUserClass:function(a){this._performUserRewrite=!a},_saveCurrentUser:function(a){b.User._currentUser!==a&&b.User.logOut(),a._isCurrentUser=!0,b.User._currentUser=a,b.User._currentUserMatchesDisk=!0;var c=a.toJSON();c._id=a.id,c._sessionToken=a._sessionToken,b.localStorage.setItem(b._getParsePath(b.User._CURRENT_USER_KEY),JSON.stringify(c))},_registerAuthenticationProvider:function(a){b.User._authProviders[a.getAuthType()]=a,b.User.current()&&b.User.current()._synchronizeAuthData(a.getAuthType())},_logInWith:function(a,c){var d=b.Object._create("_User");return d._linkWith(a,c)}})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Query=function(a){c.isString(a)&&(a=b.Object._getSubclass(a)),this.objectClass=a,this.className=a.prototype.className,this._where={},this._include=[],this._limit=-1,this._skip=0,this._extraOptions={}},b.Query.or=function(){var a=c.toArray(arguments),d=null;b._arrayEach(a,function(a){if(c.isNull(d)&&(d=a.className),d!==a.className)throw"All queries must be for the same class"});var e=new b.Query(d);return e._orQuery(a),e},b.Query.prototype={get:function(a,d){var e=this;e.equalTo("objectId",a);var f={};return d&&c.has(d,"useMasterKey")&&(f={useMasterKey:d.useMasterKey}),e.first(f).then(function(a){if(a)return a;var c=new b.Error(b.Error.OBJECT_NOT_FOUND,"Object not found.");return b.Promise.error(c)})._thenRunCallbacks(d,null)},toJSON:function(){var a={where:this._where};return this._include.length>0&&(a.include=this._include.join(",")),this._select&&(a.keys=this._select.join(",")),this._limit>=0&&(a.limit=this._limit),this._skip>0&&(a.skip=this._skip),void 0!==this._order&&(a.order=this._order.join(",")),b._objectEach(this._extraOptions,function(b,c){a[c]=b}),a},find:function(a){var d=this;a=a||{};var e=b._request({route:"classes",className:this.className,method:"GET",useMasterKey:a.useMasterKey,data:this.toJSON()});return e.then(function(a){return c.map(a.results,function(c){var e;return e=a.className?new b.Object(a.className):new d.objectClass,e._finishFetch(c,!0),e})})._thenRunCallbacks(a)},count:function(a){var c=this;a=a||{};var d=this.toJSON();d.limit=0,d.count=1;var e=b._request({route:"classes",className:c.className,method:"GET",useMasterKey:a.useMasterKey,data:d});return e.then(function(a){return a.count})._thenRunCallbacks(a)},first:function(a){var d=this;a=a||{};var e=this.toJSON();e.limit=1;var f=b._request({route:"classes",className:this.className,method:"GET",useMasterKey:a.useMasterKey,data:e});return f.then(function(a){return c.map(a.results,function(c){var e;return e=a.className?new b.Object(a.className):new d.objectClass,e._finishFetch(c,!0),e})[0]})._thenRunCallbacks(a)},collection:function(a,d){return d=d||{},new b.Collection(a,c.extend(d,{model:this.objectClass,query:this}))},skip:function(a){return this._skip=a,this},limit:function(a){return this._limit=a,this},equalTo:function(a,d){return c.isUndefined(d)?this.doesNotExist(a):(this._where[a]=b._encode(d),this)},_addCondition:function(a,c,d){return this._where[a]||(this._where[a]={}),this._where[a][c]=b._encode(d),this},notEqualTo:function(a,b){return this._addCondition(a,"$ne",b),this},lessThan:function(a,b){return this._addCondition(a,"$lt",b),this},greaterThan:function(a,b){return this._addCondition(a,"$gt",b),this},lessThanOrEqualTo:function(a,b){return this._addCondition(a,"$lte",b),this},greaterThanOrEqualTo:function(a,b){return this._addCondition(a,"$gte",b),this},containedIn:function(a,b){return this._addCondition(a,"$in",b),this},notContainedIn:function(a,b){return this._addCondition(a,"$nin",b),this},containsAll:function(a,b){return this._addCondition(a,"$all",b),this},exists:function(a){return this._addCondition(a,"$exists",!0),this},doesNotExist:function(a){return this._addCondition(a,"$exists",!1),this},matches:function(a,b,c){return this._addCondition(a,"$regex",b),c||(c=""),b.ignoreCase&&(c+="i"),b.multiline&&(c+="m"),c&&c.length&&this._addCondition(a,"$options",c),this},matchesQuery:function(a,b){var c=b.toJSON();return c.className=b.className,this._addCondition(a,"$inQuery",c),this},doesNotMatchQuery:function(a,b){var c=b.toJSON();return c.className=b.className,this._addCondition(a,"$notInQuery",c),this},matchesKeyInQuery:function(a,b,c){var d=c.toJSON();return d.className=c.className,this._addCondition(a,"$select",{key:b,query:d}),this},doesNotMatchKeyInQuery:function(a,b,c){var d=c.toJSON();return d.className=c.className,this._addCondition(a,"$dontSelect",{key:b,query:d}),this},_orQuery:function(a){var b=c.map(a,function(a){return a.toJSON().where});return this._where.$or=b,this},_quote:function(a){return"\\Q"+a.replace("\\E","\\E\\\\E\\Q")+"\\E"},contains:function(a,b){return this._addCondition(a,"$regex",this._quote(b)),this},startsWith:function(a,b){return this._addCondition(a,"$regex","^"+this._quote(b)),this},endsWith:function(a,b){return this._addCondition(a,"$regex",this._quote(b)+"$"),this},ascending:function(){return this._order=[],this.addAscending.apply(this,arguments)},addAscending:function(){var a=this;return this._order||(this._order=[]),b._arrayEach(arguments,function(b){Array.isArray(b)&&(b=b.join()),a._order=a._order.concat(b.replace(/\s/g,"").split(","))}),this},descending:function(){return this._order=[],this.addDescending.apply(this,arguments)},addDescending:function(){var a=this;return this._order||(this._order=[]),b._arrayEach(arguments,function(b){Array.isArray(b)&&(b=b.join()),a._order=a._order.concat(c.map(b.replace(/\s/g,"").split(","),function(a){return"-"+a}))}),this},near:function(a,c){return c instanceof b.GeoPoint||(c=new b.GeoPoint(c)),this._addCondition(a,"$nearSphere",c),this},withinRadians:function(a,b,c){return this.near(a,b),this._addCondition(a,"$maxDistance",c),this},withinMiles:function(a,b,c){return this.withinRadians(a,b,c/3958.8)},withinKilometers:function(a,b,c){return this.withinRadians(a,b,c/6371)},withinGeoBox:function(a,c,d){return c instanceof b.GeoPoint||(c=new b.GeoPoint(c)),d instanceof b.GeoPoint||(d=new b.GeoPoint(d)),this._addCondition(a,"$within",{$box:[c,d]}),this},include:function(){var a=this;return b._arrayEach(arguments,function(b){c.isArray(b)?a._include=a._include.concat(b):a._include.push(b)}),this},select:function(){var a=this;return this._select=this._select||[],b._arrayEach(arguments,function(b){c.isArray(b)?a._select=a._select.concat(b):a._select.push(b)}),this},each:function(a,d){if(d=d||{},this._order||this._skip||this._limit>=0){var e="Cannot iterate on a query with sort, skip, or limit.";return b.Promise.error(e)._thenRunCallbacks(d)}var f=(new b.Promise,new b.Query(this.objectClass));f._limit=d.batchSize||100,f._where=c.clone(this._where),f._include=c.clone(this._include),this._select&&(f._select=c.clone(this._select)),f.ascending("objectId");var g={};c.has(d,"useMasterKey")&&(g.useMasterKey=d.useMasterKey);var h=!1;return b.Promise._continueWhile(function(){return!h},function(){return f.find(g).then(function(c){var d=b.Promise.as();return b._.each(c,function(b){d=d.then(function(){return a(b)})}),d.then(function(){c.length>=f._limit?f.greaterThan("objectId",c[c.length-1].id):h=!0})})})._thenRunCallbacks(d)}}}(this),function(a){a.Parse=a.Parse||{};var b,c,d=a.Parse,e=d._,f=!1,g={authenticate:function(a){var c=this;FB.login(function(b){b.authResponse?a.success&&a.success(c,{id:b.authResponse.userID,access_token:b.authResponse.accessToken,expiration_date:new Date(1e3*b.authResponse.expiresIn+(new Date).getTime()).toJSON()}):a.error&&a.error(c,b)},{scope:b})},restoreAuthentication:function(a){if(a){var b={userID:a.id,accessToken:a.access_token,expiresIn:(d._parseDate(a.expiration_date).getTime()-(new Date).getTime())/1e3},f=e.clone(c);f.authResponse=b,f.status=!1;var g=FB.getAuthResponse();g&&g.userID!==b.userID&&FB.logout(),FB.init(f)}return!0},getAuthType:function(){return"facebook"},deauthenticate:function(){this.restoreAuthentication(null)}};d.FacebookUtils={init:function(a){if("undefined"==typeof FB)throw"The Facebook JavaScript SDK must be loaded before calling init.";if(c=e.clone(a)||{},c.status&&"undefined"!=typeof console){var b=console.warn||console.log||function(){};b.call(console,"The 'status' flag passed into FB.init, when set to true, can interfere with Parse Facebook integration, so it has been suppressed. Please call FB.getLoginStatus() explicitly if you require this behavior.")}c.status=!1,FB.init(c),d.User._registerAuthenticationProvider(g),f=!0},isLinked:function(a){return a._isLinked("facebook")},logIn:function(a,c){if(!a||e.isString(a)){if(!f)throw"You must initialize FacebookUtils before calling logIn.";return b=a,d.User._logInWith("facebook",c)}var g=e.clone(c)||{};return g.authData=a,d.User._logInWith("facebook",g)},link:function(a,c,d){if(!c||e.isString(c)){if(!f)throw"You must initialize FacebookUtils before calling link.";return b=c,a._linkWith("facebook",d)}var g=e.clone(d)||{};return g.authData=c,a._linkWith("facebook",g)},unlink:function(a,b){if(!f)throw"You must initialize FacebookUtils before calling unlink.";return a._unlinkFrom("facebook",b)}}}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.History=function(){this.handlers=[],c.bindAll(this,"checkUrl")};var d=/^[#\/]/,e=/msie [\w.]+/;b.History.started=!1,c.extend(b.History.prototype,b.Events,{interval:50,getHash:function(a){var b=a?a.location:window.location,c=b.href.match(/#(.*)$/);return c?c[1]:""},getFragment:function(a,c){if(b._isNullOrUndefined(a))if(this._hasPushState||c){a=window.location.pathname;var e=window.location.search;e&&(a+=e)}else a=this.getHash();return a.indexOf(this.options.root)||(a=a.substr(this.options.root.length)),a.replace(d,"")},start:function(a){if(b.History.started)throw new Error("Parse.history has already been started");b.History.started=!0,this.options=c.extend({},{root:"/"},this.options,a),this._wantsHashChange=this.options.hashChange!==!1,this._wantsPushState=!!this.options.pushState,this._hasPushState=!!(this.options.pushState&&window.history&&window.history.pushState);var f=this.getFragment(),g=document.documentMode,h=e.exec(navigator.userAgent.toLowerCase())&&(!g||7>=g);h&&(this.iframe=b.$('').hide().appendTo("body")[0].contentWindow,this.navigate(f)),this._hasPushState?b.$(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!h?b.$(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=window.setInterval(this.checkUrl,this.interval)),this.fragment=f;var i=window.location,j=i.pathname===this.options.root;return this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!j?(this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0):(this._wantsPushState&&this._hasPushState&&j&&i.hash&&(this.fragment=this.getHash().replace(d,""),window.history.replaceState({},document.title,i.protocol+"//"+i.host+this.options.root+this.fragment)),this.options.silent?void 0:this.loadUrl())},stop:function(){b.$(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl),window.clearInterval(this._checkUrlInterval),b.History.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();return a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe))),a===this.fragment?!1:(this.iframe&&this.navigate(a),void(this.loadUrl()||this.loadUrl(this.getHash())))},loadUrl:function(a){var b=this.fragment=this.getFragment(a),d=c.any(this.handlers,function(a){return a.route.test(b)?(a.callback(b),!0):void 0});return d},navigate:function(a,c){if(!b.History.started)return!1;c&&c!==!0||(c={trigger:c});var e=(a||"").replace(d,"");if(this.fragment!==e){if(this._hasPushState){0!==e.indexOf(this.options.root)&&(e=this.options.root+e),this.fragment=e;var f=c.replace?"replaceState":"pushState";window.history[f]({},document.title,e)}else this._wantsHashChange?(this.fragment=e,this._updateHash(window.location,e,c.replace),this.iframe&&e!==this.getFragment(this.getHash(this.iframe))&&(c.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,e,c.replace))):window.location.assign(this.options.root+a);c.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){if(c){var d=a.toString().replace(/(javascript:|#).*$/,"");a.replace(d+"#"+b)}else a.hash=b}})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Router=function(a){a=a||{},a.routes&&(this.routes=a.routes),this._bindRoutes(),this.initialize.apply(this,arguments)};var d=/:\w+/g,e=/\*\w+/g,f=/[\-\[\]{}()+?.,\\\^\$\|#\s]/g;c.extend(b.Router.prototype,b.Events,{initialize:function(){},route:function(a,d,e){return b.history=b.history||new b.History,c.isRegExp(a)||(a=this._routeToRegExp(a)),e||(e=this[d]),b.history.route(a,c.bind(function(c){var f=this._extractParameters(a,c);e&&e.apply(this,f),this.trigger.apply(this,["route:"+d].concat(f)),b.history.trigger("route",this,d,f)},this)),this},navigate:function(a,c){b.history.navigate(a,c)},_bindRoutes:function(){if(this.routes){var a=[];for(var b in this.routes)this.routes.hasOwnProperty(b)&&a.unshift([b,this.routes[b]]);for(var c=0,d=a.length;d>c;c++)this.route(a[c][0],a[c][1],this[a[c][1]])}},_routeToRegExp:function(a){return a=a.replace(f,"\\$&").replace(d,"([^/]+)").replace(e,"(.*?)"),new RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}}),b.Router.extend=b._extend}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse,c=b._;b.Cloud=b.Cloud||{},c.extend(b.Cloud,{run:function(a,c,d){d=d||{};var e=b._request({route:"functions",className:a,method:"POST",useMasterKey:d.useMasterKey,data:b._encode(c,null,!0)});return e.then(function(a){return b._decode(null,a).result})._thenRunCallbacks(d)}})}(this),function(a){a.Parse=a.Parse||{};var b=a.Parse;b.Installation=b.Object.extend("_Installation"),b.Push=b.Push||{},b.Push.send=function(a,c){if(c=c||{},a.where&&(a.where=a.where.toJSON().where),a.push_time&&(a.push_time=a.push_time.toJSON()),a.expiration_time&&(a.expiration_time=a.expiration_time.toJSON()),a.expiration_time&&a.expiration_interval)throw"Both expiration_time and expiration_interval can't be set";var d=b._request({route:"push",method:"POST",data:a,useMasterKey:c.useMasterKey});return d._thenRunCallbacks(c)}}(this);
\ No newline at end of file