/*! * Bmob WeChat applet SDK * http://www.bmob.cn * Copyright Bmob, Inc. * The Bmob WeChat applet SDK is freely distributable under the MIT license. * (c) 2016-2050 Magic */ (function (root) { var _ = require('underscore.js'); var Bmob = {}; Bmob.VERSION = "js0.0.1"; Bmob._ = _; var EmptyConstructor = function () { }; if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = Bmob; } exports.Bmob = Bmob; } else { root.Bmob = Bmob; } // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. var inherits = function (parent, protoProps, staticProps) { var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. if (protoProps && protoProps.hasOwnProperty('constructor')) { child = protoProps.constructor; } else { /** @ignore */ child = function () { parent.apply(this, arguments); }; } // Inherit class (static) properties from parent. Bmob._.extend(child, parent); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. EmptyConstructor.prototype = parent.prototype; child.prototype = new EmptyConstructor(); // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) { Bmob._.extend(child.prototype, protoProps); } // Add static properties to the constructor function, if supplied. if (staticProps) { Bmob._.extend(child, staticProps); } // Correctly set child's `prototype.constructor`. child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is // needed later. child.__super__ = parent.prototype; return child; }; // Set the server for Bmob to talk to. Bmob.serverURL = ""; Bmob.fileURL = "http://file.bmob.cn"; // Check whether we are running in Node.js. if (typeof (process) !== "undefined" && process.versions && process.versions.node) { Bmob._isNode = true; } /** * 初始化时需要调用这个函数。可以从bmob中获取所需的key * * @param {String} applicationId 你的 Application ID. * @param {String} applicationKey 你的 restful api Key. * @param {String} masterKey (optional) 你的 bmob Master Key. */ Bmob.initialize = function (applicationId, applicationKey, masterKey) { Bmob._initialize(applicationId, applicationKey, masterKey); }; /** * Call this method first to set up authentication tokens for Bmob. * This method is for Bmob's own private use. * @param {String} applicationId Your Bmob Application ID. * @param {String} applicationKey Your Bmob Application Key */ Bmob._initialize = function (applicationId, applicationKey, masterKey) { Bmob.applicationId = applicationId; Bmob.applicationKey = applicationKey; Bmob.masterKey = masterKey; Bmob._useMasterKey = true; Bmob.serverURL = "https://" + applicationId + ".bmobcloud.com"; }; if (Bmob._isNode) { Bmob.initialize = Bmob._initialize; } /** * Returns prefix for localStorage keys used by this instance of Bmob. * @param {String} path The relative suffix to append to it. * null or undefined is treated as the empty string. * @return {String} The full key name. */ Bmob._getBmobPath = function (path) { if (!Bmob.applicationId) { throw "You need to call Bmob.initialize before using Bmob."; } if (!path) { path = ""; } if (!Bmob._.isString(path)) { throw "Tried to get a localStorage path that wasn't a String."; } if (path[0] === "/") { path = path.substring(1); } return "Bmob/" + Bmob.applicationId + "/" + path; }; /** * Returns prefix for localStorage keys used by this instance of Bmob. * @param {String} path The relative suffix to append to it. * null or undefined is treated as the empty string. * @return {String} The full key name. */ Bmob._getBmobPath = function (path) { if (!Bmob.applicationId) { throw "You need to call Bmob.initialize before using Bmob."; } if (!path) { path = ""; } if (!Bmob._.isString(path)) { throw "Tried to get a localStorage path that wasn't a String."; } if (path[0] === "/") { path = path.substring(1); } return "Bmob/" + Bmob.applicationId + "/" + path; }; /** * Returns the unique string for this app on this machine. * Gets reset when localStorage is cleared. */ Bmob._installationId = null; Bmob._getInstallationId = function () { // See if it's cached in RAM. if (Bmob._installationId) { return Bmob._installationId; } // Try to get it from localStorage. var path = Bmob._getBmobPath("installationId"); // Bmob._installationId = Bmob.localStorage.getItem(path); wx.getStorage({ key: 'key', success: function (res) { Bmob._installationId = res.data; console.log(res.data) } }) if (!Bmob._installationId || Bmob._installationId === "") { // It wasn't in localStorage, so create a new one. var hexOctet = function () { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); }; Bmob._installationId = (hexOctet() + hexOctet() + "-" + hexOctet() + "-" + hexOctet() + "-" + hexOctet() + "-" + hexOctet() + hexOctet() + hexOctet()); wx.setStorage({ key: path, data: Bmob._installationId }) } return Bmob._installationId; }; Bmob._parseDate = function (iso8601) { var regexp = new RegExp("^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})" + "T" + "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})" + "(.([0-9]+))?" + "Z$"); var match = regexp.exec(iso8601); if (!match) { return null; } var year = match[1] || 0; var month = (match[2] || 1) - 1; var day = match[3] || 0; var hour = match[4] || 0; var minute = match[5] || 0; var second = match[6] || 0; var milli = match[8] || 0; return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); }; Bmob._ajax = function (method, url, data, success, error) { var options = { success: success, error: error }; var promise = new Bmob.Promise(); var dataObject = JSON.parse(data); var error; wx.showNavigationBarLoading() if (dataObject.category == "wechatApp") { wx.uploadFile({ url: url, filePath: dataObject.base64, name: 'file', header: { "X-Bmob-SDK-Type": "wechatApp" }, formData: dataObject, success: function (res) { console.log(res); var data = JSON.parse(res.data); promise.resolve(data, res.statusCode, res); wx.hideNavigationBarLoading() }, fail: function (e) { console.log(e); promise.reject(e); wx.hideNavigationBarLoading() } }); } else { wx.request({ method: method, url: url, data: data, header: { 'content-type': 'text/plain' }, success: function (res) { if (res.data && res.data.code) { promise.reject(res); } else if (res.statusCode != 200) { promise.reject(res); } else { promise.resolve(res.data, res.statusCode, res); } wx.hideNavigationBarLoading() }, fail: function (e) { promise.reject(e); wx.hideNavigationBarLoading() } }); } if (error) { return Bmob.Promise.error(error); } return promise._thenRunCallbacks(options); } // A self-propagating extend function. Bmob._extend = function (protoProps, classProps) { var child = inherits(this, protoProps, classProps); child.extend = this.extend; return child; }; /** * route is classes, users, login, etc. * objectId is null if there is no associated objectId. * method is the http method for the REST API. * dataObject is the payload as an object, or null if there is none. * @ignore */ Bmob._request = function (route, className, objectId, method, dataObject) { if (!Bmob.applicationId) { throw "You must specify your applicationId using Bmob.initialize"; } if (!Bmob.applicationKey && !Bmob.masterKey) { throw "You must specify a key using Bmob.initialize"; } var url = Bmob.serverURL; if (url.charAt(url.length - 1) !== "/") { url += "/"; } if (route.indexOf("2/") < 0) { //如果是使用了2版接口,则不需要加上路由 url += "1/" + route; } else { url += route; } if (className) { url += "/" + className; } if (objectId) { url += "/" + objectId; } if ((route === 'users' || route === 'classes') && method === 'PUT' && dataObject._fetchWhenSave) { delete dataObject._fetchWhenSave; url += '?new=true'; } dataObject = Bmob._.clone(dataObject || {}); if (method !== "POST") { dataObject._Method = method; method = "POST"; } dataObject._ApplicationId = Bmob.applicationId; dataObject._RestKey = Bmob.applicationKey; if (Bmob._useMasterKey && Bmob.masterKey != undefined) { dataObject._MasterKey = Bmob.masterKey; } dataObject._ClientVersion = Bmob.VERSION; dataObject._InstallationId = Bmob._getInstallationId(); // Pass the session token on every request. var currentUser = Bmob.User.current(); if (currentUser && currentUser._sessionToken) { dataObject._SessionToken = currentUser._sessionToken; } var data = JSON.stringify(dataObject); return Bmob._ajax(method, url, data).then(null, function (response) { // Transform the error into an instance of Bmob.Error by trying to parse // the error string as JSON. var error; try { if (response.data.code) { error = new Bmob.Error(response.data.code, response.data.error); } } catch (e) { // If we fail to parse the error text, that's okay. } error = error || new Bmob.Error(- 1, response.data); // By explicitly returning a rejected Promise, this will work with // either jQuery or Promises/A semantics. return Bmob.Promise.error(error); }); }; // Helper function to get a value from a Backbone object as a property // or as a function. Bmob._getValue = function (object, prop) { if (!(object && object[prop])) { return null; } return Bmob._.isFunction(object[prop]) ? object[prop]() : object[prop]; }; /** * Converts a value in a Bmob Object into the appropriate representation. * This is the JS equivalent of Java's Bmob.maybeReferenceAndEncode(Object) * if seenObjects is falsey. Otherwise any Bmob.Objects not in * seenObjects will be fully embedded rather than encoded * as a pointer. This array will be used to prevent going into an infinite * loop because we have circular references. If * is set, then none of the Bmob Objects that are serialized can be dirty. */ Bmob._encode = function (value, seenObjects, disallowObjects) { var _ = Bmob._; if (value instanceof Bmob.Object) { if (disallowObjects) { throw "Bmob.Objects not allowed here"; } if (!seenObjects || _.include(seenObjects, value) || !value._hasData) { return value._toPointer(); } if (!value.dirty()) { seenObjects = seenObjects.concat(value); return Bmob._encode(value._toFullJSON(seenObjects), seenObjects, disallowObjects); } throw "Tried to save an object with a pointer to a new, unsaved object."; } if (value instanceof Bmob.ACL) { return value.toJSON(); } if (_.isDate(value)) { return { "__type": "Date", "iso": value.toJSON() }; } if (value instanceof Bmob.GeoPoint) { return value.toJSON(); } if (_.isArray(value)) { return _.map(value, function (x) { return Bmob._encode(x, seenObjects, disallowObjects); }); } if (_.isRegExp(value)) { return value.source; } if (value instanceof Bmob.Relation) { return value.toJSON(); } if (value instanceof Bmob.Op) { return value.toJSON(); } if (value instanceof Bmob.File) { if (!value.url()) { throw "Tried to save an object containing an unsaved file."; } return { "__type": "File", "cdn": value.cdn(), "filename": value.name(), "url": value.url() }; } if (_.isObject(value)) { var output = {}; Bmob._objectEach(value, function (v, k) { output[k] = Bmob._encode(v, seenObjects, disallowObjects); }); return output; } return value; }; /** * The inverse function of Bmob._encode. * TODO: make decode not mutate value. */ Bmob._decode = function (key, value) { var _ = Bmob._; if (!_.isObject(value)) { return value; } if (_.isArray(value)) { Bmob._arrayEach(value, function (v, k) { value[k] = Bmob._decode(k, v); }); return value; } if (value instanceof Bmob.Object) { return value; } if (value instanceof Bmob.File) { return value; } if (value instanceof Bmob.Op) { return value; } if (value.__op) { return Bmob.Op._decode(value); } if (value.__type === "Pointer") { var className = value.className; var pointer = Bmob.Object._create(className); if (value.createdAt) { delete value.__type; delete value.className; pointer._finishFetch(value, true); } else { pointer._finishFetch({ objectId: value.objectId }, false); } return pointer; } if (value.__type === "Object") { // It's an Object included in a query result. var className = value.className; delete value.__type; delete value.className; var object = Bmob.Object._create(className); object._finishFetch(value, true); return object; } if (value.__type === "Date") { return value.iso; } if (value.__type === "GeoPoint") { return new Bmob.GeoPoint({ latitude: value.latitude, longitude: value.longitude }); } if (key === "ACL") { if (value instanceof Bmob.ACL) { return value; } return new Bmob.ACL(value); } if (value.__type === "Relation") { var relation = new Bmob.Relation(null, key); relation.targetClassName = value.className; return relation; } if (value.__type === "File") { // var file = new Bmob.File(value.name); // file._metaData = value.metaData || {}; // file._url = value.url; // file.id = value.objectId; if (value.url != undefined && value.url != null) { if (value.url.indexOf("http") >= 0) { var file = { "_name": value.filename, "_url": value.url, "_group": value.group }; } else { var file = { "_name": value.filename, "_url": Bmob.fileURL + "/" + value.url, "_group": value.group }; } } else { //用cdn上传的文件 var file = { "_name": value.filename, "_url": value.url, "_group": value.group }; } return file; } Bmob._objectEach(value, function (v, k) { value[k] = Bmob._decode(k, v); }); return value; }; Bmob._arrayEach = Bmob._.each; /** * Does a deep traversal of every item in object, calling func on every one. * @param {Object} object The object or array to traverse deeply. * @param {Function} func The function to call for every item. It will * be passed the item as an argument. If it returns a truthy value, that * value will replace the item in its parent container. * @returns {} the result of calling func on the top-level object itself. */ Bmob._traverse = function (object, func, seen) { if (object instanceof Bmob.Object) { seen = seen || []; if (Bmob._.indexOf(seen, object) >= 0) { // We've already visited this object in this call. return; } seen.push(object); Bmob._traverse(object.attributes, func, seen); return func(object); } if (object instanceof Bmob.Relation || object instanceof Bmob.File) { // Nothing needs to be done, but we don't want to recurse into the // object's parent infinitely, so we catch this case. return func(object); } if (Bmob._.isArray(object)) { Bmob._.each(object, function (child, index) { var newChild = Bmob._traverse(child, func, seen); if (newChild) { object[index] = newChild; } }); return func(object); } if (Bmob._.isObject(object)) { Bmob._each(object, function (child, key) { var newChild = Bmob._traverse(child, func, seen); if (newChild) { object[key] = newChild; } }); return func(object); } return func(object); }; /** * This is like _.each, except: * * it doesn't work for so-called array-like objects, * * it does work for dictionaries with a "length" attribute. */ Bmob._objectEach = Bmob._each = function (obj, callback) { var _ = Bmob._; if (_.isObject(obj)) { _.each(_.keys(obj), function (key) { callback(obj[key], key); }); } else { _.each(obj, callback); } }; // Helper function to check null or undefined. Bmob._isNullOrUndefined = function (x) { return Bmob._.isNull(x) || Bmob._.isUndefined(x); }; /** * Constructs a new Bmob.Error object with the given code and message. * @param {Number} code An error code constant from Bmob.Error. * @param {String} message A detailed description of the error. * *

Class used for all objects passed to error callbacks.

*/ Bmob.Error = function (code, message) { this.code = code; this.message = message; }; _.extend(Bmob.Error, /** @lends Bmob.Error */ { /** * Error code indicating some error other than those enumerated here. * @constant */ OTHER_CAUSE: -1, /** * Error code indicating the specified object doesn't exist. * @constant */ OBJECT_NOT_FOUND: 101, /** * Error code indicating you tried to query with a datatype that doesn't * support it, like exact matching an array or object. * @constant */ INVALID_QUERY: 102, /** * Error code indicating a missing or invalid classname. Classnames are * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the * only valid characters. * @constant */ INVALID_CLASS_NAME: 103, /** * Error code relation className not exists * @constant */ RELATIONDOCNOTEXISTS: 104, /** * Error code invalid field name * @constant */ INVALID_KEY_NAME: 105, /** * Error code indicating a malformed pointer. You should not see this unless * you have been mucking about changing internal Bmob code. * @constant */ INVALID_POINTER: 106, /** * Error code indicating that badly formed JSON was received upstream. This * either indicates you have done something unusual with modifying how * things encode to JSON, or the network is failing badly. * @constant */ INVALID_JSON: 107, /** * Error code username and password required * @constant */ USERNAME_PASSWORD_REQUIRED: 108, /** * Error code indicating that a field was set to an inconsistent type. * @constant */ INCORRECT_TYPE: 111, /** * Error code requests must be an array * @constant */ REQUEST_MUST_ARRAY: 112, /** * Error code requests must be LIKE OBJECT * @constant */ REQUEST_MUST_OBJECT: 113, /** * Error code indicating that the object is too large. * @constant */ OBJECT_TOO_LARGE: 114, /** * Error code geo error * @constant */ GEO_ERROR: 117, /** * Error code Email verify should be opened in your app setup page of bmob * @constant */ EMAIL_VERIFY_MUST_OPEN: 120, /** * Error code indicating the result was not found in the cache. * @constant */ CACHE_MISS: 120, /** * Error code Invalid device token * @constant */ INVALID_DEVICE_TOKEN: 131, /** * Error code Invalid installation ID * @constant */ INVALID_INSTALLID: 132, /** * Error code Invalid device type * @constant */ INVALID_DEVICE_TYPE: 133, /** * Error code device token EXIST * @constant */ DEVICE_TOKEN_EXIST: 134, /** * Error code indicating that the email address was invalid. * @constant */ INSTALLID_EXIST: 135, /** * Error code DEVICE_TOKEN_NOT_FOR_ANDROID * @constant */ DEVICE_TOKEN_NOT_FOR_ANDROID: 136, /** * Error code indicating a missing content length. * @constant */ INVALID_INSTALL_OPERATE: 137, /** * Error code READ_ONLY * @constant */ READ_ONLY: 138, /** * Error code Role names must be restricted to alphanumeric characters, dashes(-), underscores(_), and spaces * @constant */ INVALID_ROLE_NAME: 139, /** * Error code MISS_PUSH_DATA * @constant */ MISS_PUSH_DATA: 141, /** * Error code INVALID_PUSH_TIME * @constant */ INVALID_PUSH_TIME: 142, /** * Error code INVALID_PUSH_EXPIRE * @constant */ INVALID_PUSH_EXPIRE: 143, /** * Error code PUSHTIME cannot before now * @constant */ PUSH_TIME_MUST_BEFORE_NOW: 144, /** * Error code file size error * @constant */ FILE_SIZE_ERROR: 145, /** * Error code file name error * @constant */ FILE_NAME_ERROR: 146, FILE_NAME_ERROR: 147, /** * Error code file len error * @constant */ FILE_LEN_ERROR: 148, /** * Error code file delete error * @constant */ FILE_UPLOAD_ERROR: 150, /** * Error code indicating an unsaved file. * @constant */ FILE_DELETE_ERROR: 151, /** * Error code image error */ IMAGE_ERROR: 160, /** * Error code image mode error * @constant */ IMAGE_MODE_ERROR: 161, /** * Error code image width error * @constant */ IMAGE_WIDTH_ERROR: 162, /** * Error code image height error * @constant */ IMAGE_HEIGHT_ERROR: 163, /** * Error code image longEdge error * @constant */ IMAGE_LONGEDGE_ERROR: 164, /** * Error code image shortgEdge error * @constant */ IMAGE_SHORTEDGE_ERROR: 165, /** * Error code missing * @constant */ USER_MISSING: 201, /** * Error code username '%s' already taken * not be altered. * @constant */ USER_NAME_TOKEN: 202, /** * Error code EMAIL already taken * @constant */ EMAIL_EXIST: 203, /** * Error code you must provide an email * @constant */ NO_EMAIL: 204, /** * Error code no user found with email * @constant */ NOT_FOUND_EMAIL: 205, /** * Error code sessionToken Erro * @constant */ SESSIONTOKEN_ERROR: 206, /** * Error code valid error * @constant */ VALID_ERROR: 301 }); /** * *

Bmob.Events 是 fork of Backbone's Events module

* *

A module that can be mixed in to any object in order to provide * it with custom events. You may bind callback functions to an event * with `on`, or remove these functions with `off`. * Triggering an event fires all callbacks in the order that `on` was * called. * *

   *     var object = {};
   *     _.extend(object, Bmob.Events);
   *     object.on('expand', function(){ alert('expanded'); });
   *     object.trigger('expand');

* *

For more information, see the * Backbone * documentation.

*/ Bmob.Events = { /** * Bind one or more space separated events, `events`, to a `callback` * function. Passing `"all"` will bind the callback to all events fired. */ on: function (events, callback, context) { var calls, event, node, tail, list; if (!callback) { return this; } events = events.split(eventSplitter); calls = this._callbacks || (this._callbacks = {}); // Create an immutable callback list, allowing traversal during // modification. The tail is an empty object that will always be used // as the next node. event = events.shift(); while (event) { list = calls[event]; node = list ? list.tail : {}; node.next = tail = {}; node.context = context; node.callback = callback; calls[event] = { tail: tail, next: list ? list.next : node }; event = events.shift(); } return this; }, /** * Remove one or many callbacks. If `context` is null, removes all callbacks * with that function. If `callback` is null, removes all callbacks for the * event. If `events` is null, removes all bound callbacks for all events. */ off: function (events, callback, context) { var event, calls, node, tail, cb, ctx; // No events, or removing *all* events. if (!(calls = this._callbacks)) { return; } if (!(events || callback || context)) { delete this._callbacks; return this; } // Loop through the listed events and contexts, splicing them out of the // linked list of callbacks if appropriate. events = events ? events.split(eventSplitter) : _.keys(calls); event = events.shift(); while (event) { node = calls[event]; delete calls[event]; if (!node || !(callback || context)) { continue; } // Create a new list, omitting the indicated callbacks. tail = node.tail; node = node.next; while (node !== tail) { cb = node.callback; ctx = node.context; if ((callback && cb !== callback) || (context && ctx !== context)) { this.on(event, cb, ctx); } node = node.next; } event = events.shift(); } return this; }, /** * Trigger one or many events, firing all bound callbacks. Callbacks are * passed the same arguments as `trigger` is, apart from the event name * (unless you're listening on `"all"`, which will cause your callback to * receive the true name of the event as the first argument). */ trigger: function (events) { var event, node, calls, tail, args, all, rest; if (!(calls = this._callbacks)) { return this; } all = calls.all; events = events.split(eventSplitter); rest = slice.call(arguments, 1); // For each event, walk through the linked list of callbacks twice, // first to trigger the event, then to trigger any `"all"` callbacks. event = events.shift(); while (event) { node = calls[event]; if (node) { tail = node.tail; while ((node = node.next) !== tail) { node.callback.apply(node.context || this, rest); } } node = all; if (node) { tail = node.tail; args = [event].concat(rest); while ((node = node.next) !== tail) { node.callback.apply(node.context || this, args); } } event = events.shift(); } return this; } }; /** * @function */ Bmob.Events.bind = Bmob.Events.on; /** * @function */ Bmob.Events.unbind = Bmob.Events.off; /** * 通过下面的任意一种形式可以创建GeoPoint
*
   *   new GeoPoint(otherGeoPoint)
   *   new GeoPoint(30, 30)
   *   new GeoPoint([30, 30])
   *   new GeoPoint({latitude: 30, longitude: 30})
   *   new GeoPoint()  // defaults to (0, 0)
   *   
* @class * *

在BmobObject中使用坐标点,或者在geo查询中使用

*

在一个表中只有一个字段能使用GeoPoint.

* *

Example:

   *   var point = new Bmob.GeoPoint(30.0, -20.0);
   *   var object = new Bmob.Object("PlaceObject");
   *   object.set("location", point);
   *   object.save();

*/ Bmob.GeoPoint = function (arg1, arg2) { if (_.isArray(arg1)) { Bmob.GeoPoint._validate(arg1[0], arg1[1]); this.latitude = arg1[0]; this.longitude = arg1[1]; } else if (_.isObject(arg1)) { Bmob.GeoPoint._validate(arg1.latitude, arg1.longitude); this.latitude = arg1.latitude; this.longitude = arg1.longitude; } else if (_.isNumber(arg1) && _.isNumber(arg2)) { Bmob.GeoPoint._validate(arg1, arg2); this.latitude = arg1; this.longitude = arg2; } else { this.latitude = 0; this.longitude = 0; } // Add properties so that anyone using Webkit or Mozilla will get an error // if they try to set values that are out of bounds. var self = this; if (this.__defineGetter__ && this.__defineSetter__) { // Use _latitude and _longitude to actually store the values, and add // getters and setters for latitude and longitude. this._latitude = this.latitude; this._longitude = this.longitude; this.__defineGetter__("latitude", function () { return self._latitude; }); this.__defineGetter__("longitude", function () { return self._longitude; }); this.__defineSetter__("latitude", function (val) { Bmob.GeoPoint._validate(val, self.longitude); self._latitude = val; }); this.__defineSetter__("longitude", function (val) { Bmob.GeoPoint._validate(self.latitude, val); self._longitude = val; }); } }; /** * @lends Bmob.GeoPoint.prototype * @property {float} latitude North-south portion of the coordinate, in range * [-90, 90]. Throws an exception if set out of range in a modern browser. * @property {float} longitude East-west portion of the coordinate, in range * [-180, 180]. Throws if set out of range in a modern browser. */ /** * Throws an exception if the given lat-long is out of bounds. */ Bmob.GeoPoint._validate = function (latitude, longitude) { if (latitude < -90.0) { throw "Bmob.GeoPoint latitude " + latitude + " < -90.0."; } if (latitude > 90.0) { throw "Bmob.GeoPoint latitude " + latitude + " > 90.0."; } if (longitude < -180.0) { throw "Bmob.GeoPoint longitude " + longitude + " < -180.0."; } if (longitude > 180.0) { throw "Bmob.GeoPoint longitude " + longitude + " > 180.0."; } }; /** * 使用用户当前的位置创建GeoPoint对象。 * 成功时调用options.success,或者options.error。 * @param {Object} options 调用成功或失败的回调 */ Bmob.GeoPoint.current = function (options) { var promise = new Bmob.Promise(); navigator.geolocation.getCurrentPosition(function (location) { promise.resolve(new Bmob.GeoPoint({ latitude: location.coords.latitude, longitude: location.coords.longitude })); }, function (error) { promise.reject(error); }); return promise._thenRunCallbacks(options); }; Bmob.GeoPoint.prototype = { /** * 返回geopoint的json * @return {Object} */ toJSON: function () { Bmob.GeoPoint._validate(this.latitude, this.longitude); return { "__type": "GeoPoint", latitude: this.latitude, longitude: this.longitude }; }, /** * 返回两个geopoint之间的弧度 * @param {Bmob.GeoPoint} point 另一个Bmob.GeoPoint. * @return {Number} */ radiansTo: function (point) { var d2r = Math.PI / 180.0; var lat1rad = this.latitude * d2r; var long1rad = this.longitude * d2r; var lat2rad = point.latitude * d2r; var long2rad = point.longitude * d2r; var deltaLat = lat1rad - lat2rad; var deltaLong = long1rad - long2rad; var sinDeltaLatDiv2 = Math.sin(deltaLat / 2); var sinDeltaLongDiv2 = Math.sin(deltaLong / 2); // Square of half the straight line chord distance between both points. var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) + (Math.cos(lat1rad) * Math.cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2)); a = Math.min(1.0, a); return 2 * Math.asin(Math.sqrt(a)); }, /** * 返回两个geopoint之间的千米数 * @param {Bmob.GeoPoint} point 另一个Bmob.GeoPoint. * @return {Number} */ kilometersTo: function (point) { return this.radiansTo(point) * 6371.0; }, /** * 返回两个geopoint之间的米数 * @param {Bmob.GeoPoint} point 另一个Bmob.GeoPoint. * @return {Number} */ milesTo: function (point) { return this.radiansTo(point) * 3958.8; } }; var PUBLIC_KEY = "*"; /** * 创建ACL * 如果传任何参数,则任何人都没有权限 * 如果传入的参数是Bmob.User,那个usr会有读写权限。 * 如果传入的参数是json对象,则会有相应的acl权限。 * * @see Bmob.Object#setACL * @class * *

权限控制可以被添加到任何 * Bmob.Object,用来控制用户的访问权限 *

*/ Bmob.ACL = function (arg1) { var self = this; self.permissionsById = {}; if (_.isObject(arg1)) { if (arg1 instanceof Bmob.User) { self.setReadAccess(arg1, true); self.setWriteAccess(arg1, true); } else { if (_.isFunction(arg1)) { throw "Bmob.ACL() called with a function. Did you forget ()?"; } Bmob._objectEach(arg1, function (accessList, userId) { if (!_.isString(userId)) { throw "Tried to create an ACL with an invalid userId."; } self.permissionsById[userId] = {}; Bmob._objectEach(accessList, function (allowed, permission) { if (permission !== "read" && permission !== "write") { throw "Tried to create an ACL with an invalid permission type."; } if (!_.isBoolean(allowed)) { throw "Tried to create an ACL with an invalid permission value."; } self.permissionsById[userId][permission] = allowed; }); }); } } }; /** * 返回acl的json对象 * @return {Object} */ Bmob.ACL.prototype.toJSON = function () { return _.clone(this.permissionsById); }; Bmob.ACL.prototype._setAccess = function (accessType, userId, allowed) { if (userId instanceof Bmob.User) { userId = userId.id; } else if (userId instanceof Bmob.Role) { userId = "role:" + userId.getName(); } if (!_.isString(userId)) { throw "userId must be a string."; } if (!_.isBoolean(allowed)) { throw "allowed must be either true or false."; } var permissions = this.permissionsById[userId]; if (!permissions) { if (!allowed) { // The user already doesn't have this permission, so no action needed. return; } else { permissions = {}; this.permissionsById[userId] = permissions; } } if (allowed) { this.permissionsById[userId][accessType] = true; } else { delete permissions[accessType]; if (_.isEmpty(permissions)) { delete permissions[userId]; } } }; Bmob.ACL.prototype._getAccess = function (accessType, userId) { if (userId instanceof Bmob.User) { userId = userId.id; } else if (userId instanceof Bmob.Role) { userId = "role:" + userId.getName(); } var permissions = this.permissionsById[userId]; if (!permissions) { return false; } return permissions[accessType] ? true : false; }; /** * 设置是否允许用户读取这个对象 * @param 用户id或对象id,或Bmob.Role * @param {Boolean} 用户是否有读的权限 */ Bmob.ACL.prototype.setReadAccess = function (userId, allowed) { this._setAccess("read", userId, allowed); }; /** * 用户是否有读的权限。 * 就算是返回false,用户或许可以访问对象,如果getPublicReadAccess返回ture,或者用户的角色有写的权限。 * @param userId 户id或对象id, 或者Bmob.Role. * @return {Boolean} */ Bmob.ACL.prototype.getReadAccess = function (userId) { return this._getAccess("read", userId); }; /** * 设置是否允许用户有写的权限 * @param userId 用户id或对象id,或Bmob.Role * @param {Boolean} 用户是否有写的权限 */ Bmob.ACL.prototype.setWriteAccess = function (userId, allowed) { this._setAccess("write", userId, allowed); }; /** * 用户是否有写的权限。 * 就算是返回false,用户或许可以访问对象,如果getPublicReadAccess返回ture,或者用户的角色有写的权限。 * @param userId 用户id或对象id,或Bmob.Role * @return {Boolean} */ Bmob.ACL.prototype.getWriteAccess = function (userId) { return this._getAccess("write", userId); }; /** * 设置所有用户有读的权限。 * @param {Boolean} allowed */ Bmob.ACL.prototype.setPublicReadAccess = function (allowed) { this.setReadAccess(PUBLIC_KEY, allowed); }; /** * 是否所有用户有读的权限。 * @return {Boolean} */ Bmob.ACL.prototype.getPublicReadAccess = function () { return this.getReadAccess(PUBLIC_KEY); }; /** * 设置所有用户有写的权限。 * @param {Boolean} allowed */ Bmob.ACL.prototype.setPublicWriteAccess = function (allowed) { this.setWriteAccess(PUBLIC_KEY, allowed); }; /** * 是否所有用户有写的权限。 * @return {Boolean} */ Bmob.ACL.prototype.getPublicWriteAccess = function () { return this.getWriteAccess(PUBLIC_KEY); }; /** * 用户所属的角色是否允许读这个对象。就算返回false,这个角色或许有读的权限,如果他的父角色有读的权限。 * @param role 角色名称,或者 Bmob.Role。 * @return {Boolean} 有读的权限返回true。 * @throws {String} role不是Bmob.Role或字符串。 */ Bmob.ACL.prototype.getRoleReadAccess = function (role) { if (role instanceof Bmob.Role) { // Normalize to the String name role = role.getName(); } if (_.isString(role)) { return this.getReadAccess("role:" + role); } throw "role must be a Bmob.Role or a String"; }; /** * 用户所属的角色是否允许写这个对象。就算返回false,这个角色或许有写的权限,如果他的父角色有写的权限。 * @param role 角色名称,或者 Bmob.Role。 * @return {Boolean} 有写的权限返回true。 * @throws {String} role不是Bmob.Role或字符串。 */ Bmob.ACL.prototype.getRoleWriteAccess = function (role) { if (role instanceof Bmob.Role) { // Normalize to the String name role = role.getName(); } if (_.isString(role)) { return this.getWriteAccess("role:" + role); } throw "role must be a Bmob.Role or a String"; }; /** * 设置用户所属的角色有读的权限 * @param role 角色名称,或者 Bmob.Role。 * @param {Boolean} 允许角色读这个对象 * @throws {String} role不是Bmob.Role或字符串。 */ Bmob.ACL.prototype.setRoleReadAccess = function (role, allowed) { if (role instanceof Bmob.Role) { // Normalize to the String name role = role.getName(); } if (_.isString(role)) { this.setReadAccess("role:" + role, allowed); return; } throw "role must be a Bmob.Role or a String"; }; /** * 设置用户所属的角色有写的权限 * @param role 角色名称,或者 Bmob.Role。 * @param {Boolean} 允许角色写这个对象 * @throws {String} role不是Bmob.Role或字符串。 */ Bmob.ACL.prototype.setRoleWriteAccess = function (role, allowed) { if (role instanceof Bmob.Role) { // Normalize to the String name role = role.getName(); } if (_.isString(role)) { this.setWriteAccess("role:" + role, allowed); return; } throw "role must be a Bmob.Role or a String"; }; /** * A Bmob.Op is an atomic operation that can be applied to a field in a * Bmob.Object. For example, calling object.set("foo", "bar") * is an example of a Bmob.Op.Set. Calling object.unset("foo") * is a Bmob.Op.Unset. These operations are stored in a Bmob.Object and * sent to the server as part of object.save() operations. * Instances of Bmob.Op should be immutable. * * You should not create subclasses of Bmob.Op or instantiate Bmob.Op * directly. */ Bmob.Op = function () { this._initialize.apply(this, arguments); }; Bmob.Op.prototype = { _initialize: function () { } }; _.extend(Bmob.Op, { /** * To create a new Op, call Bmob.Op._extend(); */ _extend: Bmob._extend, // A map of __op string to decoder function. _opDecoderMap: {}, /** * Registers a function to convert a json object with an __op field into an * instance of a subclass of Bmob.Op. */ _registerDecoder: function (opName, decoder) { Bmob.Op._opDecoderMap[opName] = decoder; }, /** * Converts a json object into an instance of a subclass of Bmob.Op. */ _decode: function (json) { var decoder = Bmob.Op._opDecoderMap[json.__op]; if (decoder) { return decoder(json); } else { return undefined; } } }); /* * Add a handler for Batch ops. */ Bmob.Op._registerDecoder("Batch", function (json) { var op = null; Bmob._arrayEach(json.ops, function (nextOp) { nextOp = Bmob.Op._decode(nextOp); op = nextOp._mergeWithPrevious(op); }); return op; }); /** * @class * set操作是表明字段的值会在Bmob.Object.set中改变,或者这确定要修改值。 */ Bmob.Op.Set = Bmob.Op._extend( /** @lends Bmob.Op.Set.prototype */ { _initialize: function (value) { this._value = value; }, /** * 返回设置后的新值 */ value: function () { return this._value; }, /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { return Bmob._encode(this.value()); }, _mergeWithPrevious: function (previous) { return this; }, _estimate: function (oldValue) { return this.value(); } }); /** * A sentinel value that is returned by Bmob.Op.Unset._estimate to * indicate the field should be deleted. Basically, if you find _UNSET as a * value in your object, you should remove that key. */ Bmob.Op._UNSET = {}; /** * @class * Unset 操作表明字段将会从对象中删除。 */ Bmob.Op.Unset = Bmob.Op._extend( /** @lends Bmob.Op.Unset.prototype */ { /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { return { __op: "Delete" }; }, _mergeWithPrevious: function (previous) { return this; }, _estimate: function (oldValue) { return Bmob.Op._UNSET; } }); Bmob.Op._registerDecoder("Delete", function (json) { return new Bmob.Op.Unset(); }); /** * @class * 将字段的值自增或自减 */ Bmob.Op.Increment = Bmob.Op._extend( /** @lends Bmob.Op.Increment.prototype */ { _initialize: function (amount) { this._amount = amount; }, /** * 返回添加的数目。 * @return {Number} 增加或减少的数目。 */ amount: function () { return this._amount; }, /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { return { __op: "Increment", amount: this._amount }; }, _mergeWithPrevious: function (previous) { if (!previous) { return this; } else if (previous instanceof Bmob.Op.Unset) { return new Bmob.Op.Set(this.amount()); } else if (previous instanceof Bmob.Op.Set) { return new Bmob.Op.Set(previous.value() + this.amount()); } else if (previous instanceof Bmob.Op.Increment) { return new Bmob.Op.Increment(this.amount() + previous.amount()); } else { throw "Op is invalid after previous op."; } }, _estimate: function (oldValue) { if (!oldValue) { return this.amount(); } return oldValue + this.amount(); } }); Bmob.Op._registerDecoder("Increment", function (json) { return new Bmob.Op.Increment(json.amount); }); /** * @class * 添加一个对象到数组中,不管元素是否存在。 */ Bmob.Op.Add = Bmob.Op._extend( /** @lends Bmob.Op.Add.prototype */ { _initialize: function (objects) { this._objects = objects; }, /** * 返回添加到数组中的对象 * @return {Array} 添加到数组中的对象 */ objects: function () { return this._objects; }, /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { return { __op: "Add", objects: Bmob._encode(this.objects()) }; }, _mergeWithPrevious: function (previous) { if (!previous) { return this; } else if (previous instanceof Bmob.Op.Unset) { return new Bmob.Op.Set(this.objects()); } else if (previous instanceof Bmob.Op.Set) { return new Bmob.Op.Set(this._estimate(previous.value())); } else if (previous instanceof Bmob.Op.Add) { return new Bmob.Op.Add(previous.objects().concat(this.objects())); } else { throw "Op is invalid after previous op."; } }, _estimate: function (oldValue) { if (!oldValue) { return _.clone(this.objects()); } else { return oldValue.concat(this.objects()); } } }); Bmob.Op._registerDecoder("Add", function (json) { return new Bmob.Op.Add(Bmob._decode(undefined, json.objects)); }); /** * @class * 添加一个元素到数组中,当元素已经存在,将不会重复添加。 */ Bmob.Op.AddUnique = Bmob.Op._extend( /** @lends Bmob.Op.AddUnique.prototype */ { _initialize: function (objects) { this._objects = _.uniq(objects); }, /** * 返回添加到数组中的对象 * @return {Array} 添加到数组中的对象 */ objects: function () { return this._objects; }, /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { return { __op: "AddUnique", objects: Bmob._encode(this.objects()) }; }, _mergeWithPrevious: function (previous) { if (!previous) { return this; } else if (previous instanceof Bmob.Op.Unset) { return new Bmob.Op.Set(this.objects()); } else if (previous instanceof Bmob.Op.Set) { return new Bmob.Op.Set(this._estimate(previous.value())); } else if (previous instanceof Bmob.Op.AddUnique) { return new Bmob.Op.AddUnique(this._estimate(previous.objects())); } else { throw "Op is invalid after previous op."; } }, _estimate: function (oldValue) { if (!oldValue) { return _.clone(this.objects()); } else { // We can't just take the _.uniq(_.union(...)) of oldValue and // this.objects, because the uniqueness may not apply to oldValue // (especially if the oldValue was set via .set()) var newValue = _.clone(oldValue); Bmob._arrayEach(this.objects(), function (obj) { if (obj instanceof Bmob.Object && obj.id) { var matchingObj = _.find(newValue, function (anObj) { return (anObj instanceof Bmob.Object) && (anObj.id === obj.id); }); if (!matchingObj) { newValue.push(obj); } else { var index = _.indexOf(newValue, matchingObj); newValue[index] = obj; } } else if (!_.contains(newValue, obj)) { newValue.push(obj); } }); return newValue; } } }); Bmob.Op._registerDecoder("AddUnique", function (json) { return new Bmob.Op.AddUnique(Bmob._decode(undefined, json.objects)); }); /** * @class * 从数组中移除一个元素。 */ Bmob.Op.Remove = Bmob.Op._extend( /** @lends Bmob.Op.Remove.prototype */ { _initialize: function (objects) { this._objects = _.uniq(objects); }, /** * 返回移除出数组的对象 * @return {Array} 移除出数组的对象 */ objects: function () { return this._objects; }, /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { return { __op: "Remove", objects: Bmob._encode(this.objects()) }; }, _mergeWithPrevious: function (previous) { if (!previous) { return this; } else if (previous instanceof Bmob.Op.Unset) { return previous; } else if (previous instanceof Bmob.Op.Set) { return new Bmob.Op.Set(this._estimate(previous.value())); } else if (previous instanceof Bmob.Op.Remove) { return new Bmob.Op.Remove(_.union(previous.objects(), this.objects())); } else { throw "Op is invalid after previous op."; } }, _estimate: function (oldValue) { if (!oldValue) { return []; } else { var newValue = _.difference(oldValue, this.objects()); // If there are saved Bmob Objects being removed, also remove them. Bmob._arrayEach(this.objects(), function (obj) { if (obj instanceof Bmob.Object && obj.id) { newValue = _.reject(newValue, function (other) { return (other instanceof Bmob.Object) && (other.id === obj.id); }); } }); return newValue; } } }); Bmob.Op._registerDecoder("Remove", function (json) { return new Bmob.Op.Remove(Bmob._decode(undefined, json.objects)); }); /** * @class * 关系操作标明这个字段是Bmob.Relation的实体,同时对象可以从关系中添加或移除 */ Bmob.Op.Relation = Bmob.Op._extend( /** @lends Bmob.Op.Relation.prototype */ { _initialize: function (adds, removes) { this._targetClassName = null; var self = this; var pointerToId = function (object) { if (object instanceof Bmob.Object) { if (!object.id) { throw "You can't add an unsaved Bmob.Object to a relation."; } if (!self._targetClassName) { self._targetClassName = object.className; } if (self._targetClassName !== object.className) { throw "Tried to create a Bmob.Relation with 2 different types: " + self._targetClassName + " and " + object.className + "."; } return object.id; } return object; }; this.relationsToAdd = _.uniq(_.map(adds, pointerToId)); this.relationsToRemove = _.uniq(_.map(removes, pointerToId)); }, /** * 返回添加到关系中的Bmob.Object的数组对象 * @return {Array} */ added: function () { var self = this; return _.map(this.relationsToAdd, function (objectId) { var object = Bmob.Object._create(self._targetClassName); object.id = objectId; return object; }); }, /** * 返回移除的Bmob.Object的数组对象 * @return {Array} */ removed: function () { var self = this; return _.map(this.relationsToRemove, function (objectId) { var object = Bmob.Object._create(self._targetClassName); object.id = objectId; return object; }); }, /** * 返回发送到bmob的json * @return {Object} */ toJSON: function () { var adds = null; var removes = null; var self = this; var idToPointer = function (id) { return { __type: 'Pointer', className: self._targetClassName, objectId: id }; }; var pointers = null; if (this.relationsToAdd.length > 0) { pointers = _.map(this.relationsToAdd, idToPointer); adds = { "__op": "AddRelation", "objects": pointers }; } if (this.relationsToRemove.length > 0) { pointers = _.map(this.relationsToRemove, idToPointer); removes = { "__op": "RemoveRelation", "objects": pointers }; } if (adds && removes) { return { "__op": "Batch", "ops": [adds, removes] }; } return adds || removes || {}; }, _mergeWithPrevious: function (previous) { if (!previous) { return this; } else if (previous instanceof Bmob.Op.Unset) { throw "You can't modify a relation after deleting it."; } else if (previous instanceof Bmob.Op.Relation) { if (previous._targetClassName && previous._targetClassName !== this._targetClassName) { throw "Related object must be of class " + previous._targetClassName + ", but " + this._targetClassName + " was passed in."; } var newAdd = _.union(_.difference(previous.relationsToAdd, this.relationsToRemove), this.relationsToAdd); var newRemove = _.union(_.difference(previous.relationsToRemove, this.relationsToAdd), this.relationsToRemove); var newRelation = new Bmob.Op.Relation(newAdd, newRemove); newRelation._targetClassName = this._targetClassName; return newRelation; } else { throw "Op is invalid after previous op."; } }, _estimate: function (oldValue, object, key) { if (!oldValue) { var relation = new Bmob.Relation(object, key); relation.targetClassName = this._targetClassName; } else if (oldValue instanceof Bmob.Relation) { if (this._targetClassName) { if (oldValue.targetClassName) { if (oldValue.targetClassName !== this._targetClassName) { throw "Related object must be a " + oldValue.targetClassName + ", but a " + this._targetClassName + " was passed in."; } } else { oldValue.targetClassName = this._targetClassName; } } return oldValue; } else { throw "Op is invalid after previous op."; } } }); Bmob.Op._registerDecoder("AddRelation", function (json) { return new Bmob.Op.Relation(Bmob._decode(undefined, json.objects), []); }); Bmob.Op._registerDecoder("RemoveRelation", function (json) { return new Bmob.Op.Relation([], Bmob._decode(undefined, json.objects)); }); /** * Creates a new Relation for the given parent object and key. This * constructor should rarely be used directly, but rather created by * Bmob.Object.relation. * @param {Bmob.Object} parent The parent of this relation. * @param {String} key The key for this relation on the parent. * @see Bmob.Object#relation * *

* A class that is used to access all of the children of a many-to-many * relationship. Each instance of Bmob.Relation is associated with a * particular parent object and key. *

*/ Bmob.Relation = function (parent, key) { this.parent = parent; this.key = key; this.targetClassName = null; }; /** * Creates a query that can be used to query the parent objects in this relation. * @param {String} parentClass The parent class or name. * @param {String} relationKey The relation field key in parent. * @param {Bmob.Object} child The child object. * @return {Bmob.Query} */ Bmob.Relation.reverseQuery = function (parentClass, relationKey, child) { var query = new Bmob.Query(parentClass); query.equalTo(relationKey, child._toPointer()); return query; }; Bmob.Relation.prototype = { /** * Makes sure that this relation has the right parent and key. */ _ensureParentAndKey: function (parent, key) { this.parent = this.parent || parent; this.key = this.key || key; if (this.parent !== parent) { throw "Internal Error. Relation retrieved from two different Objects."; } if (this.key !== key) { throw "Internal Error. Relation retrieved from two different keys."; } }, /** * Adds a Bmob.Object or an array of Bmob.Objects to the relation. * @param {} objects The item or items to add. */ add: function (objects) { if (!_.isArray(objects)) { objects = [objects]; } var change = new Bmob.Op.Relation(objects, []); this.parent.set(this.key, change); this.targetClassName = change._targetClassName; }, /** * Removes a Bmob.Object or an array of Bmob.Objects from this relation. * @param {} objects The item or items to remove. */ remove: function (objects) { if (!_.isArray(objects)) { objects = [objects]; } var change = new Bmob.Op.Relation([], objects); this.parent.set(this.key, change); this.targetClassName = change._targetClassName; }, /** * Returns a JSON version of the object suitable for saving to disk. * @return {Object} */ toJSON: function () { return { "__type": "Relation", "className": this.targetClassName }; }, /** * Returns a Bmob.Query that is limited to objects in this * relation. * @return {Bmob.Query} */ query: function () { var targetClass; var query; if (!this.targetClassName) { targetClass = Bmob.Object._getSubclass(this.parent.className); query = new Bmob.Query(targetClass); query._extraOptions.redirectClassNameForKey = this.key; } else { targetClass = Bmob.Object._getSubclass(this.targetClassName); query = new Bmob.Query(targetClass); } query._addCondition("$relatedTo", "object", this.parent._toPointer()); query._addCondition("$relatedTo", "key", this.key); return query; } }; /** * A Promise is returned by async methods as a hook to provide callbacks to be * called when the async task is fulfilled. * *

Typical usage would be like:

   *    query.findAsync().then(function(results) {
   *      results[0].set("foo", "bar");
   *      return results[0].saveAsync();
   *    }).then(function(result) {
   *      console.log("Updated " + result.id);
   *    });
   * 

* * @see Bmob.Promise.prototype.next */ Bmob.Promise = function () { this._resolved = false; this._rejected = false; this._resolvedCallbacks = []; this._rejectedCallbacks = []; }; _.extend(Bmob.Promise, /** @lends Bmob.Promise */ { /** * Returns true iff the given object fulfils the Promise interface. * @return {Boolean} */ is: function (promise) { return promise && promise.then && _.isFunction(promise.then); }, /** * Returns a new promise that is resolved with a given value. * @return {Bmob.Promise} the new promise. */ as: function () { var promise = new Bmob.Promise(); promise.resolve.apply(promise, arguments); return promise; }, /** * Returns a new promise that is rejected with a given error. * @return {Bmob.Promise} the new promise. */ error: function () { var promise = new Bmob.Promise(); promise.reject.apply(promise, arguments); return promise; }, /** * Returns a new promise that is fulfilled when all of the input promises * are resolved. If any promise in the list fails, then the returned promise * will fail with the last error. If they all succeed, then the returned * promise will succeed, with the result being an array with the results of * all the input promises. * @param {Array} promises a list of promises to wait for. * @return {Bmob.Promise} the new promise. */ when: function (promises) { // Allow passing in Promises as separate arguments instead of an Array. var objects; if (promises && Bmob._isNullOrUndefined(promises.length)) { objects = arguments; } else { objects = promises; } var total = objects.length; var hadError = false; var results = []; var errors = []; results.length = objects.length; errors.length = objects.length; if (total === 0) { return Bmob.Promise.as.apply(this, results); } var promise = new Bmob.Promise(); var resolveOne = function () { total = total - 1; if (total === 0) { if (hadError) { promise.reject(errors); } else { promise.resolve.apply(promise, results); } } }; Bmob._arrayEach(objects, function (object, i) { if (Bmob.Promise.is(object)) { object.then(function (result) { results[i] = result; resolveOne(); }, function (error) { errors[i] = error; hadError = true; resolveOne(); }); } else { results[i] = object; resolveOne(); } }); return promise; }, /** * Runs the given asyncFunction repeatedly, as long as the predicate * function returns a truthy value. Stops repeating if asyncFunction returns * a rejected promise. * @param {Function} predicate should return false when ready to stop. * @param {Function} asyncFunction should return a Promise. */ _continueWhile: function (predicate, asyncFunction) { if (predicate()) { return asyncFunction().then(function () { return Bmob.Promise._continueWhile(predicate, asyncFunction); }); } return Bmob.Promise.as(); } }); _.extend(Bmob.Promise.prototype, /** @lends Bmob.Promise.prototype */ { /** * Marks this promise as fulfilled, firing any callbacks waiting on it. * @param {Object} result the result to pass to the callbacks. */ resolve: function (result) { if (this._resolved || this._rejected) { throw "A promise was resolved even though it had already been " + (this._resolved ? "resolved" : "rejected") + "."; } this._resolved = true; this._result = arguments; var results = arguments; Bmob._arrayEach(this._resolvedCallbacks, function (resolvedCallback) { resolvedCallback.apply(this, results); }); this._resolvedCallbacks = []; this._rejectedCallbacks = []; }, /** * Marks this promise as fulfilled, firing any callbacks waiting on it. * @param {Object} error the error to pass to the callbacks. */ reject: function (error) { if (this._resolved || this._rejected) { throw "A promise was rejected even though it had already been " + (this._resolved ? "resolved" : "rejected") + "."; } this._rejected = true; this._error = error; Bmob._arrayEach(this._rejectedCallbacks, function (rejectedCallback) { rejectedCallback(error); }); this._resolvedCallbacks = []; this._rejectedCallbacks = []; }, /** * Adds callbacks to be called when this promise is fulfilled. Returns a new * Promise that will be fulfilled when the callback is complete. It allows * chaining. If the callback itself returns a Promise, then the one returned * by "then" will not be fulfilled until that one returned by the callback * is fulfilled. * @param {Function} resolvedCallback Function that is called when this * Promise is resolved. Once the callback is complete, then the Promise * returned by "then" will also be fulfilled. * @param {Function} rejectedCallback Function that is called when this * Promise is rejected with an error. Once the callback is complete, then * the promise returned by "then" with be resolved successfully. If * rejectedCallback is null, or it returns a rejected Promise, then the * Promise returned by "then" will be rejected with that error. * @return {Bmob.Promise} A new Promise that will be fulfilled after this * Promise is fulfilled and either callback has completed. If the callback * returned a Promise, then this Promise will not be fulfilled until that * one is. */ then: function (resolvedCallback, rejectedCallback) { var promise = new Bmob.Promise(); var wrappedResolvedCallback = function () { var result = arguments; if (resolvedCallback) { result = [resolvedCallback.apply(this, result)]; } if (result.length === 1 && Bmob.Promise.is(result[0])) { result[0].then(function () { promise.resolve.apply(promise, arguments); }, function (error) { promise.reject(error); }); } else { promise.resolve.apply(promise, result); } }; var wrappedRejectedCallback = function (error) { var result = []; if (rejectedCallback) { result = [rejectedCallback(error)]; if (result.length === 1 && Bmob.Promise.is(result[0])) { result[0].then(function () { promise.resolve.apply(promise, arguments); }, function (error) { promise.reject(error); }); } else { // A Promises/A+ compliant implementation would call: // promise.resolve.apply(promise, result); promise.reject(result[0]); } } else { promise.reject(error); } }; if (this._resolved) { wrappedResolvedCallback.apply(this, this._result); } else if (this._rejected) { wrappedRejectedCallback(this._error); } else { this._resolvedCallbacks.push(wrappedResolvedCallback); this._rejectedCallbacks.push(wrappedRejectedCallback); } return promise; }, /** * Run the given callbacks after this promise is fulfilled. * @param optionsOrCallback {} A Backbone-style options callback, or a * callback function. If this is an options object and contains a "model" * attributes, that will be passed to error callbacks as the first argument. * @param model {} If truthy, this will be passed as the first result of * error callbacks. This is for Backbone-compatability. * @return {Bmob.Promise} A promise that will be resolved after the * callbacks are run, with the same result as this. */ _thenRunCallbacks: function (optionsOrCallback, model) { var options; if (_.isFunction(optionsOrCallback)) { var callback = optionsOrCallback; options = { success: function (result) { callback(result, null); }, error: function (error) { callback(null, error); } }; } else { options = _.clone(optionsOrCallback); } options = options || {}; return this.then(function (result) { if (options.success) { options.success.apply(this, arguments); } else if (model) { // When there's no callback, a sync event should be triggered. model.trigger('sync', model, result, options); } return Bmob.Promise.as.apply(Bmob.Promise, arguments); }, function (error) { if (options.error) { if (!_.isUndefined(model)) { options.error(model, error); } else { options.error(error); } } else if (model) { // When there's no error callback, an error event should be triggered. model.trigger('error', model, error, options); } // By explicitly returning a rejected Promise, this will work with // either jQuery or Promises/A semantics. return Bmob.Promise.error(error); }); }, /** * Adds a callback function that should be called regardless of whether * this promise failed or succeeded. The callback will be given either the * array of results for its first argument, or the error as its second, * depending on whether this Promise was rejected or resolved. Returns a * new Promise, like "then" would. * @param {Function} continuation the callback. */ _continueWith: function (continuation) { return this.then(function () { return continuation(arguments, null); }, function (error) { return continuation(null, error); }); } }); var b64Digit = function (number) { if (number < 26) { return String.fromCharCode(65 + number); } if (number < 52) { return String.fromCharCode(97 + (number - 26)); } if (number < 62) { return String.fromCharCode(48 + (number - 52)); } if (number === 62) { return "+"; } if (number === 63) { return "/"; } throw "Tried to encode large digit " + number + " in base64."; }; var encodeBase64 = function (str) { var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var out, i, len; var c1, c2, c3; len = str.length; i = 0; out = ""; while (i < len) { c1 = str.charCodeAt(i++) & 0xff; if (i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt((c1 & 0x3) << 4); out += "=="; break; } c2 = str.charCodeAt(i++); if (i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt((c2 & 0xF) << 2); out += "="; break; } c3 = str.charCodeAt(i++); out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); out += base64EncodeChars.charAt(c3 & 0x3F); } return out; }; var utf16to8 = function (str) { var out, i, len, c; out = ""; len = str.length; for (i = 0; i < len; i++) { c = str.charCodeAt(i); if ((c >= 0x0001) && (c <= 0x007F)) { out += str.charAt(i); } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } } return out; }; // A list of file extensions to mime types as found here: // http://stackoverflow.com/questions/58510/using-net-how-can-you-find-the- // mime-type-of-a-file-based-on-the-file-signature var mimeTypes = { ai: "application/postscript", aif: "audio/x-aiff", aifc: "audio/x-aiff", aiff: "audio/x-aiff", asc: "text/plain", atom: "application/atom+xml", au: "audio/basic", avi: "video/x-msvideo", bcpio: "application/x-bcpio", bin: "application/octet-stream", bmp: "image/bmp", cdf: "application/x-netcdf", cgm: "image/cgm", "class": "application/octet-stream", cpio: "application/x-cpio", cpt: "application/mac-compactpro", csh: "application/x-csh", css: "text/css", dcr: "application/x-director", dif: "video/x-dv", dir: "application/x-director", djv: "image/vnd.djvu", djvu: "image/vnd.djvu", dll: "application/octet-stream", dmg: "application/octet-stream", dms: "application/octet-stream", doc: "application/msword", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml." + "document", dotx: "application/vnd.openxmlformats-officedocument.wordprocessingml." + "template", docm: "application/vnd.ms-word.document.macroEnabled.12", dotm: "application/vnd.ms-word.template.macroEnabled.12", dtd: "application/xml-dtd", dv: "video/x-dv", dvi: "application/x-dvi", dxr: "application/x-director", eps: "application/postscript", etx: "text/x-setext", exe: "application/octet-stream", ez: "application/andrew-inset", gif: "image/gif", gram: "application/srgs", grxml: "application/srgs+xml", gtar: "application/x-gtar", hdf: "application/x-hdf", hqx: "application/mac-binhex40", htm: "text/html", html: "text/html", ice: "x-conference/x-cooltalk", ico: "image/x-icon", ics: "text/calendar", ief: "image/ief", ifb: "text/calendar", iges: "model/iges", igs: "model/iges", jnlp: "application/x-java-jnlp-file", jp2: "image/jp2", jpe: "image/jpeg", jpeg: "image/jpeg", jpg: "image/jpeg", js: "application/x-javascript", kar: "audio/midi", latex: "application/x-latex", lha: "application/octet-stream", lzh: "application/octet-stream", m3u: "audio/x-mpegurl", m4a: "audio/mp4a-latm", m4b: "audio/mp4a-latm", m4p: "audio/mp4a-latm", m4u: "video/vnd.mpegurl", m4v: "video/x-m4v", mac: "image/x-macpaint", man: "application/x-troff-man", mathml: "application/mathml+xml", me: "application/x-troff-me", mesh: "model/mesh", mid: "audio/midi", midi: "audio/midi", mif: "application/vnd.mif", mov: "video/quicktime", movie: "video/x-sgi-movie", mp2: "audio/mpeg", mp3: "audio/mpeg", mp4: "video/mp4", mpe: "video/mpeg", mpeg: "video/mpeg", mpg: "video/mpeg", mpga: "audio/mpeg", ms: "application/x-troff-ms", msh: "model/mesh", mxu: "video/vnd.mpegurl", nc: "application/x-netcdf", oda: "application/oda", ogg: "application/ogg", pbm: "image/x-portable-bitmap", pct: "image/pict", pdb: "chemical/x-pdb", pdf: "application/pdf", pgm: "image/x-portable-graymap", pgn: "application/x-chess-pgn", pic: "image/pict", pict: "image/pict", png: "image/png", pnm: "image/x-portable-anymap", pnt: "image/x-macpaint", pntg: "image/x-macpaint", ppm: "image/x-portable-pixmap", ppt: "application/vnd.ms-powerpoint", pptx: "application/vnd.openxmlformats-officedocument.presentationml." + "presentation", potx: "application/vnd.openxmlformats-officedocument.presentationml." + "template", ppsx: "application/vnd.openxmlformats-officedocument.presentationml." + "slideshow", ppam: "application/vnd.ms-powerpoint.addin.macroEnabled.12", pptm: "application/vnd.ms-powerpoint.presentation.macroEnabled.12", potm: "application/vnd.ms-powerpoint.template.macroEnabled.12", ppsm: "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", ps: "application/postscript", qt: "video/quicktime", qti: "image/x-quicktime", qtif: "image/x-quicktime", ra: "audio/x-pn-realaudio", ram: "audio/x-pn-realaudio", ras: "image/x-cmu-raster", rdf: "application/rdf+xml", rgb: "image/x-rgb", rm: "application/vnd.rn-realmedia", roff: "application/x-troff", rtf: "text/rtf", rtx: "text/richtext", sgm: "text/sgml", sgml: "text/sgml", sh: "application/x-sh", shar: "application/x-shar", silo: "model/mesh", sit: "application/x-stuffit", skd: "application/x-koan", skm: "application/x-koan", skp: "application/x-koan", skt: "application/x-koan", smi: "application/smil", smil: "application/smil", snd: "audio/basic", so: "application/octet-stream", spl: "application/x-futuresplash", src: "application/x-wais-source", sv4cpio: "application/x-sv4cpio", sv4crc: "application/x-sv4crc", svg: "image/svg+xml", swf: "application/x-shockwave-flash", t: "application/x-troff", tar: "application/x-tar", tcl: "application/x-tcl", tex: "application/x-tex", texi: "application/x-texinfo", texinfo: "application/x-texinfo", tif: "image/tiff", tiff: "image/tiff", tr: "application/x-troff", tsv: "text/tab-separated-values", txt: "text/plain", ustar: "application/x-ustar", vcd: "application/x-cdlink", vrml: "model/vrml", vxml: "application/voicexml+xml", wav: "audio/x-wav", wbmp: "image/vnd.wap.wbmp", wbmxl: "application/vnd.wap.wbxml", wml: "text/vnd.wap.wml", wmlc: "application/vnd.wap.wmlc", wmls: "text/vnd.wap.wmlscript", wmlsc: "application/vnd.wap.wmlscriptc", wrl: "model/vrml", xbm: "image/x-xbitmap", xht: "application/xhtml+xml", xhtml: "application/xhtml+xml", xls: "application/vnd.ms-excel", xml: "application/xml", xpm: "image/x-xpixmap", xsl: "application/xml", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml." + "template", xlsm: "application/vnd.ms-excel.sheet.macroEnabled.12", xltm: "application/vnd.ms-excel.template.macroEnabled.12", xlam: "application/vnd.ms-excel.addin.macroEnabled.12", xlsb: "application/vnd.ms-excel.sheet.binary.macroEnabled.12", xslt: "application/xslt+xml", xul: "application/vnd.mozilla.xul+xml", xwd: "image/x-xwindowdump", xyz: "chemical/x-xyz", zip: "application/zip" }; /** * Reads a File using a FileReader. * @param file {File} the File to read. * @param type {String} (optional) the mimetype to override with. * @return {Bmob.Promise} A Promise that will be fulfilled with a * base64-encoded string of the data and its mime type. */ var readAsync = function (file, type) { var promise = new Bmob.Promise(); if (typeof (FileReader) === "undefined") { return Bmob.Promise.error(new Bmob.Error(- 1, "Attempted to use a FileReader on an unsupported browser.")); } var reader = new FileReader(); reader.onloadend = function () { promise.resolve(reader.result); }; reader.readAsBinaryString(file); return promise; }; /** * Bmob.File 保存文件到bmob * cloud. * @param name {String} 文件名。在服务器中,这会改为唯一的文件名 * @param data {file} 文件的数据 * * 文件对象是在" file upload control"中被选中,只能在下面的浏览器使用 * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+. * 例如:
   *     
   * var fileUploadControl = $("#profilePhotoFileUpload")[0];
   * if (fileUploadControl.files.length > 0) {
   *   var file = fileUploadControl.files[0];
   *   var name = "photo.jpg";
   *   var bmobFile = new Bmob.File(name, file);
   *   bmobFile.save().then(function() {
   *     // The file has been saved to Bmob.
   *   }, function(error) {
   *     // The file either could not be read, or could not be saved to Bmob.
   *   });
   * }
* @param type {String} 文件的类型. */ Bmob.File = function (name, data, type) { data = data[0]; this._name = name; // this._name = encodeBase64(utf16to8(name)); var currentUser = Bmob.User.current(); this._metaData = { owner: (currentUser != null ? currentUser.id : 'unknown') }; // Guess the content type from the extension if we need to. var extension = /\.([^.]*)$/.exec(name); if (extension) { extension = extension[1].toLowerCase(); } var guessedType = type || mimeTypes[extension] || "text/plain"; this._guessedType = guessedType; if (typeof (File) !== "undefined" && data instanceof File) { this._source = readAsync(data, type); } else { // throw "Creating a Bmob.File from a String is not yet supported."; this._source = Bmob.Promise.as(data, guessedType); this._metaData.size = data.length; } }; Bmob.File.prototype = { /** * Gets the name of the file. Before save is called, this is the filename * given by the user. After save is called, that name gets prefixed with a * unique identifier. */ name: function () { return this._name; }, setName: function (name) { this._name = name; }, /** * Gets the url of the file. It is only available after you save the file or * after you get the file from a Bmob.Object. * @return {String} */ url: function () { return this._url; }, setUrl: function (url) { this._url = url; }, /** * Gets the group of the file. It is only available after you save the file or * after you get the file from a Bmob.Object. * @return {String} */ cdn: function () { return this._cdn; }, /** *

Returns the file's metadata JSON object if no arguments is given.Returns the * metadata value if a key is given.Set metadata value if key and value are both given.

*

    *  var metadata = file.metaData(); //Get metadata JSON object.
    *  var size = file.metaData('size');  // Get the size metadata value.
    *  file.metaData('format', 'jpeg'); //set metadata attribute and value.
    *

* @return {Object} The file's metadata JSON object. * @param {String} attr an optional metadata key. * @param {Object} value an optional metadata value. **/ metaData: function (attr, value) { if (attr != null && value != null) { this._metaData[attr] = value; return this; } else if (attr != null) { return this._metaData[attr]; } else { return this._metaData; } }, /** * Destroy the file. * @return {Bmob.Promise} A promise that is fulfilled when the destroy * completes. */ destroy: function (options) { if (!this._url && !this._cdn) return Bmob.Promise.error('The file url and cdn is not eixsts.')._thenRunCallbacks(options); var data = { cdn: this._cdn, _ContentType: "application/json", url: this._url, metaData: self._metaData, }; var request = Bmob._request("2/files", null, null, 'DELETE', data); return request._thenRunCallbacks(options); }, /** * Saves the file to the Bmob cloud. * @param {Object} options A Backbone-style options object. * @return {Bmob.Promise} Promise that is resolved when the save finishes. */ save: function (options) { var self = this; if (!self._previousSave) { if (self._source) { self._previousSave = self._source.then(function (base64, type) { var data = { base64: base64, // base64: encodeBase64(base64), _ContentType: "text/plain", mime_type: "text/plain", metaData: self._metaData, category: "wechatApp", }; if (!self._metaData.size) { self._metaData.size = base64.length; } return Bmob._request("2/files", self._name, null, 'POST', data); }).then(function (response) { self._name = response.filename; self._url = response.url; self._cdn = response.cdn; return self; }); } else { throw "not source file" } } return self._previousSave._thenRunCallbacks(options); } }; /** * 包含push的函数 * @name Bmob.Push * @namespace 推送消息 */ Bmob.Files = Bmob.Files || {}; Bmob.Files.del = function (urls, options) { var _url = urls.split(".com"); if (!_url) { return Bmob.Promise.error('The file url and cdn is not eixsts.')._thenRunCallbacks(options); } var data = { _ContentType: "application/json", // url:_url, }; var request = Bmob._request("2/files/upyun", _url[1], null, 'DELETE', data); return request.then(function (resp) { return Bmob._decode(null, resp); })._thenRunCallbacks(options); }; /** * Creates a new model with defined attributes. A client id (cid) is * automatically generated and assigned for you. * *

You won't normally call this method directly. It is recommended that * you use a subclass of Bmob.Object instead, created by calling * extend.

* *

However, if you don't want to use a subclass, or aren't sure which * subclass is appropriate, you can use this form:

   *     var object = new Bmob.Object("ClassName");
   * 
* That is basically equivalent to:
   *     var MyClass = Bmob.Object.extend("ClassName");
   *     var object = new MyClass();
   * 

* * @param {Object} attributes The initial set of data to store in the object. * @param {Object} options A set of Backbone-like options for creating the * object. The only option currently supported is "collection". * @see Bmob.Object.extend * * *

The fundamental unit of Bmob data, which implements the Backbone Model * interface.

*/ Bmob.Object = function (attributes, options) { // Allow new Bmob.Object("ClassName") as a shortcut to _create. if (_.isString(attributes)) { return Bmob.Object._create.apply(this, arguments); } attributes = attributes || {}; if (options && options.parse) { attributes = this.parse(attributes); } var defaults = Bmob._getValue(this, 'defaults'); if (defaults) { attributes = _.extend({}, defaults, attributes); } if (options && options.collection) { this.collection = options.collection; } this._serverData = {}; // The last known data for this object from cloud. this._opSetQueue = [{}]; // List of sets of changes to the data. this.attributes = {}; // The best estimate of this's current data. this._hashedJSON = {}; // Hash of values of containers at last save. this._escapedAttributes = {}; this.cid = _.uniqueId('c'); this.changed = {}; this._silent = {}; this._pending = {}; if (!this.set(attributes, { silent: true })) { throw new Error("Can't create an invalid Bmob.Object"); } this.changed = {}; this._silent = {}; this._pending = {}; this._hasData = true; this._previousAttributes = _.clone(this.attributes); this.initialize.apply(this, arguments); }; /** * @lends Bmob.Object.prototype * @property {String} id The objectId of the Bmob Object. */ /** * Saves the given list of Bmob.Object. * If any error is encountered, stops and calls the error handler. * There are two ways you can call this function. * * The Backbone way:
   *   Bmob.Object.saveAll([object1, object2, ...], {
   *     success: function(list) {
   *       // All the objects were saved.
   *     },
   *     error: function(error) {
   *       // An error occurred while saving one of the objects.
   *     },
   *   });
   * 
* A simplified syntax:
   *   Bmob.Object.saveAll([object1, object2, ...], function(list, error) {
   *     if (list) {
   *       // All the objects were saved.
   *     } else {
   *       // An error occurred.
   *     }
   *   });
   * 
* * @param {Array} list A list of Bmob.Object. * @param {Object} options A Backbone-style callback object. */ Bmob.Object.saveAll = function (list, options) { return Bmob.Object._deepSaveAsync(list)._thenRunCallbacks(options); }; // Attach all inheritable methods to the Bmob.Object prototype. _.extend(Bmob.Object.prototype, Bmob.Events, /** @lends Bmob.Object.prototype */ { _existed: false, _fetchWhenSave: false, /** * Initialize is an empty function by default. Override it with your own * initialization logic. */ initialize: function () { }, /** * Set whether to enable fetchWhenSave option when updating object. * When set true, SDK would fetch the latest object after saving. * Default is false. * @param {boolean} enable true to enable fetchWhenSave option. */ fetchWhenSave: function (enable) { if (typeof enable !== 'boolean') { throw "Expect boolean value for fetchWhenSave"; } this._fetchWhenSave = enable; }, /** * Returns a JSON version of the object suitable for saving to Bmob. * @return {Object} */ toJSON: function () { var json = this._toFullJSON(); Bmob._arrayEach(["__type", "className"], function (key) { delete json[key]; }); return json; }, _toFullJSON: function (seenObjects) { var json = _.clone(this.attributes); Bmob._objectEach(json, function (val, key) { json[key] = Bmob._encode(val, seenObjects); }); Bmob._objectEach(this._operations, function (val, key) { json[key] = val; }); if (_.has(this, "id")) { json.objectId = this.id; } if (_.has(this, "createdAt")) { if (_.isDate(this.createdAt)) { json.createdAt = this.createdAt.toJSON(); } else { json.createdAt = this.createdAt; } } if (_.has(this, "updatedAt")) { if (_.isDate(this.updatedAt)) { json.updatedAt = this.updatedAt.toJSON(); } else { json.updatedAt = this.updatedAt; } } json.__type = "Object"; json.className = this.className; return json; }, /** * Updates _hashedJSON to reflect the current state of this object. * Adds any changed hash values to the set of pending changes. */ _refreshCache: function () { var self = this; if (self._refreshingCache) { return; } self._refreshingCache = true; Bmob._objectEach(this.attributes, function (value, key) { if (value instanceof Bmob.Object) { value._refreshCache(); } else if (_.isObject(value)) { if (self._resetCacheForKey(key)) { self.set(key, new Bmob.Op.Set(value), { silent: true }); } } }); delete self._refreshingCache; }, /** * Returns true if this object has been modified since its last * save/refresh. If an attribute is specified, it returns true only if that * particular attribute has been modified since the last save/refresh. * @param {String} attr An attribute name (optional). * @return {Boolean} */ dirty: function (attr) { this._refreshCache(); var currentChanges = _.last(this._opSetQueue); if (attr) { return (currentChanges[attr] ? true : false); } if (!this.id) { return true; } if (_.keys(currentChanges).length > 0) { return true; } return false; }, /** * Gets a Pointer referencing this Object. */ _toPointer: function () { // if (!this.id) { // throw new Error("Can't serialize an unsaved Bmob.Object"); // } return { __type: "Pointer", className: this.className, objectId: this.id }; }, /** * Gets the value of an attribute. * @param {String} attr The string name of an attribute. */ get: function (attr) { return this.attributes[attr]; }, /** * Gets a relation on the given class for the attribute. * @param String attr The attribute to get the relation for. */ relation: function (attr) { var value = this.get(attr); if (value) { if (!(value instanceof Bmob.Relation)) { throw "Called relation() on non-relation field " + attr; } value._ensureParentAndKey(this, attr); return value; } else { return new Bmob.Relation(this, attr); } }, /** * Gets the HTML-escaped value of an attribute. */ escape: function (attr) { var html = this._escapedAttributes[attr]; if (html) { return html; } var val = this.attributes[attr]; var escaped; if (Bmob._isNullOrUndefined(val)) { escaped = ''; } else { escaped = _.escape(val.toString()); } this._escapedAttributes[attr] = escaped; return escaped; }, /** * Returns true if the attribute contains a value that is not * null or undefined. * @param {String} attr The string name of the attribute. * @return {Boolean} */ has: function (attr) { return !Bmob._isNullOrUndefined(this.attributes[attr]); }, /** * Pulls "special" fields like objectId, createdAt, etc. out of attrs * and puts them on "this" directly. Removes them from attrs. * @param attrs - A dictionary with the data for this Bmob.Object. */ _mergeMagicFields: function (attrs) { // Check for changes of magic fields. var model = this; var specialFields = ["id", "objectId", "createdAt", "updatedAt"]; Bmob._arrayEach(specialFields, function (attr) { if (attrs[attr]) { if (attr === "objectId") { model.id = attrs[attr]; } else { model[attr] = attrs[attr]; } delete attrs[attr]; } }); }, /** * Returns the json to be sent to the server. */ _startSave: function () { this._opSetQueue.push({}); }, /** * Called when a save fails because of an error. Any changes that were part * of the save need to be merged with changes made after the save. This * might throw an exception is you do conflicting operations. For example, * if you do: * object.set("foo", "bar"); * object.set("invalid field name", "baz"); * object.save(); * object.increment("foo"); * then this will throw when the save fails and the client tries to merge * "bar" with the +1. */ _cancelSave: function () { var self = this; var failedChanges = _.first(this._opSetQueue); this._opSetQueue = _.rest(this._opSetQueue); var nextChanges = _.first(this._opSetQueue); Bmob._objectEach(failedChanges, function (op, key) { var op1 = failedChanges[key]; var op2 = nextChanges[key]; if (op1 && op2) { nextChanges[key] = op2._mergeWithPrevious(op1); } else if (op1) { nextChanges[key] = op1; } }); this._saving = this._saving - 1; }, /** * Called when a save completes successfully. This merges the changes that * were saved into the known server data, and overrides it with any data * sent directly from the server. */ _finishSave: function (serverData) { // Grab a copy of any object referenced by this object. These instances // may have already been fetched, and we don't want to lose their data. // Note that doing it like this means we will unify separate copies of the // same object, but that's a risk we have to take. var fetchedObjects = {}; Bmob._traverse(this.attributes, function (object) { if (object instanceof Bmob.Object && object.id && object._hasData) { fetchedObjects[object.id] = object; } }); var savedChanges = _.first(this._opSetQueue); this._opSetQueue = _.rest(this._opSetQueue); this._applyOpSet(savedChanges, this._serverData); this._mergeMagicFields(serverData); var self = this; Bmob._objectEach(serverData, function (value, key) { self._serverData[key] = Bmob._decode(key, value); // Look for any objects that might have become unfetched and fix them // by replacing their values with the previously observed values. var fetched = Bmob._traverse(self._serverData[key], function (object) { if (object instanceof Bmob.Object && fetchedObjects[object.id]) { return fetchedObjects[object.id]; } }); if (fetched) { self._serverData[key] = fetched; } }); this._rebuildAllEstimatedData(); this._saving = this._saving - 1; }, /** * Called when a fetch or login is complete to set the known server data to * the given object. */ _finishFetch: function (serverData, hasData) { // Clear out any changes the user might have made previously. this._opSetQueue = [{}]; // Bring in all the new server data. this._mergeMagicFields(serverData); var self = this; Bmob._objectEach(serverData, function (value, key) { self._serverData[key] = Bmob._decode(key, value); }); // Refresh the attributes. this._rebuildAllEstimatedData(); // Clear out the cache of mutable containers. this._refreshCache(); this._opSetQueue = [{}]; this._hasData = hasData; }, /** * Applies the set of Bmob.Op in opSet to the object target. */ _applyOpSet: function (opSet, target) { var self = this; Bmob._objectEach(opSet, function (change, key) { target[key] = change._estimate(target[key], self, key); if (target[key] === Bmob.Op._UNSET) { delete target[key]; } }); }, /** * Replaces the cached value for key with the current value. * Returns true if the new value is different than the old value. */ _resetCacheForKey: function (key) { var value = this.attributes[key]; if (_.isObject(value) && !(value instanceof Bmob.Object) && !(value instanceof Bmob.File)) { value = value.toJSON ? value.toJSON() : value; var json = JSON.stringify(value); if (this._hashedJSON[key] !== json) { this._hashedJSON[key] = json; return true; } } return false; }, /** * Populates attributes[key] by starting with the last known data from the * server, and applying all of the local changes that have been made to that * key since then. */ _rebuildEstimatedDataForKey: function (key) { var self = this; delete this.attributes[key]; if (this._serverData[key]) { this.attributes[key] = this._serverData[key]; } Bmob._arrayEach(this._opSetQueue, function (opSet) { var op = opSet[key]; if (op) { self.attributes[key] = op._estimate(self.attributes[key], self, key); if (self.attributes[key] === Bmob.Op._UNSET) { delete self.attributes[key]; } else { self._resetCacheForKey(key); } } }); }, /** * Populates attributes by starting with the last known data from the * server, and applying all of the local changes that have been made since * then. */ _rebuildAllEstimatedData: function () { var self = this; var previousAttributes = _.clone(this.attributes); this.attributes = _.clone(this._serverData); Bmob._arrayEach(this._opSetQueue, function (opSet) { self._applyOpSet(opSet, self.attributes); Bmob._objectEach(opSet, function (op, key) { self._resetCacheForKey(key); }); }); // Trigger change events for anything that changed because of the fetch. Bmob._objectEach(previousAttributes, function (oldValue, key) { if (self.attributes[key] !== oldValue) { self.trigger('change:' + key, self, self.attributes[key], {}); } }); Bmob._objectEach(this.attributes, function (newValue, key) { if (!_.has(previousAttributes, key)) { self.trigger('change:' + key, self, newValue, {}); } }); }, /** * Sets a hash of model attributes on the object, firing * "change" unless you choose to silence it. * *

You can call it with an object containing keys and values, or with one * key and value. For example:

         *   gameTurn.set({
         *     player: player1,
         *     diceRoll: 2
         *   }, {
         *     error: function(gameTurnAgain, error) {
         *       // The set failed validation.
         *     }
         *   });
         *
         *   game.set("currentPlayer", player2, {
         *     error: function(gameTurnAgain, error) {
         *       // The set failed validation.
         *     }
         *   });
         *
         *   game.set("finished", true);

* * @param {String} key The key to set. * @param {} value The value to give it. * @param {Object} options A set of Backbone-like options for the set. * The only supported options are silent, * error, and promise. * @return {Boolean} true if the set succeeded. * @see Bmob.Object#validate * @see Bmob.Error */ set: function (key, value, options) { var attrs, attr; if (_.isObject(key) || Bmob._isNullOrUndefined(key)) { attrs = key; Bmob._objectEach(attrs, function (v, k) { attrs[k] = Bmob._decode(k, v); }); options = value; } else { attrs = {}; attrs[key] = Bmob._decode(key, value); } // Extract attributes and options. options = options || {}; if (!attrs) { return this; } if (attrs instanceof Bmob.Object) { attrs = attrs.attributes; } // If the unset option is used, every attribute should be a Unset. if (options.unset) { Bmob._objectEach(attrs, function (unused_value, key) { attrs[key] = new Bmob.Op.Unset(); }); } // Apply all the attributes to get the estimated values. var dataToValidate = _.clone(attrs); var self = this; Bmob._objectEach(dataToValidate, function (value, key) { if (value instanceof Bmob.Op) { dataToValidate[key] = value._estimate(self.attributes[key], self, key); if (dataToValidate[key] === Bmob.Op._UNSET) { delete dataToValidate[key]; } } }); // Run validation. if (!this._validate(attrs, options)) { return false; } this._mergeMagicFields(attrs); options.changes = {}; var escaped = this._escapedAttributes; var prev = this._previousAttributes || {}; // Update attributes. Bmob._arrayEach(_.keys(attrs), function (attr) { var val = attrs[attr]; // If this is a relation object we need to set the parent correctly, // since the location where it was parsed does not have access to // this object. if (val instanceof Bmob.Relation) { val.parent = self; } if (!(val instanceof Bmob.Op)) { val = new Bmob.Op.Set(val); } // See if this change will actually have any effect. var isRealChange = true; if (val instanceof Bmob.Op.Set && _.isEqual(self.attributes[attr], val.value)) { isRealChange = false; } if (isRealChange) { delete escaped[attr]; if (options.silent) { self._silent[attr] = true; } else { options.changes[attr] = true; } } var currentChanges = _.last(self._opSetQueue); currentChanges[attr] = val._mergeWithPrevious(currentChanges[attr]); self._rebuildEstimatedDataForKey(attr); if (isRealChange) { self.changed[attr] = self.attributes[attr]; if (!options.silent) { self._pending[attr] = true; } } else { delete self.changed[attr]; delete self._pending[attr]; } }); if (!options.silent) { this.change(options); } return this; }, /** * Remove an attribute from the model, firing "change" unless * you choose to silence it. This is a noop if the attribute doesn't * exist. */ unset: function (attr, options) { options = options || {}; options.unset = true; return this.set(attr, null, options); }, /** * Atomically increments the value of the given attribute the next time the * object is saved. If no amount is specified, 1 is used by default. * * @param attr {String} The key. * @param amount {Number} The amount to increment by. */ increment: function (attr, amount) { if (_.isUndefined(amount) || _.isNull(amount)) { amount = 1; } return this.set(attr, new Bmob.Op.Increment(amount)); }, /** * Atomically add an object to the end of the array associated with a given * key. * @param attr {String} The key. * @param item {} The item to add. */ add: function (attr, item) { return this.set(attr, new Bmob.Op.Add([item])); }, /** * Atomically add an object to the array associated with a given key, only * if it is not already present in the array. The position of the insert is * not guaranteed. * * @param attr {String} The key. * @param item {} The object to add. */ addUnique: function (attr, item) { return this.set(attr, new Bmob.Op.AddUnique([item])); }, /** * Atomically remove all instances of an object from the array associated * with a given key. * * @param attr {String} The key. * @param item {} The object to remove. */ remove: function (attr, item) { return this.set(attr, new Bmob.Op.Remove([item])); }, /** * Returns an instance of a subclass of Bmob.Op describing what kind of * modification has been performed on this field since the last time it was * saved. For example, after calling object.increment("x"), calling * object.op("x") would return an instance of Bmob.Op.Increment. * * @param attr {String} The key. * @returns {Bmob.Op} The operation, or undefined if none. */ op: function (attr) { return _.last(this._opSetQueue)[attr]; }, /** * Clear all attributes on the model, firing "change" unless * you choose to silence it. */ clear: function (options) { options = options || {}; options.unset = true; var keysToClear = _.extend(this.attributes, this._operations); return this.set(keysToClear, options); }, /** * Returns a JSON-encoded set of operations to be sent with the next save * request. */ _getSaveJSON: function () { var json = _.clone(_.first(this._opSetQueue)); Bmob._objectEach(json, function (op, key) { json[key] = op.toJSON(); }); return json; }, /** * Returns true if this object can be serialized for saving. */ _canBeSerialized: function () { return Bmob.Object._canBeSerializedAsValue(this.attributes); }, /** * Fetch the model from the server. If the server's representation of the * model differs from its current attributes, they will be overriden, * triggering a "change" event. * @return {Bmob.Promise} A promise that is fulfilled when the fetch * completes. */ fetch: function (options) { var self = this; var request = Bmob._request("classes", this.className, this.id, 'GET'); return request.then(function (response, status, xhr) { self._finishFetch(self.parse(response, status, xhr), true); return self; })._thenRunCallbacks(options, this); }, /** * Set a hash of model attributes, and save the model to the server. * updatedAt will be updated when the request returns. * You can either call it as:
         *   object.save();
* or
         *   object.save(null, options);
* or
         *   object.save(attrs, options);
* or
         *   object.save(key, value, options);
* * For example,
         *   gameTurn.save({
         *     player: "Jake Cutter",
         *     diceRoll: 2
         *   }, {
         *     success: function(gameTurnAgain) {
         *       // The save was successful.
         *     },
         *     error: function(gameTurnAgain, error) {
         *       // The save failed.  Error is an instance of Bmob.Error.
         *     }
         *   });
* or with promises:
         *   gameTurn.save({
         *     player: "Jake Cutter",
         *     diceRoll: 2
         *   }).then(function(gameTurnAgain) {
         *     // The save was successful.
         *   }, function(error) {
         *     // The save failed.  Error is an instance of Bmob.Error.
         *   });
* * @return {Bmob.Promise} A promise that is fulfilled when the save * completes. * @see Bmob.Error */ save: function (arg1, arg2, arg3) { var i, attrs, current, options, saved; if (_.isObject(arg1) || Bmob._isNullOrUndefined(arg1)) { attrs = arg1; options = arg2; } else { attrs = {}; attrs[arg1] = arg2; options = arg3; } // Make save({ success: function() {} }) work. if (!options && attrs) { var extra_keys = _.reject(attrs, function (value, key) { return _.include(["success", "error", "wait"], key); }); if (extra_keys.length === 0) { var all_functions = true; if (_.has(attrs, "success") && !_.isFunction(attrs.success)) { all_functions = false; } if (_.has(attrs, "error") && !_.isFunction(attrs.error)) { all_functions = false; } if (all_functions) { // This attrs object looks like it's really an options object, // and there's no other options object, so let's just use it. return this.save(null, attrs); } } } options = _.clone(options) || {}; if (options.wait) { current = _.clone(this.attributes); } var setOptions = _.clone(options) || {}; if (setOptions.wait) { setOptions.silent = true; } var setError; setOptions.error = function (model, error) { setError = error; }; if (attrs && !this.set(attrs, setOptions)) { return Bmob.Promise.error(setError)._thenRunCallbacks(options, this); } var model = this; // If there is any unsaved child, save it first. model._refreshCache(); var unsavedChildren = []; var unsavedFiles = []; Bmob.Object._findUnsavedChildren(model.attributes, unsavedChildren, unsavedFiles); if (unsavedChildren.length + unsavedFiles.length > 0) { return Bmob.Object._deepSaveAsync(this.attributes).then(function () { return model.save(null, options); }, function (error) { return Bmob.Promise.error(error)._thenRunCallbacks(options, model); }); } this._startSave(); this._saving = (this._saving || 0) + 1; this._allPreviousSaves = this._allPreviousSaves || Bmob.Promise.as(); this._allPreviousSaves = this._allPreviousSaves._continueWith(function () { var method = model.id ? 'PUT' : 'POST'; var json = model._getSaveJSON(); if (method === 'PUT' && model._fetchWhenSave) { //Sepcial-case fetchWhenSave when updating object. json._fetchWhenSave = true; } var route = "classes"; var className = model.className; if (model.className === "_User" && !model.id) { // Special-case user sign-up. route = "users"; className = null; } var request = Bmob._request(route, className, model.id, method, json); request = request.then(function (resp, status, xhr) { var serverAttrs = model.parse(resp, status, xhr); if (options.wait) { serverAttrs = _.extend(attrs || {}, serverAttrs); } model._finishSave(serverAttrs); if (options.wait) { model.set(current, setOptions); } return model; }, function (error) { model._cancelSave(); return Bmob.Promise.error(error); })._thenRunCallbacks(options, model); return request; }); return this._allPreviousSaves; }, /** * Destroy this model on the server if it was already persisted. * Optimistically removes the model from its collection, if it has one. * If `wait: true` is passed, waits for the server to respond * before removal. * * @return {Bmob.Promise} A promise that is fulfilled when the destroy * completes. */ destroy: function (options) { options = options || {}; var model = this; var triggerDestroy = function () { model.trigger('destroy', model, model.collection, options); }; if (!this.id) { return triggerDestroy(); } if (!options.wait) { triggerDestroy(); } var request = Bmob._request("classes", this.className, this.id, 'DELETE'); return request.then(function () { if (options.wait) { triggerDestroy(); } return model; })._thenRunCallbacks(options, this); }, /** * Converts a response into the hash of attributes to be set on the model. * @ignore */ parse: function (resp, status, xhr) { var output = _.clone(resp); _(["createdAt", "updatedAt"]).each(function (key) { if (output[key]) { output[key] = output[key]; } }); if (!output.updatedAt) { output.updatedAt = output.createdAt; } if (status) { this._existed = (status !== 201); } return output; }, /** * Creates a new model with identical attributes to this one. * @return {Bmob.Object} */ clone: function () { return new this.constructor(this.attributes); }, /** * Returns true if this object has never been saved to Bmob. * @return {Boolean} */ isNew: function () { return !this.id; }, /** * Call this method to manually fire a `"change"` event for this model and * a `"change:attribute"` event for each changed attribute. * Calling this will cause all objects observing the model to update. */ change: function (options) { options = options || {}; var changing = this._changing; this._changing = true; // Silent changes become pending changes. var self = this; Bmob._objectEach(this._silent, function (attr) { self._pending[attr] = true; }); // Silent changes are triggered. var changes = _.extend({}, options.changes, this._silent); this._silent = {}; Bmob._objectEach(changes, function (unused_value, attr) { self.trigger('change:' + attr, self, self.get(attr), options); }); if (changing) { return this; } // This is to get around lint not letting us make a function in a loop. var deleteChanged = function (value, attr) { if (!self._pending[attr] && !self._silent[attr]) { delete self.changed[attr]; } }; // Continue firing `"change"` events while there are pending changes. while (!_.isEmpty(this._pending)) { this._pending = {}; this.trigger('change', this, options); // Pending and silent changes still remain. Bmob._objectEach(this.changed, deleteChanged); self._previousAttributes = _.clone(this.attributes); } this._changing = false; return this; }, /** * Returns true if this object was created by the Bmob server when the * object might have already been there (e.g. in the case of a Facebook * login) */ existed: function () { return this._existed; }, /** * Determine if the model has changed since the last "change" * event. If you specify an attribute name, determine if that attribute * has changed. * @param {String} attr Optional attribute name * @return {Boolean} */ hasChanged: function (attr) { if (!arguments.length) { return !_.isEmpty(this.changed); } return this.changed && _.has(this.changed, attr); }, /** * Returns an object containing all the attributes that have changed, or * false if there are no changed attributes. Useful for determining what * parts of a view need to be updated and/or what attributes need to be * persisted to the server. Unset attributes will be set to undefined. * You can also pass an attributes object to diff against the model, * determining if there *would be* a change. */ changedAttributes: function (diff) { if (!diff) { return this.hasChanged() ? _.clone(this.changed) : false; } var changed = {}; var old = this._previousAttributes; Bmob._objectEach(diff, function (diffVal, attr) { if (!_.isEqual(old[attr], diffVal)) { changed[attr] = diffVal; } }); return changed; }, /** * Gets the previous value of an attribute, recorded at the time the last * "change" event was fired. * @param {String} attr Name of the attribute to get. */ previous: function (attr) { if (!arguments.length || !this._previousAttributes) { return null; } return this._previousAttributes[attr]; }, /** * Gets all of the attributes of the model at the time of the previous * "change" event. * @return {Object} */ previousAttributes: function () { return _.clone(this._previousAttributes); }, /** * Checks if the model is currently in a valid state. It's only possible to * get into an *invalid* state if you're using silent changes. * @return {Boolean} */ isValid: function () { return !this.validate(this.attributes); }, /** * You should not call this function directly unless you subclass * Bmob.Object, in which case you can override this method * to provide additional validation on set and * save. Your implementation should return * * @param {Object} attrs The current data to validate. * @param {Object} options A Backbone-like options object. * @return {} False if the data is valid. An error object otherwise. * @see Bmob.Object#set */ validate: function (attrs, options) { if (_.has(attrs, "ACL") && !(attrs.ACL instanceof Bmob.ACL)) { return new Bmob.Error(Bmob.Error.OTHER_CAUSE, "ACL must be a Bmob.ACL."); } return false; }, /** * Run validation against a set of incoming attributes, returning `true` * if all is well. If a specific `error` callback has been passed, * call that instead of firing the general `"error"` event. */ _validate: function (attrs, options) { if (options.silent || !this.validate) { return true; } attrs = _.extend({}, this.attributes, attrs); var error = this.validate(attrs, options); if (!error) { return true; } if (options && options.error) { options.error(this, error, options); } else { this.trigger('error', this, error, options); } return false; }, /** * Returns the ACL for this object. * @returns {Bmob.ACL} An instance of Bmob.ACL. * @see Bmob.Object#get */ getACL: function () { return this.get("ACL"); }, /** * Sets the ACL to be used for this object. * @param {Bmob.ACL} acl An instance of Bmob.ACL. * @param {Object} options Optional Backbone-like options object to be * passed in to set. * @return {Boolean} Whether the set passed validation. * @see Bmob.Object#set */ setACL: function (acl, options) { return this.set("ACL", acl, options); } }); /** * Creates an instance of a subclass of Bmob.Object for the give classname * and id. * @param {String} className The name of the Bmob class backing this model. * @param {String} id The object id of this model. * @return {Bmob.Object} A new subclass instance of Bmob.Object. */ Bmob.Object.createWithoutData = function (className, id, hasData) { var result = new Bmob.Object(className); result.id = id; result._hasData = hasData; return result; }; /** * Delete objects in batch.The objects className must be the same. * @param {Array} The ParseObject array to be deleted. * @param {Object} options Standard options object with success and error * callbacks. * @return {Bmob.Promise} A promise that is fulfilled when the save * completes. */ Bmob.Object.destroyAll = function (objects, options) { if (objects == null || objects.length == 0) { return Bmob.Promise.as()._thenRunCallbacks(options); } var className = objects[0].className; var id = ""; var wasFirst = true; objects.forEach(function (obj) { if (obj.className != className) throw "Bmob.Object.destroyAll requires the argument object array's classNames must be the same"; if (!obj.id) throw "Could not delete unsaved object"; if (wasFirst) { id = obj.id; wasFirst = false; } else { id = id + ',' + obj.id; } }); var request = Bmob._request("classes", className, id, 'DELETE'); return request._thenRunCallbacks(options); }; /** * Returns the appropriate subclass for making new instances of the given * className string. */ Bmob.Object._getSubclass = function (className) { if (!_.isString(className)) { throw "Bmob.Object._getSubclass requires a string argument."; } var ObjectClass = Bmob.Object._classMap[className]; if (!ObjectClass) { ObjectClass = Bmob.Object.extend(className); Bmob.Object._classMap[className] = ObjectClass; } return ObjectClass; }; /** * Creates an instance of a subclass of Bmob.Object for the given classname. */ Bmob.Object._create = function (className, attributes, options) { var ObjectClass = Bmob.Object._getSubclass(className); return new ObjectClass(attributes, options); }; // Set up a map of className to class so that we can create new instances of // Bmob Objects from JSON automatically. Bmob.Object._classMap = {}; Bmob.Object._extend = Bmob._extend; /** * Creates a new subclass of Bmob.Object for the given Bmob class name. * *

Every extension of a Bmob class will inherit from the most recent * previous extension of that class. When a Bmob.Object is automatically * created by parsing JSON, it will use the most recent extension of that * class.

* *

You should call either:

   *     var MyClass = Bmob.Object.extend("MyClass", {
   *         Instance properties
   *     }, {
   *         Class properties
   *     });
* or, for Backbone compatibility:
   *     var MyClass = Bmob.Object.extend({
   *         className: "MyClass",
   *         Other instance properties
   *     }, {
   *         Class properties
   *     });

* * @param {String} className The name of the Bmob class backing this model. * @param {Object} protoProps Instance properties to add to instances of the * class returned from this method. * @param {Object} classProps Class properties to add the class returned from * this method. * @return {Class} A new subclass of Bmob.Object. */ Bmob.Object.extend = function (className, protoProps, classProps) { // Handle the case with only two args. if (!_.isString(className)) { if (className && _.has(className, "className")) { return Bmob.Object.extend(className.className, className, protoProps); } else { throw new Error("Bmob.Object.extend's first argument should be the className."); } } // If someone tries to subclass "User", coerce it to the right type. if (className === "User") { className = "_User"; } var NewClassObject = null; if (_.has(Bmob.Object._classMap, className)) { var OldClassObject = Bmob.Object._classMap[className]; // This new subclass has been told to extend both from "this" and from // OldClassObject. This is multiple inheritance, which isn't supported. // For now, let's just pick one. NewClassObject = OldClassObject._extend(protoProps, classProps); } else { protoProps = protoProps || {}; protoProps.className = className; NewClassObject = this._extend(protoProps, classProps); } // Extending a subclass should reuse the classname automatically. NewClassObject.extend = function (arg0) { if (_.isString(arg0) || (arg0 && _.has(arg0, "className"))) { return Bmob.Object.extend.apply(NewClassObject, arguments); } var newArguments = [className].concat(Bmob._.toArray(arguments)); return Bmob.Object.extend.apply(NewClassObject, newArguments); }; Bmob.Object._classMap[className] = NewClassObject; return NewClassObject; }; Bmob.Object._findUnsavedChildren = function (object, children, files) { Bmob._traverse(object, function (object) { if (object instanceof Bmob.Object) { object._refreshCache(); if (object.dirty()) { children.push(object); } return; } if (object instanceof Bmob.File) { if (!object.url()) { files.push(object); } return; } }); }; Bmob.Object._canBeSerializedAsValue = function (object) { var canBeSerializedAsValue = true; if (object instanceof Bmob.Object) { canBeSerializedAsValue = !!object.id; } else if (_.isArray(object)) { Bmob._arrayEach(object, function (child) { if (!Bmob.Object._canBeSerializedAsValue(child)) { canBeSerializedAsValue = false; } }); } else if (_.isObject(object)) { Bmob._objectEach(object, function (child) { if (!Bmob.Object._canBeSerializedAsValue(child)) { canBeSerializedAsValue = false; } }); } return canBeSerializedAsValue; }; Bmob.Object._deepSaveAsync = function (object) { var unsavedChildren = []; var unsavedFiles = []; Bmob.Object._findUnsavedChildren(object, unsavedChildren, unsavedFiles); var promise = Bmob.Promise.as(); _.each(unsavedFiles, function (file) { promise = promise.then(function () { return file.save(); }); }); var objects = _.uniq(unsavedChildren); var remaining = _.uniq(objects); return promise.then(function () { return Bmob.Promise._continueWhile(function () { return remaining.length > 0; }, function () { // Gather up all the objects that can be saved in this batch. var batch = []; var newRemaining = []; Bmob._arrayEach(remaining, function (object) { // Limit batches to 20 objects. if (batch.length > 20) { newRemaining.push(object); return; } if (object._canBeSerialized()) { batch.push(object); } else { newRemaining.push(object); } }); remaining = newRemaining; // If we can't save any objects, there must be a circular reference. if (batch.length === 0) { return Bmob.Promise.error(new Bmob.Error(Bmob.Error.OTHER_CAUSE, "Tried to save a batch with a cycle.")); } // Reserve a spot in every object's save queue. var readyToStart = Bmob.Promise.when(_.map(batch, function (object) { return object._allPreviousSaves || Bmob.Promise.as(); })); var batchFinished = new Bmob.Promise(); Bmob._arrayEach(batch, function (object) { object._allPreviousSaves = batchFinished; }); // Save a single batch, whether previous saves succeeded or failed. return readyToStart._continueWith(function () { return Bmob._request("batch", null, null, "POST", { requests: _.map(batch, function (object) { var json = object._getSaveJSON(); var method = "POST"; var path = "/1/classes/" + object.className; if (object.id) { path = path + "/" + object.id; method = "PUT"; } object._startSave(); return { method: method, path: path, body: json }; }) }).then(function (response, status, xhr) { var error; Bmob._arrayEach(batch, function (object, i) { if (response[i].success) { object._finishSave(object.parse(response[i].success, status, xhr)); } else { error = error || response[i].error; object._cancelSave(); } }); if (error) { return Bmob.Promise.error(new Bmob.Error(error.code, error.error)); } }).then(function (results) { batchFinished.resolve(results); return results; }, function (error) { batchFinished.reject(error); return Bmob.Promise.error(error); }); }); }); }).then(function () { return object; }); }; /** * Bmob.Role acl权限控制中的用户角色类 * *

角色必须要有名称(名称创建后不能修改), 同时必须指定ACL

* @class * @namespace acl权限控制中的用户角色类 */ Bmob.Role = Bmob.Object.extend("_Role", /** @lends Bmob.Role.prototype */ { // Instance Methods /** * 通过名称和ACL构造一个BmobRole * @param {String} name 创建role的名称 * @param {Bmob.ACL} acl 这个角色的acl,角色必须要有一个ACL。 */ constructor: function (name, acl) { if (_.isString(name) && (acl instanceof Bmob.ACL)) { Bmob.Object.prototype.constructor.call(this, null, null); this.setName(name); this.setACL(acl); } else { Bmob.Object.prototype.constructor.call(this, name, acl); } }, /** * 获取角色的name。同时可以使用role.get("name") * @return {String} 角色的名称 */ getName: function () { return this.get("name"); }, /** * 设置角色的名称。这个值必须要在保存前设置,而且只能设置一次 *

* 角色的名称只能包含数字,字母, _, -。 *

* *

等同于使用 role.set("name", name)

* @param {String} name 角色的名称 * @param {Object} options 标准options对象 */ setName: function (name, options) { return this.set("name", name, options); }, /** * 获取这个角色对应的用户Bmob.Users。这些用户已经被分配了权限(例如读写的权限)。 * 你能通过relation添加和移除这些用户 *

这等同于使用 role.relation("users")

* * @return {Bmob.Relation} the relation for the users belonging to this * role. */ getUsers: function () { return this.relation("users"); }, /** * 获取这个角色对应的角色Bmob.Roles。这些用户已经被分配了权限(例如读写的权限)。 * 你能通过relation添加和移除这些用户 *

这等同于使用 role.relation("roles")

* * @return {Bmob.Relation} the relation for the roles belonging to this * role. */ getRoles: function () { return this.relation("roles"); }, /** * @ignore */ validate: function (attrs, options) { if ("name" in attrs && attrs.name !== this.getName()) { var newName = attrs.name; if (this.id && this.id !== attrs.objectId) { // Check to see if the objectId being set matches this.id. // This happens during a fetch -- the id is set before calling fetch. // Let the name be set in this case. return new Bmob.Error(Bmob.Error.OTHER_CAUSE, "A role's name can only be set before it has been saved."); } if (!_.isString(newName)) { return new Bmob.Error(Bmob.Error.OTHER_CAUSE, "A role's name must be a String."); } if (!(/^[0-9a-zA-Z\-_ ]+$/).test(newName)) { return new Bmob.Error(Bmob.Error.OTHER_CAUSE, "A role's name can only contain alphanumeric characters, _," + " -, and spaces."); } } if (Bmob.Object.prototype.validate) { return Bmob.Object.prototype.validate.call(this, attrs, options); } return false; } }); /** *创建model和options的实体。特别地,你不会直接调用这个方法,你会Bmob.Collection.extend通过创建一*个子类。 * @param {Array} Bmob.Object数组. * * @param {Object} options Backbone-style options 的可选options object. * 有效的 options * * @see Bmob.Collection.extend * * *

提供标准的 collection class。 更详细的信息请看 * Backbone * documentation.

*/ Bmob.Collection = function (models, options) { options = options || {}; if (options.comparator) { this.comparator = options.comparator; } if (options.model) { this.model = options.model; } if (options.query) { this.query = options.query; } this._reset(); this.initialize.apply(this, arguments); if (models) { this.reset(models, { silent: true, parse: options.parse }); } }; // Define the Collection's inheritable methods. _.extend(Bmob.Collection.prototype, Bmob.Events, /** @lends Bmob.Collection.prototype */ { // The default model for a collection is just a Bmob.Object. // This should be overridden in most cases. model: Bmob.Object, /** * Initialize 默认是空函数. 请根据自身的逻辑重写这个方法 */ initialize: function () { }, /** * * json 格式的models'属性数组 */ toJSON: function () { return this.map(function (model) { return model.toJSON(); }); }, /** * 添加model,或者一系列的对象集合。传入**silent**避免触发`add`事件。 */ add: function (models, options) { var i, index, length, model, cid, id, cids = {}, ids = {}; options = options || {}; models = _.isArray(models) ? models.slice() : [models]; // Begin by turning bare objects into model references, and preventing // invalid models or duplicate models from being added. for (i = 0, length = models.length; i < length; i++) { models[i] = this._prepareModel(models[i], options); model = models[i]; if (!model) { throw new Error("Can't add an invalid model to a collection"); } cid = model.cid; if (cids[cid] || this._byCid[cid]) { throw new Error("Duplicate cid: can't add the same model " + "to a collection twice"); } id = model.id; if (!Bmob._isNullOrUndefined(id) && (ids[id] || this._byId[id])) { throw new Error("Duplicate id: can't add the same model " + "to a collection twice"); } ids[id] = model; cids[cid] = model; } // Listen to added models' events, and index models for lookup by // `id` and by `cid`. for (i = 0; i < length; i++) { (model = models[i]).on('all', this._onModelEvent, this); this._byCid[model.cid] = model; if (model.id) { this._byId[model.id] = model; } } // Insert models into the collection, re-sorting if needed, and triggering // `add` events unless silenced. this.length += length; index = Bmob._isNullOrUndefined(options.at) ? this.models.length : options.at; this.models.splice.apply(this.models, [index, 0].concat(models)); if (this.comparator) { this.sort({ silent: true }); } if (options.silent) { return this; } for (i = 0, length = this.models.length; i < length; i++) { model = this.models[i]; if (cids[model.cid]) { options.index = i; model.trigger('add', model, this, options); } } return this; }, /** * 移除一个model,或者从集合中移除一系列models。当移除对象时,传入silent避免触发remove事件。 */ remove: function (models, options) { var i, l, index, model; options = options || {}; models = _.isArray(models) ? models.slice() : [models]; for (i = 0, l = models.length; i < l; i++) { model = this.getByCid(models[i]) || this.get(models[i]); if (!model) { continue; } delete this._byId[model.id]; delete this._byCid[model.cid]; index = this.indexOf(model); this.models.splice(index, 1); this.length--; if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); } this._removeReference(model); } return this; }, /** * 通过id获取一个model */ get: function (id) { return id && this._byId[id.id || id]; }, /** * 通过client id获取一个model */ getByCid: function (cid) { return cid && this._byCid[cid.cid || cid]; }, /** * 通过下标获取一个model */ at: function (index) { return this.models[index]; }, /** * 强制collection对自身的元素进行重新排序。一般情况下你不需要调用这个函数,因为当添加对象时这个函数会自动调用 */ sort: function (options) { options = options || {}; if (!this.comparator) { throw new Error('Cannot sort a set without a comparator'); } var boundComparator = _.bind(this.comparator, this); if (this.comparator.length === 1) { this.models = this.sortBy(boundComparator); } else { this.models.sort(boundComparator); } if (!options.silent) { this.trigger('reset', this, options); } return this; }, /** * 采集集合中每个对象的属性 */ pluck: function (attr) { return _.map(this.models, function (model) { return model.get(attr); }); }, /** * When you have more items than you want to add or remove individually, * you can reset the entire set with a new list of models, without firing * any `add` or `remove` events. Fires `reset` when finished. */ reset: function (models, options) { var self = this; models = models || []; options = options || {}; Bmob._arrayEach(this.models, function (model) { self._removeReference(model); }); this._reset(); this.add(models, { silent: true, parse: options.parse }); if (!options.silent) { this.trigger('reset', this, options); } return this; }, /** * Fetches the default set of models for this collection, resetting the * collection when they arrive. If `add: true` is passed, appends the * models to the collection instead of resetting. */ fetch: function (options) { options = _.clone(options) || {}; if (options.parse === undefined) { options.parse = true; } var collection = this; var query = this.query || new Bmob.Query(this.model); return query.find().then(function (results) { if (options.add) { collection.add(results, options); } else { collection.reset(results, options); } return collection; })._thenRunCallbacks(options, this); }, /** * Creates a new instance of a model in this collection. Add the model to * the collection immediately, unless `wait: true` is passed, in which case * we wait for the server to agree. */ create: function (model, options) { var coll = this; options = options ? _.clone(options) : {}; model = this._prepareModel(model, options); if (!model) { return false; } if (!options.wait) { coll.add(model, options); } var success = options.success; options.success = function (nextModel, resp, xhr) { if (options.wait) { coll.add(nextModel, options); } if (success) { success(nextModel, resp); } else { nextModel.trigger('sync', model, resp, options); } }; model.save(null, options); return model; }, /** * Converts a response into a list of models to be added to the collection. * The default implementation is just to pass it through. * @ignore */ parse: function (resp, xhr) { return resp; }, /** * Proxy to _'s chain. Can't be proxied the same way the rest of the * underscore methods are proxied because it relies on the underscore * constructor. */ chain: function () { return _(this.models).chain(); }, /** * Reset all internal state. Called when the collection is reset. */ _reset: function (options) { this.length = 0; this.models = []; this._byId = {}; this._byCid = {}; }, /** * Prepare a model or hash of attributes to be added to this collection. */ _prepareModel: function (model, options) { if (!(model instanceof Bmob.Object)) { var attrs = model; options.collection = this; model = new this.model(attrs, options); if (!model._validate(model.attributes, options)) { model = false; } } else if (!model.collection) { model.collection = this; } return model; }, /** * Internal method to remove a model's ties to a collection. */ _removeReference: function (model) { if (this === model.collection) { delete model.collection; } model.off('all', this._onModelEvent, this); }, /** * Internal method called every time a model in the set fires an event. * Sets need to update their indexes when models change ids. All other * events simply proxy through. "add" and "remove" events that originate * in other collections are ignored. */ _onModelEvent: function (ev, model, collection, options) { if ((ev === 'add' || ev === 'remove') && collection !== this) { return; } if (ev === 'destroy') { this.remove(model, options); } if (model && ev === 'change:objectId') { delete this._byId[model.previous("objectId")]; this._byId[model.id] = model; } this.trigger.apply(this, arguments); } }); // Underscore methods that we want to implement on the Collection. var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; // Mix in each Underscore method as a proxy to `Collection#models`. Bmob._arrayEach(methods, function (method) { Bmob.Collection.prototype[method] = function () { return _[method].apply(_, [this.models].concat(_.toArray(arguments))); }; }); /** * Creates a new subclass of Bmob.Collection. For example,
   *   var MyCollection = Bmob.Collection.extend({
   *     // Instance properties
   *
   *     model: MyClass,
   *     query: MyQuery,
   *
   *     getFirst: function() {
   *       return this.at(0);
   *     }
   *   }, {
   *     // Class properties
   *
   *     makeOne: function() {
   *       return new MyCollection();
   *     }
   *   });
   *
   *   var collection = new MyCollection();
   * 
* * @function * @param {Object} instanceProps Instance properties for the collection. * @param {Object} classProps Class properies for the collection. * @return {Class} A new subclass of Bmob.Collection. */ Bmob.Collection.extend = Bmob._extend; /** * Creating a Bmob.View creates its initial element outside of the DOM, * if an existing element is not provided... * *

A fork of Backbone.View, provided for your convenience. If you use this * class, you must also include jQuery, or another library that provides a * jQuery-compatible $ function. For more information, see the * Backbone * documentation.

*

Available in the client SDK only.

*/ Bmob.View = function (options) { this.cid = _.uniqueId('view'); this._configure(options || {}); this._ensureElement(); this.initialize.apply(this, arguments); this.delegateEvents(); }; // Cached regex to split keys for `delegate`. var eventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; // Set up all inheritable **Bmob.View** properties and methods. _.extend(Bmob.View.prototype, Bmob.Events, /** @lends Bmob.View.prototype */ { // The default `tagName` of a View's element is `"div"`. tagName: 'div', /** * jQuery delegate for element lookup, scoped to DOM elements within the * current view. This should be prefered to global lookups where possible. */ $: function (selector) { return this.$el.find(selector); }, /** * Initialize is an empty function by default. Override it with your own * initialization logic. */ initialize: function () { }, /** * The core function that your view should override, in order * to populate its element (`this.el`), with the appropriate HTML. The * convention is for **render** to always return `this`. */ render: function () { return this; }, /** * Remove this view from the DOM. Note that the view isn't present in the * DOM by default, so calling this method may be a no-op. */ remove: function () { this.$el.remove(); return this; }, /** * For small amounts of DOM Elements, where a full-blown template isn't * needed, use **make** to manufacture elements, one at a time. *
         *     var el = this.make('li', {'class': 'row'},
         *                        this.model.escape('title'));
*/ make: function (tagName, attributes, content) { var el = document.createElement(tagName); if (attributes) { Bmob.$(el).attr(attributes); } if (content) { Bmob.$(el).html(content); } return el; }, /** * Changes the view's element (`this.el` property), including event * re-delegation. */ setElement: function (element, delegate) { this.$el = Bmob.$(element); this.el = this.$el[0]; if (delegate !== false) { this.delegateEvents(); } return this; }, /** * Set callbacks. this.events is a hash of *
         * *{"event selector": "callback"}*
         *
         *     {
         *       'mousedown .title':  'edit',
         *       'click .button':     'save'
         *       'click .open':       function(e) { ... }
         *     }
         * 
* pairs. Callbacks will be bound to the view, with `this` set properly. * Uses event delegation for efficiency. * Omitting the selector binds the event to `this.el`. * This only works for delegate-able events: not `focus`, `blur`, and * not `change`, `submit`, and `reset` in Internet Explorer. */ delegateEvents: function (events) { events = events || Bmob._getValue(this, 'events'); if (!events) { return; } this.undelegateEvents(); var self = this; Bmob._objectEach(events, function (method, key) { if (!_.isFunction(method)) { method = self[events[key]]; } if (!method) { throw new Error('Event "' + events[key] + '" does not exist'); } var match = key.match(eventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, self); eventName += '.delegateEvents' + self.cid; if (selector === '') { self.$el.bind(eventName, method); } else { self.$el.delegate(selector, eventName, method); } }); }, /** * Clears all callbacks previously bound to the view with `delegateEvents`. * You usually don't need to use this, but may wish to if you have multiple * Backbone views attached to the same DOM element. */ undelegateEvents: function () { this.$el.unbind('.delegateEvents' + this.cid); }, /** * Performs the initial configuration of a View with a set of options. * Keys with special meaning *(model, collection, id, className)*, are * attached directly to the view. */ _configure: function (options) { if (this.options) { options = _.extend({}, this.options, options); } var self = this; _.each(viewOptions, function (attr) { if (options[attr]) { self[attr] = options[attr]; } }); this.options = options; }, /** * Ensure that the View has a DOM element to render into. * If `this.el` is a string, pass it through `$()`, take the first * matching element, and re-assign it to `el`. Otherwise, create * an element from the `id`, `className` and `tagName` properties. */ _ensureElement: function () { if (!this.el) { var attrs = Bmob._getValue(this, 'attributes') || {}; if (this.id) { attrs.id = this.id; } if (this.className) { attrs['class'] = this.className; } this.setElement(this.make(this.tagName, attrs), false); } else { this.setElement(this.el, false); } } }); /** * @function * @param {Object} instanceProps Instance properties for the view. * @param {Object} classProps Class properies for the view. * @return {Class} A new subclass of Bmob.View. */ Bmob.View.extend = Bmob._extend; /** * @class * *

这个类是Bmob.Object的子类,同时拥有Bmob.Object的所有函数,但是扩展了用户的特殊函数,例如验证,登录等

*/ Bmob.User = Bmob.Object.extend("_User", /** @lends Bmob.User.prototype */ { // Instance Variables _isCurrentUser: false, // Instance Methods /** * Internal method to handle special fields in a _User response. */ _mergeMagicFields: function (attrs) { if (attrs.sessionToken) { this._sessionToken = attrs.sessionToken; delete attrs.sessionToken; } Bmob.User.__super__._mergeMagicFields.call(this, attrs); }, /** * Removes null values from authData (which exist temporarily for * unlinking) */ _cleanupAuthData: function () { if (!this.isCurrent()) { return; } var authData = this.get('authData'); if (!authData) { return; } Bmob._objectEach(this.get('authData'), function (value, key) { if (!authData[key]) { delete authData[key]; } }); }, /** * Synchronizes authData for all providers. */ _synchronizeAllAuthData: function () { var authData = this.get('authData'); if (!authData) { return; } var self = this; Bmob._objectEach(this.get('authData'), function (value, key) { self._synchronizeAuthData(key); }); }, /** * Synchronizes auth data for a provider (e.g. puts the access token in the * right place to be used by the Facebook SDK). */ _synchronizeAuthData: function (provider) { if (!this.isCurrent()) { return; } var authType; if (_.isString(provider)) { authType = provider; provider = Bmob.User._authProviders[authType]; } else { authType = provider.getAuthType(); } var authData = this.get('authData'); if (!authData || !provider) { return; } var success = provider.restoreAuthentication(authData[authType]); if (!success) { this._unlinkFrom(provider); } }, _handleSaveResult: function (makeCurrent) { // Clean up and synchronize the authData object, removing any unset values if (makeCurrent) { this._isCurrentUser = true; } this._cleanupAuthData(); this._synchronizeAllAuthData(); // Don't keep the password around. delete this._serverData.password; this._rebuildEstimatedDataForKey("password"); this._refreshCache(); if (makeCurrent || this.isCurrent()) { Bmob.User._saveCurrentUser(this); } }, /** * 使用第三方登录,登录或注册 * @Magic 2.0.0 * @return {Bmob.User} */ _linkWith: function _linkWith(provider, data) { var _this = this; var authType; if (_.isString(provider)) { authType = provider; provider = Bmob.User._authProviders[provider]; } else { authType = provider.getAuthType(); } if (data) { var authData = this.get('authData') || {}; authData[authType] = data; this.set('authData', authData); var promise = new Bmob.Promise(); this.save({ 'authData': authData }, newOptions).then( function (model) { model._handleSaveResult(true); promise.resolve(model); } ); return promise._thenRunCallbacks({}); // Overridden so that the user can be made the current user. var newOptions = _.clone(data) || {}; newOptions.success = function (model) { model._handleSaveResult(true); if (data.success) { data.success.apply(this, arguments); } }; return this.save({ 'authData': authData }, newOptions); } else { return provider.authenticate().then(function (result) { return _this._linkWith(provider, result); }); } }, /** * 使用当前使用小程序的微信用户身份注册或登录,成功后用户的 session 会在设备上持久化保存,之后可以使用 Bmob.User.current() 获取当前登录用户。 * @Magic 2.0.0 * @return {Bmob.User} */ loginWithWeapp: function (code) { var that = this; var promise = new Bmob.Promise(); Bmob.User.requestOpenId(code, { success: function (authData) {//获取授权成功 var platform = "weapp"; var user = Bmob.Object._create("_User"); user._linkWith(platform, authData).then(function (resp) { promise.resolve(resp); }, function (error) { promise.reject(error); }); }, error: function (error) { promise.reject(error); } } ); return promise._thenRunCallbacks({}); }, /** * Unlinks a user from a service. */ _unlinkFrom: function (provider, options) { var authType; if (_.isString(provider)) { authType = provider; provider = Bmob.User._authProviders[provider]; } else { authType = provider.getAuthType(); } var newOptions = _.clone(options); var self = this; newOptions.authData = null; newOptions.success = function (model) { self._synchronizeAuthData(provider); if (options.success) { options.success.apply(this, arguments); } }; return this._linkWith(provider, newOptions); }, /** * Checks whether a user is linked to a service. */ _isLinked: function (provider) { var authType; if (_.isString(provider)) { authType = provider; } else { authType = provider.getAuthType(); } var authData = this.get('authData') || {}; return !!authData[authType]; }, /** * Deauthenticates all providers. */ _logOutWithAll: function () { var authData = this.get('authData'); if (!authData) { return; } var self = this; Bmob._objectEach(this.get('authData'), function (value, key) { self._logOutWith(key); }); }, /** * Deauthenticates a single provider (e.g. removing access tokens from the * Facebook SDK). */ _logOutWith: function (provider) { if (!this.isCurrent()) { return; } if (_.isString(provider)) { provider = Bmob.User._authProviders[provider]; } if (provider && provider.deauthenticate) { provider.deauthenticate(); } }, /** * 注册一个新用户。当创建一个新用户时,应该调用这个方法而不是save方法。这个方法会创建 * 一个新的Bmob.User在服务器上,同时保存session在本地磁盘因此你可以通过current访问user *

在注册前必须设置username和password

*

完成后调用options.success 或者 options.error

* * @param {Object} attrs 用户的额外的属性,或者null * @param {Object} options Backbone-style options 对象。 * @return {Bmob.Promise} 当调用结束将会返回promise。 * @see Bmob.User.signUp */ signUp: function (attrs, options) { var error; options = options || {}; var username = (attrs && attrs.username) || this.get("username"); if (!username || (username === "")) { error = new Bmob.Error(Bmob.Error.OTHER_CAUSE, "Cannot sign up user with an empty name."); if (options && options.error) { options.error(this, error); } return Bmob.Promise.error(error); } var password = (attrs && attrs.password) || this.get("password"); if (!password || (password === "")) { error = new Bmob.Error(Bmob.Error.OTHER_CAUSE, "Cannot sign up user with an empty password."); if (options && options.error) { options.error(this, error); } return Bmob.Promise.error(error); } // Overridden so that the user can be made the current user. var newOptions = _.clone(options); newOptions.success = function (model) { model._handleSaveResult(true); if (options.success) { options.success.apply(this, arguments); } }; return this.save(attrs, newOptions); }, /** * 用户登录。当登录成功,将会保存session在本地,可以通过current获取用户对象。 *

在注册前必须设置username和password

*

完成后调用options.success 或者 options.error

* * @param {Object} options Backbone-style options 对象。 * @see Bmob.User.logIn * @return {Bmob.Promise} 当调用结束将会返回promise。 */ logIn: function (options) { var model = this; var request = Bmob._request("login", null, null, "GET", this.toJSON()); return request.then(function (resp, status, xhr) { var serverAttrs = model.parse(resp, status, xhr); model._finishFetch(serverAttrs); model._handleSaveResult(true); return model; })._thenRunCallbacks(options, this); }, /** * 保存对象 * @see Bmob.Object#save */ save: function (arg1, arg2, arg3) { var i, attrs, current, options, saved; if (_.isObject(arg1) || _.isNull(arg1) || _.isUndefined(arg1)) { attrs = arg1; options = arg2; } else { attrs = {}; attrs[arg1] = arg2; options = arg3; } options = options || {}; var newOptions = _.clone(options); newOptions.success = function (model) { model._handleSaveResult(false); if (options.success) { options.success.apply(this, arguments); } }; return Bmob.Object.prototype.save.call(this, attrs, newOptions); }, /** * 获取一个对象 * @see Bmob.Object#fetch */ fetch: function (options) { var newOptions = options ? _.clone(options) : {}; newOptions.success = function (model) { model._handleSaveResult(false); if (options && options.success) { options.success.apply(this, arguments); } }; return Bmob.Object.prototype.fetch.call(this, newOptions); }, /** * 返回true 如果current可以返回这个user。 * @see Bmob.User#cu 你不能添加一个没保存的Bmob.Object到关系中 */ isCurrent: function () { return this._isCurrentUser; }, /** * 返回 get("username"). * @return {String} * @see Bmob.Object#get */ getUsername: function () { return this.get("username"); }, /** * 调用 set("username", username, options) 同时返回结果 * @param {String} username * @param {Object} options Backbone-style options 对象。 * @return {Boolean} * @see Bmob.Object.set */ setUsername: function (username, options) { return this.set("username", username, options); }, /** * 调用 set("password", password, options) 同时返回结果 * @param {String} password * @param {Object} options Backbone-style options 对象。 * @return {Boolean} * @see Bmob.Object.set */ setPassword: function (password, options) { return this.set("password", password, options); }, /** * 返回 get("email"). * @return {String} * @see Bmob.Object#get */ getEmail: function () { return this.get("email"); }, /** * 调用 set("email", email, options) 同时返回结果 * @param {String} email * @param {Object} options Backbone-style options 对象。 * @return {Boolean} * @see Bmob.Object.set */ setEmail: function (email, options) { return this.set("email", email, options); }, /** * 检查这个用户是否当前用户并且已经登录。 * @return (Boolean) 这个用户是否当前用户并且已经登录。 */ authenticated: function () { return !!this._sessionToken && (Bmob.User.current() && Bmob.User.current().id === this.id); } }, /** @lends Bmob.User */ { // Class Variables // The currently logged-in user. _currentUser: null, // Whether currentUser is known to match the serialized version on disk. // This is useful for saving a localstorage check if you try to load // _currentUser frequently while there is none stored. _currentUserMatchesDisk: false, // The localStorage key suffix that the current user is stored under. _CURRENT_USER_KEY: "currentUser", // The mapping of auth provider names to actual providers _authProviders: {}, // Class Methods /** * 注册一个新用户。当创建一个新用户时,应该调用这个方法而不是save方法。这个方法会创建 * 一个新的Bmob.User在服务器上,同时保存session在本地磁盘因此你可以通过current访问user * *

完成后调用options.success 或者 options.error

* * @param {String} username 注册的用户名或email * @param {String} password 注册的密码 * @param {Object} attrs 新用户所需要的额外数据 * @param {Object} options Backbone-style options 对象。 * @return {Bmob.Promise} 当调用结束将会返回promise。 * @see Bmob.User#signUp */ signUp: function (username, password, attrs, options) { attrs = attrs || {}; attrs.username = username; attrs.password = password; var user = Bmob.Object._create("_User"); return user.signUp(attrs, options); }, /** * 用户登录。当登录成功,将会保存session在本地,可以通过current获取用户对象。 * *

完成后调用options.success 或者 options.error

* * @param {String} username 注册的用户名或email * @param {String} password 注册的密码 * @param {Object} options 新用户所需要的额外数据 * @return {Bmob.Promise} Backbone-style options 对象。 * @see Bmob.User#logIn */ logIn: function (username, password, options) { var user = Bmob.Object._create("_User"); user._finishFetch({ username: username, password: password }); return user.logIn(options); }, /** * 退出当前登录的用户。磁盘中的session将会被移除,调用current将会 * 返回null。 */ logOut: function () { if (Bmob.User._currentUser !== null) { Bmob.User._currentUser._logOutWithAll(); Bmob.User._currentUser._isCurrentUser = false; } Bmob.User._currentUserMatchesDisk = true; Bmob.User._currentUser = null; wx.removeStorage({ key: Bmob._getBmobPath(Bmob.User._CURRENT_USER_KEY), success: function (res) { console.log(res.data) } }); }, /** * 把重设密码的邮件发送到用户的注册邮箱。邮件允许用户在bmob网站上重设密码。 *

完成后调用options.success 或者 options.error

* * @param {String} email 用户注册的邮箱 * @param {Object} options Backbone-style options 对象。 */ requestPasswordReset: function (email, options) { var json = { email: email }; var request = Bmob._request("requestPasswordReset", null, null, "POST", json); return request._thenRunCallbacks(options); }, /** * 请求验证email *

完成后调用options.success 或者 options.error

* * @param {String} email 需要验证email的email的地址 * @param {Object} options Backbone-style options 对象。 */ requestEmailVerify: function (email, options) { var json = { email: email }; var request = Bmob._request("requestEmailVerify", null, null, "POST", json); return request._thenRunCallbacks(options); }, /** * 请求openid值 *

完成后调用options.success 或者 options.error

* * @param {String} code 微信的code * @param {Object} options Backbone-style options 对象。 */ requestOpenId: function (code, options) { var json = { code: code }; var request = Bmob._request("wechatApp", code, null, "POST", json); return request._thenRunCallbacks(options); }, /** * 返回当前已经登陆的用户。 * @return {Bmob.Object} 已经登录的Bmob.User. */ current: function () { if (Bmob.User._currentUser) { return Bmob.User._currentUser; } if (Bmob.User._currentUserMatchesDisk) { return Bmob.User._currentUser; } // Load the user from local storage. Bmob.User._currentUserMatchesDisk = true; // var userData = Bmob.localStorage.getItem(Bmob._getBmobPath( // Bmob.User._CURRENT_USER_KEY)); var userData = false; try { var userData = wx.getStorageSync(Bmob._getBmobPath(Bmob.User._CURRENT_USER_KEY)) if (userData) { // Do something with return value Bmob.User._currentUser = Bmob.Object._create("_User"); Bmob.User._currentUser._isCurrentUser = true; var json = JSON.parse(userData); Bmob.User._currentUser.id = json._id; delete json._id; Bmob.User._currentUser._sessionToken = json._sessionToken; delete json._sessionToken; Bmob.User._currentUser.set(json); Bmob.User._currentUser._synchronizeAllAuthData(); Bmob.User._currentUser._refreshCache(); Bmob.User._currentUser._opSetQueue = [{}]; return Bmob.User._currentUser; } } catch (e) { // Do something when catch error return null; } }, /** * Persists a user as currentUser to localStorage, and into the singleton. */ _saveCurrentUser: function (user) { if (Bmob.User._currentUser !== user) { Bmob.User.logOut(); } user._isCurrentUser = true; Bmob.User._currentUser = user; Bmob.User._currentUserMatchesDisk = true; var json = user.toJSON(); json._id = user.id; json._sessionToken = user._sessionToken; wx.setStorage({ key: Bmob._getBmobPath(Bmob.User._CURRENT_USER_KEY), data: JSON.stringify(json) }) }, _registerAuthenticationProvider: function (provider) { Bmob.User._authProviders[provider.getAuthType()] = provider; // Synchronize the current user with the auth provider. if (Bmob.User.current()) { Bmob.User.current()._synchronizeAuthData(provider.getAuthType()); } }, _logInWith: function (provider, options) { var user = Bmob.Object._create("_User"); return user._linkWith(provider, options); } }); /** * 为Bmob.Object类创建一个新的bmob Bmob.Query 。 * @param objectClass - * Bmob.Object的实例,或者Bmob类名 * * *

Bmob.Query 为Bmob.Objects定义了query操作。最常用的操作就是用queryfind * 操作去获取所有的对象。例如,下面简单的操作是获取所有的MyClass。根据操作的成功或失败, * 会回调不同的函数。 *

   * var query = new Bmob.Query(MyClass);
   * query.find({
   *   success: function(results) {
   *     // results is an array of Bmob.Object.
   *   },
   *
   *   error: function(error) {
   *     // error is an instance of Bmob.Error.
   *   }
   * });

* *

Bmob.Query也可以用来获取一个id已知的对象。例如,下面的例子获取了MyClass 和 id myId * 根据操作的成功或失败,会回调不同的函数。 *

   * var query = new Bmob.Query(MyClass);
   * query.get(myId, {
   *   success: function(object) {
   *     // object is an instance of Bmob.Object.
   *   },
   *
   *   error: function(object, error) {
   *     // error is an instance of Bmob.Error.
   *   }
   * });

* *

Bmob.Query 同时也能获取查询结果的数目。例如,下面的例子获取了MyClass的数目

   
   * var query = new Bmob.Query(MyClass);
   * query.count({
   *   success: function(number) {
   *     // There are number instances of MyClass.
   *   },
   *
   *   error: function(error) {
   *     // error is an instance of Bmob.Error.
   *   }
   * });

* @class Bmob.Query 为Bmob.Objects定义了query操作 */ Bmob.Query = function (objectClass) { if (_.isString(objectClass)) { objectClass = Bmob.Object._getSubclass(objectClass); } this.objectClass = objectClass; this.className = objectClass.prototype.className; this._where = {}; this._include = []; this._limit = -1; // negative limit means, do not send a limit this._skip = 0; this._extraOptions = {}; }; /** * 通过传递query构造or的Bmob.Query对象。 For * example: *
var compoundQuery = Bmob.Query.or(query1, query2, query3);
* 通过query1, query2, 和 query3创建一个or查询 * @param {...Bmob.Query} var_args or的query查询. * @return {Bmob.Query} 查询结果. */ Bmob.Query.or = function () { var queries = _.toArray(arguments); var className = null; Bmob._arrayEach(queries, function (q) { if (_.isNull(className)) { className = q.className; } if (className !== q.className) { throw "All queries must be for the same class"; } }); var query = new Bmob.Query(className); query._orQuery(queries); return query; }; Bmob.Query._extend = Bmob._extend; Bmob.Query.prototype = { //hook to iterate result. Added by dennis. _processResult: function (obj) { return obj; }, /** * 获取Bmob.Object,适用于id已经知道的情况。当查询完成会调用options.success 或 options.error。 * @param {} objectId 要获取的对象id * @param {Object} options Backbone-style options 对象. */ get: function (objectId, options) { var self = this; self.equalTo('objectId', objectId); return self.first().then(function (response) { if (response) { return response; } var errorObject = new Bmob.Error(Bmob.Error.OBJECT_NOT_FOUND, "Object not found."); return Bmob.Promise.error(errorObject); })._thenRunCallbacks(options, null); }, /** * 返回json的结局 * @return {Object} */ toJSON: function () { var params = { where: this._where }; if (this._include.length > 0) { params.include = this._include.join(","); } if (this._select) { params.keys = this._select.join(","); } if (this._limit >= 0) { params.limit = this._limit; } if (this._skip > 0) { params.skip = this._skip; } if (this._order !== undefined) { params.order = this._order; } Bmob._objectEach(this._extraOptions, function (v, k) { params[k] = v; }); return params; }, _newObject: function (response) { if (typeof (obj) === "undefined") { var obj; } if (response && response.className) { obj = new Bmob.Object(response.className); } else { obj = new this.objectClass(); } return obj; }, _createRequest: function (params) { return Bmob._request("classes", this.className, null, "GET", params || this.toJSON()); }, /** * 查找满足查询条件的对象。完成后,options.success 或 options.error 会被调用。 * @param {Object} options A Backbone-style options 对象. * @return {Bmob.Promise} 当查询完成后,结果的 promise 会被调用。 */ find: function (options) { var self = this; var request = this._createRequest(); return request.then(function (response) { return _.map(response.results, function (json) { var obj = self._newObject(response); obj._finishFetch(self._processResult(json), true); return obj; }); })._thenRunCallbacks(options); }, /** * 把查询到的所有对象删除。 * @param {Object} options 标准的带 success and error回调的options对象。 * @return {Bmob.Promise} 当完成后,结果的 promise 会被调用。 */ destroyAll: function (options) { var self = this; return self.find().then(function (objects) { return Bmob.Object.destroyAll(objects); })._thenRunCallbacks(options); }, /** * 查询结果的数目。 * 完成后,options.success 或 options.error 会被调用。 * * @param {Object} options A Backbone-style options 对象. * @return {Bmob.Promise} 完成后,结果的 promise 会被调用。 */ count: function (options) { var params = this.toJSON(); params.limit = 0; params.count = 1; var request = this._createRequest(params); return request.then(function (response) { return response.count; })._thenRunCallbacks(options); }, /** * 在返回的结果中,返回第一个对象 * 完成后,options.success 或 options.error 会被调用。 * * @param {Object} options A Backbone-style options 对象. * @return {Bmob.Promise} 完成后,结果的 promise 会被调用。 */ first: function (options) { var self = this; var params = this.toJSON(); params.limit = 1; var request = this._createRequest(params); return request.then(function (response) { return _.map(response.results, function (json) { var obj = self._newObject(); obj._finishFetch(self._processResult(json), true); return obj; })[0]; })._thenRunCallbacks(options); }, /** * 查询后返回一个Bmob.Collection * @return {Bmob.Collection} */ collection: function (items, options) { options = options || {}; return new Bmob.Collection(items, _.extend(options, { model: this._objectClass || this.objectClass, query: this })); }, /** * 在返回结果前设置跳过的结果数目。是在分页时使用的。默认是跳过0条结果。 * @param {Number} n 跳过的数目。 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ skip: function (n) { this._skip = n; return this; }, /** * 限制返回结果的数目。默认限制是100,最大限制数是1000. * @param {Number} n 限制的数目。 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ limit: function (n) { this._limit = n; return this; }, /** * 添加一个equal查询(key value 形式)。 * @param {String} key key * @param value key对应的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ equalTo: function (key, value) { this._where[key] = Bmob._encode(value); return this; }, /** * Helper for condition queries */ _addCondition: function (key, condition, value) { // Check if we already have a condition if (!this._where[key]) { this._where[key] = {}; } this._where[key][condition] = Bmob._encode(value); return this; }, /** * 添加一个not equal查询(key value 形式)。 * @param {String} key key * @param value key对应的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ notEqualTo: function (key, value) { this._addCondition(key, "$ne", value); return this; }, /** * 添加一个小于查询。 * @param {String} key 需要检查的key. * @param value key所对应的必须少于的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ lessThan: function (key, value) { this._addCondition(key, "$lt", value); return this; }, /** * 添加一个大于查询。 * @param {String} key 需要检查的key. * @param value key所对应的必须大于的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ greaterThan: function (key, value) { this._addCondition(key, "$gt", value); return this; }, /** * 添加一个小于等于查询。 * @param {String} key 需要检查的key. * @param value key所对应的必须少于等于的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ lessThanOrEqualTo: function (key, value) { this._addCondition(key, "$lte", value); return this; }, /** * 添加一个大于等于查询。 * @param {String} key 需要检查的key. * @param value key所对应的必须大于等于的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ greaterThanOrEqualTo: function (key, value) { this._addCondition(key, "$gte", value); return this; }, /** * 添加key中包含任意一个值查询。 * @param {String} key 需要检查的key. * @param {Array} values 需要包含的值的数组 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ containedIn: function (key, values) { this._addCondition(key, "$in", values); return this; }, /** * 添加key中不包含任意一个值查询。 * @param {String} key 需要检查的key. * @param {Array} values 不需要包含的值的数组 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ notContainedIn: function (key, values) { this._addCondition(key, "$nin", values); return this; }, /** * 添加key中包含全部值查询。 * @param {String} key 需要检查的key * @param {Array} values 需要包含的全部值的数组 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ containsAll: function (key, values) { this._addCondition(key, "$all", values); return this; }, /** * 添加key是否存在的查询。 * @param {String} key 需要检查是否存在的key。 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ exists: function (key) { this._addCondition(key, "$exists", true); return this; }, /** * 添加key是否不存在的查询。 * @param {String} key 需要检查是否不存在的key。 * @return {Bmob.Query}返回查询对象,因此可以使用链式调用。 */ doesNotExist: function (key) { this._addCondition(key, "$exists", false); return this; }, /** * 添加正则表达式的查询。 * 当数据很大的时候这个操作可能很慢。 * @param {String} key 需要检查的key * @param {RegExp} regex 需要匹配的正则表达式 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ matches: function (key, regex, modifiers) { this._addCondition(key, "$regex", regex); if (!modifiers) { modifiers = ""; } // Javascript regex options support mig as inline options but store them // as properties of the object. We support mi & should migrate them to // modifiers if (regex.ignoreCase) { modifiers += 'i'; } if (regex.multiline) { modifiers += 'm'; } if (modifiers && modifiers.length) { this._addCondition(key, "$options", modifiers); } return this; }, /** * 添加一个Bmob.Query的匹配查询。 * @param {String} key 需要检查的key。 * @param {Bmob.Query} query 需要匹配的query * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ matchesQuery: function (key, query) { var queryJSON = query.toJSON(); queryJSON.className = query.className; this._addCondition(key, "$inQuery", queryJSON); return this; }, /** * 添加一个Bmob.Query的不匹配查询。 * @param {String} key 需要检查的key。 * @param {Bmob.Query} 不需要匹配的query * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ doesNotMatchQuery: function (key, query) { var queryJSON = query.toJSON(); queryJSON.className = query.className; this._addCondition(key, "$notInQuery", queryJSON); return this; }, /** * 添加查询: key's value 匹配一个对象,这个对象通过不同的Bmob.Query返回。 * @param {String} key 需要匹配的key值 * @param {String} queryKey 返回通过匹配的查询的对象的键 * @param {Bmob.Query} query 需要运行的查询 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ matchesKeyInQuery: function (key, queryKey, query) { var queryJSON = query.toJSON(); queryJSON.className = query.className; this._addCondition(key, "$select", { key: queryKey, query: queryJSON }); return this; }, /** * 添加查询: key's value 不匹配一个对象,这个对象通过不同的Bmob.Query返回。 * @param {String} key 需要匹配的key值 * excluded. * @param {String} queryKey 返回通过不匹配的查询的对象的键 * @param {Bmob.Query} query 需要运行的查询 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ doesNotMatchKeyInQuery: function (key, queryKey, query) { var queryJSON = query.toJSON(); queryJSON.className = query.className; this._addCondition(key, "$dontSelect", { key: queryKey, query: queryJSON }); return this; }, /** * Add constraint that at least one of the passed in queries matches. * @param {Array} queries * @return {Bmob.Query} Returns the query, so you can chain this call. */ _orQuery: function (queries) { var queryJSON = _.map(queries, function (q) { return q.toJSON().where; }); this._where.$or = queryJSON; return this; }, /** * Converts a string into a regex that matches it. * Surrounding with \Q .. \E does this, we just need to escape \E's in * the text separately. */ _quote: function (s) { return "\\Q" + s.replace("\\E", "\\E\\\\E\\Q") + "\\E"; }, /** * 查找一个值中是否包含某个子串。在大量的数据中,这个操作可能很慢。 * @param {String} key 需要查找的值 * @param {String} substring 需要匹配子串 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ contains: function (key, value) { this._addCondition(key, "$regex", this._quote(value)); return this; }, /** * 检查某个值是否以特殊的字符串开头。 这查询使用了backend index,因此在大数据中也很快。 * @param {String} key 需要查找的值 * @param {String} prefix 需要匹配子串 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ startsWith: function (key, value) { this._addCondition(key, "$regex", "^" + this._quote(value)); return this; }, /** * 检查某个值是否以特殊的字符串结尾。在大量的数据中,这个操作可能很慢。 * @param {String} key 需要查找的值 * @param {String} suffix 需要匹配子串 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ endsWith: function (key, value) { this._addCondition(key, "$regex", this._quote(value) + "$"); return this; }, /** * 根据key对结果进行升序。 * * @param {String} key 排序的key * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ ascending: function (key) { if (Bmob._isNullOrUndefined(this._order)) { this._order = key; } else { this._order = this._order + "," + key; } return this; }, cleanOrder: function (key) { this._order = null; return this; }, /** * 根据key对结果进行降序。 * * @param {String} key 排序的key * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ descending: function (key) { if (Bmob._isNullOrUndefined(this._order)) { this._order = "-" + key; } else { this._order = this._order + ",-" + key; } return this; }, /** * 查找一个geo point 附近的坐标。 * @param {String} key Bmob.GeoPoint的key * @param {Bmob.GeoPoint} point 指向一个 Bmob.GeoPoint * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ near: function (key, point) { if (!(point instanceof Bmob.GeoPoint)) { // Try to cast it to a GeoPoint, so that near("loc", [20,30]) works. point = new Bmob.GeoPoint(point); } this._addCondition(key, "$nearSphere", point); return this; }, /** * 添加用于查找附近的对象,并基于弧度给出最大距离内的点。 * @param {String} key Bmob.GeoPoint的key * @param {Bmob.GeoPoint} point 指向一个 Bmob.GeoPoint * @param maxDistance 返回的最大距离,基于弧度. * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ withinRadians: function (key, point, distance) { this.near(key, point); this._addCondition(key, "$maxDistance", distance); return this; }, /** * 添加用于查找附近的对象,并基于米给出最大距离内的点。 * @param {String} key Bmob.GeoPoint的key * @param {Bmob.GeoPoint} point 指向一个 Bmob.GeoPoint * @param maxDistance 返回的最大距离,基于弧度. * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ withinMiles: function (key, point, distance) { return this.withinRadians(key, point, distance / 3958.8); }, /** * 添加用于查找附近的对象,并基于千米给出最大距离内的点。 * @param {String} key Bmob.GeoPoint的key * @param {Bmob.GeoPoint} point 指向一个 Bmob.GeoPoint * @param maxDistance 返回的最大距离,基于弧度. * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ withinKilometers: function (key, point, distance) { return this.withinRadians(key, point, distance / 6371.0); }, /** * 在一个四边形范围内,查找某个点附近的对象 * @param {String} key The key to be constrained. * @param {Bmob.GeoPoint} southwest 这个四边形的南西方向 * @param {Bmob.GeoPoint} northeast 这个四边形的东北方向 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ withinGeoBox: function (key, southwest, northeast) { if (!(southwest instanceof Bmob.GeoPoint)) { southwest = new Bmob.GeoPoint(southwest); } if (!(northeast instanceof Bmob.GeoPoint)) { northeast = new Bmob.GeoPoint(northeast); } this._addCondition(key, '$within', { '$box': [southwest, northeast] }); return this; }, /** * 当获取的Bmob.Objects有指向其子对象的Pointer类型指针Key时,你可以加入inclue选项来获取指针指向的子对象 * @param {String} key 需要包含的key的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ include: function () { var self = this; Bmob._arrayEach(arguments, function (key) { if (_.isArray(key)) { self._include = self._include.concat(key); } else { self._include.push(key); } }); return this; }, /** * 匹配另一个查询的返回值,如果这个函数被调用多次,每次调用时所有的keys将会被包含。 * @param {Array} keys 需要包含的key的值 * @return {Bmob.Query} 返回查询对象,因此可以使用链式调用。 */ select: function () { var self = this; this._select = this._select || []; Bmob._arrayEach(arguments, function (key) { if (_.isArray(key)) { self._select = self._select.concat(key); } else { self._select.push(key); } }); return this; }, /** * 对查询的每个结果调用回调函数。 * 如果callback返回promise,这个迭代器不会继续直到所有的promise调用完毕。 * 如果回调返回拒绝promise,迭代会停止。 * 所有对象将以不排序的形式处理。 * 查询将不会有任何的排序,同时limit 或skip 将无效。 * @param callback {Function} 每个结果调用的回调函数 * @param options {Object} 可选的 Backbone-like 带成功或失败的回调,回调将会执行当迭代结束的时候。 * @return {Bmob.Promise} 当迭代结束的时候A promise 将会执行一次。 */ each: function (callback, options) { options = options || {}; if (this._order || this._skip || (this._limit >= 0)) { var error = "Cannot iterate on a query with sort, skip, or limit."; return Bmob.Promise.error(error)._thenRunCallbacks(options); } var promise = new Bmob.Promise(); var query = new Bmob.Query(this.objectClass); // We can override the batch size from the options. // This is undocumented, but useful for testing. query._limit = options.batchSize || 100; query._where = _.clone(this._where); query._include = _.clone(this._include); query.ascending('objectId'); var finished = false; return Bmob.Promise._continueWhile(function () { return !finished; }, function () { return query.find().then(function (results) { var callbacksDone = Bmob.Promise.as(); Bmob._.each(results, function (result) { callbacksDone = callbacksDone.then(function () { return callback(result); }); }); return callbacksDone.then(function () { if (results.length >= query._limit) { query.greaterThan("objectId", results[results.length - 1].id); } else { finished = true; } }); }); })._thenRunCallbacks(options); } }; Bmob.FriendShipQuery = Bmob.Query._extend({ _objectClass: Bmob.User, _newObject: function () { return new Bmob.User(); }, _processResult: function (json) { var user = json[this._friendshipTag]; if (user.__type === 'Pointer' && user.className === '_User') { delete user.__type; delete user.className; } return user; }, }); /** * History serves as a global router (per frame) to handle hashchange * events or pushState, match the appropriate route, and trigger * callbacks. You shouldn't ever have to create one of these yourself * — you should use the reference to Bmob.history * that will be created for you automatically if you make use of * Routers with routes. * *

A fork of Backbone.History, provided for your convenience. If you * use this class, you must also include jQuery, or another library * that provides a jQuery-compatible $ function. For more information, * see the * Backbone documentation.

*

Available in the client SDK only.

*/ Bmob.History = function () { this.handlers = []; _.bindAll(this, 'checkUrl'); }; // Cached regex for cleaning leading hashes and slashes . var routeStripper = /^[#\/]/; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; // Has the history handling already been started? Bmob.History.started = false; // Set up all inheritable **Bmob.History** properties and methods. _.extend(Bmob.History.prototype, Bmob.Events, /** @lends Bmob.History.prototype */ { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. getHash: function (windowOverride) { var loc = windowOverride ? windowOverride.location : window.location; var match = loc.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the cross-browser normalized URL fragment, either from the URL, // the hash, or the override. getFragment: function (fragment, forcePushState) { if (Bmob._isNullOrUndefined(fragment)) { if (this._hasPushState || forcePushState) { fragment = window.location.pathname; var search = window.location.search; if (search) { fragment += search; } } else { fragment = this.getHash(); } } if (!fragment.indexOf(this.options.root)) { fragment = fragment.substr(this.options.root.length); } return fragment.replace(routeStripper, ''); }, /** * Start the hash change handling, returning `true` if the current * URL matches an existing route, and `false` otherwise. */ start: function (options) { if (Bmob.History.started) { throw new Error("Bmob.history has already been started"); } Bmob.History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({}, { root: '/' }, this.options, options); this._wantsHashChange = this.options.hashChange !== false; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); if (oldIE) { this.iframe = Bmob.$('