├── .editorconfig ├── .gitignore ├── Gruntfile.js ├── README.md ├── dist └── parse-angular-sdk.js ├── lib ├── ACL.js ├── Cloud.js ├── Config.js ├── Core.js ├── Error.js ├── Events.js ├── FacebookUtils.js ├── GeoPoint.js ├── Object.js ├── Op.js ├── Query.js ├── Relation.js ├── Role.js ├── User.js └── _ParseAngular.js ├── original_sdk └── parse-1.3.4.js ├── package.json └── test ├── index.html └── test.js /.editorconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trycom/parse-angular-sdk/f845a78795a214064071137539bb9263fd371200/.editorconfig -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.loadNpmTasks('grunt-contrib-concat'); 4 | grunt.loadNpmTasks('grunt-contrib-uglify'); 5 | 6 | 7 | grunt.initConfig({ 8 | concat: { 9 | options: { 10 | separator: '\n' 11 | }, 12 | dist: { 13 | src: [ 14 | // 'lib/**.js' 15 | 'lib/Error.js', 16 | 'lib/Core.js', 17 | 'lib/Cloud.js', 18 | 'lib/Config.js', 19 | 'lib/Events.js', 20 | 'lib/Op.js', 21 | 'lib/Object.js', 22 | 'lib/Relation.js', 23 | 'lib/GeoPoint.js', 24 | 'lib/Query.js', 25 | 'lib/User.js', 26 | 'lib/Role.js', 27 | 'lib/ACL.js', 28 | 'lib/FacebookUtils.js', 29 | 'lib/_ParseAngular.js', 30 | 31 | ], 32 | dest: 'dist/parse-angular-sdk.js' 33 | } 34 | } 35 | }); 36 | 37 | 38 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parse-angular-sdk 2 | 3 | This is an unofficial port of the Parse JavaScript SDK to a native Angular SDK. 4 | 5 | Disclaimer: we are not affiliated in any ways with Parse and we this is offered as is; we don't take responsibility for any bugs you might encouter in production. However you are encouraged to help us maintain this SDK, as the Parse SDK evolves. 6 | 7 | This version of SDK does **not** contain the following Parse. modules: 8 | 9 | - Promise (turned useless by $q) 10 | - View 11 | - Router 12 | - History 13 | - File 14 | 15 | It also drops support for callback hashes. All methods are promises only. You need to have underscore/lodash included in your app too. 16 | 17 | Feel free to include them in your app using the method we used; which is isolating every module inside an Angular component, and replacing http requests using $http, promises using $q. 18 | 19 | Usage 20 | 21 | ```javascript 22 | 23 | var module = angular.module('MyApp', ['Parse']); 24 | 25 | module.controller('MyCtrl', function($scope, ParseSDK){ 26 | 27 | var Parse = ParseSDK; 28 | 29 | var query = new Parse.Query('Monsters'); 30 | query.equalTo('name', 'Casper'); 31 | query.find() 32 | .then(function(results){ 33 | $scope.results = results; 34 | }) 35 | 36 | }); 37 | 38 | 39 | 40 | ``` -------------------------------------------------------------------------------- /lib/ACL.js: -------------------------------------------------------------------------------- 1 | var module = angular.module('ParseAngular.ACL', [ 2 | 'ParseAngular.User', 3 | 'ParseAngular.Role', 4 | 'ParseAngular.Core' 5 | ]); 6 | 7 | module.factory('ParseACL', function(ParseUser, ParseRole, ParseCore){ 8 | 9 | var PUBLIC_KEY = "*", ParseACL; 10 | 11 | /** 12 | * Creates a new ACL. 13 | * If no argument is given, the ACL has no permissions for anyone. 14 | * If the argument is a ParseUser, the ACL will have read and write 15 | * permission for only that user. 16 | * If the argument is any other JSON object, that object will be interpretted 17 | * as a serialized ACL created with toJSON(). 18 | * @see ParseCoreObject#setACL 19 | * @class 20 | * 21 | *
An ACL, or Access Control List can be added to any
22 | * ParseCoreObject
to restrict access to only a subset of users
23 | * of your application.
8 | * Some functions are only available from Cloud Code. 9 | *
10 | */ 11 | var ParseCloud = {}; 12 | 13 | _.extend(ParseCloud, /** @lends Parse.Cloud */ { 14 | /** 15 | * Makes a call to a cloud function. 16 | * @param {String} name The function name. 17 | * @param {Object} data The parameters to send to the cloud function. 18 | * @param {Object} options A Backbone-style options object 19 | * options.success, if set, should be a function to handle a successful 20 | * call to a cloud function. options.error should be a function that 21 | * handles an error running the cloud function. Both functions are 22 | * optional. Both functions take a single argument. 23 | * @return {Parse.Promise} A promise that will be resolved with the result 24 | * of the function. 25 | */ 26 | run: function(name, data, options) { 27 | options = options || {}; 28 | 29 | var request = ParseCore._request({ 30 | route: "functions", 31 | className: name, 32 | method: 'POST', 33 | useMasterKey: options.useMasterKey, 34 | data: ParseCore._encode(data, null, true) 35 | }); 36 | 37 | return request.then(function(resp) { 38 | return ParseCore._decode(null, resp).result; 39 | }) 40 | // ._thenRunCallbacks(options); 41 | } 42 | }); 43 | 44 | return ParseCloud; 45 | }); -------------------------------------------------------------------------------- /lib/Config.js: -------------------------------------------------------------------------------- 1 | var module = angular.module("ParseAngular.Config", [ 2 | 'ParseAngular.Core', 3 | 'ParseAngular.Error' 4 | ]); 5 | 6 | module.factory('ParseConfig', function(ParseCore, ParseError, $q){ 7 | /** 8 | * @class ParseConfig is a local representation of configuration data that 9 | * can be set from the Parse dashboard. 10 | */ 11 | ParseConfig = function() { 12 | this.attributes = {}; 13 | this._escapedAttributes = {}; 14 | }; 15 | 16 | /** 17 | * Retrieves the most recently-fetched configuration object, either from 18 | * memory or from local storage if necessary. 19 | * 20 | * @return {ParseConfig} The most recently-fetched ParseConfig if it 21 | * exists, else an empty ParseConfig. 22 | */ 23 | ParseConfig.current = function() { 24 | if (ParseConfig._currentConfig) { 25 | return ParseConfig._currentConfig; 26 | } 27 | 28 | var configData = ParseCore.localStorage.getItem( 29 | ParseCore._getParsePath(ParseConfig._CURRENT_CONFIG_KEY) 30 | ); 31 | 32 | var config = new ParseConfig(); 33 | if (configData) { 34 | config._finishFetch(JSON.parse(configData)); 35 | ParseConfig._currentConfig = config; 36 | } 37 | return config; 38 | }; 39 | 40 | /** 41 | * Gets a new configuration object from the server. 42 | * 43 | * @return {ParseCore.Promise} A promise that is resolved with a newly-created 44 | * configuration object when the get completes. 45 | */ 46 | ParseConfig.get = function() { 47 | 48 | var request = ParseCore._request({ 49 | route: "config", 50 | method: "GET", 51 | }); 52 | 53 | return request.then(function(response) { 54 | if (!response || !response.params) { 55 | var errorObject = new ParseError( 56 | ParseCore.Error.INVALID_JSON, 57 | "Config JSON response invalid." 58 | ); 59 | return $q.reject(errorObject); 60 | } 61 | 62 | var config = new ParseConfig(); 63 | config._finishFetch(response); 64 | ParseConfig._currentConfig = config; 65 | return config; 66 | }) 67 | // ._thenRunCallbacks(options); 68 | }; 69 | 70 | ParseConfig.prototype = { 71 | 72 | /** 73 | * Gets the HTML-escaped value of an attribute. 74 | */ 75 | escape: function(attr) { 76 | var html = this._escapedAttributes[attr]; 77 | if (html) { 78 | return html; 79 | } 80 | var val = this.attributes[attr]; 81 | var escaped; 82 | if (ParseCore._isNullOrUndefined(val)) { 83 | escaped = ''; 84 | } else { 85 | escaped = _.escape(val.toString()); 86 | } 87 | this._escapedAttributes[attr] = escaped; 88 | return escaped; 89 | }, 90 | 91 | /** 92 | * Gets the value of an attribute. 93 | * @param {String} attr The name of an attribute. 94 | */ 95 | get: function(attr) { 96 | return this.attributes[attr]; 97 | }, 98 | 99 | _finishFetch: function(serverData) { 100 | this.attributes = ParseCore._decode(null, _.clone(serverData.params)); 101 | ParseCore.localStorage.setItem( 102 | ParseCore._getParsePath(ParseConfig._CURRENT_CONFIG_KEY), 103 | JSON.stringify(serverData) 104 | ); 105 | } 106 | }; 107 | 108 | ParseConfig._currentConfig = null; 109 | 110 | ParseConfig._CURRENT_CONFIG_KEY = "currentConfig"; 111 | 112 | return ParseConfig; 113 | 114 | }); -------------------------------------------------------------------------------- /lib/Core.js: -------------------------------------------------------------------------------- 1 | var module = angular.module("ParseAngular.Core", [ 2 | 'ParseAngular.Error' 3 | ]); 4 | 5 | module.factory('ParseCore', function($http, $q, ParseError, $injector){ 6 | 7 | var ParseCore = {}; 8 | /** 9 | * Contains all Parse API classes and functions. 10 | * @name Parse 11 | * @namespace 12 | * 13 | * Contains all Parse API classes and functions. 14 | */ 15 | 16 | 17 | // Load references to other dependencies 18 | if (typeof(localStorage) !== 'undefined') { 19 | ParseCore.localStorage = localStorage; 20 | } 21 | 22 | // if (typeof(XMLHttpRequest) !== 'undefined') { 23 | // ParseCore.XMLHttpRequest = XMLHttpRequest; 24 | // } else if (typeof(require) !== 'undefined') { 25 | // ParseCore.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; 26 | // } 27 | // Import Parse's local copy of underscore. 28 | // if (typeof(exports) !== 'undefined' && exports._) { 29 | // // We're running in a CommonJS environment 30 | // _ = exports._.noConflict(); 31 | // exports.Parse = Parse; 32 | // } else { 33 | // _ = _.noConflict(); 34 | // } 35 | 36 | // If jQuery or Zepto has been included, grab a reference to it. 37 | if (typeof($) !== "undefined") { 38 | ParseCore.$ = $; 39 | } 40 | 41 | // Helpers 42 | // ------- 43 | 44 | // Shared empty constructor function to aid in prototype-chain creation. 45 | var EmptyConstructor = function() {}; 46 | 47 | 48 | // Helper function to correctly set up the prototype chain, for subclasses. 49 | // Similar to `goog.inherits`, but uses a hash of prototype properties and 50 | // class properties to be extended. 51 | var inherits = function(parent, protoProps, staticProps) { 52 | var child; 53 | 54 | // The constructor function for the new subclass is either defined by you 55 | // (the "constructor" property in your `extend` definition), or defaulted 56 | // by us to simply call the parent's constructor. 57 | if (protoProps && protoProps.hasOwnProperty('constructor')) { 58 | child = protoProps.constructor; 59 | } else { 60 | /** @ignore */ 61 | child = function(){ parent.apply(this, arguments); }; 62 | } 63 | 64 | // Inherit class (static) properties from parent. 65 | _.extend(child, parent); 66 | 67 | // Set the prototype chain to inherit from `parent`, without calling 68 | // `parent`'s constructor function. 69 | EmptyConstructor.prototype = parent.prototype; 70 | child.prototype = new EmptyConstructor(); 71 | 72 | // Add prototype properties (instance properties) to the subclass, 73 | // if supplied. 74 | if (protoProps) { 75 | _.extend(child.prototype, protoProps); 76 | } 77 | 78 | // Add static properties to the constructor function, if supplied. 79 | if (staticProps) { 80 | _.extend(child, staticProps); 81 | } 82 | 83 | // Correctly set child's `prototype.constructor`. 84 | child.prototype.constructor = child; 85 | 86 | // Set a convenience property in case the parent's prototype is 87 | // needed later. 88 | child.__super__ = parent.prototype; 89 | 90 | return child; 91 | }; 92 | 93 | // Set the server for Parse to talk to. 94 | ParseCore.serverURL = "https://api.parse.com"; 95 | 96 | /** 97 | * Call this method first to set up your authentication tokens for ParseCore. 98 | * You can get your keys from the Data Browser on ParseCore.com. 99 | * @param {String} applicationId Your Parse Application ID. 100 | * @param {String} javaScriptKey Your Parse JavaScript Key. 101 | * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!) 102 | */ 103 | ParseCore.initialize = function(applicationId, javaScriptKey, masterKey) { 104 | if (masterKey) { 105 | throw "ParseCore.initialize() was passed a Master Key, which is only " + 106 | "allowed from within Node.js."; 107 | } 108 | _initialize(applicationId, javaScriptKey); 109 | }; 110 | 111 | /** 112 | * Call this method first to set up master authentication tokens for ParseCore. 113 | * This method is for Parse's own private use. 114 | * @param {String} applicationId Your Parse Application ID. 115 | * @param {String} javaScriptKey Your Parse JavaScript Key. 116 | * @param {String} masterKey Your Parse Master Key. 117 | */ 118 | var _initialize = function(applicationId, javaScriptKey, masterKey) { 119 | ParseCore.applicationId = applicationId; 120 | ParseCore.javaScriptKey = javaScriptKey; 121 | ParseCore.masterKey = masterKey; 122 | _useMasterKey = false; 123 | }; 124 | 125 | 126 | /** 127 | * Returns prefix for localStorage keys used by this instance of ParseCore. 128 | * @param {String} path The relative suffix to append to it. 129 | * null or undefined is treated as the empty string. 130 | * @return {String} The full key name. 131 | */ 132 | ParseCore._getParsePath = _getParsePath = function(path) { 133 | if (!ParseCore.applicationId) { 134 | throw "You need to call ParseCore.initialize before using ParseCore."; 135 | } 136 | if (!path) { 137 | path = ""; 138 | } 139 | if (!_.isString(path)) { 140 | throw "Tried to get a localStorage path that wasn't a String."; 141 | } 142 | if (path[0] === "/") { 143 | path = path.substring(1); 144 | } 145 | return "Parse/" + ParseCore.applicationId + "/" + path; 146 | }; 147 | 148 | /** 149 | * Returns the unique string for this app on this machine. 150 | * Gets reset when localStorage is cleared. 151 | */ 152 | var _installationId = null; 153 | ParseCore._getInstallationId = function() { 154 | // See if it's cached in RAM. 155 | if (_installationId) { 156 | return _installationId; 157 | } 158 | 159 | // Try to get it from localStorage. 160 | var path = _getParsePath("installationId"); 161 | _installationId = ParseCore.localStorage.getItem(path); 162 | 163 | if (!_installationId || _installationId === "") { 164 | // It wasn't in localStorage, so create a new one. 165 | var hexOctet = function() { 166 | return Math.floor((1+Math.random())*0x10000).toString(16).substring(1); 167 | }; 168 | _installationId = ( 169 | hexOctet() + hexOctet() + "-" + 170 | hexOctet() + "-" + 171 | hexOctet() + "-" + 172 | hexOctet() + "-" + 173 | hexOctet() + hexOctet() + hexOctet()); 174 | ParseCore.localStorage.setItem(path, _installationId); 175 | } 176 | 177 | return _installationId; 178 | }; 179 | 180 | ParseCore._parseDate = function(iso8601) { 181 | var regexp = new RegExp( 182 | "^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})" + "T" + 183 | "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})" + 184 | "(.([0-9]+))?" + "Z$"); 185 | var match = regexp.exec(iso8601); 186 | if (!match) { 187 | return null; 188 | } 189 | 190 | var year = match[1] || 0; 191 | var month = (match[2] || 1) - 1; 192 | var day = match[3] || 0; 193 | var hour = match[4] || 0; 194 | var minute = match[5] || 0; 195 | var second = match[6] || 0; 196 | var milli = match[8] || 0; 197 | 198 | return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); 199 | }; 200 | 201 | 202 | // $http takes care of the compatibility for us. 203 | // _ajaxIE8 = function(method, url, data) { 204 | // var promise = new ParseCore.Promise(); 205 | // var xdr = new XDomainRequest(); 206 | // xdr.onload = function() { 207 | // var response; 208 | // try { 209 | // response = JSON.parse(xdr.responseText); 210 | // } catch (e) { 211 | // promise.reject(e); 212 | // } 213 | // if (response) { 214 | // promise.resolve(response); 215 | // } 216 | // }; 217 | // xdr.onerror = xdr.ontimeout = function() { 218 | // // Let's fake a real error message. 219 | // var fakeResponse = { 220 | // responseText: JSON.stringify({ 221 | // code: ParseError.X_DOMAIN_REQUEST, 222 | // error: "IE's XDomainRequest does not supply error info." 223 | // }) 224 | // }; 225 | 226 | // promise.reject(fakeResponse); 227 | // }; 228 | // xdr.onprogress = function() {}; 229 | // xdr.open(method, url); 230 | // xdr.send(data); 231 | // return promise; 232 | // }; 233 | 234 | // _useXDomainRequest = function() { 235 | // if (typeof(XDomainRequest) !== "undefined") { 236 | // // We're in IE 8+. 237 | // if ('withCredentials' in new XMLHttpRequest()) { 238 | // // We're in IE 10+. 239 | // return false; 240 | // } 241 | // return true; 242 | // } 243 | // return false; 244 | // }; 245 | 246 | 247 | // Updated _ajax function for angular 248 | var _ajax; 249 | ParseCore._ajax = _ajax = function(method, url, data, success, error) { 250 | return $http({ 251 | method: method, 252 | url: url, 253 | data: data, 254 | headers: { 255 | "Content-Type": "text/plain" 256 | } 257 | }) 258 | .then(function(httpResult){ 259 | return httpResult.data; 260 | }, 261 | function(err){ 262 | return $q.reject(httpResult.data); 263 | }); 264 | }; 265 | 266 | // A self-propagating extend function. 267 | ParseCore._extend = function(protoProps, classProps) { 268 | var child = inherits(this, protoProps, classProps); 269 | child.extend = this.extend; 270 | return child; 271 | }; 272 | 273 | /** 274 | * Options: 275 | * route: is classes, users, login, etc. 276 | * objectId: null if there is no associated objectId. 277 | * method: the http method for the REST API. 278 | * dataObject: the payload as an object, or null if there is none. 279 | * useMasterKey: overrides whether to use the master key if set. 280 | * @ignore 281 | */ 282 | ParseCore._request = function(options) { 283 | var route = options.route; 284 | var className = options.className; 285 | var objectId = options.objectId; 286 | var method = options.method; 287 | var useMasterKey = options.useMasterKey; 288 | var sessionToken = options.sessionToken; 289 | var dataObject = options.data; 290 | 291 | if (!ParseCore.applicationId) { 292 | throw "You must specify your applicationId using ParseCore.initialize."; 293 | } 294 | 295 | if (!ParseCore.javaScriptKey && !ParseCore.masterKey) { 296 | throw "You must specify a key using ParseCore.initialize."; 297 | } 298 | 299 | 300 | if (!sessionToken) { 301 | 302 | var ParseUser = $injector.get('ParseUser'); 303 | // Use the current user session token if none was provided. 304 | var currentUser = ParseUser.current(); 305 | if (currentUser && currentUser._sessionToken) { 306 | sessionToken = currentUser._sessionToken; 307 | } 308 | } 309 | 310 | 311 | if (route !== "batch" && 312 | route !== "classes" && 313 | route !== "events" && 314 | route !== "files" && 315 | route !== "functions" && 316 | route !== "login" && 317 | route !== "push" && 318 | route !== "requestPasswordReset" && 319 | route !== "rest_verify_analytics" && 320 | route !== "users" && 321 | route !== "jobs" && 322 | route !== "config") { 323 | throw "Bad route: '" + route + "'."; 324 | } 325 | 326 | var url = ParseCore.serverURL; 327 | if (url.charAt(url.length - 1) !== "/") { 328 | url += "/"; 329 | } 330 | url += "1/" + route; 331 | if (className) { 332 | url += "/" + className; 333 | } 334 | if (objectId) { 335 | url += "/" + objectId; 336 | } 337 | 338 | dataObject = _.clone(dataObject || {}); 339 | if (method !== "POST") { 340 | dataObject._method = method; 341 | method = "POST"; 342 | } 343 | 344 | if (_.isUndefined(useMasterKey)) { 345 | useMasterKey = _useMasterKey; 346 | } 347 | 348 | dataObject._ApplicationId = ParseCore.applicationId; 349 | 350 | if (!useMasterKey) { 351 | dataObject._JavaScriptKey = ParseCore.javaScriptKey; 352 | } else { 353 | dataObject._MasterKey = ParseCore.masterKey; 354 | } 355 | 356 | dataObject._ClientVersion = ParseCore.VERSION; 357 | dataObject._InstallationId = ParseCore._getInstallationId(); 358 | if (sessionToken) { 359 | dataObject._SessionToken = sessionToken; 360 | } 361 | var data = JSON.stringify(dataObject); 362 | 363 | return _ajax(method, url, data).then(null, function(response) { 364 | // Transform the error into an instance of ParseError by trying to parse 365 | // the error string as JSON. 366 | var error; 367 | if (response && response.responseText) { 368 | try { 369 | var errorJSON = JSON.parse(response.responseText); 370 | error = new ParseError(errorJSON.code, errorJSON.error); 371 | } catch (e) { 372 | // If we fail to parse the error text, that's okay. 373 | error = new ParseError( 374 | ParseError.INVALID_JSON, 375 | "Received an error with invalid JSON from Parse: " + 376 | response.responseText 377 | ); 378 | } 379 | } else { 380 | error = new ParseError( 381 | ParseError.CONNECTION_FAILED, 382 | "XMLHttpRequest failed: " + JSON.stringify(response) 383 | ); 384 | } 385 | // By explicitly returning a rejected Promise, this will work with 386 | // either jQuery or Promises/A semantics. 387 | return $q.reject(error); 388 | }); 389 | }; 390 | 391 | // Helper function to get a value from a Backbone object as a property 392 | // or as a function. 393 | ParseCore._getValue = function(object, prop) { 394 | if (!(object && object[prop])) { 395 | return null; 396 | } 397 | return _.isFunction(object[prop]) ? object[prop]() : object[prop]; 398 | }; 399 | 400 | /** 401 | * Converts a value in a Parse Object into the appropriate representation. 402 | * This is the JS equivalent of Java's ParseCore.maybeReferenceAndEncode(Object) 403 | * if seenObjects is falsey. Otherwise any ParseObjects not in 404 | * seenObjects will be fully embedded rather than encoded 405 | * as a pointer. This array will be used to prevent going into an infinite 406 | * loop because we have circular references. If seenObjects 407 | * is set, then none of the Parse Objects that are serialized can be dirty. 408 | */ 409 | ParseCore._encode = _encode = function(value, seenObjects, disallowObjects) { 410 | 411 | var ParseObject = $injector.get('ParseObject'), 412 | ParseACL = $injector.get('ParseACL'), 413 | ParseGeoPoint = $injector.get('ParseGeoPoint'), 414 | ParseRelation = $injector.get('ParseRelation'), 415 | ParseOp = $injector.get('ParseOp'); 416 | 417 | 418 | if (value instanceof ParseObject) { 419 | if (disallowObjects) { 420 | throw "ParseObjects not allowed here"; 421 | } 422 | if (!seenObjects || _.include(seenObjects, value) || !value._hasData) { 423 | return value._toPointer(); 424 | } 425 | if (!value.dirty()) { 426 | seenObjects = seenObjects.concat(value); 427 | return _encode(value._toFullJSON(seenObjects), 428 | seenObjects, 429 | disallowObjects); 430 | } 431 | throw "Tried to save an object with a pointer to a new, unsaved object."; 432 | } 433 | if (value instanceof ParseACL) { 434 | return value.toJSON(); 435 | } 436 | if (_.isDate(value)) { 437 | return { "__type": "Date", "iso": value.toJSON() }; 438 | } 439 | if (value instanceof ParseGeoPoint) { 440 | return value.toJSON(); 441 | } 442 | if (_.isArray(value)) { 443 | return _.map(value, function(x) { 444 | return _encode(x, seenObjects, disallowObjects); 445 | }); 446 | } 447 | if (_.isRegExp(value)) { 448 | return value.source; 449 | } 450 | if (value instanceof ParseRelation) { 451 | return value.toJSON(); 452 | } 453 | if (value instanceof ParseOp) { 454 | return value.toJSON(); 455 | } 456 | // if (value instanceof ParseCore.File) { 457 | // if (!value.url()) { 458 | // throw "Tried to save an object containing an unsaved file."; 459 | // } 460 | 461 | // return { 462 | // __type: "File", 463 | // name: value.name(), 464 | // url: value.url() 465 | // }; 466 | // } 467 | if (_.isObject(value)) { 468 | var output = {}; 469 | ParseCore._objectEach(value, function(v, k) { 470 | output[k] = _encode(v, seenObjects, disallowObjects); 471 | }); 472 | return output; 473 | } 474 | 475 | return value; 476 | }; 477 | 478 | /** 479 | * The inverse function of _encode. 480 | * TODO: make decode not mutate value. 481 | */ 482 | ParseCore._decode = _decode = function(key, value) { 483 | 484 | var ParseObject = $injector.get('ParseObject'), 485 | ParseACL = $injector.get('ParseACL'), 486 | ParseGeoPoint = $injector.get('ParseGeoPoint'), 487 | ParseRelation = $injector.get('ParseRelation'), 488 | ParseOp = $injector.get('ParseOp'); 489 | 490 | if (!_.isObject(value)) { 491 | return value; 492 | } 493 | if (_.isArray(value)) { 494 | ParseCore._arrayEach(value, function(v, k) { 495 | value[k] = _decode(k, v); 496 | }); 497 | return value; 498 | } 499 | if (value instanceof ParseObject) { 500 | return value; 501 | } 502 | // if (value instanceof ParseCore.File) { 503 | // return value; 504 | // } 505 | if (value instanceof ParseOp) { 506 | return value; 507 | } 508 | if (value.__op) { 509 | return ParseOp._decode(value); 510 | } 511 | if (value.__type === "Pointer" && value.className) { 512 | var pointer = ParseObject._create(value.className); 513 | pointer._finishFetch({ objectId: value.objectId }, false); 514 | return pointer; 515 | } 516 | if (value.__type === "Object" && value.className) { 517 | // It's an Object included in a query result. 518 | var className = value.className; 519 | delete value.__type; 520 | delete value.className; 521 | var object = ParseObject._create(className); 522 | object._finishFetch(value, true); 523 | return object; 524 | } 525 | if (value.__type === "Date") { 526 | return ParseCore._parseDate(value.iso); 527 | } 528 | if (value.__type === "GeoPoint") { 529 | return new ParseGeoPoint({ 530 | latitude: value.latitude, 531 | longitude: value.longitude 532 | }); 533 | } 534 | if (key === "ACL") { 535 | if (value instanceof ParseACL) { 536 | return value; 537 | } 538 | return new ParseACL(value); 539 | } 540 | if (value.__type === "Relation") { 541 | var relation = new ParseRelation(null, key); 542 | relation.targetClassName = value.className; 543 | return relation; 544 | } 545 | 546 | // if (value.__type === "File") { 547 | // var file = new ParseCore.File(value.name); 548 | // file._url = value.url; 549 | // return file; 550 | // } 551 | 552 | ParseCore._objectEach(value, function(v, k) { 553 | value[k] = _decode(k, v); 554 | }); 555 | return value; 556 | }; 557 | 558 | ParseCore._arrayEach = _.each; 559 | 560 | /** 561 | * Does a deep traversal of every item in object, calling func on every one. 562 | * @param {Object} object The object or array to traverse deeply. 563 | * @param {Function} func The function to call for every item. It will 564 | * be passed the item as an argument. If it returns a truthy value, that 565 | * value will replace the item in its parent container. 566 | * @returns {} the result of calling func on the top-level object itself. 567 | */ 568 | ParseCore._traverse = _traverse = function(object, func, seen) { 569 | 570 | var ParseObject = $injector.get('ParseObject'), 571 | ParseACL = $injector.get('ParseACL'), 572 | ParseGeoPoint = $injector.get('ParseGeoPoint'), 573 | ParseRelation = $injector.get('ParseRelation'), 574 | ParseOp = $injector.get('ParseOp'); 575 | 576 | if (object instanceof ParseObject) { 577 | seen = seen || []; 578 | if (_.indexOf(seen, object) >= 0) { 579 | // We've already visited this object in this call. 580 | return; 581 | } 582 | seen.push(object); 583 | _traverse(object.attributes, func, seen); 584 | return func(object); 585 | } 586 | if (object instanceof ParseRelation) { 587 | // Nothing needs to be done, but we don't want to recurse into the 588 | // object's parent infinitely, so we catch this case. 589 | return func(object); 590 | } 591 | if (_.isArray(object)) { 592 | _.each(object, function(child, index) { 593 | var newChild = _traverse(child, func, seen); 594 | if (newChild) { 595 | object[index] = newChild; 596 | } 597 | }); 598 | return func(object); 599 | } 600 | if (_.isObject(object)) { 601 | _each(object, function(child, key) { 602 | var newChild = _traverse(child, func, seen); 603 | if (newChild) { 604 | object[key] = newChild; 605 | } 606 | }); 607 | return func(object); 608 | } 609 | return func(object); 610 | }; 611 | 612 | /** 613 | * This is like _.each, except: 614 | * * it doesn't work for so-called array-like objects, 615 | * * it does work for dictionaries with a "length" attribute. 616 | */ 617 | ParseCore._objectEach = _each = function(obj, callback) { 618 | if (_.isObject(obj)) { 619 | _.each(_.keys(obj), function(key) { 620 | callback(obj[key], key); 621 | }); 622 | } else { 623 | _.each(obj, callback); 624 | } 625 | }; 626 | 627 | // Helper function to check null or undefined. 628 | ParseCore._isNullOrUndefined = _isNullOrUndefined = function(x) { 629 | return _.isNull(x) || _.isUndefined(x); 630 | }; 631 | 632 | 633 | ParseCore._continueWhile = _continueWhile = function(predicate, asyncFunction) { 634 | if (predicate()) { 635 | return asyncFunction().then(function() { 636 | return _continueWhile(predicate, asyncFunction); 637 | }); 638 | } 639 | var defer = $q.defer(); 640 | defer.resolve(); 641 | return defer.promise; 642 | }; 643 | 644 | 645 | 646 | return ParseCore; 647 | 648 | }); -------------------------------------------------------------------------------- /lib/Error.js: -------------------------------------------------------------------------------- 1 | var module = angular.module("ParseAngular.Error", []); 2 | 3 | module.factory('ParseError', function(){ 4 | 5 | /** 6 | * Constructs a new Parse.Error object with the given code and message. 7 | * @param {Number} code An error code constant fromParse.Error
.
8 | * @param {String} message A detailed description of the error.
9 | * @class
10 | *
11 | * Class used for all objects passed to error callbacks.
12 | */ 13 | var ParseError = function(code, message) { 14 | this.code = code; 15 | this.message = message; 16 | }; 17 | 18 | _.extend(ParseError, /** @lends Parse.Error */ { 19 | /** 20 | * Error code indicating some error other than those enumerated here. 21 | * @constant 22 | */ 23 | OTHER_CAUSE: -1, 24 | 25 | /** 26 | * Error code indicating that something has gone wrong with the server. 27 | * If you get this error code, it is Parse's fault. Contact us at 28 | * https://parse.com/help 29 | * @constant 30 | */ 31 | INTERNAL_SERVER_ERROR: 1, 32 | 33 | /** 34 | * Error code indicating the connection to the Parse servers failed. 35 | * @constant 36 | */ 37 | CONNECTION_FAILED: 100, 38 | 39 | /** 40 | * Error code indicating the specified object doesn't exist. 41 | * @constant 42 | */ 43 | OBJECT_NOT_FOUND: 101, 44 | 45 | /** 46 | * Error code indicating you tried to query with a datatype that doesn't 47 | * support it, like exact matching an array or object. 48 | * @constant 49 | */ 50 | INVALID_QUERY: 102, 51 | 52 | /** 53 | * Error code indicating a missing or invalid classname. Classnames are 54 | * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the 55 | * only valid characters. 56 | * @constant 57 | */ 58 | INVALID_CLASS_NAME: 103, 59 | 60 | /** 61 | * Error code indicating an unspecified object id. 62 | * @constant 63 | */ 64 | MISSING_OBJECT_ID: 104, 65 | 66 | /** 67 | * Error code indicating an invalid key name. Keys are case-sensitive. They 68 | * must start with a letter, and a-zA-Z0-9_ are the only valid characters. 69 | * @constant 70 | */ 71 | INVALID_KEY_NAME: 105, 72 | 73 | /** 74 | * Error code indicating a malformed pointer. You should not see this unless 75 | * you have been mucking about changing internal Parse code. 76 | * @constant 77 | */ 78 | INVALID_POINTER: 106, 79 | 80 | /** 81 | * Error code indicating that badly formed JSON was received upstream. This 82 | * either indicates you have done something unusual with modifying how 83 | * things encode to JSON, or the network is failing badly. 84 | * @constant 85 | */ 86 | INVALID_JSON: 107, 87 | 88 | /** 89 | * Error code indicating that the feature you tried to access is only 90 | * available internally for testing purposes. 91 | * @constant 92 | */ 93 | COMMAND_UNAVAILABLE: 108, 94 | 95 | /** 96 | * You must call Parse.initialize before using the Parse library. 97 | * @constant 98 | */ 99 | NOT_INITIALIZED: 109, 100 | 101 | /** 102 | * Error code indicating that a field was set to an inconsistent type. 103 | * @constant 104 | */ 105 | INCORRECT_TYPE: 111, 106 | 107 | /** 108 | * Error code indicating an invalid channel name. A channel name is either 109 | * an empty string (the broadcast channel) or contains only a-zA-Z0-9_ 110 | * characters and starts with a letter. 111 | * @constant 112 | */ 113 | INVALID_CHANNEL_NAME: 112, 114 | 115 | /** 116 | * Error code indicating that push is misconfigured. 117 | * @constant 118 | */ 119 | PUSH_MISCONFIGURED: 115, 120 | 121 | /** 122 | * Error code indicating that the object is too large. 123 | * @constant 124 | */ 125 | OBJECT_TOO_LARGE: 116, 126 | 127 | /** 128 | * Error code indicating that the operation isn't allowed for clients. 129 | * @constant 130 | */ 131 | OPERATION_FORBIDDEN: 119, 132 | 133 | /** 134 | * Error code indicating the result was not found in the cache. 135 | * @constant 136 | */ 137 | CACHE_MISS: 120, 138 | 139 | /** 140 | * Error code indicating that an invalid key was used in a nested 141 | * JSONObject. 142 | * @constant 143 | */ 144 | INVALID_NESTED_KEY: 121, 145 | 146 | /** 147 | * Error code indicating that an invalid filename was used for ParseFile. 148 | * A valid file name contains only a-zA-Z0-9_. characters and is between 1 149 | * and 128 characters. 150 | * @constant 151 | */ 152 | INVALID_FILE_NAME: 122, 153 | 154 | /** 155 | * Error code indicating an invalid ACL was provided. 156 | * @constant 157 | */ 158 | INVALID_ACL: 123, 159 | 160 | /** 161 | * Error code indicating that the request timed out on the server. Typically 162 | * this indicates that the request is too expensive to run. 163 | * @constant 164 | */ 165 | TIMEOUT: 124, 166 | 167 | /** 168 | * Error code indicating that the email address was invalid. 169 | * @constant 170 | */ 171 | INVALID_EMAIL_ADDRESS: 125, 172 | 173 | /** 174 | * Error code indicating a missing content type. 175 | * @constant 176 | */ 177 | MISSING_CONTENT_TYPE: 126, 178 | 179 | /** 180 | * Error code indicating a missing content length. 181 | * @constant 182 | */ 183 | MISSING_CONTENT_LENGTH: 127, 184 | 185 | /** 186 | * Error code indicating an invalid content length. 187 | * @constant 188 | */ 189 | INVALID_CONTENT_LENGTH: 128, 190 | 191 | /** 192 | * Error code indicating a file that was too large. 193 | * @constant 194 | */ 195 | FILE_TOO_LARGE: 129, 196 | 197 | /** 198 | * Error code indicating an error saving a file. 199 | * @constant 200 | */ 201 | FILE_SAVE_ERROR: 130, 202 | 203 | /** 204 | * Error code indicating that a unique field was given a value that is 205 | * already taken. 206 | * @constant 207 | */ 208 | DUPLICATE_VALUE: 137, 209 | 210 | /** 211 | * Error code indicating that a role's name is invalid. 212 | * @constant 213 | */ 214 | INVALID_ROLE_NAME: 139, 215 | 216 | /** 217 | * Error code indicating that an application quota was exceeded. Upgrade to 218 | * resolve. 219 | * @constant 220 | */ 221 | EXCEEDED_QUOTA: 140, 222 | 223 | /** 224 | * Error code indicating that a Cloud Code script failed. 225 | * @constant 226 | */ 227 | SCRIPT_FAILED: 141, 228 | 229 | /** 230 | * Error code indicating that a Cloud Code validation failed. 231 | * @constant 232 | */ 233 | VALIDATION_ERROR: 142, 234 | 235 | /** 236 | * Error code indicating that invalid image data was provided. 237 | * @constant 238 | */ 239 | INVALID_IMAGE_DATA: 150, 240 | 241 | /** 242 | * Error code indicating an unsaved file. 243 | * @constant 244 | */ 245 | UNSAVED_FILE_ERROR: 151, 246 | 247 | /** 248 | * Error code indicating an invalid push time. 249 | */ 250 | INVALID_PUSH_TIME_ERROR: 152, 251 | 252 | /** 253 | * Error code indicating an error deleting a file. 254 | * @constant 255 | */ 256 | FILE_DELETE_ERROR: 153, 257 | 258 | /** 259 | * Error code indicating that the application has exceeded its request 260 | * limit. 261 | * @constant 262 | */ 263 | REQUEST_LIMIT_EXCEEDED: 155, 264 | 265 | /** 266 | * Error code indicating an invalid event name. 267 | */ 268 | INVALID_EVENT_NAME: 160, 269 | 270 | /** 271 | * Error code indicating that the username is missing or empty. 272 | * @constant 273 | */ 274 | USERNAME_MISSING: 200, 275 | 276 | /** 277 | * Error code indicating that the password is missing or empty. 278 | * @constant 279 | */ 280 | PASSWORD_MISSING: 201, 281 | 282 | /** 283 | * Error code indicating that the username has already been taken. 284 | * @constant 285 | */ 286 | USERNAME_TAKEN: 202, 287 | 288 | /** 289 | * Error code indicating that the email has already been taken. 290 | * @constant 291 | */ 292 | EMAIL_TAKEN: 203, 293 | 294 | /** 295 | * Error code indicating that the email is missing, but must be specified. 296 | * @constant 297 | */ 298 | EMAIL_MISSING: 204, 299 | 300 | /** 301 | * Error code indicating that a user with the specified email was not found. 302 | * @constant 303 | */ 304 | EMAIL_NOT_FOUND: 205, 305 | 306 | /** 307 | * Error code indicating that a user object without a valid session could 308 | * not be altered. 309 | * @constant 310 | */ 311 | SESSION_MISSING: 206, 312 | 313 | /** 314 | * Error code indicating that a user can only be created through signup. 315 | * @constant 316 | */ 317 | MUST_CREATE_USER_THROUGH_SIGNUP: 207, 318 | 319 | /** 320 | * Error code indicating that an an account being linked is already linked 321 | * to another user. 322 | * @constant 323 | */ 324 | ACCOUNT_ALREADY_LINKED: 208, 325 | 326 | /** 327 | * Error code indicating that a user cannot be linked to an account because 328 | * that account's id could not be found. 329 | * @constant 330 | */ 331 | LINKED_ID_MISSING: 250, 332 | 333 | /** 334 | * Error code indicating that a user with a linked (e.g. Facebook) account 335 | * has an invalid session. 336 | * @constant 337 | */ 338 | INVALID_LINKED_SESSION: 251, 339 | 340 | /** 341 | * Error code indicating that a service being linked (e.g. Facebook or 342 | * Twitter) is unsupported. 343 | * @constant 344 | */ 345 | UNSUPPORTED_SERVICE: 252, 346 | 347 | /** 348 | * Error code indicating that there were multiple errors. Aggregate errors 349 | * have an "errors" property, which is an array of error objects with more 350 | * detail about each error that occurred. 351 | * @constant 352 | */ 353 | AGGREGATE_ERROR: 600, 354 | 355 | /** 356 | * Error code indicating the client was unable to read an input file. 357 | * @constant 358 | */ 359 | FILE_READ_ERROR: 601, 360 | 361 | /** 362 | * Error code indicating a real error code is unavailable because 363 | * we had to use an XDomainRequest object to allow CORS requests in 364 | * Internet Explorer, which strips the body from HTTP responses that have 365 | * a non-2XX status code. 366 | * @constant 367 | */ 368 | X_DOMAIN_REQUEST: 602 369 | }); 370 | 371 | return ParseError; 372 | }); -------------------------------------------------------------------------------- /lib/Events.js: -------------------------------------------------------------------------------- 1 | var module = angular.module('ParseAngular.Events', []); 2 | 3 | module.factory('ParseEvents', function(){ 4 | var ParseEvents; 5 | 6 | var eventSplitter = /\s+/; 7 | var slice = Array.prototype.slice; 8 | 9 | /** 10 | * @class 11 | * 12 | *ParseEvents is a fork of Backbone's Events module, provided for your 13 | * convenience.
14 | * 15 | *A module that can be mixed in to any object in order to provide 16 | * it with custom events. You may bind callback functions to an event 17 | * with `on`, or remove these functions with `off`. 18 | * Triggering an event fires all callbacks in the order that `on` was 19 | * called. 20 | * 21 | *
22 | * var object = {}; 23 | * _.extend(object, ParseEvents); 24 | * object.on('expand', function(){ alert('expanded'); }); 25 | * object.trigger('expand');26 | * 27 | *
For more information, see the 28 | * Backbone 29 | * documentation.
30 | */ 31 | ParseEvents = { 32 | /** 33 | * Bind one or more space separated events, `events`, to a `callback` 34 | * function. Passing `"all"` will bind the callback to all events fired. 35 | */ 36 | on: function(events, callback, context) { 37 | 38 | var calls, event, node, tail, list; 39 | if (!callback) { 40 | return this; 41 | } 42 | events = events.split(eventSplitter); 43 | calls = this._callbacks || (this._callbacks = {}); 44 | 45 | // Create an immutable callback list, allowing traversal during 46 | // modification. The tail is an empty object that will always be used 47 | // as the next node. 48 | event = events.shift(); 49 | while (event) { 50 | list = calls[event]; 51 | node = list ? list.tail : {}; 52 | node.next = tail = {}; 53 | node.context = context; 54 | node.callback = callback; 55 | calls[event] = {tail: tail, next: list ? list.next : node}; 56 | event = events.shift(); 57 | } 58 | 59 | return this; 60 | }, 61 | 62 | /** 63 | * Remove one or many callbacks. If `context` is null, removes all callbacks 64 | * with that function. If `callback` is null, removes all callbacks for the 65 | * event. If `events` is null, removes all bound callbacks for all events. 66 | */ 67 | off: function(events, callback, context) { 68 | var event, calls, node, tail, cb, ctx; 69 | 70 | // No events, or removing *all* events. 71 | if (!(calls = this._callbacks)) { 72 | return; 73 | } 74 | if (!(events || callback || context)) { 75 | delete this._callbacks; 76 | return this; 77 | } 78 | 79 | // Loop through the listed events and contexts, splicing them out of the 80 | // linked list of callbacks if appropriate. 81 | events = events ? events.split(eventSplitter) : _.keys(calls); 82 | event = events.shift(); 83 | while (event) { 84 | node = calls[event]; 85 | delete calls[event]; 86 | if (!node || !(callback || context)) { 87 | event = events.shift(); 88 | continue; 89 | } 90 | // Create a new list, omitting the indicated callbacks. 91 | tail = node.tail; 92 | node = node.next; 93 | while (node !== tail) { 94 | cb = node.callback; 95 | ctx = node.context; 96 | if ((callback && cb !== callback) || (context && ctx !== context)) { 97 | this.on(event, cb, ctx); 98 | } 99 | node = node.next; 100 | } 101 | event = events.shift(); 102 | } 103 | 104 | return this; 105 | }, 106 | 107 | /** 108 | * Trigger one or many events, firing all bound callbacks. Callbacks are 109 | * passed the same arguments as `trigger` is, apart from the event name 110 | * (unless you're listening on `"all"`, which will cause your callback to 111 | * receive the true name of the event as the first argument). 112 | */ 113 | trigger: function(events) { 114 | var event, node, calls, tail, args, all, rest; 115 | if (!(calls = this._callbacks)) { 116 | return this; 117 | } 118 | all = calls.all; 119 | events = events.split(eventSplitter); 120 | rest = slice.call(arguments, 1); 121 | 122 | // For each event, walk through the linked list of callbacks twice, 123 | // first to trigger the event, then to trigger any `"all"` callbacks. 124 | event = events.shift(); 125 | while (event) { 126 | node = calls[event]; 127 | if (node) { 128 | tail = node.tail; 129 | while ((node = node.next) !== tail) { 130 | node.callback.apply(node.context || this, rest); 131 | } 132 | } 133 | node = all; 134 | if (node) { 135 | tail = node.tail; 136 | args = [event].concat(rest); 137 | while ((node = node.next) !== tail) { 138 | node.callback.apply(node.context || this, args); 139 | } 140 | } 141 | event = events.shift(); 142 | } 143 | 144 | return this; 145 | } 146 | }; 147 | 148 | /** 149 | * @function 150 | */ 151 | ParseEvents.bind = ParseEvents.on; 152 | 153 | /** 154 | * @function 155 | */ 156 | ParseEvents.unbind = ParseEvents.off; 157 | 158 | return ParseEvents; 159 | 160 | }); -------------------------------------------------------------------------------- /lib/FacebookUtils.js: -------------------------------------------------------------------------------- 1 | var module = angular.module("ParseAngular.FacebookUtils", [ 2 | 'ParseAngular.User', 3 | 'ParseAngular.Core' 4 | ]); 5 | 6 | module.factory('ParseFacebookUtils', function(ParseUser, ParseCore){ 7 | 8 | var PUBLIC_KEY = "*", ParseFacebookUtils; 9 | 10 | var initialized = false; 11 | var requestedPermissions; 12 | var initOptions; 13 | var provider = { 14 | authenticate: function(options) { 15 | var self = this; 16 | FB.login(function(response) { 17 | if (response.authResponse) { 18 | if (options.success) { 19 | options.success(self, { 20 | id: response.authResponse.userID, 21 | access_token: response.authResponse.accessToken, 22 | expiration_date: new Date(response.authResponse.expiresIn * 1000 + 23 | (new Date()).getTime()).toJSON() 24 | }); 25 | } 26 | } else { 27 | if (options.error) { 28 | options.error(self, response); 29 | } 30 | } 31 | }, { 32 | scope: requestedPermissions 33 | }); 34 | }, 35 | restoreAuthentication: function(authData) { 36 | if (authData) { 37 | var authResponse = { 38 | userID: authData.id, 39 | accessToken: authData.access_token, 40 | expiresIn: (ParseCore._parseDate(authData.expiration_date).getTime() - 41 | (new Date()).getTime()) / 1000 42 | }; 43 | var newOptions = _.clone(initOptions); 44 | newOptions.authResponse = authResponse; 45 | 46 | // Suppress checks for login status from the browser. 47 | newOptions.status = false; 48 | 49 | // If the user doesn't match the one known by the FB SDK, log out. 50 | // Most of the time, the users will match -- it's only in cases where 51 | // the FB SDK knows of a different user than the one being restored 52 | // from a Parse User that logged in with username/password. 53 | var existingResponse = FB.getAuthResponse(); 54 | if (existingResponse && 55 | existingResponse.userID !== authResponse.userID) { 56 | FB.logout(); 57 | } 58 | 59 | FB.init(newOptions); 60 | } 61 | return true; 62 | }, 63 | getAuthType: function() { 64 | return "facebook"; 65 | }, 66 | deauthenticate: function() { 67 | this.restoreAuthentication(null); 68 | } 69 | }; 70 | 71 | /** 72 | * Provides a set of utilities for using Parse with Facebook. 73 | * @namespace 74 | * Provides a set of utilities for using Parse with Facebook. 75 | */ 76 | ParseFacebookUtils = { 77 | /** 78 | * Initializes Parse Facebook integration. Call this function after you 79 | * have loaded the Facebook Javascript SDK with the same parameters 80 | * as you would pass to
81 | *
83 | * FB.init()
. ParseFacebookUtils will invoke FB.init() for you
84 | * with these arguments.
85 | *
86 | * @param {Object} options Facebook options argument as described here:
87 | *
89 | * FB.init(). The status flag will be coerced to 'false' because it
90 | * interferes with Parse Facebook integration. Call FB.getLoginStatus()
91 | * explicitly if this behavior is required by your application.
92 | */
93 | init: function(options) {
94 | if (typeof(FB) === 'undefined') {
95 | throw "The Facebook JavaScript SDK must be loaded before calling init.";
96 | }
97 | initOptions = _.clone(options) || {};
98 | if (initOptions.status && typeof(console) !== "undefined") {
99 | var warn = console.warn || console.log || function() {};
100 | warn.call(console, "The 'status' flag passed into" +
101 | " FB.init, when set to true, can interfere with Parse Facebook" +
102 | " integration, so it has been suppressed. Please call" +
103 | " FB.getLoginStatus() explicitly if you require this behavior.");
104 | }
105 | initOptions.status = false;
106 | FB.init(initOptions);
107 | ParseUser._registerAuthenticationProvider(provider);
108 | initialized = true;
109 | },
110 |
111 | /**
112 | * Gets whether the user has their account linked to Facebook.
113 | *
114 | * @param {Parse.User} user User to check for a facebook link.
115 | * The user must be logged in on this device.
116 | * @return {Boolean} true
if the user has their account
117 | * linked to Facebook.
118 | */
119 | isLinked: function(user) {
120 | return user._isLinked("facebook");
121 | },
122 |
123 | /**
124 | * Logs in a user using Facebook. This method delegates to the Facebook
125 | * SDK to authenticate the user, and then automatically logs in (or
126 | * creates, in the case where it is a new user) a Parse.User.
127 | *
128 | * @param {String, Object} permissions The permissions required for Facebook
129 | * log in. This is a comma-separated string of permissions.
130 | * Alternatively, supply a Facebook authData object as described in our
131 | * REST API docs if you want to handle getting facebook auth tokens
132 | * yourself.
133 | * @param {Object} options Standard options object with success and error
134 | * callbacks.
135 | */
136 | logIn: function(permissions, options) {
137 | if (!permissions || _.isString(permissions)) {
138 | if (!initialized) {
139 | throw "You must initialize FacebookUtils before calling logIn.";
140 | }
141 | requestedPermissions = permissions;
142 | return ParseUser._logInWith("facebook", options);
143 | } else {
144 | var newOptions = _.clone(options) || {};
145 | newOptions.authData = permissions;
146 | return ParseUser._logInWith("facebook", newOptions);
147 | }
148 | },
149 |
150 | /**
151 | * Links Facebook to an existing PFUser. This method delegates to the
152 | * Facebook SDK to authenticate the user, and then automatically links
153 | * the account to the Parse.User.
154 | *
155 | * @param {Parse.User} user User to link to Facebook. This must be the
156 | * current user.
157 | * @param {String, Object} permissions The permissions required for Facebook
158 | * log in. This is a comma-separated string of permissions.
159 | * Alternatively, supply a Facebook authData object as described in our
160 | * REST API docs if you want to handle getting facebook auth tokens
161 | * yourself.
162 | * @param {Object} options Standard options object with success and error
163 | * callbacks.
164 | */
165 | link: function(user, permissions, options) {
166 | if (!permissions || _.isString(permissions)) {
167 | if (!initialized) {
168 | throw "You must initialize FacebookUtils before calling link.";
169 | }
170 | requestedPermissions = permissions;
171 | return user._linkWith("facebook", options);
172 | } else {
173 | var newOptions = _.clone(options) || {};
174 | newOptions.authData = permissions;
175 | return user._linkWith("facebook", newOptions);
176 | }
177 | },
178 |
179 | /**
180 | * Unlinks the Parse.User from a Facebook account.
181 | *
182 | * @param {Parse.User} user User to unlink from Facebook. This must be the
183 | * current user.
184 | * @param {Object} options Standard options object with success and error
185 | * callbacks.
186 | */
187 | unlink: function(user, options) {
188 | if (!initialized) {
189 | throw "You must initialize FacebookUtils before calling unlink.";
190 | }
191 | return user._unlinkFrom("facebook", options);
192 | }
193 | };
194 |
195 | return ParseFacebookUtils;
196 |
197 | });
--------------------------------------------------------------------------------
/lib/GeoPoint.js:
--------------------------------------------------------------------------------
1 | var module = angular.module('ParseAngular.GeoPoint', []);
2 |
3 | module.factory('ParseGeoPoint', function($q){
4 |
5 | var ParseGeoPoint;
6 |
7 | /**
8 | * Creates a new GeoPoint with any of the following forms:10 | * new GeoPoint(otherGeoPoint) 11 | * new GeoPoint(30, 30) 12 | * new GeoPoint([30, 30]) 13 | * new GeoPoint({latitude: 30, longitude: 30}) 14 | * new GeoPoint() // defaults to (0, 0) 15 | *16 | * @class 17 | * 18 | *
Represents a latitude / longitude point that may be associated 19 | * with a key in a ParseObject or used as a reference point for geo queries. 20 | * This allows proximity-based queries on the key.
21 | * 22 | *Only one key in a class may contain a GeoPoint.
23 | * 24 | *Example:
25 | * var point = new ParseGeoPoint(30.0, -20.0); 26 | * var object = new Parse.Object("PlaceObject"); 27 | * object.set("location", point); 28 | * object.save();29 | */ 30 | ParseGeoPoint = function(arg1, arg2) { 31 | if (_.isArray(arg1)) { 32 | ParseGeoPoint._validate(arg1[0], arg1[1]); 33 | this.latitude = arg1[0]; 34 | this.longitude = arg1[1]; 35 | } else if (_.isObject(arg1)) { 36 | ParseGeoPoint._validate(arg1.latitude, arg1.longitude); 37 | this.latitude = arg1.latitude; 38 | this.longitude = arg1.longitude; 39 | } else if (_.isNumber(arg1) && _.isNumber(arg2)) { 40 | ParseGeoPoint._validate(arg1, arg2); 41 | this.latitude = arg1; 42 | this.longitude = arg2; 43 | } else { 44 | this.latitude = 0; 45 | this.longitude = 0; 46 | } 47 | 48 | // Add properties so that anyone using Webkit or Mozilla will get an error 49 | // if they try to set values that are out of bounds. 50 | var self = this; 51 | if (this.__defineGetter__ && this.__defineSetter__) { 52 | // Use _latitude and _longitude to actually store the values, and add 53 | // getters and setters for latitude and longitude. 54 | this._latitude = this.latitude; 55 | this._longitude = this.longitude; 56 | this.__defineGetter__("latitude", function() { 57 | return self._latitude; 58 | }); 59 | this.__defineGetter__("longitude", function() { 60 | return self._longitude; 61 | }); 62 | this.__defineSetter__("latitude", function(val) { 63 | ParseGeoPoint._validate(val, self.longitude); 64 | self._latitude = val; 65 | }); 66 | this.__defineSetter__("longitude", function(val) { 67 | ParseGeoPoint._validate(self.latitude, val); 68 | self._longitude = val; 69 | }); 70 | } 71 | }; 72 | 73 | /** 74 | * @lends ParseGeoPoint.prototype 75 | * @property {float} latitude North-south portion of the coordinate, in range 76 | * [-90, 90]. Throws an exception if set out of range in a modern browser. 77 | * @property {float} longitude East-west portion of the coordinate, in range 78 | * [-180, 180]. Throws if set out of range in a modern browser. 79 | */ 80 | 81 | /** 82 | * Throws an exception if the given lat-long is out of bounds. 83 | */ 84 | ParseGeoPoint._validate = function(latitude, longitude) { 85 | if (latitude < -90.0) { 86 | throw "ParseGeoPoint latitude " + latitude + " < -90.0."; 87 | } 88 | if (latitude > 90.0) { 89 | throw "ParseGeoPoint latitude " + latitude + " > 90.0."; 90 | } 91 | if (longitude < -180.0) { 92 | throw "ParseGeoPoint longitude " + longitude + " < -180.0."; 93 | } 94 | if (longitude > 180.0) { 95 | throw "ParseGeoPoint longitude " + longitude + " > 180.0."; 96 | } 97 | }; 98 | 99 | /** 100 | * Creates a GeoPoint with the user's current location, if available. 101 | * Calls options.success with a new GeoPoint instance or calls options.error. 102 | * @param {Object} options An object with success and error callbacks. 103 | */ 104 | ParseGeoPoint.current = function() { 105 | var defer = $q.defer(); 106 | navigator.geolocation.getCurrentPosition(function(location) { 107 | defer.resolve(new ParseGeoPoint({ 108 | latitude: location.coords.latitude, 109 | longitude: location.coords.longitude 110 | })); 111 | }, function(error) { 112 | defer.reject(error); 113 | }); 114 | 115 | return defer.promise; 116 | }; 117 | 118 | ParseGeoPoint.prototype = { 119 | /** 120 | * Returns a JSON representation of the GeoPoint, suitable for Parse. 121 | * @return {Object} 122 | */ 123 | toJSON: function() { 124 | ParseGeoPoint._validate(this.latitude, this.longitude); 125 | return { 126 | "__type": "GeoPoint", 127 | latitude: this.latitude, 128 | longitude: this.longitude 129 | }; 130 | }, 131 | 132 | /** 133 | * Returns the distance from this GeoPoint to another in radians. 134 | * @param {ParseGeoPoint} point the other ParseGeoPoint. 135 | * @return {Number} 136 | */ 137 | radiansTo: function(point) { 138 | var d2r = Math.PI / 180.0; 139 | var lat1rad = this.latitude * d2r; 140 | var long1rad = this.longitude * d2r; 141 | var lat2rad = point.latitude * d2r; 142 | var long2rad = point.longitude * d2r; 143 | var deltaLat = lat1rad - lat2rad; 144 | var deltaLong = long1rad - long2rad; 145 | var sinDeltaLatDiv2 = Math.sin(deltaLat / 2); 146 | var sinDeltaLongDiv2 = Math.sin(deltaLong / 2); 147 | // Square of half the straight line chord distance between both points. 148 | var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) + 149 | (Math.cos(lat1rad) * Math.cos(lat2rad) * 150 | sinDeltaLongDiv2 * sinDeltaLongDiv2)); 151 | a = Math.min(1.0, a); 152 | return 2 * Math.asin(Math.sqrt(a)); 153 | }, 154 | 155 | /** 156 | * Returns the distance from this GeoPoint to another in kilometers. 157 | * @param {ParseGeoPoint} point the other ParseGeoPoint. 158 | * @return {Number} 159 | */ 160 | kilometersTo: function(point) { 161 | return this.radiansTo(point) * 6371.0; 162 | }, 163 | 164 | /** 165 | * Returns the distance from this GeoPoint to another in miles. 166 | * @param {ParseGeoPoint} point the other ParseGeoPoint. 167 | * @return {Number} 168 | */ 169 | milesTo: function(point) { 170 | return this.radiansTo(point) * 3958.8; 171 | } 172 | }; 173 | 174 | return ParseGeoPoint; 175 | 176 | }); -------------------------------------------------------------------------------- /lib/Op.js: -------------------------------------------------------------------------------- 1 | var module = angular.module("ParseAngular.Op", [ 2 | 'ParseAngular.Core' 3 | ]); 4 | 5 | module.factory('ParseOp', function(ParseCore, $injector){ 6 | 7 | var ParseOp; 8 | /** 9 | * @class 10 | * A ParseOp is an atomic operation that can be applied to a field in a 11 | * ParseObject. For example, calling
object.set("foo", "bar")
12 | * is an example of a ParseOp.Set. Calling object.unset("foo")
13 | * is a ParseOp.Unset. These operations are stored in a ParseObject and
14 | * sent to the server as part of object.save()
operations.
15 | * Instances of ParseOp should be immutable.
16 | *
17 | * You should not create subclasses of ParseOp or instantiate ParseOp
18 | * directly.
19 | */
20 |
21 | var isInstanceOfParseObject = function(o) {
22 | var ParseObject = $injector.get('ParseObject');
23 | return o instanceof ParseObject;
24 | };
25 |
26 |
27 | ParseOp = function() {
28 | this._initialize.apply(this, arguments);
29 | };
30 |
31 | ParseOp.prototype = {
32 | _initialize: function() {}
33 | };
34 |
35 | _.extend(ParseOp, {
36 | /**
37 | * To create a new Op, call ParseOp._extend();
38 | */
39 | _extend: ParseCore._extend,
40 |
41 | // A map of __op string to decoder function.
42 | _opDecoderMap: {},
43 |
44 | /**
45 | * Registers a function to convert a json object with an __op field into an
46 | * instance of a subclass of ParseOp.
47 | */
48 | _registerDecoder: function(opName, decoder) {
49 | ParseOp._opDecoderMap[opName] = decoder;
50 | },
51 |
52 | /**
53 | * Converts a json object into an instance of a subclass of ParseOp.
54 | */
55 | _decode: function(json) {
56 | var decoder = ParseOp._opDecoderMap[json.__op];
57 | if (decoder) {
58 | return decoder(json);
59 | } else {
60 | return undefined;
61 | }
62 | }
63 | });
64 |
65 | /*
66 | * Add a handler for Batch ops.
67 | */
68 | ParseOp._registerDecoder("Batch", function(json) {
69 | var op = null;
70 | ParseCore._arrayEach(json.ops, function(nextOp) {
71 | nextOp = ParseOp._decode(nextOp);
72 | op = nextOp._mergeWithPrevious(op);
73 | });
74 | return op;
75 | });
76 |
77 | /**
78 | * @class
79 | * A Set operation indicates that either the field was changed using
80 | * ParseObject.set, or it is a mutable container that was detected as being
81 | * changed.
82 | */
83 | ParseOp.Set = ParseOp._extend(/** @lends ParseOp.Set.prototype */ {
84 | _initialize: function(value) {
85 | this._value = value;
86 | },
87 |
88 | /**
89 | * Returns the new value of this field after the set.
90 | */
91 | value: function() {
92 | return this._value;
93 | },
94 |
95 | /**
96 | * Returns a JSON version of the operation suitable for sending to Parse.
97 | * @return {Object}
98 | */
99 | toJSON: function() {
100 | return ParseCore._encode(this.value());
101 | },
102 |
103 | _mergeWithPrevious: function(previous) {
104 | return this;
105 | },
106 |
107 | _estimate: function(oldValue) {
108 | return this.value();
109 | }
110 | });
111 |
112 | /**
113 | * A sentinel value that is returned by ParseOp.Unset._estimate to
114 | * indicate the field should be deleted. Basically, if you find _UNSET as a
115 | * value in your object, you should remove that key.
116 | */
117 | ParseOp._UNSET = {};
118 |
119 | /**
120 | * @class
121 | * An Unset operation indicates that this field has been deleted from the
122 | * object.
123 | */
124 | ParseOp.Unset = ParseOp._extend(/** @lends ParseOp.Unset.prototype */ {
125 | /**
126 | * Returns a JSON version of the operation suitable for sending to Parse.
127 | * @return {Object}
128 | */
129 | toJSON: function() {
130 | return { __op: "Delete" };
131 | },
132 |
133 | _mergeWithPrevious: function(previous) {
134 | return this;
135 | },
136 |
137 | _estimate: function(oldValue) {
138 | return ParseOp._UNSET;
139 | }
140 | });
141 |
142 | ParseOp._registerDecoder("Delete", function(json) {
143 | return new ParseOp.Unset();
144 | });
145 |
146 | /**
147 | * @class
148 | * An Increment is an atomic operation where the numeric value for the field
149 | * will be increased by a given amount.
150 | */
151 | ParseOp.Increment = ParseOp._extend(
152 | /** @lends ParseOp.Increment.prototype */ {
153 |
154 | _initialize: function(amount) {
155 | this._amount = amount;
156 | },
157 |
158 | /**
159 | * Returns the amount to increment by.
160 | * @return {Number} the amount to increment by.
161 | */
162 | amount: function() {
163 | return this._amount;
164 | },
165 |
166 | /**
167 | * Returns a JSON version of the operation suitable for sending to Parse.
168 | * @return {Object}
169 | */
170 | toJSON: function() {
171 | return { __op: "Increment", amount: this._amount };
172 | },
173 |
174 | _mergeWithPrevious: function(previous) {
175 | if (!previous) {
176 | return this;
177 | } else if (previous instanceof ParseOp.Unset) {
178 | return new ParseOp.Set(this.amount());
179 | } else if (previous instanceof ParseOp.Set) {
180 | return new ParseOp.Set(previous.value() + this.amount());
181 | } else if (previous instanceof ParseOp.Increment) {
182 | return new ParseOp.Increment(this.amount() + previous.amount());
183 | } else {
184 | throw "Op is invalid after previous op.";
185 | }
186 | },
187 |
188 | _estimate: function(oldValue) {
189 | if (!oldValue) {
190 | return this.amount();
191 | }
192 | return oldValue + this.amount();
193 | }
194 | });
195 |
196 | ParseOp._registerDecoder("Increment", function(json) {
197 | return new ParseOp.Increment(json.amount);
198 | });
199 |
200 | /**
201 | * @class
202 | * Add is an atomic operation where the given objects will be appended to the
203 | * array that is stored in this field.
204 | */
205 | ParseOp.Add = ParseOp._extend(/** @lends ParseOp.Add.prototype */ {
206 | _initialize: function(objects) {
207 | this._objects = objects;
208 | },
209 |
210 | /**
211 | * Returns the objects to be added to the array.
212 | * @return {Array} The objects to be added to the array.
213 | */
214 | objects: function() {
215 | return this._objects;
216 | },
217 |
218 | /**
219 | * Returns a JSON version of the operation suitable for sending to Parse.
220 | * @return {Object}
221 | */
222 | toJSON: function() {
223 | return { __op: "Add", objects: ParseCore._encode(this.objects()) };
224 | },
225 |
226 | _mergeWithPrevious: function(previous) {
227 | if (!previous) {
228 | return this;
229 | } else if (previous instanceof ParseOp.Unset) {
230 | return new ParseOp.Set(this.objects());
231 | } else if (previous instanceof ParseOp.Set) {
232 | return new ParseOp.Set(this._estimate(previous.value()));
233 | } else if (previous instanceof ParseOp.Add) {
234 | return new ParseOp.Add(previous.objects().concat(this.objects()));
235 | } else {
236 | throw "Op is invalid after previous op.";
237 | }
238 | },
239 |
240 | _estimate: function(oldValue) {
241 | if (!oldValue) {
242 | return _.clone(this.objects());
243 | } else {
244 | return oldValue.concat(this.objects());
245 | }
246 | }
247 | });
248 |
249 | ParseOp._registerDecoder("Add", function(json) {
250 | return new ParseOp.Add(ParseCore._decode(undefined, json.objects));
251 | });
252 |
253 | /**
254 | * @class
255 | * AddUnique is an atomic operation where the given items will be appended to
256 | * the array that is stored in this field only if they were not already
257 | * present in the array.
258 | */
259 | ParseOp.AddUnique = ParseOp._extend(
260 | /** @lends ParseOp.AddUnique.prototype */ {
261 |
262 | _initialize: function(objects) {
263 | this._objects = _.uniq(objects);
264 | },
265 |
266 | /**
267 | * Returns the objects to be added to the array.
268 | * @return {Array} The objects to be added to the array.
269 | */
270 | objects: function() {
271 | return this._objects;
272 | },
273 |
274 | /**
275 | * Returns a JSON version of the operation suitable for sending to Parse.
276 | * @return {Object}
277 | */
278 | toJSON: function() {
279 | return { __op: "AddUnique", objects: ParseCore._encode(this.objects()) };
280 | },
281 |
282 | _mergeWithPrevious: function(previous) {
283 | if (!previous) {
284 | return this;
285 | } else if (previous instanceof ParseOp.Unset) {
286 | return new ParseOp.Set(this.objects());
287 | } else if (previous instanceof ParseOp.Set) {
288 | return new ParseOp.Set(this._estimate(previous.value()));
289 | } else if (previous instanceof ParseOp.AddUnique) {
290 | return new ParseOp.AddUnique(this._estimate(previous.objects()));
291 | } else {
292 | throw "Op is invalid after previous op.";
293 | }
294 | },
295 |
296 | _estimate: function(oldValue) {
297 | if (!oldValue) {
298 | return _.clone(this.objects());
299 | } else {
300 | // We can't just take the _.uniq(_.union(...)) of oldValue and
301 | // this.objects, because the uniqueness may not apply to oldValue
302 | // (especially if the oldValue was set via .set())
303 | var newValue = _.clone(oldValue);
304 | ParseCore._arrayEach(this.objects(), function(obj) {
305 | if (isInstanceOfParseObject(obj) && obj.id) {
306 | var matchingObj = _.find(newValue, function(anObj) {
307 | return (isInstanceOfParseObject(anObj)) && (anObj.id === obj.id);
308 | });
309 | if (!matchingObj) {
310 | newValue.push(obj);
311 | } else {
312 | var index = _.indexOf(newValue, matchingObj);
313 | newValue[index] = obj;
314 | }
315 | } else if (!_.contains(newValue, obj)) {
316 | newValue.push(obj);
317 | }
318 | });
319 | return newValue;
320 | }
321 | }
322 | });
323 |
324 | ParseOp._registerDecoder("AddUnique", function(json) {
325 | return new ParseOp.AddUnique(ParseCore._decode(undefined, json.objects));
326 | });
327 |
328 | /**
329 | * @class
330 | * Remove is an atomic operation where the given objects will be removed from
331 | * the array that is stored in this field.
332 | */
333 | ParseOp.Remove = ParseOp._extend(/** @lends ParseOp.Remove.prototype */ {
334 | _initialize: function(objects) {
335 | this._objects = _.uniq(objects);
336 | },
337 |
338 | /**
339 | * Returns the objects to be removed from the array.
340 | * @return {Array} The objects to be removed from the array.
341 | */
342 | objects: function() {
343 | return this._objects;
344 | },
345 |
346 | /**
347 | * Returns a JSON version of the operation suitable for sending to Parse.
348 | * @return {Object}
349 | */
350 | toJSON: function() {
351 | return { __op: "Remove", objects: ParseCore._encode(this.objects()) };
352 | },
353 |
354 | _mergeWithPrevious: function(previous) {
355 | if (!previous) {
356 | return this;
357 | } else if (previous instanceof ParseOp.Unset) {
358 | return previous;
359 | } else if (previous instanceof ParseOp.Set) {
360 | return new ParseOp.Set(this._estimate(previous.value()));
361 | } else if (previous instanceof ParseOp.Remove) {
362 | return new ParseOp.Remove(_.union(previous.objects(), this.objects()));
363 | } else {
364 | throw "Op is invalid after previous op.";
365 | }
366 | },
367 |
368 | _estimate: function(oldValue) {
369 | if (!oldValue) {
370 | return [];
371 | } else {
372 | var newValue = _.difference(oldValue, this.objects());
373 | // If there are saved Parse Objects being removed, also remove them.
374 | ParseCore._arrayEach(this.objects(), function(obj) {
375 | if (isInstanceOfParseObject(obj) && obj.id) {
376 | newValue = _.reject(newValue, function(other) {
377 | return (isInstanceOfParseObject(other)) && (other.id === obj.id);
378 | });
379 | }
380 | });
381 | return newValue;
382 | }
383 | }
384 | });
385 |
386 | ParseOp._registerDecoder("Remove", function(json) {
387 | return new ParseOp.Remove(ParseCore._decode(undefined, json.objects));
388 | });
389 |
390 | /**
391 | * @class
392 | * A Relation operation indicates that the field is an instance of
393 | * $injector.get('ParseRelation'), and objects are being added to, or removed from, that
394 | * relation.
395 | */
396 | ParseOp.Relation = ParseOp._extend(
397 | /** @lends ParseOp.Relation.prototype */ {
398 |
399 | _initialize: function(adds, removes) {
400 | this._targetClassName = null;
401 |
402 | var self = this;
403 |
404 | var pointerToId = function(object) {
405 | if (isInstanceOfParseObject(object)) {
406 | if (!object.id) {
407 | throw "You can't add an unsaved ParseObject to a relation.";
408 | }
409 | if (!self._targetClassName) {
410 | self._targetClassName = object.className;
411 | }
412 | if (self._targetClassName !== object.className) {
413 | throw "Tried to create a $injector.get('ParseRelation') with 2 different types: " +
414 | self._targetClassName + " and " + object.className + ".";
415 | }
416 | return object.id;
417 | }
418 | return object;
419 | };
420 |
421 | this.relationsToAdd = _.uniq(_.map(adds, pointerToId));
422 | this.relationsToRemove = _.uniq(_.map(removes, pointerToId));
423 | },
424 |
425 | /**
426 | * Returns an array of unfetched ParseObject that are being added to the
427 | * relation.
428 | * @return {Array}
429 | */
430 | added: function() {
431 | var self = this;
432 | var ParseObject = $injector.get('ParseObject');
433 | return _.map(this.relationsToAdd, function(objectId) {
434 | var object = ParseObject._create(self._targetClassName);
435 | object.id = objectId;
436 | return object;
437 | });
438 | },
439 |
440 | /**
441 | * Returns an array of unfetched ParseObject that are being removed from
442 | * the relation.
443 | * @return {Array}
444 | */
445 | removed: function() {
446 | var self = this;
447 | var ParseObject = $injector.get('ParseObject');
448 |
449 | return _.map(this.relationsToRemove, function(objectId) {
450 | var object = ParseObject._create(self._targetClassName);
451 | object.id = objectId;
452 | return object;
453 | });
454 | },
455 |
456 | /**
457 | * Returns a JSON version of the operation suitable for sending to Parse.
458 | * @return {Object}
459 | */
460 | toJSON: function() {
461 | var adds = null;
462 | var removes = null;
463 | var self = this;
464 | var idToPointer = function(id) {
465 | return { __type: 'Pointer',
466 | className: self._targetClassName,
467 | objectId: id };
468 | };
469 | var pointers = null;
470 | if (this.relationsToAdd.length > 0) {
471 | pointers = _.map(this.relationsToAdd, idToPointer);
472 | adds = { "__op": "AddRelation", "objects": pointers };
473 | }
474 |
475 | if (this.relationsToRemove.length > 0) {
476 | pointers = _.map(this.relationsToRemove, idToPointer);
477 | removes = { "__op": "RemoveRelation", "objects": pointers };
478 | }
479 |
480 | if (adds && removes) {
481 | return { "__op": "Batch", "ops": [adds, removes]};
482 | }
483 |
484 | return adds || removes || {};
485 | },
486 |
487 | _mergeWithPrevious: function(previous) {
488 | if (!previous) {
489 | return this;
490 | } else if (previous instanceof ParseOp.Unset) {
491 | throw "You can't modify a relation after deleting it.";
492 | } else if (previous instanceof ParseOp.Relation) {
493 | if (previous._targetClassName &&
494 | previous._targetClassName !== this._targetClassName) {
495 | throw "Related object must be of class " + previous._targetClassName +
496 | ", but " + this._targetClassName + " was passed in.";
497 | }
498 | var newAdd = _.union(_.difference(previous.relationsToAdd,
499 | this.relationsToRemove),
500 | this.relationsToAdd);
501 | var newRemove = _.union(_.difference(previous.relationsToRemove,
502 | this.relationsToAdd),
503 | this.relationsToRemove);
504 |
505 | var newRelation = new ParseOp.Relation(newAdd, newRemove);
506 | newRelation._targetClassName = this._targetClassName;
507 | return newRelation;
508 | } else {
509 | throw "Op is invalid after previous op.";
510 | }
511 | },
512 |
513 | _estimate: function(oldValue, object, key) {
514 | var ParseRelation = $injector.get('ParseRelation');
515 | if (!oldValue) {
516 | var relation = new (ParseRelation)(object, key);
517 | relation.targetClassName = this._targetClassName;
518 | } else if (oldValue instanceof ParseRelation) {
519 | if (this._targetClassName) {
520 | if (oldValue.targetClassName) {
521 | if (oldValue.targetClassName !== this._targetClassName) {
522 | throw "Related object must be a " + oldValue.targetClassName +
523 | ", but a " + this._targetClassName + " was passed in.";
524 | }
525 | } else {
526 | oldValue.targetClassName = this._targetClassName;
527 | }
528 | }
529 | return oldValue;
530 | } else {
531 | throw "Op is invalid after previous op.";
532 | }
533 | }
534 | });
535 |
536 | ParseOp._registerDecoder("AddRelation", function(json) {
537 | return new ParseOp.Relation(ParseCore._decode(undefined, json.objects), []);
538 | });
539 | ParseOp._registerDecoder("RemoveRelation", function(json) {
540 | return new ParseOp.Relation([], ParseCore._decode(undefined, json.objects));
541 | });
542 |
543 | return ParseOp;
544 |
545 | });
546 |
547 |
--------------------------------------------------------------------------------
/lib/Query.js:
--------------------------------------------------------------------------------
1 | var module = angular.module('ParseAngular.Query', [
2 | 'ParseAngular.Object',
3 | 'ParseAngular.Core',
4 | 'ParseAngular.GeoPoint',
5 | 'ParseAngular.Error'
6 | ]);
7 | module.factory('ParseQuery', function(ParseObject, ParseCore, ParseGeoPoint, ParseError, $q, $injector){
8 |
9 | var ParseQuery;
10 |
11 | /**
12 | * Creates a new parse ParseQuery for the given ParseObject subclass.
13 | * @param objectClass -
14 | * An instance of a subclass of ParseObject, or a Parse className string.
15 | * @class
16 | *
17 | * ParseQuery defines a query that is used to fetch ParseObjects. The
18 | * most common use case is finding all objects that match a query through the
19 | * find
method. For example, this sample code fetches all objects
20 | * of class MyClass
. It calls a different function depending on
21 | * whether the fetch succeeded or not.
22 | *
23 | *
24 | * var query = new ParseQuery(MyClass); 25 | * query.find({ 26 | * success: function(results) { 27 | * // results is an array of ParseObject. 28 | * }, 29 | * 30 | * error: function(error) { 31 | * // error is an instance of ParseError. 32 | * } 33 | * });34 | * 35 | *
A ParseQuery can also be used to retrieve a single object whose id is
36 | * known, through the get method. For example, this sample code fetches an
37 | * object of class MyClass
and id myId
. It calls a
38 | * different function depending on whether the fetch succeeded or not.
39 | *
40 | *
41 | * var query = new ParseQuery(MyClass); 42 | * query.get(myId, { 43 | * success: function(object) { 44 | * // object is an instance of ParseObject. 45 | * }, 46 | * 47 | * error: function(object, error) { 48 | * // error is an instance of ParseError. 49 | * } 50 | * });51 | * 52 | *
A ParseQuery can also be used to count the number of objects that match
53 | * the query without retrieving all of those objects. For example, this
54 | * sample code counts the number of objects of the class MyClass
55 | *
56 | * var query = new ParseQuery(MyClass); 57 | * query.count({ 58 | * success: function(number) { 59 | * // There are number instances of MyClass. 60 | * }, 61 | * 62 | * error: function(error) { 63 | * // error is an instance of ParseError. 64 | * } 65 | * });66 | */ 67 | ParseQuery = function(objectClass) { 68 | if (_.isString(objectClass)) { 69 | objectClass = ParseObject._getSubclass(objectClass); 70 | } 71 | 72 | this.objectClass = objectClass; 73 | 74 | this.className = objectClass.prototype.className; 75 | 76 | this._where = {}; 77 | this._include = []; 78 | this._limit = -1; // negative limit means, do not send a limit 79 | this._skip = 0; 80 | this._extraOptions = {}; 81 | }; 82 | 83 | /** 84 | * Constructs a ParseQuery that is the OR of the passed in queries. For 85 | * example: 86 | *
var compoundQuery = ParseQuery.or(query1, query2, query3);87 | * 88 | * will create a compoundQuery that is an or of the query1, query2, and 89 | * query3. 90 | * @param {...ParseQuery} var_args The list of queries to OR. 91 | * @return {ParseQuery} The query that is the OR of the passed in queries. 92 | */ 93 | ParseQuery.or = function() { 94 | var queries = _.toArray(arguments); 95 | var className = null; 96 | ParseCore._arrayEach(queries, function(q) { 97 | if (_.isNull(className)) { 98 | className = q.className; 99 | } 100 | 101 | if (className !== q.className) { 102 | throw "All queries must be for the same class"; 103 | } 104 | }); 105 | var query = new ParseQuery(className); 106 | query._orQuery(queries); 107 | return query; 108 | }; 109 | 110 | ParseQuery.prototype = { 111 | /** 112 | * Constructs a ParseObject whose id is already known by fetching data from 113 | * the server. Either options.success or options.error is called when the 114 | * find completes. 115 | * 116 | * @param {String} objectId The id of the object to be fetched. 117 | * @param {Object} options A Backbone-style options object. 118 | * Valid options are:
ParseObject
307 | * with which to start this Collection.
308 | * @param {Object} options An optional object with Backbone-style options.
309 | * Valid options are:20 | * A class that is used to access all of the children of a many-to-many 21 | * relationship. Each instance of ParseRelation is associated with a 22 | * particular parent object and key. 23 | *
24 | */ 25 | ParseRelation = function(parent, key) { 26 | this.parent = parent; 27 | this.key = key; 28 | this.targetClassName = null; 29 | }; 30 | 31 | ParseRelation.prototype = { 32 | /** 33 | * Makes sure that this relation has the right parent and key. 34 | */ 35 | _ensureParentAndKey: function(parent, key) { 36 | this.parent = this.parent || parent; 37 | this.key = this.key || key; 38 | if (this.parent !== parent) { 39 | throw "Internal Error. Relation retrieved from two different Objects."; 40 | } 41 | if (this.key !== key) { 42 | throw "Internal Error. Relation retrieved from two different keys."; 43 | } 44 | }, 45 | 46 | /** 47 | * Adds a ParseObject or an array of ParseObjects to the relation. 48 | * @param {} objects The item or items to add. 49 | */ 50 | add: function(objects) { 51 | if (!_.isArray(objects)) { 52 | objects = [objects]; 53 | } 54 | 55 | var change = new ParseOp.Relation(objects, []); 56 | this.parent.set(this.key, change); 57 | this.targetClassName = change._targetClassName; 58 | }, 59 | 60 | /** 61 | * Removes a ParseObject or an array of ParseObjects from this relation. 62 | * @param {} objects The item or items to remove. 63 | */ 64 | remove: function(objects) { 65 | if (!_.isArray(objects)) { 66 | objects = [objects]; 67 | } 68 | 69 | var change = new ParseOp.Relation([], objects); 70 | this.parent.set(this.key, change); 71 | this.targetClassName = change._targetClassName; 72 | }, 73 | 74 | /** 75 | * Returns a JSON version of the object suitable for saving to disk. 76 | * @return {Object} 77 | */ 78 | toJSON: function() { 79 | return { "__type": "Relation", "className": this.targetClassName }; 80 | }, 81 | 82 | /** 83 | * Returns a ParseQuery that is limited to objects in this 84 | * relation. 85 | * @return {ParseQuery} 86 | */ 87 | query: function() { 88 | var targetClass; 89 | var query; 90 | if (!this.targetClassName) { 91 | targetClass = ParseObject._getSubclass(this.parent.className); 92 | query = new ParseQuery(targetClass); 93 | query._extraOptions.redirectClassNameForKey = this.key; 94 | } else { 95 | targetClass = ParseObject._getSubclass(this.targetClassName); 96 | query = new ParseQuery(targetClass); 97 | } 98 | query._addCondition("$relatedTo", "object", this.parent._toPointer()); 99 | query._addCondition("$relatedTo", "key", this.key); 100 | 101 | return query; 102 | } 103 | }; 104 | 105 | return ParseRelation; 106 | 107 | }); -------------------------------------------------------------------------------- /lib/Role.js: -------------------------------------------------------------------------------- 1 | var module = angular.module('ParseAngular.Role', [ 2 | 'ParseAngular.Object', 3 | 'ParseAngular.Error', 4 | 'ParseAngular.ACL' 5 | ]); 6 | 7 | module.factory('ParseRole', function(ParseObject, ParseError, $injector){ 8 | 9 | /** 10 | * Represents a Role on the Parse server. Roles represent groupings of 11 | * Users for the purposes of granting permissions (e.g. specifying an ACL 12 | * for an Object). Roles are specified by their sets of child users and 13 | * child roles, all of which are granted any permissions that the parent 14 | * role has. 15 | * 16 | *Roles must have a name (which cannot be changed after creation of the 17 | * role), and must specify an ACL.
18 | * @class 19 | * A ParseRole is a local representation of a role persisted to the Parse 20 | * cloud. 21 | */ 22 | ParseRole = ParseObject.extend("_Role", /** @lends ParseRole.prototype */ { 23 | // Instance Methods 24 | 25 | /** 26 | * Constructs a new ParseRole with the given name and ACL. 27 | * 28 | * @param {String} name The name of the Role to create. 29 | * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. 30 | */ 31 | constructor: function(name, acl) { 32 | var ParseACL = $injector.get('ParseACL'); 33 | if (_.isString(name) && (acl instanceof ParseACL)) { 34 | ParseObject.prototype.constructor.call(this, null, null); 35 | this.setName(name); 36 | this.setACL(acl); 37 | } else { 38 | ParseObject.prototype.constructor.call(this, name, acl); 39 | } 40 | }, 41 | 42 | /** 43 | * Gets the name of the role. You can alternatively call role.get("name") 44 | * 45 | * @return {String} the name of the role. 46 | */ 47 | getName: function() { 48 | return this.get("name"); 49 | }, 50 | 51 | /** 52 | * Sets the name for a role. This value must be set before the role has 53 | * been saved to the server, and cannot be set once the role has been 54 | * saved. 55 | * 56 | *57 | * A role's name can only contain alphanumeric characters, _, -, and 58 | * spaces. 59 | *
60 | * 61 | *This is equivalent to calling role.set("name", name)
62 | * 63 | * @param {String} name The name of the role. 64 | * @param {Object} options Standard options object with success and error 65 | * callbacks. 66 | */ 67 | setName: function(name, options) { 68 | return this.set("name", name, options); 69 | }, 70 | 71 | /** 72 | * Gets the Parse.Relation for the Parse.Users that are direct 73 | * children of this role. These users are granted any privileges that this 74 | * role has been granted (e.g. read or write access through ACLs). You can 75 | * add or remove users from the role through this relation. 76 | * 77 | *This is equivalent to calling role.relation("users")
78 | * 79 | * @return {Parse.Relation} the relation for the users belonging to this 80 | * role. 81 | */ 82 | getUsers: function() { 83 | return this.relation("users"); 84 | }, 85 | 86 | /** 87 | * Gets the Parse.Relation for the ParseRoles that are direct 88 | * children of this role. These roles' users are granted any privileges that 89 | * this role has been granted (e.g. read or write access through ACLs). You 90 | * can add or remove child roles from this role through this relation. 91 | * 92 | *This is equivalent to calling role.relation("roles")
93 | * 94 | * @return {Parse.Relation} the relation for the roles belonging to this 95 | * role. 96 | */ 97 | getRoles: function() { 98 | return this.relation("roles"); 99 | }, 100 | 101 | /** 102 | * @ignore 103 | */ 104 | validate: function(attrs, options) { 105 | if ("name" in attrs && attrs.name !== this.getName()) { 106 | var newName = attrs.name; 107 | if (this.id && this.id !== attrs.objectId) { 108 | // Check to see if the objectId being set matches this.id. 109 | // This happens during a fetch -- the id is set before calling fetch. 110 | // Let the name be set in this case. 111 | return new ParseError(ParseError.OTHER_CAUSE, 112 | "A role's name can only be set before it has been saved."); 113 | } 114 | if (!_.isString(newName)) { 115 | return new ParseError(ParseError.OTHER_CAUSE, 116 | "A role's name must be a String."); 117 | } 118 | if (!(/^[0-9a-zA-Z\-_ ]+$/).test(newName)) { 119 | return new ParseError(ParseError.OTHER_CAUSE, 120 | "A role's name can only contain alphanumeric characters, _," + 121 | " -, and spaces."); 122 | } 123 | } 124 | if (ParseObject.prototype.validate) { 125 | return ParseObject.prototype.validate.call(this, attrs, options); 126 | } 127 | return false; 128 | } 129 | }); 130 | 131 | return ParseRole; 132 | }); -------------------------------------------------------------------------------- /lib/User.js: -------------------------------------------------------------------------------- 1 | var module = angular.module('ParseAngular.User', [ 2 | 'ParseAngular.Object', 3 | 'ParseAngular.Core' 4 | ]); 5 | 6 | module.factory('ParseUser', function(ParseObject, ParseCore){ 7 | 8 | ParseUser = ParseObject.extend("_User", /** @lends ParseUser.prototype */ { 9 | // Instance Variables 10 | _isCurrentUser: false, 11 | 12 | 13 | // Instance Methods 14 | 15 | /** 16 | * Merges another object's attributes into this object. 17 | */ 18 | _mergeFromObject: function(other) { 19 | if (other.getSessionToken()) { 20 | this._sessionToken = other.getSessionToken(); 21 | } 22 | ParseUser.__super__._mergeFromObject.call(this, other); 23 | }, 24 | 25 | /** 26 | * Internal method to handle special fields in a _User response. 27 | */ 28 | _mergeMagicFields: function(attrs) { 29 | if (attrs.sessionToken) { 30 | this._sessionToken = attrs.sessionToken; 31 | delete attrs.sessionToken; 32 | } 33 | ParseUser.__super__._mergeMagicFields.call(this, attrs); 34 | }, 35 | 36 | /** 37 | * Removes null values from authData (which exist temporarily for 38 | * unlinking) 39 | */ 40 | _cleanupAuthData: function() { 41 | if (!this.isCurrent()) { 42 | return; 43 | } 44 | var authData = this.get('authData'); 45 | if (!authData) { 46 | return; 47 | } 48 | ParseCore._objectEach(this.get('authData'), function(value, key) { 49 | if (!authData[key]) { 50 | delete authData[key]; 51 | } 52 | }); 53 | }, 54 | 55 | /** 56 | * Synchronizes authData for all providers. 57 | */ 58 | _synchronizeAllAuthData: function() { 59 | var authData = this.get('authData'); 60 | if (!authData) { 61 | return; 62 | } 63 | 64 | var self = this; 65 | ParseCore._objectEach(this.get('authData'), function(value, key) { 66 | self._synchronizeAuthData(key); 67 | }); 68 | }, 69 | 70 | /** 71 | * Synchronizes auth data for a provider (e.g. puts the access token in the 72 | * right place to be used by the Facebook SDK). 73 | */ 74 | _synchronizeAuthData: function(provider) { 75 | if (!this.isCurrent()) { 76 | return; 77 | } 78 | var authType; 79 | if (_.isString(provider)) { 80 | authType = provider; 81 | provider = ParseUser._authProviders[authType]; 82 | } else { 83 | authType = provider.getAuthType(); 84 | } 85 | var authData = this.get('authData'); 86 | if (!authData || !provider) { 87 | return; 88 | } 89 | var success = provider.restoreAuthentication(authData[authType]); 90 | if (!success) { 91 | this._unlinkFrom(provider); 92 | } 93 | }, 94 | 95 | _handleSaveResult: function(makeCurrent) { 96 | // Clean up and synchronize the authData object, removing any unset values 97 | if (makeCurrent) { 98 | this._isCurrentUser = true; 99 | } 100 | this._cleanupAuthData(); 101 | this._synchronizeAllAuthData(); 102 | // Don't keep the password around. 103 | delete this._serverData.password; 104 | this._rebuildEstimatedDataForKey("password"); 105 | this._refreshCache(); 106 | if (makeCurrent || this.isCurrent()) { 107 | ParseUser._saveCurrentUser(this); 108 | } 109 | }, 110 | 111 | /** 112 | * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can 113 | * call linkWith on the user (even if it doesn't exist yet on the server). 114 | */ 115 | _linkWith: function(provider, options) { 116 | var authType; 117 | if (_.isString(provider)) { 118 | authType = provider; 119 | provider = ParseUser._authProviders[provider]; 120 | } else { 121 | authType = provider.getAuthType(); 122 | } 123 | if (_.has(options, 'authData')) { 124 | var authData = this.get('authData') || {}; 125 | authData[authType] = options.authData; 126 | this.set('authData', authData); 127 | 128 | // Overridden so that the user can be made the current user. 129 | var newOptions = _.clone(options) || {}; 130 | newOptions.success = function(model) { 131 | model._handleSaveResult(true); 132 | if (options.success) { 133 | options.success.apply(this, arguments); 134 | } 135 | }; 136 | return this.save({'authData': authData}, newOptions); 137 | } else { 138 | var self = this; 139 | var defer = $q.defer(); 140 | var promise = defer.promise; 141 | provider.authenticate({ 142 | success: function(provider, result) { 143 | self._linkWith(provider, { 144 | authData: result, 145 | success: options.success, 146 | error: options.error 147 | }).then(function() { 148 | promise.resolve(self); 149 | }); 150 | }, 151 | error: function(provider, error) { 152 | if (options.error) { 153 | options.error(self, error); 154 | } 155 | promise.reject(error); 156 | } 157 | }); 158 | return promise; 159 | } 160 | }, 161 | 162 | /** 163 | * Unlinks a user from a service. 164 | */ 165 | _unlinkFrom: function(provider, options) { 166 | var authType; 167 | if (_.isString(provider)) { 168 | authType = provider; 169 | provider = ParseUser._authProviders[provider]; 170 | } else { 171 | authType = provider.getAuthType(); 172 | } 173 | var newOptions = _.clone(options); 174 | var self = this; 175 | newOptions.authData = null; 176 | newOptions.success = function(model) { 177 | self._synchronizeAuthData(provider); 178 | if (options.success) { 179 | options.success.apply(this, arguments); 180 | } 181 | }; 182 | return this._linkWith(provider, newOptions); 183 | }, 184 | 185 | /** 186 | * Checks whether a user is linked to a service. 187 | */ 188 | _isLinked: function(provider) { 189 | var authType; 190 | if (_.isString(provider)) { 191 | authType = provider; 192 | } else { 193 | authType = provider.getAuthType(); 194 | } 195 | var authData = this.get('authData') || {}; 196 | return !!authData[authType]; 197 | }, 198 | 199 | /** 200 | * Deauthenticates all providers. 201 | */ 202 | _logOutWithAll: function() { 203 | var authData = this.get('authData'); 204 | if (!authData) { 205 | return; 206 | } 207 | var self = this; 208 | ParseCore._objectEach(this.get('authData'), function(value, key) { 209 | self._logOutWith(key); 210 | }); 211 | }, 212 | 213 | /** 214 | * Deauthenticates a single provider (e.g. removing access tokens from the 215 | * Facebook SDK). 216 | */ 217 | _logOutWith: function(provider) { 218 | if (!this.isCurrent()) { 219 | return; 220 | } 221 | if (_.isString(provider)) { 222 | provider = ParseUser._authProviders[provider]; 223 | } 224 | if (provider && provider.deauthenticate) { 225 | provider.deauthenticate(); 226 | } 227 | }, 228 | 229 | /** 230 | * Signs up a new user. You should call this instead of save for 231 | * new ParseUsers. This will create a new ParseUser on the server, and 232 | * also persist the session on disk so that you can access the user using 233 | *current
.
234 | *
235 | * A username and password must be set before calling signUp.
236 | * 237 | *Calls options.success or options.error on completion.
238 | * 239 | * @param {Object} attrs Extra fields to set on the new user, or null. 240 | * @param {Object} options A Backbone-style options object. 241 | * @return {Parse.Promise} A promise that is fulfilled when the signup 242 | * finishes. 243 | * @see ParseUser.signUp 244 | */ 245 | signUp: function(attrs, options) { 246 | var error; 247 | options = options || {}; 248 | 249 | var username = (attrs && attrs.username) || this.get("username"); 250 | if (!username || (username === "")) { 251 | error = new ParseError( 252 | ParseError.OTHER_CAUSE, 253 | "Cannot sign up user with an empty name."); 254 | return $q.reject(error); 255 | } 256 | 257 | var password = (attrs && attrs.password) || this.get("password"); 258 | if (!password || (password === "")) { 259 | error = new ParseError( 260 | ParseError.OTHER_CAUSE, 261 | "Cannot sign up user with an empty password."); 262 | if (options && options.error) { 263 | options.error(this, error); 264 | } 265 | return $q.reject(error); 266 | } 267 | 268 | return this.save(attrs) 269 | .then(function(model){ 270 | model._handleSaveResult(true); 271 | return model; 272 | }) 273 | }, 274 | 275 | /** 276 | * Logs in a ParseUser. On success, this saves the session to localStorage, 277 | * so you can retrieve the currently logged in user using 278 | *current
.
279 | *
280 | * A username and password must be set before calling logIn.
281 | * 282 | *Calls options.success or options.error on completion.
283 | * 284 | * @param {Object} options A Backbone-style options object. 285 | * @see ParseUser.logIn 286 | * @return {Parse.Promise} A promise that is fulfilled with the user when 287 | * the login is complete. 288 | */ 289 | logIn: function(options) { 290 | var model = this; 291 | options = options || {}; 292 | var request = ParseCore._request({ 293 | route: "login", 294 | method: "GET", 295 | useMasterKey: options.useMasterKey, 296 | data: this.toJSON() 297 | }); 298 | return request.then(function(resp, status, xhr) { 299 | var serverAttrs = model.parse(resp, status, xhr); 300 | model._finishFetch(serverAttrs); 301 | model._handleSaveResult(true); 302 | return model; 303 | }) 304 | // ._thenRunCallbacks(options, this); 305 | }, 306 | 307 | /** 308 | * @see ParseObject#save 309 | */ 310 | save: function(arg1, arg2, arg3) { 311 | var i, attrs, current, options, saved; 312 | if (_.isObject(arg1) || _.isNull(arg1) || _.isUndefined(arg1)) { 313 | attrs = arg1; 314 | options = arg2; 315 | } else { 316 | attrs = {}; 317 | attrs[arg1] = arg2; 318 | options = arg3; 319 | } 320 | options = options || {}; 321 | 322 | return ParseObject.prototype.save.call(this, attrs, newOptions) 323 | .then(function(model){ 324 | model._handleSaveResult(false); 325 | return model; 326 | }); 327 | }, 328 | 329 | /** 330 | * @see ParseObject#fetch 331 | */ 332 | fetch: function(options) { 333 | return ParseObject.prototype.fetch.call(this, newOptions) 334 | .then(function(model){ 335 | model._handleSaveResult(false); 336 | return model; 337 | }); 338 | }, 339 | 340 | /** 341 | * Returns true ifcurrent
would return this user.
342 | * @see ParseUser#current
343 | */
344 | isCurrent: function() {
345 | return this._isCurrentUser;
346 | },
347 |
348 | /**
349 | * Returns get("username").
350 | * @return {String}
351 | * @see ParseObject#get
352 | */
353 | getUsername: function() {
354 | return this.get("username");
355 | },
356 |
357 | /**
358 | * Calls set("username", username, options) and returns the result.
359 | * @param {String} username
360 | * @param {Object} options A Backbone-style options object.
361 | * @return {Boolean}
362 | * @see ParseObject.set
363 | */
364 | setUsername: function(username, options) {
365 | return this.set("username", username, options);
366 | },
367 |
368 | /**
369 | * Calls set("password", password, options) and returns the result.
370 | * @param {String} password
371 | * @param {Object} options A Backbone-style options object.
372 | * @return {Boolean}
373 | * @see ParseObject.set
374 | */
375 | setPassword: function(password, options) {
376 | return this.set("password", password, options);
377 | },
378 |
379 | /**
380 | * Returns get("email").
381 | * @return {String}
382 | * @see ParseObject#get
383 | */
384 | getEmail: function() {
385 | return this.get("email");
386 | },
387 |
388 | /**
389 | * Calls set("email", email, options) and returns the result.
390 | * @param {String} email
391 | * @param {Object} options A Backbone-style options object.
392 | * @return {Boolean}
393 | * @see ParseObject.set
394 | */
395 | setEmail: function(email, options) {
396 | return this.set("email", email, options);
397 | },
398 |
399 | /**
400 | * Checks whether this user is the current user and has been authenticated.
401 | * @return (Boolean) whether this user is the current user and is logged in.
402 | */
403 | authenticated: function() {
404 | return !!this._sessionToken &&
405 | (ParseUser.current() && ParseUser.current().id === this.id);
406 | },
407 |
408 | /**
409 | * Returns the session token for this user, if the user has been logged in,
410 | * or if it is the result of a query with the master key. Otherwise, returns
411 | * undefined.
412 | * @return {String} the session token, or undefined
413 | */
414 | getSessionToken: function() {
415 | return this._sessionToken;
416 | }
417 |
418 | }, /** @lends ParseUser */ {
419 | // Class Variables
420 |
421 | // The currently logged-in user.
422 | _currentUser: null,
423 |
424 | // Whether currentUser is known to match the serialized version on disk.
425 | // This is useful for saving a localstorage check if you try to load
426 | // _currentUser frequently while there is none stored.
427 | _currentUserMatchesDisk: false,
428 |
429 | // The localStorage key suffix that the current user is stored under.
430 | _CURRENT_USER_KEY: "currentUser",
431 |
432 | // The mapping of auth provider names to actual providers
433 | _authProviders: {},
434 |
435 | // Whether to rewrite className User to _User
436 | _performUserRewrite: true,
437 |
438 |
439 | // Class Methods
440 |
441 | /**
442 | * Signs up a new user with a username (or email) and password.
443 | * This will create a new ParseUser on the server, and also persist the
444 | * session in localStorage so that you can access the user using
445 | * {@link #current}.
446 | *
447 | * Calls options.success or options.error on completion.
448 | * 449 | * @param {String} username The username (or email) to sign up with. 450 | * @param {String} password The password to sign up with. 451 | * @param {Object} attrs Extra fields to set on the new user. 452 | * @param {Object} options A Backbone-style options object. 453 | * @return {Parse.Promise} A promise that is fulfilled with the user when 454 | * the signup completes. 455 | * @see ParseUser#signUp 456 | */ 457 | signUp: function(username, password, attrs, options) { 458 | attrs = attrs || {}; 459 | attrs.username = username; 460 | attrs.password = password; 461 | var user = ParseObject._create("_User"); 462 | return user.signUp(attrs, options); 463 | }, 464 | 465 | /** 466 | * Logs in a user with a username (or email) and password. On success, this 467 | * saves the session to disk, so you can retrieve the currently logged in 468 | * user usingcurrent
.
469 | *
470 | * Calls options.success or options.error on completion.
471 | * 472 | * @param {String} username The username (or email) to log in with. 473 | * @param {String} password The password to log in with. 474 | * @param {Object} options A Backbone-style options object. 475 | * @return {Parse.Promise} A promise that is fulfilled with the user when 476 | * the login completes. 477 | * @see ParseUser#logIn 478 | */ 479 | logIn: function(username, password, options) { 480 | var user = ParseObject._create("_User"); 481 | user._finishFetch({ username: username, password: password }); 482 | return user.logIn(options); 483 | }, 484 | 485 | /** 486 | * Logs in a user with a session token. On success, this saves the session 487 | * to disk, so you can retrieve the currently logged in user using 488 | *current
.
489 | *
490 | * Calls options.success or options.error on completion.
491 | * 492 | * @param {String} sessionToken The sessionToken to log in with. 493 | * @param {Object} options A Backbone-style options object. 494 | * @return {Parse.Promise} A promise that is fulfilled with the user when 495 | * the login completes. 496 | */ 497 | become: function(sessionToken, options) { 498 | options = options || {}; 499 | 500 | var user = ParseObject._create("_User"); 501 | return ParseCore._request({ 502 | route: "users", 503 | className: "me", 504 | method: "GET", 505 | useMasterKey: options.useMasterKey, 506 | sessionToken: sessionToken 507 | }).then(function(resp, status, xhr) { 508 | var serverAttrs = user.parse(resp, status, xhr); 509 | user._finishFetch(serverAttrs); 510 | user._handleSaveResult(true); 511 | return user; 512 | }) 513 | // ._thenRunCallbacks(options, user); 514 | }, 515 | 516 | /** 517 | * Logs out the currently logged in user session. This will remove the 518 | * session from disk, log out of linked services, and future calls to 519 | *current
will return null
.
520 | */
521 | logOut: function() {
522 | if (ParseUser._currentUser !== null) {
523 | ParseUser._currentUser._logOutWithAll();
524 | ParseUser._currentUser._isCurrentUser = false;
525 | }
526 | ParseUser._currentUserMatchesDisk = true;
527 | ParseUser._currentUser = null;
528 | ParseCore.localStorage.removeItem(
529 | ParseCore._getParsePath(ParseUser._CURRENT_USER_KEY));
530 | },
531 |
532 | /**
533 | * Requests a password reset email to be sent to the specified email address
534 | * associated with the user account. This email allows the user to securely
535 | * reset their password on the Parse site.
536 | *
537 | * Calls options.success or options.error on completion.
538 | * 539 | * @param {String} email The email address associated with the user that 540 | * forgot their password. 541 | * @param {Object} options A Backbone-style options object. 542 | */ 543 | requestPasswordReset: function(email, options) { 544 | options = options || {}; 545 | var request = ParseCore._request({ 546 | route: "requestPasswordReset", 547 | method: "POST", 548 | useMasterKey: options.useMasterKey, 549 | data: { email: email } 550 | }); 551 | return request 552 | // ._thenRunCallbacks(options); 553 | }, 554 | 555 | /** 556 | * Retrieves the currently logged in ParseUser with a valid session, 557 | * either from memory or localStorage, if necessary. 558 | * @return {ParseObject} The currently logged in ParseUser. 559 | */ 560 | current: function() { 561 | if (ParseUser._currentUser) { 562 | return ParseUser._currentUser; 563 | } 564 | 565 | if (ParseUser._currentUserMatchesDisk) { 566 | return ParseUser._currentUser; 567 | } 568 | 569 | // Load the user from local storage. 570 | ParseUser._currentUserMatchesDisk = true; 571 | 572 | var userData = ParseCore.localStorage.getItem(ParseCore._getParsePath( 573 | ParseUser._CURRENT_USER_KEY) 574 | ); 575 | if (!userData) { 576 | return null; 577 | } 578 | ParseUser._currentUser = ParseObject._create("_User"); 579 | ParseUser._currentUser._isCurrentUser = true; 580 | 581 | var json = JSON.parse(userData); 582 | ParseUser._currentUser.id = json._id; 583 | delete json._id; 584 | ParseUser._currentUser._sessionToken = json._sessionToken; 585 | delete json._sessionToken; 586 | ParseUser._currentUser._finishFetch(json); 587 | 588 | ParseUser._currentUser._synchronizeAllAuthData(); 589 | ParseUser._currentUser._refreshCache(); 590 | ParseUser._currentUser._opSetQueue = [{}]; 591 | return ParseUser._currentUser; 592 | }, 593 | 594 | /** 595 | * Allow someone to define a custom User class without className 596 | * being rewritten to _User. The default behavior is to rewrite 597 | * User to _User for legacy reasons. This allows developers to 598 | * override that behavior. 599 | * 600 | * @param {Boolean} isAllowed Whether or not to allow custom User class 601 | */ 602 | allowCustomUserClass: function(isAllowed) { 603 | this._performUserRewrite = !isAllowed; 604 | }, 605 | 606 | /** 607 | * Persists a user as currentUser to localStorage, and into the singleton. 608 | */ 609 | _saveCurrentUser: function(user) { 610 | if (ParseUser._currentUser !== user) { 611 | ParseUser.logOut(); 612 | } 613 | user._isCurrentUser = true; 614 | ParseUser._currentUser = user; 615 | ParseUser._currentUserMatchesDisk = true; 616 | 617 | var json = user.toJSON(); 618 | json._id = user.id; 619 | json._sessionToken = user._sessionToken; 620 | ParseCore.localStorage.setItem( 621 | ParseCore._getParsePath(ParseUser._CURRENT_USER_KEY), 622 | JSON.stringify(json)); 623 | }, 624 | 625 | _registerAuthenticationProvider: function(provider) { 626 | ParseUser._authProviders[provider.getAuthType()] = provider; 627 | // Synchronize the current user with the auth provider. 628 | if (ParseUser.current()) { 629 | ParseUser.current()._synchronizeAuthData(provider.getAuthType()); 630 | } 631 | }, 632 | 633 | _logInWith: function(provider, options) { 634 | var user = ParseObject._create("_User"); 635 | return user._linkWith(provider, options); 636 | } 637 | 638 | }); 639 | 640 | return ParseUser; 641 | 642 | }); -------------------------------------------------------------------------------- /lib/_ParseAngular.js: -------------------------------------------------------------------------------- 1 | var module = angular.module('Parse', [ 2 | 'ParseAngular.ACL', 3 | 'ParseAngular.Cloud', 4 | 'ParseAngular.Config', 5 | 'ParseAngular.Core', 6 | // 'ParseAngular.Error', 7 | // 'ParseAngular.Events', 8 | 'ParseAngular.FacebookUtils', 9 | 'ParseAngular.GeoPoint', 10 | 'ParseAngular.Object', 11 | // 'ParseAngular.Op', 12 | 'ParseAngular.Query', 13 | 'ParseAngular.Relation', 14 | 'ParseAngular.User' 15 | ]); 16 | 17 | 18 | module.factory('ParseSDK', function( 19 | ParseACL, 20 | ParseCloud, 21 | ParseConfig, 22 | ParseCore, 23 | ParseFacebookUtils, 24 | ParseGeoPoint, 25 | ParseObject, 26 | ParseQuery, 27 | ParseRelation, 28 | ParseUser 29 | ){ 30 | 31 | var ParseSDK = ParseCore; 32 | 33 | var toExtend = { 34 | ACL: ParseACL, 35 | Cloud: ParseCloud, 36 | Config: ParseConfig, 37 | FacebookUtils: ParseFacebookUtils, 38 | GeoPoint: ParseGeoPoint, 39 | Object: ParseObject, 40 | Query: ParseQuery, 41 | Relation: ParseRelation, 42 | User: ParseUser 43 | }; 44 | 45 | // Augment the Core with extra modules 46 | _.extend(ParseSDK, toExtend); 47 | 48 | return ParseSDK; 49 | 50 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-angular-sdk", 3 | "version": "1.0.0", 4 | "description": "Parse Angular custom SDK", 5 | "main": "dist/parse-angular-sdk.min.js", 6 | "scripts": { 7 | "test": "npm test" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "grunt": "^0.4.5", 13 | "grunt-contrib-concat": "^0.5.0", 14 | "grunt-contrib-uglify": "^0.7.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |