Created
July 10, 2012 00:38
-
-
Save jpadilla/3080164 to your computer and use it in GitHub Desktop.
Parse 1.0.8 with withinBox method
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Parse JavaScript SDK | |
* Version: 1.0.8 | |
* Built: Mon Jul 09 2012 15:57:07 | |
* http://parse.com | |
* | |
* Copyright 2012 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 = "1.0.8"; | |
}(this)); | |
// Underscore.js 1.3.3 | |
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. | |
// Underscore is freely distributable under the MIT license. | |
// Portions of Underscore are inspired or borrowed from Prototype, | |
// Oliver Steele's Functional, and John Resig's Micro-Templating. | |
// For all details and documentation: | |
// http://documentcloud.github.com/underscore | |
(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 slice = ArrayProto.slice, | |
unshift = ArrayProto.unshift, | |
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) { return new wrapper(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.3.3'; | |
// 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 (i in obj && 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); | |
}); | |
if (obj.length === +obj.length) results.length = obj.length; | |
return results; | |
}; | |
// **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('Reduce of empty array with no initial value'); | |
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 reversed = _.toArray(obj).reverse(); | |
if (context && !initial) iterator = _.bind(iterator, context); | |
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); | |
}; | |
// 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) { | |
var results = []; | |
if (obj == null) return results; | |
each(obj, function(value, index, list) { | |
if (!iterator.call(context, value, index, list)) results[results.length] = value; | |
}); | |
return results; | |
}; | |
// 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) { | |
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 a given value is included in the array or object using `===`. | |
// Aliased as `contains`. | |
_.include = _.contains = function(obj, target) { | |
var found = false; | |
if (obj == null) return found; | |
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; | |
found = any(obj, function(value) { | |
return value === target; | |
}); | |
return found; | |
}; | |
// Invoke a method (with arguments) on every item in a collection. | |
_.invoke = function(obj, method) { | |
var args = slice.call(arguments, 2); | |
return _.map(obj, function(value) { | |
return (_.isFunction(method) ? method || value : 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]; }); | |
}; | |
// Return the maximum element or (element-based computation). | |
_.max = function(obj, iterator, context) { | |
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj); | |
if (!iterator && _.isEmpty(obj)) return -Infinity; | |
var result = {computed : -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]) return Math.min.apply(Math, obj); | |
if (!iterator && _.isEmpty(obj)) return Infinity; | |
var result = {computed : 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 shuffled = [], rand; | |
each(obj, function(value, index, list) { | |
rand = Math.floor(Math.random() * (index + 1)); | |
shuffled[index] = shuffled[rand]; | |
shuffled[rand] = value; | |
}); | |
return shuffled; | |
}; | |
// Sort the object's values by a criterion produced by an iterator. | |
_.sortBy = function(obj, val, context) { | |
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; | |
return _.pluck(_.map(obj, function(value, index, list) { | |
return { | |
value : value, | |
criteria : iterator.call(context, value, index, list) | |
}; | |
}).sort(function(left, right) { | |
var a = left.criteria, b = right.criteria; | |
if (a === void 0) return 1; | |
if (b === void 0) return -1; | |
return a < b ? -1 : a > b ? 1 : 0; | |
}), 'value'); | |
}; | |
// 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, val) { | |
var result = {}; | |
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; | |
each(obj, function(value, index) { | |
var key = iterator(value, index); | |
(result[key] || (result[key] = [])).push(value); | |
}); | |
return result; | |
}; | |
// Use a comparator function to figure out at what index an object should | |
// be inserted so as to maintain order. Uses binary search. | |
_.sortedIndex = function(array, obj, iterator) { | |
iterator || (iterator = _.identity); | |
var low = 0, high = array.length; | |
while (low < high) { | |
var mid = (low + high) >> 1; | |
iterator(array[mid]) < iterator(obj) ? 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 (_.isArguments(obj)) return slice.call(obj); | |
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray(); | |
return _.values(obj); | |
}; | |
// Return the number of elements in an object. | |
_.size = function(obj) { | |
return _.isArray(obj) ? 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) { | |
return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; | |
}; | |
// Returns everything but the last entry of the array. Especcialy 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 ((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`. | |
// Especially useful on the arguments object. Passing an **index** will return | |
// the rest of the values in the array from that index onward. The **guard** | |
// check allows it to work with `_.map`. | |
_.rest = _.tail = function(array, index, guard) { | |
return slice.call(array, (index == null) || guard ? 1 : index); | |
}; | |
// Trim out all falsy values from an array. | |
_.compact = function(array) { | |
return _.filter(array, function(value){ return !!value; }); | |
}; | |
// Return a completely flattened version of an array. | |
_.flatten = function(array, shallow) { | |
return _.reduce(array, function(memo, value) { | |
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); | |
memo[memo.length] = value; | |
return memo; | |
}, []); | |
}; | |
// 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) { | |
var initial = iterator ? _.map(array, iterator) : array; | |
var results = []; | |
// The `isSorted` flag is irrelevant if the array only contains two elements. | |
if (array.length < 3) isSorted = true; | |
_.reduce(initial, function (memo, value, index) { | |
if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { | |
memo.push(value); | |
results.push(array[index]); | |
} | |
return memo; | |
}, []); | |
return results; | |
}; | |
// Produce an array that contains the union: each distinct element from all of | |
// the passed-in arrays. | |
_.union = function() { | |
return _.uniq(_.flatten(arguments, true)); | |
}; | |
// Produce an array that contains every item shared between all the | |
// passed-in arrays. (Aliased as "intersect" for back-compat.) | |
_.intersection = _.intersect = 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 = _.flatten(slice.call(arguments, 1), true); | |
return _.filter(array, function(value){ return !_.include(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; | |
}; | |
// 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, l; | |
if (isSorted) { | |
i = _.sortedIndex(array, item); | |
return array[i] === item ? i : -1; | |
} | |
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); | |
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; | |
return -1; | |
}; | |
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. | |
_.lastIndexOf = function(array, item) { | |
if (array == null) return -1; | |
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); | |
var i = array.length; | |
while (i--) if (i in array && 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 | |
// ------------------ | |
// Reusable constructor function for prototype setting. | |
var ctor = function(){}; | |
// Create a function bound to a given object (assigning `this`, and arguments, | |
// optionally). Binding with arguments is also known as `curry`. | |
// Delegates to **ECMAScript 5**'s native `Function.bind` if available. | |
// We check for `func.bind` first, to fail fast when `func` is undefined. | |
_.bind = function bind(func, context) { | |
var bound, args; | |
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); | |
if (!_.isFunction(func)) throw new TypeError; | |
args = slice.call(arguments, 2); | |
return bound = function() { | |
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); | |
ctor.prototype = func.prototype; | |
var self = new ctor; | |
var result = func.apply(self, args.concat(slice.call(arguments))); | |
if (Object(result) === result) return result; | |
return self; | |
}; | |
}; | |
// 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, throttling, more, result; | |
var whenDone = _.debounce(function(){ more = throttling = false; }, wait); | |
return function() { | |
context = this; args = arguments; | |
var later = function() { | |
timeout = null; | |
if (more) func.apply(context, args); | |
whenDone(); | |
}; | |
if (!timeout) timeout = setTimeout(later, wait); | |
if (throttling) { | |
more = true; | |
} else { | |
result = func.apply(context, args); | |
} | |
whenDone(); | |
throttling = true; | |
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; | |
return function() { | |
var context = this, args = arguments; | |
var later = function() { | |
timeout = null; | |
if (!immediate) func.apply(context, args); | |
}; | |
if (immediate && !timeout) func.apply(context, args); | |
clearTimeout(timeout); | |
timeout = setTimeout(later, wait); | |
}; | |
}; | |
// 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; | |
return memo = func.apply(this, arguments); | |
}; | |
}; | |
// 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].concat(slice.call(arguments, 0)); | |
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) { | |
return _.map(obj, _.identity); | |
}; | |
// 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) { | |
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 result = {}; | |
each(_.flatten(slice.call(arguments, 1)), function(key) { | |
if (key in obj) result[key] = obj[key]; | |
}); | |
return result; | |
}; | |
// Fill in a given object with default properties. | |
_.defaults = function(obj) { | |
each(slice.call(arguments, 1), function(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. | |
function eq(a, b, stack) { | |
// 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._chain) a = a._wrapped; | |
if (b._chain) b = b._wrapped; | |
// Invoke a custom `isEqual` method if one is provided. | |
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); | |
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); | |
// 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 = stack.length; | |
while (length--) { | |
// Linear search. Performance is inversely proportional to the number of | |
// unique nested structures. | |
if (stack[length] == a) return true; | |
} | |
// Add the first object to the stack of traversed objects. | |
stack.push(a); | |
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--) { | |
// Ensure commutative equality for sparse arrays. | |
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; | |
} | |
} | |
} else { | |
// Objects with different constructors are not equivalent. | |
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) 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], stack))) 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. | |
stack.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); | |
}; | |
// Is a given variable an arguments object? | |
_.isArguments = function(obj) { | |
return toString.call(obj) == '[object Arguments]'; | |
}; | |
if (!_.isArguments(arguments)) { | |
_.isArguments = function(obj) { | |
return !!(obj && _.has(obj, 'callee')); | |
}; | |
} | |
// Is a given value a function? | |
_.isFunction = function(obj) { | |
return toString.call(obj) == '[object Function]'; | |
}; | |
// Is a given value a string? | |
_.isString = function(obj) { | |
return toString.call(obj) == '[object String]'; | |
}; | |
// Is a given value a number? | |
_.isNumber = function(obj) { | |
return toString.call(obj) == '[object Number]'; | |
}; | |
// Is a given object a finite number? | |
_.isFinite = function(obj) { | |
return _.isNumber(obj) && isFinite(obj); | |
}; | |
// Is the given value `NaN`? | |
_.isNaN = function(obj) { | |
// `NaN` is the only value for which `===` is not reflexive. | |
return 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 a date? | |
_.isDate = function(obj) { | |
return toString.call(obj) == '[object Date]'; | |
}; | |
// Is the given value a regular expression? | |
_.isRegExp = function(obj) { | |
return toString.call(obj) == '[object RegExp]'; | |
}; | |
// 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; | |
}; | |
// Has own property? | |
_.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) { | |
for (var i = 0; i < n; i++) iterator.call(context, i); | |
}; | |
// Escape a string for HTML interpolation. | |
_.escape = function(string) { | |
return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); | |
}; | |
// 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, ensuring that | |
// they're correctly added to the OOP wrapper as well. | |
_.mixin = function(obj) { | |
each(_.functions(obj), function(name){ | |
addToWrapper(name, _[name] = obj[name]); | |
}); | |
}; | |
// 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' | |
}; | |
for (var p in escapes) escapes[escapes[p]] = p; | |
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; | |
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; | |
// Within an interpolation, evaluation, or escaping, remove HTML escaping | |
// that had been previously added. | |
var unescape = function(code) { | |
return code.replace(unescaper, function(match, escape) { | |
return escapes[escape]; | |
}); | |
}; | |
// 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) { | |
settings = _.defaults(settings || {}, _.templateSettings); | |
// Compile the template source, taking care to escape characters that | |
// cannot be included in a string literal and then unescape them in code | |
// blocks. | |
var source = "__p+='" + text | |
.replace(escaper, function(match) { | |
return '\\' + escapes[match]; | |
}) | |
.replace(settings.escape || noMatch, function(match, code) { | |
return "'+\n_.escape(" + unescape(code) + ")+\n'"; | |
}) | |
.replace(settings.interpolate || noMatch, function(match, code) { | |
return "'+\n(" + unescape(code) + ")+\n'"; | |
}) | |
.replace(settings.evaluate || noMatch, function(match, code) { | |
return "';\n" + unescape(code) + "\n;__p+='"; | |
}) + "';\n"; | |
// If a variable is not specified, place data values in local scope. | |
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; | |
source = "var __p='';" + | |
"var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + | |
source + "return __p;\n"; | |
var render = new Function(settings.variable || 'obj', '_', source); | |
if (data) return render(data, _); | |
var template = function(data) { | |
return render.call(this, data, _); | |
}; | |
// Provide the compiled function source as a convenience for build time | |
// 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(); | |
}; | |
// The OOP Wrapper | |
// --------------- | |
// 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. | |
var wrapper = function(obj) { this._wrapped = obj; }; | |
// Expose `wrapper.prototype` as `_.prototype` | |
_.prototype = wrapper.prototype; | |
// Helper function to continue chaining intermediate results. | |
var result = function(obj, chain) { | |
return chain ? _(obj).chain() : obj; | |
}; | |
// A method to easily add functions to the OOP wrapper. | |
var addToWrapper = function(name, func) { | |
wrapper.prototype[name] = function() { | |
var args = slice.call(arguments); | |
unshift.call(args, this._wrapped); | |
return result(func.apply(_, args), this._chain); | |
}; | |
}; | |
// 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]; | |
wrapper.prototype[name] = function() { | |
var wrapped = this._wrapped; | |
method.apply(wrapped, arguments); | |
var length = wrapped.length; | |
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; | |
return result(wrapped, this._chain); | |
}; | |
}); | |
// Add all accessor Array functions to the wrapper. | |
each(['concat', 'join', 'slice'], function(name) { | |
var method = ArrayProto[name]; | |
wrapper.prototype[name] = function() { | |
return result(method.apply(this._wrapped, arguments), this._chain); | |
}; | |
}); | |
// Start chaining a wrapped Underscore object. | |
wrapper.prototype.chain = function() { | |
this._chain = true; | |
return this; | |
}; | |
// Extracts the result from a wrapped and chained object. | |
wrapper.prototype.value = function() { | |
return this._wrapped; | |
}; | |
}).call(this); | |
/*global _: false, $: false, localStorage: false, XMLHttpRequest: false, | |
XDomainRequest: false, exports: false */ | |
(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; | |
// Import Parse's local copy of underscore. | |
if (typeof(exports) !== "undefined" && exports._) { | |
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"; | |
/** | |
* 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. | |
*/ | |
Parse.initialize = function(applicationId, javaScriptKey) { | |
Parse.applicationId = applicationId; | |
Parse.javaScriptKey = javaScriptKey; | |
}; | |
/** | |
* 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 = 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()); | |
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, success, error) { | |
var xdr = new XDomainRequest(); | |
xdr.onload = function() { | |
var response; | |
try { | |
response = JSON.parse(xdr.responseText); | |
} catch (e) { | |
if (error) { | |
error(xdr); | |
} | |
} | |
if (response) { | |
if (success) { | |
success(response, xdr); | |
} | |
} | |
}; | |
xdr.onerror = xdr.ontimeout = function() { | |
error(xdr); | |
}; | |
xdr.onprogress = function() {}; | |
xdr.open(method, url); | |
xdr.send(data); | |
}; | |
Parse._ajax = function(method, url, data, success, error) { | |
if (typeof(XDomainRequest) !== "undefined") { | |
return Parse._ajaxIE8(method, url, data, success, error); | |
} | |
var xhr = new XMLHttpRequest(); | |
xhr.onreadystatechange = function() { | |
if (xhr.readyState === 4) { | |
if (xhr.status >= 200 && xhr.status < 300) { | |
var response; | |
try { | |
response = JSON.parse(xhr.responseText); | |
} catch (e) { | |
if (error) { | |
error(xhr); | |
} | |
} | |
if (response) { | |
if (success) { | |
success(response, xhr); | |
} | |
} | |
} else { | |
if (error) { | |
error(xhr); | |
} | |
} | |
} | |
}; | |
xhr.open(method, url, true); | |
xhr.setRequestHeader("Content-Type", "text/plain"); // avoid pre-flight. | |
xhr.send(data); | |
}; | |
// A self-propagating extend function. | |
Parse._extend = function(protoProps, classProps) { | |
var child = inherits(this, protoProps, classProps); | |
child.extend = this.extend; | |
return child; | |
}; | |
/** | |
* route is classes, users, login, etc. | |
* objectId is null if there is no associated objectId. | |
* method is the http method for the REST API. | |
* dataObject is the payload as an object, or null if there is none. | |
* options is just a success/error callback hash. | |
* @ignore | |
*/ | |
Parse._request = function(route, className, objectId, method, dataObject, | |
options) { | |
if (!Parse.applicationId || !Parse.javaScriptKey) { | |
throw "You must specify your applicationId and javaScriptKey using " + | |
"Parse.initialize"; | |
} | |
if (route !== "classes" && | |
route !== "users" && | |
route !== "login" && | |
route !== "requestPasswordReset") { | |
throw "First argument must be one of classes, users, or login, not '" + | |
route + "'."; | |
} | |
var url = Parse.serverURL + "/1/" + route; | |
if (className) { | |
url += "/" + className; | |
} | |
if (objectId) { | |
url += "/" + objectId; | |
} | |
dataObject = Parse._.clone(dataObject || {}); | |
if (method !== "POST") { | |
dataObject._method = method; | |
method = "POST"; | |
} | |
dataObject._ApplicationId = Parse.applicationId; | |
dataObject._JavaScriptKey = Parse.javaScriptKey; | |
dataObject._ClientVersion = "js" + Parse.VERSION; | |
dataObject._InstallationId = Parse._getInstallationId(); | |
// Pass the session token on every request. | |
var currentUser = Parse.User.current(); | |
if (currentUser && currentUser._sessionToken) { | |
dataObject._SessionToken = currentUser._sessionToken; | |
} | |
var data = JSON.stringify(dataObject); | |
Parse._ajax(method, url, data, options.success, options.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). | |
*/ | |
Parse._encode = function(value) { | |
var _ = Parse._; | |
if (value instanceof Parse.Object) { | |
return value._toPointer(); | |
} else if (value instanceof Parse.ACL) { | |
return value.toJSON(); | |
} else if (value instanceof Date) { | |
return { "__type": "Date", "iso": value.toJSON() }; | |
} else if (_.isArray(value)) { | |
return _.map(value, Parse._encode); | |
} else if (_.isRegExp(value)) { | |
return value.source; | |
} else if (value instanceof Parse.Relation) { | |
return value.toJSON(); | |
} else { | |
return value; | |
} | |
}; | |
/** | |
* The inverse function of Parse._encode. | |
*/ | |
Parse._decode = function(key, value) { | |
var _ = Parse._; | |
if (!_.isObject(value)) { | |
return value; | |
} else if (_.isArray(value)) { | |
Parse._each(value, function(v, k) { | |
value[k] = Parse._decode(k, v); | |
}); | |
return value; | |
} else if (value instanceof Parse.Object) { | |
return value; | |
} else if (value.objectId) { | |
// Must be a Pointer or a JSON Parse Object. | |
if (value.__type === "Pointer") { | |
var pointer = Parse.Object._create(value.className, | |
{objectId: value.objectId}); | |
pointer._refreshCache(); | |
pointer._dirty = {}; | |
return pointer; | |
} else if (value.__type === "Object") { | |
// 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, value); | |
object._refreshCache(); | |
object._dirty = {}; | |
return object; | |
} else { | |
Parse._each(value, function(val, key) { | |
value[key] = Parse._decode(key, val); | |
}); | |
return Parse.Object._create(value.className, value); | |
} | |
} else if (value.__type === "Date") { | |
return Parse._parseDate(value.iso); | |
} else if (value.__type === "GeoPoint") { | |
return new Parse.GeoPoint({ | |
latitude: value.latitude, | |
longitude: value.longitude | |
}); | |
} else if (key === "ACL") { | |
if (value instanceof Parse.ACL) { | |
return value; | |
} else { | |
return new Parse.ACL(value); | |
} | |
} else if (value.__type === "Relation") { | |
var relation = new Parse.Relation(this, key); | |
relation.targetClassName = value.className; | |
return relation; | |
} else { | |
// Just some random JSON object. | |
return value; | |
} | |
}; | |
/** | |
* 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._each = function(obj, callback) { | |
var _ = Parse._; | |
if (_.isObject(obj)) { | |
_.each(_.keys(obj), function(key) { | |
callback(obj[key], key); | |
}); | |
} else { | |
_.each(obj, callback); | |
} | |
}; | |
}(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 | |
* | |
* <p> | |
* 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. | |
* </p> | |
*/ | |
Parse.Relation = function(parent, key) { | |
this.parent = parent; | |
this.key = key; | |
this.targetClassName = null; | |
this.relationsToAdd = []; | |
this.relationsToRemove = []; | |
}; | |
Parse.Relation.prototype = { | |
/** | |
* 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]; | |
} | |
if (!this.targetClassName) { | |
this.targetClassName = objects[0].className; | |
} | |
var self = this; | |
_.each(objects, function(object) { | |
if (self.targetClassName !== object.className) { | |
throw "This relation is on objects of class:" + | |
self.targetClassName + " but got object of class:" + | |
object.className; | |
} | |
}); | |
objects = _.map(objects, | |
function(object) { return object.id; }); | |
this.relationsToAdd = _.union(this.relationsToAdd, objects); | |
this.relationsToRemove = _.difference(this.relationsToRemove, objects); | |
}, | |
/** | |
* 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]; | |
} | |
if (!this.targetClassName) { | |
this.targetClassName = objects[0].className; | |
} | |
var self = this; | |
_.each(objects, function(object) { | |
if (self.targetClassName !== object.className) { | |
throw "This relation is on objects of class:" + | |
self.targetClassName + " but got object of class:" + | |
object.className; | |
} | |
}); | |
objects = _.map(objects, | |
function(object) { return object.id; }); | |
this.relationsToRemove = _.union(this.relationsToRemove, objects); | |
this.relationsToAdd = _.difference(this.relationsToAdd, objects); | |
}, | |
_dirty: function() { | |
return this.relationsToAdd.length > 0 || | |
this.relationsToRemove.length > 0; | |
}, | |
/** | |
* Returns a JSON version of the object suitable for saving 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 || | |
{ "__type": "Relation", "className": this.targetClassName }; | |
}, | |
/** | |
* Returns a Parse.Query that is limited to objects in this | |
* relation. | |
* @return {Parse.Query} | |
*/ | |
query: function() { | |
var targetClass = Parse.Object._getSubclass(this.targetClassName); | |
var query = new Parse.Query(targetClass); | |
query._addCondition("$relatedTo", "object", this.parent._toPointer()); | |
query._addCondition("$relatedTo", "key", this.key); | |
return query; | |
}, | |
_clearUpdates: function () { | |
this.relationsToRemove = []; | |
this.relationsToAdd = []; | |
} | |
}; | |
}(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 <code>Parse.Error</code>. | |
* @param {String} message A detailed description of the error. | |
* @class | |
* | |
* <p>Class used for all objects passed to error callbacks.</p> | |
*/ | |
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. Email feedback@parse.com | |
* to criticize us. | |
* @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 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 | |
}); | |
}(this)); | |
/*global _: false */ | |
(function() { | |
var root = this; | |
var Parse = (root.Parse || (root.Parse = {})); | |
var eventSplitter = /\s+/; | |
var slice = Array.prototype.slice; | |
/** | |
* @class | |
* | |
* <p>Parse.Events is a fork of Backbone's Events module, provided for your | |
* convenience.</p> | |
* | |
* <p>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. | |
* | |
* <pre> | |
* var object = {}; | |
* _.extend(object, Parse.Events); | |
* object.on('expand', function(){ alert('expanded'); }); | |
* object.trigger('expand');</pre></p> | |
* | |
* <p>For more information, see the | |
* <a href="http://documentcloud.github.com/backbone/#Events">Backbone | |
* documentation</a>.</p> | |
*/ | |
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)) { | |
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:<br> | |
* <pre> | |
* new GeoPoint(otherGeoPoint) | |
* new GeoPoint(30, 30) | |
* new GeoPoint([30, 30]) | |
* new GeoPoint({latitude: 30, longitude: 30}) | |
* new GeoPoint() // defaults to (0, 0) | |
* </pre> | |
* @class | |
* | |
* <p>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.</p> | |
* | |
* <p>Only one key in a class may contain a GeoPoint.</p> | |
* | |
* <p>Example:<pre> | |
* var point = new Parse.GeoPoint(30.0, -20.0); | |
* var object = new Parse.Object("PlaceObject"); | |
* object.set("location", point); | |
* object.save();</pre></p> | |
*/ | |
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 success = function(location) { | |
if (options.success) { | |
options.success(new Parse.GeoPoint({ | |
latitude: location.coords.latitude, | |
longitude: location.coords.longitude | |
})); | |
} | |
}; | |
var error = function(e) { | |
if (options.error) { | |
options.error(e); | |
} | |
}; | |
navigator.geolocation.getCurrentPosition(success, error); | |
}; | |
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 | |
* | |
* <p>An ACL, or Access Control List can be added to any | |
* <code>Parse.Object</code> to restrict access to only a subset of users | |
* of your application.</p> | |
*/ | |
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._each(arg1, function(accessList, userId) { | |
if (!_.isString(userId)) { | |
throw "Tried to create an ACL with an invalid userId."; | |
} | |
self.permissionsById[userId] = {}; | |
Parse._each(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)); | |
// 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._; | |
// Helper function to check null or undefined. | |
var isNullOrUndefined = function(x) { | |
return _.isNull(x) || _.isUndefined(x); | |
}; | |
/** | |
* Creates a new model with defined attributes. A client id (cid) is | |
* automatically generated and assigned for you. | |
* | |
* <p>You won't normally call this method directly. It is recommended that | |
* you use a subclass of <code>Parse.Object</code> instead, created by calling | |
* <code>extend</code>.<p> | |
* | |
* <p>However, if you don't want to use a subclass, or aren't sure which | |
* subclass is appropriate, you can use this form:<pre> | |
* var object = new Parse.Object("ClassName"); | |
* </pre> | |
* That is basically equivalent to:<pre> | |
* var MyClass = Parse.Object.extend("ClassName"); | |
* var object = new MyClass(); | |
* </pre></p> | |
* | |
* @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 | |
* | |
* <p>The fundamental unit of Parse data, which implements the Backbone Model | |
* interface.</p> | |
*/ | |
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.attributes = {}; // The actual data for the Parse Object. | |
this._operations = {}; // Operations such as increment and unset. | |
this._dirty = {}; // The keys in the object that haven't been saved. | |
this._hashedJSON = {}; // Hash of values of containers at last save. | |
this._escapedAttributes = {}; | |
this.cid = _.uniqueId('c'); | |
if (!this.set(attributes, {silent: true})) { | |
throw new Error("Can't create an invalid Parse.Object"); | |
} | |
delete this._changed; | |
this._previousAttributes = _.clone(this.attributes); | |
this.initialize.apply(this, arguments); | |
}; | |
/** | |
* @lends Parse.Object.prototype | |
* @property {String} id The objectId of the Parse Object. | |
*/ | |
/** | |
* Internal function for saveAll. This calls func on every item in list, | |
* and adds the results to results. When it's done, optionsOrCallback is | |
* called with the accumulated results. See saveAll for more info. | |
* | |
* @param list - A list of Parse.Object. | |
* @param func - function(Parse.Object, callback); | |
* @param results - List of results. Should be [] for non-recursion. | |
* @param optionsOrCallback - See saveAll. | |
*/ | |
var _doAll = function(list, func, results, optionsOrCallback) { | |
results = results || []; | |
var options; | |
if (_.isFunction(optionsOrCallback)) { | |
var callback = optionsOrCallback; | |
options = { | |
success: function(list) { callback(list, null); }, | |
error: function(e) { callback(null, e); } | |
}; | |
} else { | |
options = optionsOrCallback; | |
} | |
if (list.length) { | |
var oldOptions = options; | |
var newOptions = options ? _.clone(options) : {}; | |
newOptions.success = function(model, response) { | |
results.push(model); | |
_doAll(list.slice(1), func, results, oldOptions); | |
}; | |
func.call(this, list[0], newOptions); | |
} else { | |
if (options.success) { | |
options.success(results); | |
} | |
} | |
}; | |
/** | |
* Saves the given list of Parse.Object. | |
* If any error is encountered, stops and calls the error handler. | |
* There are two ways you can call this function. | |
* | |
* The Backbone way:<pre> | |
* 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. | |
* }, | |
* }); | |
* </pre> | |
* A simplified syntax:<pre> | |
* Parse.Object.saveAll([object1, object2, ...], function(list, error) { | |
* if (list) { | |
* // All the objects were saved. | |
* } else { | |
* // An error occurred. | |
* } | |
* }); | |
* </pre> | |
* | |
* @param {Array} list A list of <code>Parse.Object</code>. | |
* @param {Object} optionsOrCallback A Backbone-style callback object. | |
*/ | |
Parse.Object.saveAll = function(list, optionsOrCallback) { | |
_doAll(list, function(obj, options) { | |
obj.save(null, options); | |
}, [], optionsOrCallback); | |
}; | |
Parse.Object._signUpAll = function(list, optionsOrCallback) { | |
_doAll(list, function(obj, options) { | |
obj.signUp(null, options); | |
}, [], optionsOrCallback); | |
}; | |
// 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 = _.clone(this.attributes); | |
_.each(["createdAt", "objectId", "updatedAt"], function(key) { | |
delete json[key]; | |
}); | |
Parse._each(json, function(val, key) { | |
json[key] = Parse._encode(val); | |
}); | |
Parse._each(this._operations, function(val, key) { | |
json[key] = val; | |
}); | |
return json; | |
}, | |
/** | |
* Updates _hashedJSON to reflect the current state of this object. | |
* Adds any changed hash values to the _dirty set. | |
*/ | |
_refreshCache: function() { | |
var self = this; | |
Parse._each(this.attributes, function(value, key) { | |
if (value instanceof Parse.Object) { | |
value._refreshCache(); | |
} else if (_.isObject(value)) { | |
if (value.toJSON) { | |
value = value.toJSON(); | |
} | |
var json = JSON.stringify(value); | |
if (self._hashedJSON[key] !== json) { | |
self._hashedJSON[key] = json; | |
self._dirty[key] = true; | |
} | |
} | |
}); | |
// If any of these special keys get in the dirty list, take them out. | |
_.each(["createdAt", "objectId", "updatedAt"], function(key) { | |
delete self._dirty[key]; | |
}); | |
}, | |
/** | |
* 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(); | |
if (attr) { | |
return (this._dirty[attr] ? true : false); | |
} | |
if (!this.id) { | |
return true; | |
} | |
if (_.keys(this._dirty).length > 0) { | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* 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 oldValue = this.get(attr); | |
if (oldValue) { | |
if (!(oldValue instanceof Parse.Relation)) { | |
throw attr + " does contain have a relation"; | |
} | |
return oldValue; | |
} | |
var returnValue = new Parse.Relation(this, attr); | |
this.set(attr, returnValue); | |
return returnValue; | |
}, | |
/** | |
* 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 (isNullOrUndefined(val)) { | |
escaped = ''; | |
} else { | |
escaped = _.escape(val.toString()); | |
} | |
this._escapedAttributes[attr] = escaped; | |
return escaped; | |
}, | |
/** | |
* Returns <code>true</code> 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 !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; | |
_.each(["id", "objectId", "createdAt", "updatedAt"], function(attr) { | |
if (attrs[attr]) { | |
if (attr === "objectId") { | |
model.id = attrs[attr]; | |
} else { | |
model[attr] = attrs[attr]; | |
} | |
delete attrs[attr]; | |
} | |
}); | |
}, | |
_handleSetOp: function(key, op) { | |
if (op.__op === 'Batch') { | |
var self = this; | |
var success = true; | |
Parse._each(op.ops, function(subOp) { | |
success = success && self._handleSetOp(key, subOp); | |
}); | |
} else if (op.__op === 'Increment') { | |
this.attributes[key] = this.attributes[key] || 0; | |
this.attributes[key] += op.amount; | |
this._dirty[key] = true; | |
} else if (op.__op === 'AddRelation') { | |
var relationForAdd = this.relation(key); | |
relationForAdd.add(op.objects); | |
this._dirty[key] = true; | |
} else if (op.__op === 'RemoveRelation') { | |
var relationForDelete = this.relation(key); | |
relationForDelete.remove(op.objects); | |
this._dirty[key] = true; | |
} else if (op.__op === 'Delete') { | |
this._dirty[key] = true; | |
delete this.attributes[key]; | |
} else { | |
return false; | |
} | |
return true; | |
}, | |
/** | |
* Sets a hash of model attributes on the object, firing | |
* <code>"change"</code> unless you choose to silence it. | |
* | |
* <p>You can call it with an object containing keys and values, or with one | |
* key and value. For example:<pre> | |
* 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);</pre></p> | |
* | |
* @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 <code>silent</code> and | |
* <code>error</code>. | |
* @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) || isNullOrUndefined(key)) { | |
attrs = key; | |
Parse._each(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 (options.unset) { | |
Parse._each(attrs, function(unused_value, attr) { | |
attrs[attr] = undefined; | |
}); | |
} | |
// Run validation. | |
if (!this._validate(attrs, options)) { | |
return false; | |
} | |
this._mergeMagicFields(attrs); | |
var now = this.attributes; | |
var escaped = this._escapedAttributes; | |
var prev = this._previousAttributes || {}; | |
var alreadySetting = this._setting; | |
this._changed = this._changed || {}; | |
this._setting = true; | |
// Update attributes. | |
var self = this; | |
Parse._each(_.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; | |
} | |
var handledOp = false; | |
if (_.isObject(val) && _.has(val, '__op')) { | |
handledOp = self._handleSetOp(attr, val); | |
} else { | |
if (!_.isEqual(now[attr], val)) { | |
delete escaped[attr]; | |
} | |
if (options.unset) { | |
delete now[attr]; | |
self._dirty[attr] = true; | |
self._operations[attr] = { __op: 'Delete' }; | |
} else { | |
now[attr] = val; | |
self._dirty[attr] = true; | |
// If this field was previously scheduled for deletion, undo that. | |
if (_.isObject(self._operations[attr]) && | |
self._operations[attr].__op === 'Delete') { | |
delete self._operations[attr]; | |
} | |
} | |
} | |
if (self._changing && !_.isEqual(self._changed[attr], val)) { | |
self.trigger('change:' + attr, self, val, options); | |
self._moreChanges = true; | |
} | |
delete self._changed[attr]; | |
if (!_.isEqual(prev[attr], val) || | |
handledOp || | |
(_.has(now, attr) !== _.has(prev, attr))) { | |
self._changed[attr] = val; | |
} | |
}); | |
// Fire the `"change"` events, if the model has been changed. | |
if (!alreadySetting) { | |
if (!options.silent && this.hasChanged()) { | |
this.change(options); | |
} | |
this._setting = false; | |
} | |
return this; | |
}, | |
/** | |
* Remove an attribute from the model, firing <code>"change"</code> 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); | |
}, | |
/** | |
* Clear all attributes on the model, firing <code>"change"</code> 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); | |
}, | |
/** | |
* 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 <code>"change"</code> event. | |
*/ | |
fetch: function(options) { | |
options = options ? _.clone(options) : {}; | |
var model = this; | |
var success = options.success; | |
options.success = function(resp, status, xhr) { | |
if (!model.set(model.parse(resp, status, xhr), options)) { | |
return false; | |
} | |
if (success) { | |
model._refreshCache(); | |
model._dirty = {}; | |
success(model, resp); | |
} | |
}; | |
options.error = Parse.Object._wrapError(options.error, model, options); | |
Parse._request( | |
"classes", model.className, model.id, 'GET', null, options); | |
}, | |
/** | |
* 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:<pre> | |
* object.save();</pre> | |
* or<pre> | |
* object.save(null, options);</pre> | |
* or<pre> | |
* object.save(attrs, options);</pre> | |
* or<pre> | |
* object.save(key, value, options);</pre> | |
* | |
* For example, <pre> | |
* 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. | |
* } | |
* });</pre> | |
* | |
* @see Parse.Error | |
*/ | |
save: function(arg1, arg2, arg3) { | |
var i, attrs, current, options, saved; | |
if (_.isObject(arg1) || 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 = options ? _.clone(options) : {}; | |
if (options.wait) { | |
current = _.clone(this.attributes); | |
} | |
var silentOptions = _.extend({}, options, {silent: true}); | |
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { | |
return false; | |
} | |
var oldOptions = options; // Psuedonym more accurate in some contexts. | |
var newOptions = _.clone(options); | |
var model = this; | |
// A function that will re-call this function with the same arguments. | |
// This will be used in the loop below, but JSLint insists it go here. | |
var saveThisModel = function(child, resp) { | |
model.save(null, oldOptions); | |
}; | |
// If there is any unsaved child, save it first. | |
model._refreshCache(); | |
var keys = _.keys(model.attributes); | |
for (i = 0; i < keys.length; ++i) { | |
var key = keys[i]; | |
var child = model.attributes[key]; | |
if (child instanceof Parse.Object) { | |
if (child.dirty()) { | |
// This child is unsaved, so save it, and have the callback try to | |
// save this model again. | |
newOptions.success = saveThisModel; | |
child.save(null, newOptions); | |
return this; | |
} | |
} else if (child instanceof Parse.Relation && child._dirty()) { | |
model._dirty[key] = true; | |
} | |
} | |
// Record what was saved, so we can update dirty fields correctly. | |
var savedData = _.clone(model.attributes); | |
var savedOperations = _.clone(model._operations); | |
/** ignore */ | |
newOptions.success = function(resp, status, xhr) { | |
var serverAttrs = model.parse(resp, status, xhr); | |
if (newOptions.wait) { | |
serverAttrs = _.extend(attrs || {}, serverAttrs); | |
} | |
if (!model.set(serverAttrs, newOptions)) { | |
return false; | |
} | |
var keys = _.keys(model.attributes); | |
Parse._each(model.attributes, function(child, key) { | |
if (child instanceof Parse.Relation) { | |
child._clearUpdates(); | |
} | |
}); | |
if (oldOptions.success) { | |
model._refreshCache(); | |
Parse._each(savedData, function(savedValue, savedKey) { | |
if (savedValue === model.get(savedKey)) { | |
delete model._dirty[savedKey]; | |
} | |
}); | |
Parse._each(savedOperations, function(unusedValue, savedKey) { | |
delete model._dirty[savedKey]; | |
}); | |
oldOptions.success(model, resp); | |
} else { | |
model.trigger('sync', model, resp, newOptions); | |
} | |
}; | |
newOptions.error = Parse.Object._wrapError(oldOptions.error, model, | |
newOptions); | |
var method = this.id ? 'PUT' : 'POST'; | |
var json = this.toJSON(); | |
// Remove fields that aren't dirty. | |
model._refreshCache(); | |
Parse._each(json, function(value, key) { | |
if (!model._dirty[key]) { | |
delete json[key]; | |
} | |
}); | |
var route = "classes"; | |
var className = this.className; | |
if (this.className === "_User" && !this.id) { | |
// Special-case user sign-up. | |
route = "users"; | |
className = null; | |
} | |
Parse._request(route, className, this.id, method, json, newOptions); | |
if (newOptions.wait) { | |
this.set(current, silentOptions); | |
} | |
return this; | |
}, | |
/** | |
* 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. | |
*/ | |
destroy: function(options) { | |
options = options ? _.clone(options) : {}; | |
var model = this; | |
var success = options.success; | |
var triggerDestroy = function() { | |
model.trigger('destroy', model, model.collection, options); | |
}; | |
if (!this.id) { | |
return triggerDestroy(); | |
} | |
/** ignore */ | |
options.success = function(resp) { | |
if (options.wait) { | |
triggerDestroy(); | |
} | |
if (success) { | |
success(model, resp); | |
} else { | |
model.trigger('sync', model, resp, options); | |
} | |
}; | |
options.error = Parse.Object._wrapError(options.error, model, options); | |
Parse._request("classes", this.className, this.id, 'DELETE', null, | |
options); | |
if (!options.wait) { | |
triggerDestroy(); | |
} | |
}, | |
/** | |
* 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.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; | |
}, | |
/** | |
* 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; | |
}, | |
/** | |
* Call this method to manually fire a <code>"change"</code> event for this | |
* model and a <code>"change:attribute"</code> event for each changed | |
* attribute. Calling this will cause all objects observing the model to | |
* update. | |
*/ | |
change: function(options) { | |
var self = this; | |
if (this._changing || !this.hasChanged()) { | |
return this; | |
} | |
this._changing = true; | |
this._moreChanges = true; | |
Parse._each(this._changed, function(value, attr) { | |
self.trigger('change:' + attr, self, value, options); | |
}); | |
while (this._moreChanges) { | |
this._moreChanges = false; | |
this.trigger('change', this, options); | |
} | |
this._previousAttributes = _.clone(this.attributes); | |
delete this._changed; | |
this._changing = false; | |
return this; | |
}, | |
/** | |
* Determine if the model has changed since the last <code>"change"</code> | |
* 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._each(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 | |
* <code>"change"</code> 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 | |
* <code>"change"</code> 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 | |
* <code>Parse.Object</code>, in which case you can override this method | |
* to provide additional validation on <code>set</code> and | |
* <code>save</code>. 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."); | |
} | |
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); | |
}; | |
// 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. | |
* | |
* <p>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.</p> | |
* | |
* <p>You should call either:<pre> | |
* var MyClass = Parse.Object.extend("MyClass", { | |
* <i>Instance properties</i> | |
* }, { | |
* <i>Class properties</i> | |
* });</pre> | |
* or, for Backbone compatibility:<pre> | |
* var MyClass = Parse.Object.extend({ | |
* className: "MyClass", | |
* <i>Other instance properties</i> | |
* }, { | |
* <i>Class properties</i> | |
* });</pre></p> | |
* | |
* @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") { | |
className = "_User"; | |
} | |
var NewClassObject = null; | |
if (_.has(Parse.Object._classMap, className)) { | |
var OldClassObject = Parse.Object._classMap[className]; | |
NewClassObject = OldClassObject._extend(protoProps, classProps); | |
} else { | |
protoProps = protoProps || {}; | |
protoProps.className = className; | |
NewClassObject = Parse.Object._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); | |
}; | |
Parse.Object._classMap[className] = NewClassObject; | |
return NewClassObject; | |
}; | |
/** | |
* Wrap an optional error callback with a fallback error event. | |
*/ | |
// Wrap an optional error callback with a fallback error event. | |
Parse.Object._wrapError = function(onError, originalModel, options) { | |
return function(model, response) { | |
if (model !== originalModel) { | |
response = model; | |
} | |
var error = new Parse.Error(-1, response.responseText); | |
if (response.responseText) { | |
var errorJSON = JSON.parse(response.responseText); | |
if (errorJSON) { | |
error = new Parse.Error(errorJSON.code, errorJSON.error); | |
} | |
} | |
if (onError) { | |
onError(originalModel, error, options); | |
} else { | |
originalModel.trigger('error', originalModel, error, options); | |
} | |
}; | |
}; | |
}(this)); | |
/*global localStorage: false */ | |
(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. | |
* | |
* <p>Roles must have a name (which cannot be changed after creation of the | |
* role), and must specify an ACL.</p> | |
* @class | |
*/ | |
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. | |
* | |
* <p> | |
* A role's name can only contain alphanumeric characters, _, -, and | |
* spaces. | |
* </p> | |
* | |
* <p>This is equivalent to calling role.set("name", name)</p> | |
* | |
* @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. | |
* | |
* <p>This is equivalent to calling role.relation("users")</p> | |
* | |
* @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. | |
* | |
* <p>This is equivalent to calling role.relation("roles")</p> | |
* | |
* @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 (!(/^[a-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._; | |
// Helper function to check null or undefined. | |
var isNullOrUndefined = function(x) { | |
return _.isNull(x) || _.isUndefined(x); | |
}; | |
/** | |
* Creates a new instance with the given models and options. Typically, you | |
* will not call this method directory, but will instead make a subclass using | |
* <code>Parse.Collection.extend</code>. | |
* | |
* @param {Array} models An array of instances of <code>Parse.Object</code>. | |
* | |
* @param {Object} options An optional object with Backbone-style options. | |
* Valid options are:<ul> | |
* <li>model: The Parse.Object subclass that this collection contains. | |
* <li>query: An instance of Parse.Query to use when fetching items. | |
* <li>comparator: A string property name or function to sort by. | |
* </ul> | |
* | |
* @see Parse.Collection.extend | |
* | |
* @class | |
* | |
* <p>Provides a standard collection class for our sets of models, ordered | |
* or unordered. For more information, see the | |
* <a href="http://documentcloud.github.com/backbone/#Collection">Backbone | |
* documentation</a>.</p> | |
*/ | |
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. | |
*/ | |
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 (!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 = options.at || this.models.length; | |
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 <code>remove</code> event for every model removed. | |
*/ | |
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. | |
*/ | |
get: function(id) { | |
return id && this._byId[id.id || id]; | |
}, | |
/** | |
* Gets a model from the set by client id. | |
*/ | |
getByCid: function(cid) { | |
return cid && this._byCid[cid.cid || cid]; | |
}, | |
/** | |
* Gets the model at the given index. | |
*/ | |
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. | |
*/ | |
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. | |
*/ | |
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. | |
*/ | |
reset: function(models, options) { | |
var self = this; | |
models = models || []; | |
options = options || {}; | |
_.each(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. | |
*/ | |
fetch: function(options) { | |
options = options ? _.clone(options) : {}; | |
if (options.parse === undefined) { | |
options.parse = true; | |
} | |
var collection = this; | |
var success = options.success; | |
options.success = function(results, resp) { | |
if (options.add) { | |
collection.add(results, options); | |
} else { | |
collection.reset(results, options); | |
} | |
if (success) { | |
success(collection, resp); | |
} | |
}; | |
options.error = Parse.Object._wrapError(options.error, collection, | |
options); | |
var query = this.query || new Parse.Query(this.model); | |
query.find(options); | |
}, | |
/** | |
* 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. | |
*/ | |
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`. | |
_.each(methods, function(method) { | |
Parse.Collection.prototype[method] = function() { | |
return _[method].apply(_, [this.models].concat(_.toArray(arguments))); | |
}; | |
}); | |
/** | |
* Creates a new subclass of <code>Parse.Collection</code>. For example,<pre> | |
* 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(); | |
* </pre> | |
* | |
* @function | |
* @param {Object} instanceProps Instance properties for the collection. | |
* @param {Object} classProps Class properies for the collection. | |
* @return {Class} A new subclass of <code>Parse.Collection</code>. | |
*/ | |
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 | |
* | |
* <p>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 | |
* <a href="http://documentcloud.github.com/backbone/#View">Backbone | |
* documentation</a>.</p> | |
*/ | |
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. | |
* <pre> | |
* var el = this.make('li', {'class': 'row'}, | |
* this.model.escape('title'));</pre> | |
*/ | |
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. <code>this.events</code> is a hash of | |
* <pre> | |
* *{"event selector": "callback"}* | |
* | |
* { | |
* 'mousedown .title': 'edit', | |
* 'click .button': 'save' | |
* 'click .open': function(e) { ... } | |
* } | |
* </pre> | |
* 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._each(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 collection. | |
* @param {Object} classProps Class properies for the collection. | |
* @return {Class} A new subclass of <code>Parse.View</code>. | |
*/ | |
Parse.View.extend = Parse._extend; | |
}(this)); | |
/*global localStorage: false */ | |
(function(root) { | |
root.Parse = root.Parse || {}; | |
var Parse = root.Parse; | |
var _ = Parse._; | |
/** | |
* @class | |
* | |
* <p>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.</p> | |
*/ | |
Parse.User = Parse.Object.extend("_User", /** @lends Parse.User.prototype */ { | |
// Instance Variables | |
_isCurrentUser: false, | |
// Instance Methods | |
/** | |
* 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() { | |
var authData = this.get('authData'); | |
if (!authData) { | |
return; | |
} | |
_.each(this.get('authData'), function(value, key) { | |
if (!authData[key]) { | |
delete authData[key]; | |
} | |
}); | |
var dirty = this._dirty; | |
delete dirty.authData; | |
}, | |
/** | |
* Synchronizes authData for all providers. | |
*/ | |
_synchronizeAllAuthData: function() { | |
var authData = this.get('authData'); | |
if (!authData) { | |
return; | |
} | |
var self = this; | |
_.each(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) { | |
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 | |
this._cleanupAuthData(); | |
this._synchronizeAllAuthData(); | |
// Don't keep the password around. | |
this.unset("password"); | |
var dirty = this._dirty; | |
delete dirty.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; | |
return provider.authenticate({ | |
success: function(provider, result) { | |
self._linkWith(provider, { | |
authData: result, | |
success: options.success, | |
error: options.error | |
}); | |
}, | |
error: function(provider, error) { | |
if (options.error) { | |
options.error(self, error); | |
} | |
} | |
}); | |
} | |
}, | |
/** | |
* 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; | |
_.each(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 (_.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 | |
* <code>current</code>. | |
* | |
* <p>A username and password must be set before calling signUp.</p> | |
* | |
* <p>Calls options.success or options.error on completion.</p> | |
* | |
* @param {Object} attrs Extra fields to set on the new user, or null. | |
* @param {Object} options A Backbone-style options object. | |
* @return {} False if validation failed. The user otherwise. | |
* @see Parse.User.signUp | |
*/ | |
signUp: function(attrs, options) { | |
var error; | |
var username = (attrs && attrs.username) || this.get("username"); | |
if (!username || (username === "")) { | |
if (options && options.error) { | |
error = new Parse.Error( | |
Parse.Error.OTHER_CAUSE, | |
"Cannot sign up user with an empty name."); | |
options.error(this, error); | |
} | |
return false; | |
} | |
var password = (attrs && attrs.password) || this.get("password"); | |
if (!password || (password === "")) { | |
if (options && options.error) { | |
error = new Parse.Error( | |
Parse.Error.OTHER_CAUSE, | |
"Cannot sign up user with an empty password."); | |
options.error(this, error); | |
} | |
return false; | |
} | |
// 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 | |
* <code>current</code>. | |
* | |
* <p>A username and password must be set before calling logIn.</p> | |
* | |
* <p>Calls options.success or options.error on completion.</p> | |
* | |
* @param {Object} options A Backbone-style options object. | |
* @see Parse.User.logIn | |
*/ | |
logIn: function(options) { | |
var model = this; | |
var newOptions = _.clone(options); | |
newOptions.success = function(resp, status, xhr) { | |
var serverAttrs = model.parse(resp, status, xhr); | |
if (!model.set(serverAttrs, newOptions)) { | |
return false; | |
} | |
model._handleSaveResult(true); | |
if (options.success) { | |
options.success(model, resp); | |
} else { | |
model.trigger('sync', model, resp, newOptions); | |
} | |
}; | |
newOptions.error = Parse.Object._wrapError(options.error, model, | |
newOptions); | |
Parse._request("login", null, null, "GET", this.toJSON(), newOptions); | |
}, | |
/** | |
* @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; | |
} | |
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 = _.clone(options); | |
newOptions.success = function(model) { | |
model._handleSaveResult(false); | |
if (options.success) { | |
options.success.apply(this, arguments); | |
} | |
}; | |
return Parse.Object.prototype.fetch.call(this, newOptions); | |
}, | |
/** | |
* Returns true if <code>current</code> 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); | |
} | |
}, /** @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: {}, | |
// 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}. | |
* | |
* <p>A username and password must be set before calling signUp.</p> | |
* | |
* <p>Calls options.success or options.error on completion.</p> | |
* | |
* @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 {} False if validation failed. The user otherwise. | |
* @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 <code>current</code>. | |
* | |
* <p>Calls options.success or options.error on completion.</p> | |
* | |
* @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. | |
* @see Parse.User#logIn | |
*/ | |
logIn: function(username, password, options) { | |
var user = Parse.Object._create("_User"); | |
user.set("username", username); | |
user.set("password", password); | |
user.logIn(options); | |
}, | |
/** | |
* Logs out the currently logged in user session. This will remove the | |
* session from disk, log out of linked services, and future calls to | |
* <code>current</code> will return <code>null</code>. | |
*/ | |
logOut: function() { | |
if (Parse.User._currentUser !== null) { | |
Parse.User._currentUser._isCurrentUser = false; | |
Parse.User._currentUser._logOutWithAll(); | |
} | |
Parse.User._currentUserMatchesDisk = true; | |
Parse.User._currentUser = null; | |
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. | |
* | |
* <p>Calls options.success or options.error on completion.</p> | |
* | |
* @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) { | |
var json = { email: email }; | |
options.error = Parse.Query._wrapError(options.error, options); | |
Parse._request("requestPasswordReset", null, null, "POST", json, 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 = localStorage.getItem(Parse._getParsePath( | |
Parse.User._CURRENT_USER_KEY)); | |
if (!userData) { | |
return null; | |
} | |
Parse.User._currentUser = new 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.set(json); | |
Parse.User._currentUser._synchronizeAllAuthData(); | |
Parse.User._currentUser._refreshCache(); | |
Parse.User._currentUser._dirty = {}; | |
return Parse.User._currentUser; | |
}, | |
/** | |
* 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; | |
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 = new Parse.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 | |
* | |
* <p>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 | |
* <code>find</code> method. For example, this sample code fetches all objects | |
* of class <code>MyClass</code>. It calls a different function depending on | |
* whether the fetch succeeded or not. | |
* | |
* <pre> | |
* 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. | |
* } | |
* });</pre></p> | |
* | |
* <p>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 <code>MyClass</code> and id <code>myId</code>. It calls a | |
* different function depending on whether the fetch succeeded or not. | |
* | |
* <pre> | |
* 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. | |
* } | |
* });</pre></p> | |
* | |
* <p>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 <code>MyClass</code> | |
* <pre> | |
* 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. | |
* } | |
* });</pre></p> | |
*/ | |
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; | |
}; | |
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 {} objectId The id of the object to be fetched. | |
* @param {Object} options A Backbone-style options object. | |
*/ | |
get: function(objectId, options) { | |
var object = new this.objectClass({objectId: objectId}); | |
object.fetch(options); | |
}, | |
/** | |
* Returns a JSON representation of this query. | |
* @return {Object} | |
*/ | |
toJSON: function() { | |
var params = { | |
where: this._where | |
}; | |
if (this._include.length > 0) { | |
params.include = this._include.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; | |
} | |
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. | |
*/ | |
find: function(options) { | |
var self = this; | |
var success = options.success; | |
/** ignore */ | |
var ajaxOptions = { | |
error: options.error, | |
success: function(response) { | |
success(_.map(response.results, function(json) { | |
var obj = new self.objectClass(json); | |
obj._refreshCache(); | |
obj._dirty = {}; | |
return obj; | |
})); | |
} | |
}; | |
var params = this.toJSON(); | |
ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions); | |
Parse._request("classes", this.className, null, "GET", params, | |
ajaxOptions); | |
}, | |
/** | |
* 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. | |
*/ | |
count: function(options) { | |
var self = this; | |
var success = options.success; | |
/** ignore */ | |
var ajaxOptions = { | |
error: options.error, | |
success: function(response) { | |
success(response.count); | |
} | |
}; | |
var params = this.toJSON(); | |
params.limit = 0; | |
params.count = 1; | |
ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions); | |
Parse._request("classes", this.className, null, "GET", params, | |
ajaxOptions); | |
}, | |
/** | |
* Retrieves at most one Parse.Object that satisfies this query. | |
* | |
* Either options.success or options.error is called when the find completes. | |
* success is passed the object if there is one. otherwise, undefined. | |
* | |
* @param {Object} options A Backbone-style options object. | |
*/ | |
first: function(options) { | |
var self = this; | |
var success = options.success; | |
/** ignore */ | |
var ajaxOptions = { | |
error: options.error, | |
success: function(response) { | |
success(_.map(response.results, function(json) { | |
var obj = new self.objectClass(json); | |
obj._refreshCache(); | |
obj._dirty = {}; | |
return obj; | |
})[0]); | |
} | |
}; | |
var params = this.toJSON(); | |
params.limit = 1; | |
ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions); | |
Parse._request("classes", this.className, null, "GET", params, | |
ajaxOptions); | |
}, | |
/** | |
* Returns a new instance of Parse.Collection backed by this query. | |
* @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. | |
* @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) { | |
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 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 of in the objects returned by the query | |
* 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; | |
}, | |
/** | |
* 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} key The key to order by. | |
* @return {Parse.Query} Returns the query, so you can chain this call. | |
*/ | |
ascending: function(key) { | |
this._order = key; | |
return this; | |
}, | |
/** | |
* Sorts the results in descending order by the given key. | |
* | |
* @param {String} key The key to order by. | |
* @return {Parse.Query} Returns the query, so you can chain this call. | |
*/ | |
descending: function(key) { | |
this._order = "-" + key; | |
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 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 that the Parse.GeoPoint is stored in. | |
* @param {Parse.GeoPoint} point The reference southwest Parse.GeoPoint that is used. | |
* @param {Parse.GeoPoint} point The reference northeast Parse.GeoPoint that is used. | |
* @return {Parse.Query} Returns the query, so you can chain this call. | |
*/ | |
withinBox: function(key, swPoint, nePoint) { | |
this._addCondition(key, "$within", {"$box": [swPoint, nePoint]}); | |
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(key) { | |
if (_.isArray(key)) { | |
this._include = this._include.concat(key); | |
} else { | |
this._include.push(key); | |
} | |
return this; | |
} | |
}; | |
// Wrap an optional error callback with a fallback error event. | |
Parse.Query._wrapError = function(onError, options) { | |
return function(response) { | |
if (onError) { | |
var error = new Parse.Error(-1, response.responseText); | |
if (response.responseText) { | |
var errorJSON = JSON.parse(response.responseText); | |
if (errorJSON) { | |
error = new Parse.Error(errorJSON.code, errorJSON.error); | |
} | |
} | |
onError(error, options); | |
} | |
}; | |
}; | |
}(this)); | |
/*global FB: false */ | |
(function(root) { | |
root.Parse = root.Parse || {}; | |
var Parse = root.Parse; | |
var _ = Parse._; | |
var PUBLIC_KEY = "*"; | |
var initialized = false; | |
var requestedPermissions; | |
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) { | |
var authResponse; | |
if (authData) { | |
authResponse = { | |
userID: authData.id, | |
accessToken: authData.access_token, | |
expiresIn: (Parse._parseDate(authData.expiration_date).getTime() - | |
(new Date()).getTime()) / 1000 | |
}; | |
} else { | |
authResponse = { | |
userID: null, | |
accessToken: null, | |
expiresIn: null | |
}; | |
} | |
FB.Auth.setAuthResponse(authResponse); | |
if (!authData) { | |
FB.logout(); | |
} | |
return true; | |
}, | |
getAuthType: function() { | |
return "facebook"; | |
}, | |
deauthenticate: function() { | |
this.restoreAuthentication(null); | |
} | |
}; | |
/** | |
* Provides a set of utilities for using Parse with Facebook. | |
* @namespace | |
*/ | |
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<code> | |
* <a href= | |
* "https://developers.facebook.com/docs/reference/javascript/FB.init/"> | |
* FB.init()</a></code>. Parse.FacebookUtils will invoke FB.init() for you | |
* with these arguments. | |
* | |
* @param {Object} options Facebook options argument as described here: | |
* <a href= | |
* "https://developers.facebook.com/docs/reference/javascript/FB.init/"> | |
* FB.init()</a> | |
*/ | |
init: function(options) { | |
if (typeof(FB) === 'undefined') { | |
throw "The Javascript Facebook SDK must be loaded before calling init."; | |
} | |
FB.init(options); | |
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} <code>true</code> 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} permissions The permissions required for Facebook | |
* log in. This is a comma-separated string of permissions. | |
* @param {Object} options Standard options object with success and error | |
* callbacks. | |
*/ | |
logIn: function(permissions, options) { | |
if (!initialized) { | |
throw "You must initialize FacebookUtils before calling logIn."; | |
} | |
requestedPermissions = permissions; | |
return Parse.User._logInWith("facebook", options); | |
}, | |
/** | |
* 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} permissions The permissions required for Facebook | |
* log in. This is a comma-separated string of permissions. | |
* @param {Object} options Standard options object with success and error | |
* callbacks. | |
*/ | |
link: function(user, permissions, options) { | |
if (!initialized) { | |
throw "You must initialize FacebookUtils before calling link."; | |
} | |
requestedPermissions = permissions; | |
return user._linkWith("facebook", options); | |
}, | |
/** | |
* 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)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment