You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7101 lines
256 KiB

11 months ago
/*!
* 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 <seenObjects>
* 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 <code>Bmob.Error</code>.
* @param {String} message A detailed description of the error.
*
* <p>Class used for all objects passed to error callbacks.</p>
*/
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
});
/**
*
* <p>Bmob.Events fork of Backbone's Events module</p>
*
* <p>A module that can be mixed in to any object in order to provide
* it with custom events. You may bind callback functions to an event
* with `on`, or remove these functions with `off`.
* Triggering an event fires all callbacks in the order that `on` was
* called.
*
* <pre>
* var object = {};
* _.extend(object, Bmob.Events);
* object.on('expand', function(){ alert('expanded'); });
* object.trigger('expand');</pre></p>
*
* <p>For more information, see the
* <a href="http://documentcloud.github.com/backbone/#Events">Backbone
* documentation</a>.</p>
*/
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<br>
* <pre>
* new GeoPoint(otherGeoPoint)
* new GeoPoint(30, 30)
* new GeoPoint([30, 30])
* new GeoPoint({latitude: 30, longitude: 30})
* new GeoPoint() // defaults to (0, 0)
* </pre>
* @class
*
* <p>在BmobObject中使用坐标点或者在geo查询中使用</p>
* <p>在一个表中只有一个字段能使用GeoPoint.</p>
*
* <p>Example:<pre>
* var point = new Bmob.GeoPoint(30.0, -20.0);
* var object = new Bmob.Object("PlaceObject");
* object.set("location", point);
* object.save();</pre></p>
*/
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
*
* <p>权限控制可以被添加到任何
* <code>Bmob.Object</code>访
* </p>
*/
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 <code>object.set("foo", "bar")</code>
* is an example of a Bmob.Op.Set. Calling <code>object.unset("foo")</code>
* is a Bmob.Op.Unset. These operations are stored in a Bmob.Object and
* sent to the server as part of <code>object.save()</code> 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
*
* <p>
* 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.
* </p>
*/
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.
*
* <p>Typical usage would be like:<pre>
* query.findAsync().then(function(results) {
* results[0].set("foo", "bar");
* return results[0].saveAsync();
* }).then(function(result) {
* console.log("Updated " + result.id);
* });
* </pre></p>
*
* @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+.
* 例如:<pre>
*
* 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.
* });
* }</pre>
* @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;
},
/**
* <p>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.</p>
* <p><pre>
* 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.
*</pre></p>
* @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.
*
* <p>You won't normally call this method directly. It is recommended that
* you use a subclass of <code>Bmob.Object</code> instead, created by calling
* <code>extend</code>.</p>
*
* <p>However, if you don't want to use a subclass, or aren't sure which
* subclass is appropriate, you can use this form:<pre>
* var object = new Bmob.Object("ClassName");
* </pre>
* That is basically equivalent to:<pre>
* var MyClass = Bmob.Object.extend("ClassName");
* var object = new MyClass();
* </pre></p>
*
* @param {Object} attributes The initial set of data to store in the object.
* @param {Object} options A set of Backbone-like options for creating the
* object. The only option currently supported is "collection".
* @see Bmob.Object.extend
*
*
* <p>The fundamental unit of Bmob data, which implements the Backbone Model
* interface.</p>
*/
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:<pre>
* 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.
* },
* });
* </pre>
* A simplified syntax:<pre>
* Bmob.Object.saveAll([object1, object2, ...], function(list, error) {
* if (list) {
* // All the objects were saved.
* } else {
* // An error occurred.
* }
* });
* </pre>
*
* @param {Array} list A list of <code>Bmob.Object</code>.
* @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 <code>true</code> if the attribute contains a value that is not
* null or undefined.
* @param {String} attr The string name of the attribute.
* @return {Boolean}
*/
has: function (attr) {
return !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
* <code>"change"</code> unless you choose to silence it.
*
* <p>You can call it with an object containing keys and values, or with one
* key and value. For example:<pre>
* gameTurn.set({
* player: player1,
* diceRoll: 2
* }, {
* error: function(gameTurnAgain, error) {
* // The set failed validation.
* }
* });
*
* game.set("currentPlayer", player2, {
* error: function(gameTurnAgain, error) {
* // The set failed validation.
* }
* });
*
* game.set("finished", true);</pre></p>
*
* @param {String} key The key to set.
* @param {} value The value to give it.
* @param {Object} options A set of Backbone-like options for the set.
* The only supported options are <code>silent</code>,
* <code>error</code>, and <code>promise</code>.
* @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 <code>"change"</code> unless
* you choose to silence it. This is a noop if the attribute doesn't
* exist.
*/
unset: function (attr, options) {
options = options || {};
options.unset = true;
return this.set(attr, null, options);
},
/**
* 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 <code>"change"</code> unless
* you choose to silence it.
*/
clear: function (options) {
options = options || {};
options.unset = true;
var keysToClear = _.extend(this.attributes, this._operations);
return this.set(keysToClear, options);
},
/**
* 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 <code>"change"</code> 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:<pre>
* object.save();</pre>
* or<pre>
* object.save(null, options);</pre>
* or<pre>
* object.save(attrs, options);</pre>
* or<pre>
* object.save(key, value, options);</pre>
*
* For example, <pre>
* gameTurn.save({
* player: "Jake Cutter",
* diceRoll: 2
* }, {
* success: function(gameTurnAgain) {
* // The save was successful.
* },
* error: function(gameTurnAgain, error) {
* // The save failed. Error is an instance of Bmob.Error.
* }
* });</pre>
* or with promises:<pre>
* 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.
* });</pre>
*
* @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 <code>"change"</code>
* event. If you specify an attribute name, determine if that attribute
* has changed.
* @param {String} attr Optional attribute name
* @return {Boolean}
*/
hasChanged: function (attr) {
if (!arguments.length) {
return !_.isEmpty(this.changed);
}
return this.changed && _.has(this.changed, attr);
},
/**
* Returns an object containing all the attributes that have changed, or
* false if there are no changed attributes. Useful for determining what
* parts of a view need to be updated and/or what attributes need to be
* persisted to the server. Unset attributes will be set to undefined.
* You can also pass an attributes object to diff against the model,
* determining if there *would be* a change.
*/
changedAttributes: function (diff) {
if (!diff) {
return this.hasChanged() ? _.clone(this.changed) : false;
}
var changed = {};
var old = this._previousAttributes;
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
* <code>"change"</code> event was fired.
* @param {String} attr Name of the attribute to get.
*/
previous: function (attr) {
if (!arguments.length || !this._previousAttributes) {
return null;
}
return this._previousAttributes[attr];
},
/**
* Gets all of the attributes of the model at the time of the previous
* <code>"change"</code> event.
* @return {Object}
*/
previousAttributes: function () {
return _.clone(this._previousAttributes);
},
/**
* Checks if the model is currently in a valid state. It's only possible to
* get into an *invalid* state if you're using silent changes.
* @return {Boolean}
*/
isValid: function () {
return !this.validate(this.attributes);
},
/**
* You should not call this function directly unless you subclass
* <code>Bmob.Object</code>, in which case you can override this method
* to provide additional validation on <code>set</code> and
* <code>save</code>. Your implementation should return
*
* @param {Object} attrs The current data to validate.
* @param {Object} options A Backbone-like options object.
* @return {} False if the data is valid. An error object otherwise.
* @see 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.
*
* <p>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.</p>
*
* <p>You should call either:<pre>
* var MyClass = Bmob.Object.extend("MyClass", {
* <i>Instance properties</i>
* }, {
* <i>Class properties</i>
* });</pre>
* or, for Backbone compatibility:<pre>
* var MyClass = Bmob.Object.extend({
* className: "MyClass",
* <i>Other instance properties</i>
* }, {
* <i>Class properties</i>
* });</pre></p>
*
* @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权限控制中的用户角色类
*
* <p>角色必须要有名称(名称创建后不能修改), 同时必须指定ACL</p>
* @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");
},
/**
* 设置角色的名称这个值必须要在保存前设置而且只能设置一次
* <p>
* 角色的名称只能包含数字字母 _, -
* </p>
*
* <p>等同于使用 role.set("name", name)</p>
* @param {String} name 角色的名称
* @param {Object} options 标准options对象
*/
setName: function (name, options) {
return this.set("name", name, options);
},
/**
* 获取这个角色对应的用户Bmob.Users这些用户已经被分配了权限例如读写的权限
* 你能通过relation添加和移除这些用户
* <p>这等同于使用 role.relation("users")</p>
*
* @return {Bmob.Relation} the relation for the users belonging to this
* role.
*/
getUsers: function () {
return this.relation("users");
},
/**
* 获取这个角色对应的角色Bmob.Roles这些用户已经被分配了权限例如读写的权限
* 你能通过relation添加和移除这些用户
* <p>这等同于使用 role.relation("roles")</p>
*
* @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的实体特别地你不会直接调用这个方法你会<code>Bmob.Collection.extend</code>*
* @param {Array} <code>Bmob.Object</code>.
*
* @param {Object} options Backbone-style options 的可选options object.
* 有效的 options<ul>
* <li>model: Bmob.Object
* <li>query: Bmob.Query
* <li>comparator: 属性名称或排序函数
* </ul>
*
* @see Bmob.Collection.extend
*
*
* <p>提供标准的 collection class 更详细的信息请看
* <a href="http://documentcloud.github.com/backbone/#Collection">Backbone
* documentation</a>.</p>
*/
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避免触发<code>remove</code>
*/
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 <code>Bmob.Collection</code>. For example,<pre>
* 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();
* </pre>
*
* @function
* @param {Object} instanceProps Instance properties for the collection.
* @param {Object} classProps Class properies for the collection.
* @return {Class} A new subclass of <code>Bmob.Collection</code>.
*/
Bmob.Collection.extend = Bmob._extend;
/**
* Creating a Bmob.View creates its initial element outside of the DOM,
* if an existing element is not provided...
*
* <p>A fork of Backbone.View, provided for your convenience. If you use this
* class, you must also include jQuery, or another library that provides a
* jQuery-compatible $ function. For more information, see the
* <a href="http://documentcloud.github.com/backbone/#View">Backbone
* documentation</a>.</p>
* <p><strong><em>Available in the client SDK only.</em></strong></p>
*/
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.
* <pre>
* var el = this.make('li', {'class': 'row'},
* this.model.escape('title'));</pre>
*/
make: function (tagName, attributes, content) {
var el = document.createElement(tagName);
if (attributes) {
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. <code>this.events</code> is a hash of
* <pre>
* *{"event selector": "callback"}*
*
* {
* 'mousedown .title': 'edit',
* 'click .button': 'save'
* 'click .open': function(e) { ... }
* }
* </pre>
* pairs. Callbacks will be bound to the view, with `this` set properly.
* Uses event delegation for efficiency.
* Omitting the selector binds the event to `this.el`.
* This only works for delegate-able events: not `focus`, `blur`, and
* not `change`, `submit`, and `reset` in Internet Explorer.
*/
delegateEvents: function (events) {
events = events || 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 <code>Bmob.View</code>.
*/
Bmob.View.extend = Bmob._extend;
/**
* @class
*
* <p>这个类是Bmob.Object的子类同时拥有Bmob.Object的所有函数但是扩展了用户的特殊函数例如验证登录等</p>
*/
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在本地磁盘因此你可以通过<code>current</code>访user
* <p>在注册前必须设置username和password</p>
* <p>完成后调用options.success 或者 options.error</p>
*
* @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在本地可以通过<code>current</code>
* <p>在注册前必须设置username和password</p>
* <p>完成后调用options.success 或者 options.error</p>
*
* @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 如果<code>current</code>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在本地磁盘因此你可以通过<code>current</code>访user
*
* <p>完成后调用options.success 或者 options.error</p>
*
* @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在本地可以通过<code>current</code>
*
* <p>完成后调用options.success 或者 options.error</p>
*
* @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将会被移除调用<code>current</code>
* 返回<code>null</code>
*/
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网站上重设密码
* <p>完成后调用options.success 或者 options.error</p>
*
* @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
* <p>完成后调用options.success 或者 options.error</p>
*
* @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值
* <p>完成后调用options.success 或者 options.error</p>
*
* @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类名
*
*
* <p>Bmob.Query 为Bmob.Objects定义了query操作最常用的操作就是用query<code>find</code>
* 操作去获取所有的对象例如下面简单的操作是获取所有的<code>MyClass</code>
* 会回调不同的函数
* <pre>
* 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.
* }
* });</pre></p>
*
* <p>Bmob.Query也可以用来获取一个id已知的对象例如下面的例子获取了<code>MyClass</code> id <code>myId</code>
* 根据操作的成功或失败会回调不同的函数
* <pre>
* 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.
* }
* });</pre></p>
*
* <p>Bmob.Query 同时也能获取查询结果的数目例如下面的例子获取了<code>MyClass</code><pre>
* 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.
* }
* });</pre></p>
* @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:
* <pre>var compoundQuery = Bmob.Query.or(query1, query2, query3);</pre>
* 通过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<xzhuang@bmob.cn>.
_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 <code>Bmob.history</code>
* that will be created for you automatically if you make use of
* Routers with routes.
*
* <p>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 <a href="http://documentcloud.github.com/backbone/#History">
* Backbone documentation</a>.</p>
* <p><strong><em>Available in the client SDK only.</em></strong></p>
*/
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.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
Bmob.$(window).bind('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
Bmob.$(window).bind('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
this._checkUrlInterval = window.setInterval(this.checkUrl, this.interval);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = window.location;
var atRoot = loc.pathname === this.options.root;
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
window.location.replace(this.options.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({},
document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
if (!this.options.silent) {
return this.loadUrl();
}
},
// Disable Bmob.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function () {
Bmob.$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
window.clearInterval(this._checkUrlInterval);
Bmob.History.started = false;
},
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
route: function (route, callback) {
this.handlers.unshift({
route: route,
callback: callback
});
},
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function (e) {
var current = this.getFragment();
if (current === this.fragment && this.iframe) {
current = this.getFragment(this.getHash(this.iframe));
}
if (current === this.fragment) {
return false;
}
if (this.iframe) {
this.navigate(current);
}
if (!this.loadUrl()) {
this.loadUrl(this.getHash());
}
},
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function (fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers,
function (handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
return matched;
},
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the
// history.
navigate: function (fragment, options) {
if (!Bmob.History.started) {
return false;
}
if (!options || options === true) {
options = {
trigger: options
};
}
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment === frag) {
return;
}
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
if (frag.indexOf(this.options.root) !== 0) {
frag = this.options.root + frag;
}
this.fragment = frag;
var replaceOrPush = options.replace ? 'replaceState' : 'pushState';
window.history[replaceOrPush]({},
document.title, frag);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag !== this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier
// to push a history entry on hash-tag change.
// When replace is true, we don't want this.
if (!options.replace) {
this.iframe.document.open().close();
}
this._updateHash(this.iframe.location, frag, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
window.location.assign(this.options.root + fragment);
}
if (options.trigger) {
this.loadUrl(fragment);
}
},
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
_updateHash: function (location, fragment, replace) {
if (replace) {
var s = location.toString().replace(/(javascript:|#).*$/, '');
location.replace(s + '#' + fragment);
} else {
location.hash = fragment;
}
}
});
/**
* Routers map faux-URLs to actions, and fire events when routes are
* matched. Creating a new one sets its `routes` hash, if not set statically.
*
* <p>A fork of Backbone.Router, provided for your convenience.
* For more information, see the
* <a href="http://documentcloud.github.com/backbone/#Router">Backbone
* documentation</a>.</p>
* <p><strong><em>Available in the client SDK only.</em></strong></p>
*/
Bmob.Router = function (options) {
options = options || {};
if (options.routes) {
this.routes = options.routes;
}
this._bindRoutes();
this.initialize.apply(this, arguments);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[\-\[\]{}()+?.,\\\^\$\|#\s]/g;
// Set up all inheritable **Bmob.Router** properties and methods.
_.extend(Bmob.Router.prototype, Bmob.Events,
/** @lends Bmob.Router.prototype */
{
/**
* Initialize is an empty function by default. Override it with your own
* initialization logic.
*/
initialize: function () { },
/**
* Manually bind a single named route to a callback. For example:
*
* <pre>this.route('search/:query/p:num', 'search', function(query, num) {
* ...
* });</pre>
*/
route: function (route, name, callback) {
Bmob.history = Bmob.history || new Bmob.History();
if (!_.isRegExp(route)) {
route = this._routeToRegExp(route);
}
if (!callback) {
callback = this[name];
}
Bmob.history.route(route, _.bind(function (fragment) {
var args = this._extractParameters(route, fragment);
if (callback) {
callback.apply(this, args);
}
this.trigger.apply(this, ['route:' + name].concat(args));
Bmob.history.trigger('route', this, name, args);
},
this));
return this;
},
/**
* Whenever you reach a point in your application that you'd
* like to save as a URL, call navigate in order to update the
* URL. If you wish to also call the route function, set the
* trigger option to true. To update the URL without creating
* an entry in the browser's history, set the replace option
* to true.
*/
navigate: function (fragment, options) {
Bmob.history.navigate(fragment, options);
},
// Bind all defined routes to `Bmob.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes: function () {
if (!this.routes) {
return;
}
var routes = [];
for (var route in this.routes) {
if (this.routes.hasOwnProperty(route)) {
routes.unshift([route, this.routes[route]]);
}
}
for (var i = 0,
l = routes.length; i < l; i++) {
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function (route) {
route = route.replace(escapeRegExp, '\\$&').replace(namedParam, '([^\/]+)').replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters: function (route, fragment) {
return route.exec(fragment).slice(1);
}
});
/**
* @function
* @param {Object} instanceProps Instance properties for the router.
* @param {Object} classProps Class properies for the router.
* @return {Class} A new subclass of <code>Bmob.Router</code>.
*/
Bmob.Router.extend = Bmob._extend;
/**
* @namespace 生成二维码
*/
Bmob.generateCode = Bmob.generateCode || {};
Bmob.generateCode = function (data, options) {
var request = Bmob._request("wechatApp/qr/generatecode", null, null, 'POST', Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
}
/**
* @namespace 发送模板消息
*/
Bmob.sendMessage = Bmob.sendMessage || {};
Bmob.sendMessage = function (data, options) {
var request = Bmob._request("wechatApp/SendWeAppMessage", null, null, 'POST', Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
}
/**
* @namespace 处理短信的函数
*/
Bmob.Sms = Bmob.Sms || {};
_.extend(Bmob.Sms,
/** @lends Bmob.Sms */
{
/**
* 请求发送短信内容
* @param {Object} 相应的参数
* @param {Object} Backbone-style options 对象 options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise}
*/
requestSms: function (data, options) {
var request = Bmob._request("requestSms", null, null, 'POST', Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
},
/**
* 请求短信验证码
* @param {Object} 相应的参数
* @param {Object} Backbone-style options 对象 options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise}
*/
requestSmsCode: function (data, options) {
var request = Bmob._request("requestSmsCode", null, null, 'POST', Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
},
/**
* 验证短信验证码
* @param {Object} 相应的参数
* @param {Object} Backbone-style options 对象 options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise}
*/
verifySmsCode: function (mob, verifyCode, options) {
var data = {
"mobilePhoneNumber": mob
};
var request = Bmob._request("verifySmsCode/" + verifyCode, null, null, 'POST', Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
},
/**
* 查询短信状态
* @param {Object} 相应的参数
* @param {Object} Backbone-style options 对象 options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise}
*/
querySms: function (smsId, options) {
var request = Bmob._request("querySms/" + smsId, null, null, 'GET', null);
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
}
});
/**
* @namespace 支付功能
* <a href="http://docs.bmob.cn/restful/developdoc/index.html?menukey=develop_doc&key=develop_restful#index_支付服务">cloud functions</a>.
*/
Bmob.Pay = Bmob.Pay || {};
_.extend(Bmob.Pay, /** @lends Bmob.Cloud */
{
/**
* 网页端调起小程序支付接口
* @param {float} 价格
* @param {String} 商品名称
* @param {String} 描述
* @param {String} OPEN ID
* @param {Object} options -style options 对象
* options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise} A promise 将会处理云端代码调用的情况
*/
wechatPay: function (price, product_name, body, openid, options) {
var data = { "order_price": price, "product_name": product_name, "body": body, "open_id": openid, "pay_type": 4 }
var request = Bmob._request("pay", null, null, 'POST',
Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
},
/**
* 查询订单
* @param {String} 订单id
* @param {Object} options Backbone-style options 对象
* options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise} A promise 将会处理云端代码调用的情况
*/
queryOrder: function (orderId, options) {
var request = Bmob._request("pay/" + orderId, null, null, 'GET',
null);
return request.then(function (resp) {
return Bmob._decode(null, resp);
})._thenRunCallbacks(options);
}
});
/**
* @namespace 运行云端代码
* <a href="cloudcode/developdoc/index.html?menukey=develop_doc&key=develop_cloudcode">cloud functions</a>.
*/
Bmob.Cloud = Bmob.Cloud || {};
_.extend(Bmob.Cloud,
/** @lends Bmob.Cloud */
{
/**
* 运行云端代码
* @param {String} 云端代码的函数名
* @param {Object} 传人云端代码的参数
* @param {Object} options Backbone-style options 对象
* options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
* @return {Bmob.Promise} A promise 将会处理云端代码调用的情况
*/
run: function (name, data, options) {
var request = Bmob._request("functions", name, null, 'POST', Bmob._encode(data, null, true));
return request.then(function (resp) {
return Bmob._decode(null, resp).result;
})._thenRunCallbacks(options);
}
});
Bmob.Installation = Bmob.Object.extend("_Installation");
/**
* 包含push的函数
* @name Bmob.Push
* @namespace 推送消息
*/
Bmob.Push = Bmob.Push || {};
/**
* 推送消息
* @param {Object} data - 具体的参数请查看<a href="http://docs.bmob.cn/restful/developdoc/index.html?menukey=develop_doc&key=develop_restful">推送文档</a>.
* @param {Object} options options 对象 options.success, 如果设置了将会处理云端代码调用成功的情况 options.error 如果设置了将会处理云端代码调用失败的情况 两个函数都是可选的两个函数都只有一个参数
*/
Bmob.Push.send = function (data, options) {
if (data.where) {
data.where = data.where.toJSON().where;
}
if (data.push_time) {
data.push_time = data.push_time.toJSON();
}
if (data.expiration_time) {
data.expiration_time = data.expiration_time.toJSON();
}
if (data.expiration_time && data.expiration_time_interval) {
throw "Both expiration_time and expiration_time_interval can't be set";
}
var request = Bmob._request('push', null, null, 'POST', data);
return request._thenRunCallbacks(options);
};
}.call(this));