UNPKG

clearblade-js-client

Version:

ClearBlade JavaScript API =========================

1,475 lines (1,380 loc) 123 kB
/******************************************************************************* * Copyright 2013 ClearBlade, Inc * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Any redistribution of this program in any form must include this copyright *******************************************************************************/ if (!window.console) { window.console = window.console || {}; window.console.log = window.console.log || function () {}; } function createClearBladeInstance (window, options) { // 'use strict'; var ClearBlade; /** * This is the base module for the ClearBlade Platform API * @namespace ClearBlade * @example <caption>Initialize ClearBladeAPI</caption> * initOptions = {systemKey: 'asdfknafikjasd3853n34kj2vc', systemSecret: 'SHHG245F6GH7SDFG823HGSDFG9'}; * var cb = new ClearBlade(); * cb.init(initOptions); * */ if (typeof exports != 'undefined') { // window = exports; } ClearBlade = function () {}; ClearBlade.MESSAGING_QOS_AT_MOST_ONCE = 0; ClearBlade.MESSAGING_QOS_AT_LEAST_ONCE = 1; ClearBlade.MESSAGING_QOS_EXACTLY_ONCE = 2; /** * This method initializes the ClearBlade module with the values needed to connect to the platform * @method ClearBlade.init * @param {Object} options This value contains the config object for initializing the ClearBlade module. A number of reasonable defaults are set for the option if none are set. *<p> *The connect options and their defaults are: * <p>{String} [systemKey] This is the app key that will identify your app in order to connect to the Platform</p> * <p>{String} [systemSecret] This is the app secret that will be used in combination with the systemKey to authenticate your app</p> * <p>{String} [URI] This is the URI used to identify where the Platform is located. Default is https://platform.clearblade.com</p> * <p>{String} [messagingURI] This is the URI used to identify where the Messaging server is located. Default is platform.clearblade.com</p> n * <p>{Number} [messagingPort] This is the default port used when connecting to the messaging server. Default is 8904</p> * <p>{Boolean} [logging] This is the property that tells the API whether or not the API will log to the console. This should be left `false` in production. Default is false</p> * <p>{Number} [callTimeout] This is the amount of time that the API will use to determine a timeout. Default is 30 seconds</p> * <p>{Boolean} [mqttAuth] Setting this to true and providing an email and password will use mqtt websockets to authenticate, rather than http.</p> * <p>{String} [messagingAuthPort] is the port that the messaging auth websocket server is listening on.</p> *</p> */ ClearBlade.prototype.init = function (options) { var _this = this; //check for undefined/null then check if they are the correct types for required params if (!options || typeof options !== 'object') throw new Error('Options must be an object or it is undefined'); if (!options.systemKey || typeof options.systemKey !== 'string') throw new Error('systemKey must be defined/a string'); if (!options.systemSecret || typeof options.systemSecret !== 'string') throw new Error('systemSecret must be defined/a string'); //check for optional params. if (options.logging && typeof options.logging !== 'boolean') throw new Error('logging must be a true boolean if present'); if (options.callback && typeof options.callback !== 'function') { throw new Error('callback must be a function'); } if (options.email && typeof options.email !== 'string') { throw new Error('email must be a string'); } if (options.password && typeof options.password !== 'string') { throw new Error('password must be a string'); } if (options.registerUser && typeof options.registerUser !== 'boolean') { throw new Error('registerUser must be a true boolean if present'); } if ( options.useUser && (!options.useUser.email || !options.useUser.authToken) ) { throw new Error( 'useUser must contain both an email and an authToken ' + '{"email":email,"authToken":authToken}' ); } if (options.email && !options.password) { throw new Error('Must provide a password for email'); } if (options.password && !options.email) { throw new Error('Must provide a email for password'); } if (options.registerUser && !options.email) { throw new Error('Cannot register anonymous user. Must provide an email'); } if ( options.useUser && (options.email || options.password || options.registerUser) ) { throw new Error( 'Cannot authenticate or register a new user when useUser is set' ); } // store keys /** * This is the app key that will identify your app in order to connect to the Platform * @property systemKey * @type String */ ClearBlade.prototype.systemKey = options.systemKey; this.systemKey = options.systemKey; /** * This is the app secret that will be used in combination with the systemKey to authenticate your app * @property systemSecret * @type String */ ClearBlade.prototype.systemSecret = options.systemSecret; this.systemSecret = options.systemSecret; /** * This is the master secret that is used during development to test many apps at a time * This is not currently not in use * @property masterSecret * @type String */ ClearBlade.prototype.masterSecret = options.masterSecret; this.masterSecret = options.masterSecret || null; /** * This is the URI used to identify where the Platform is located * @property URI * @type String */ ClearBlade.prototype.URI = options.URI; this.URI = options.URI || 'https://platform.clearblade.com'; /** * This is the URI used to identify where the Messaging server is located * @property messagingURI * @type String */ ClearBlade.prototype.messagingURI = options.messagingURI; this.messagingURI = options.messagingURI || 'platform.clearblade.com'; /** * This is the default port used when connecting to the messaging server * @prpopert messagingPort * @type Number */ ClearBlade.prototype.messagingPort = options.messagingPort; this.messagingPort = options.messagingPort || 8904; /** * This is the property that tells the API whether or not the API will log to the console * This should be left `false` in production * @property logging * @type Boolean */ ClearBlade.prototype.logging = options.logging; this.logging = options.logging || false; ClearBlade.prototype.defaultQoS = options.defaultQoS; this.defaultQoS = options.defaultQoS || 0; /** * This is the amount of time that the API will use to determine a timeout * @property _callTimeout=30000 * @type Number * @private */ ClearBlade.prototype._callTimeout = options.callTimeout; this._callTimeout = options.callTimeout || 30000; //default to 30 seconds /** * This property tells us which port to use for websocket mqtt auth. * @property messagingAuthPort * @type Number */ this.messagingAuthPort = options.messagingAuthPort || 8907; this.user = {}; if (options.useUser) { _this.setUser(options.useUser.email, options.useUser.authToken); } else if (options.registerUser) { this.registerUser(options.email, options.password, function ( err, response ) { if (err) { execute(err, response, options.callback); } else { _this.loginUser(options.email, options.password, function ( err, user ) { execute(err, user, options.callback); }); } }); } else if (options.email) { if (options.mqttAuth) { this.loginUserMqtt(options.email, options.password, function ( err, user ) { execute(err, user, options.callback); }); } else { this.loginUser(options.email, options.password, function (err, user) { execute(err, user, options.callback); }); } } else { this.loginAnon(function (err, user) { execute(err, user, options.callback); }); } }; var _validateEmailPassword = function (email, password) { if (email == null || email == undefined || typeof email != 'string') { throw new Error('Email must be given and must be a string'); } if ( password == null || password == undefined || typeof password != 'string' ) { throw new Error('Password must be given and must be a string'); } }; /** * Used when assuming the role of a user to make subsequent requests * @method ClearBlade.setUser * @param email {String} the email of the user * @param authToken {String} the authToken for the user */ ClearBlade.prototype.setUser = function (email, authToken) { this.user.email = email; this.user.authToken = authToken; ClearBlade.prototype.user = this.user; }; /** * Method to register a user with the ClearBlade Platform * @method ClearBlade.registerUser * @param email {String} the users email * @param password {String} the password for the user * @param callback {function} returns a Boolean error value and a response as parameters * @example <caption> Register User </caption> * cb.registerUser("newUser@domain.com", "qwerty", function(err, body) { * if(err) { * //handle error * } else { * console.log(body); * } * }); */ ClearBlade.prototype.registerUser = function (email, password, callback) { _validateEmailPassword(email, password); ClearBlade.request( { method: 'POST', endpoint: 'api/v/1/user/reg', useUser: true, user: this.user, body: { email: email, password: password }, authToken: this.user.authToken, systemKey: this.systemKey, systemSecret: this.systemSecret, timeout: this._callTimeout, URI: this.URI, }, function (err, response) { if (err) { execute(true, response, callback); } else { this.setUser(email, response.user_token); execute(false, this.user, callback); } }.bind(this) ); }; /** * Method to check if the current user has an active server session * @method ClearBlade.isCurrentUserAuthenticated * @param {Function} callback * @example * cb.isCurrentUserAuthenticated(function(err, body) { * if(err) { * //handle error * } else { * //check authentication boolean * } * }) */ ClearBlade.prototype.isCurrentUserAuthenticated = function (callback) { ClearBlade.request( { method: 'POST', endpoint: 'api/v/1/user/checkauth', systemKey: this.systemKey, systemSecret: this.systemSecret, timeout: this._callTimeout, user: this.user, URI: this.URI, }, function (err, response) { if (err) { execute(true, response, callback); } else { execute(false, response.is_authenticated, callback); } } ); }; /** * Method to end the server session for the current user * @method ClearBlade.logoutUser * @param {Function} callback * @example * cb.logoutUser(function(err, body) { * if(err) { * //handle error * } else { * //do post logout stuff * } * }) */ ClearBlade.prototype.logoutUser = function (callback) { ClearBlade.request( { method: 'POST', endpoint: 'api/v/1/user/logout', systemKey: this.systemKey, systemSecret: this.systemSecret, timeout: this._callTimeout, user: this.user, URI: this.URI, }, function (err, response) { if (err) { execute(true, response, callback); } else { execute(false, 'User Logged out', callback); } } ); }; /** * Method to create an anonymous session with the ClearBlade Platform * @method ClearBlade.loginAnon * @param {Function} callback * @example * cb.loginAnon(function(err, body) { * if(err) { * //handle error * } else { * //do post login stuff * } * }) */ ClearBlade.prototype.loginAnon = function (callback) { var _this = this; ClearBlade.request( { method: 'POST', useUser: false, endpoint: 'api/v/1/user/anon', systemKey: this.systemKey, systemSecret: this.systemSecret, timeout: this._callTimeout, URI: this.URI, }, function (err, response) { if (err) { execute(true, response, callback); } else { _this.setUser(null, response.user_token); execute(false, _this.user, callback); } } ); }; /** * Method to create an authenticated session with the ClearBlade Platform * @method * @param {String} email * @param {String} password * @param {Function} callback * @example * cb.loginUser("existentUser@domain.com", "qwerty", function(err, body) { * if(err) { * //handle error * } else { * //do post login stuff * } * }) */ ClearBlade.prototype.loginUser = function (email, password, callback) { var _this = this; _validateEmailPassword(email, password); ClearBlade.request( { method: 'POST', useUser: false, endpoint: 'api/v/1/user/auth', systemKey: this.systemKey, systemSecret: this.systemSecret, URI: this.URI, timeout: this._callTimeout, body: { email: email, password: password }, }, function (err, response) { if (err) { execute(true, response, callback); } else { _this.setUser(email, response.user_token); execute(false, _this.user, callback); } } ); }; /** * Method to log user or developer in via MQTT over websockets * @method ClearBlade.loginUserMqtt * @param {String} email * @param {String} password * @param {Function} callback * @example * cb.loginUserMqtt("foo@bar.baz","secret_password", function(err, body) { * if(err) { * //handle error * } else { * //do post login stuff * } * }) */ ClearBlade.prototype.loginUserMqtt = function (email, password, callback) { var _this = this; _validateEmailPassword(email, password); var clientid = email + ':' + password; var client = new Paho.MQTT.Client( _this.messagingURI, _this.messagingAuthPort, '/mqtt_auth', clientid ); var ourTopic = _this.systemKey + '/' + email; //helper var getString = function (msg) { //take two bytes if (msg.length < 2) { var err = new Error('Bad Return Value from mqtt auth: bad length'); callback(err, null); } var b1 = msg[0]; var b2 = msg[1]; //get the string length var len = b1.charCodeAt(0) + b2.charCodeAt(0); if (msg.length < len + 2) { var err = new Error( 'Bad return value from mqtt auth: length longer than substring' ); callback(err, null); } return msg.substring(0, len); }; var onConnect = function () { //subscribe to our topic client.subscribe(ourTopic, { qos: 0 }); }; var success = false; var msgArrived = function (msg) { //we only anticipate receiving one message if (msg.destinationName != ourTopic) { return; } var body = msg.payloadString; var tok = getString(body); body = body.substring(tok.length + 2); //usrid is unused by the sdk at the momment var usrid = getString(body); body = body.substring(usrid.length + 2); var msgingHost = getString(body); _this.setUser(email, tok); _this.messagingURI = msgingHost; success = true; client.disconnect(); callback(false, _this.user); }; var mqtt_options = { useSSL: true, cleanSession: true, userName: _this.systemKey, password: _this.systemSecret, onSuccess: onConnect, onFailure: function (msg) { if (!success) { var err = new Error('failed to authenticate: ' + JSON.stringify(msg)); console.log(err); callback(err, null); } }, }; client.onConnectionLost = function (msg) { if (!success) { var err = new Error('connection lost ' + JSON.stringify(msg)); callback(err, null); } }; client.onMessageArrived = msgArrived; client.connect(mqtt_options); }; var masterCallback = null; ClearBlade.prototype.registerMasterCallback = function (callback) { if (typeof callback === 'function') { masterCallback = callback; } else { logger('Did you forget to supply a valid Callback!'); } }; /* * Helper functions */ var execute = function (error, response, callback) { if (typeof callback === 'function') { if (masterCallback !== null) { masterCallback(error, response); } callback(error, response); } else { logger('Did you forget to supply a valid Callback!'); } }; var logger = function (message) { if (ClearBlade.logging) { console.log(message); } return; }; var isObjectEmpty = function (object) { /*jshint forin:false */ if (typeof object !== 'object') { return true; } for (var keys in object) { return false; } return true; }; /* * request method * */ var _createItemList = function (err, data, options, callback) { if (data === undefined) { callback(true, 'There was some problem. Data is undefined'); } else { var itemArray = []; for (var i = 0; i < data.length; i++) { itemArray.push(ClearBlade.prototype.Item(data[i], options)); } callback(err, itemArray); } }; var _request = function (options, callback) { var method = options.method || 'GET'; var endpoint = options.endpoint || ''; var body = options.body || {}; var qs = options.qs || ''; var url = options.URI || 'https://platform.clearblade.com'; var useUser = options.useUser || true; var authToken = useUser && options.authToken; var callTimeout = options.timeout || 30000; if (useUser && !authToken && options.user && options.user.authToken) { authToken = options.user.authToken; } var params = qs; if (endpoint) { url += '/' + endpoint; } if (params) { url += '?' + params; } //begin XMLHttpRequest var httpRequest; if (typeof window.XMLHttpRequest !== 'undefined') { // Mozilla, Safari, IE 10 .. httpRequest = new XMLHttpRequest(); // if "withCredentials is not in the XMLHttpRequest object CORS is not supported // if (!("withCredentials" in httpRequest)) { // logger("Sorry it seems that CORS is not supported on your Browser. The RESTful api calls will not work!"); // httpRequest = null; // throw new Error("CORS is not supported!"); // } httpRequest.open(method, url, true); } else if (typeof window.XDomainRequest !== 'undefined') { // IE 8/9 httpRequest = new XDomainRequest(); httpRequest.open(method, url); } else { alert( 'Sorry it seems that CORS is not supported on your Browser. The RESTful api calls will not work!' ); httpRequest = null; throw new Error('CORS is not supported!'); } // Set Credentials; Maybe some encryption later if (authToken) { httpRequest.setRequestHeader('CLEARBLADE-USERTOKEN', authToken); } if (options.systemKey) { httpRequest.setRequestHeader('ClearBlade-SystemKey', options.systemKey); } if (options.systemSecret) { httpRequest.setRequestHeader( 'ClearBlade-SystemSecret', options.systemSecret ); } if (!isObjectEmpty(body) || params) { if (method === 'POST' || method === 'PUT') { // Content-Type is expected for POST and PUT; bad things can happen if you don't specify this. httpRequest.setRequestHeader('Content-Type', 'application/json'); } httpRequest.setRequestHeader('Accept', 'application/json'); } httpRequest.onreadystatechange = function () { if (httpRequest.readyState === 4) { // Looks like we didn't time out! clearTimeout(xhrTimeout); //define error for the entire scope of the if statement var error = false; if (httpRequest.status >= 200 && httpRequest.status < 300) { var parsedResponse; var response; var flag = true; // try to parse response, it should be JSON if ( httpRequest.responseText == '[{}]' || httpRequest.responseText == '[]' ) { error = false; execute(error, [], callback); } else { try { response = JSON.parse(httpRequest.responseText); parsedResponse = []; for (var item in response) { if (response[item] instanceof Object) { for (var key in response[item]) { if (response[item][key] instanceof Object) { if (response[item][key]['_key']) { delete response[item][key]['_key']; } if (response[item][key]['collectionID']) { delete response[item][key]['collectionID']; } parsedResponse.push(response[item][key]); continue; } else { if (response[item]['_key']) { delete response[item]['_key']; } if (response[item]['collectionID']) { delete response[item]['collectionID']; } parsedResponse.push(response[item]); break; } } } else { flag = true; } } } catch (e) { // the response probably was not JSON; Probably had html in it, just output it until all requirements of our backend are defined. if (e instanceof SyntaxError) { response = httpRequest.responseText; // some other error occured; log message , execute callback } else { logger('Error during JSON response parsing: ' + e); error = true; execute(error, e, callback); } } // end of catch // execute callback with whatever was in the response if (flag) { execute(error, response, callback); } else { execute(error, parsedResponse, callback); } } } else { var msg = httpRequest.responseText; // var msg = "Request Failed: Status " + httpRequest.status + " " + (httpRequest.statusText); /*jshint expr: true */ // httpRequest.responseText && (msg += "\nmessage:" + httpRequest.responseText); logger(msg); error = true; execute(error, msg, callback); } } }; logger('calling: ' + method + ' ' + url); body = JSON.stringify(body); // set up our own TimeOut function, because XMLHttpRequest.onTimeOut is not implemented by all browsers yet. function callAbort() { httpRequest.abort(); logger('It seems the request has timed Out, please try again.'); execute(true, 'API Request TimeOut', callback); } // set timeout and timeout function var xhrTimeout = setTimeout(callAbort, callTimeout); httpRequest.send(body); }; ClearBlade.request = options.request || function (options, callback) { if (!options || typeof options !== 'object') { throw new Error('Request: options is not an object or is empty'); } _request(options, callback); }; var _parseOperationQuery = function (_query) { return encodeURIComponent(JSON.stringify(_query.FILTERS || [])); }; var _parseQuery = function (_query) { var parsed = encodeURIComponent(JSON.stringify(_query)); return parsed; }; /** * Creates a new Collection that represents the server-side collection with the specified collection ID * @class ClearBlade.Collection * @classdesc This class represents a server-side collection. It does not actully make a connection upon instantiation, but has all the methods necessary to do so. It also has all the methods necessary to do operations on the server-side collections. * @param {String} collectionID The string ID for the collection you want to represent. * @example * var col = cb.Collection("12asd3049qwe834qe23asdf1234"); */ ClearBlade.prototype.Collection = function (options) { var collection = {}; if (typeof options === 'string') { collection.endpoint = 'api/v/1/data/' + options; options = { collectionID: options }; } else if (options.collectionName && options.collectionName !== '') { collection.isUsingCollectionName = true; collection.name = options.collectionName; collection.endpoint = 'api/v/1/collection/' + this.systemKey + '/' + options.collectionName; } else if (options.collectionID && options.collectionID !== '') { collection.endpoint = 'api/v/1/data/' + options.collectionID; } else { throw new Error( 'Must supply a collectionID or collectionName key in options object' ); } collection.user = this.user; collection.URI = this.URI; collection.systemKey = this.systemKey; collection.systemSecret = this.systemSecret; /** * Reqests an item or a set of items from the collection. * @method ClearBlade.Collection.prototype.fetch * @param {Query} _query Used to request a specific item or subset of items from the collection on the server. Optional. * @param {function} callback Supplies processing for what to do with the data that is returned from the collection * @return {ClearBlade.Item} An array of ClearBlade Items * @example <caption>Fetching data from a collection</caption> * var returnedData = []; * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * returnedData = data; * } * }; * * col.fetch(query, callback); * //this will give returnedData the value of what ever was returned from the server. */ collection.fetch = function (_query, callback) { var query; /* * The following logic may look funny, but it is intentional. * I do this because it is typeical for the callback to be the last parameter. * However, '_query' is an optional parameter, so I have to check if 'callback' is undefined * in order to see weather or not _query is defined. */ if (callback === undefined) { callback = _query; query = { FILTERS: [], }; query = 'query=' + _parseQuery(query); } else { if (Object.keys(_query) < 1) { query = ''; } else { query = 'query=' + _parseQuery(_query.query); } } var reqOptions = { method: 'GET', endpoint: this.endpoint, qs: query, user: this.user, URI: this.URI, }; var callCallback = function (err, data) { if (err) { callback(err, data); } else { _createItemList(err, data.DATA, options, callback); } }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callCallback); } else { logger('No callback was defined!'); } }; /** * Creates a new item in the collection and returns the created item to the callback * @method ClearBlade.Collection.prototype.create * @param {Object} newItem An object that represents an item that you want to add to the collection * @param {function} callback Supplies processing for what to do with the data that is returned from the collection * @example <caption>Creating a new item in the collection</caption> * //This example assumes a collection of items that have the columns: name, height, and age. * var newPerson = { * name: 'Jim', * height: 70, * age: 32 * }; * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * console.log(data); * } * }; * col.create(newPerson, callback); * //this inserts the the newPerson item into the collection that col represents * */ collection.create = function (newItem, callback) { var reqOptions = { method: 'POST', endpoint: this.endpoint, body: newItem, user: this.user, URI: this.URI, }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callback); } else { logger('No callback was defined!'); } }; /** * Updates an existing item or set of items * @method ClearBlade.Collection.prototype.update * @param {Query} _query Query object to denote which items or set of Items will be changed * @param {Object} changes Object representing the attributes that you want changed * @param {function} callback Function that handles the response of the server * @example <caption>Updating a set of items</caption> * //This example assumes a collection of items that have the columns name and age. * var query = ClearBlade.Query(); * query.equalTo('name', 'John'); * var changes = { * age: 23 * }; * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * console.log(data); * } * }; * * col.update(query, changes, callback); * //sets John's age to 23 */ collection.update = function (_query, changes, callback) { var reqOptions = { method: 'PUT', endpoint: this.endpoint, body: { query: _query.query.FILTERS, $set: changes }, user: this.user, URI: this.URI, }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callback); } else { logger('No callback was defined!'); } }; /** * Removes an item or set of items from the specified collection * @method ClearBlade.Collection.prototype.remove * @param {Query} _query Query object that used to define what item or set of items to remove * @param {function} callback Function that handles the response from the server * @example <caption>Removing an item in a collection</caption> * //This example assumes that you have a collection with the item whose 'name' attribute is 'John' * var query = ClearBlade.Query(); * query.equalTo('name', 'John'); * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * console.log(data); * } * }; * * col.remove(query, callback); * //removes every item whose 'name' attribute is equal to 'John' */ collection.remove = function (_query, callback) { var query; if (_query === undefined) { throw new Error('no query defined!'); } else { query = 'query=' + _parseOperationQuery(_query.query); } var reqOptions = { method: 'DELETE', endpoint: this.endpoint, qs: query, user: this.user, URI: this.URI, }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callback); } else { logger('No callback was defined!'); } }; collection.columns = function (callback) { if (typeof callback === 'function') { ClearBlade.request( { method: 'GET', URI: this.URI, endpoint: this.isUsingCollectionName ? 'api/v/2/collection/' + this.systemKey + '/' + this.name + '/columns' : this.endpoint + '/columns', systemKey: this.systemKey, systemSecret: this.systemSecret, user: this.user, }, callback ); } else { logger('No callback was defined!'); } }; collection.count = function (_query, callback) { if (typeof callback === 'function') { var query; if (_query === undefined || Object.keys(_query).length < 1) { query = ''; } else { query = 'query=' + _parseQuery(_query.query); } ClearBlade.request( { method: 'GET', URI: this.URI, qs: query, endpoint: this.isUsingCollectionName ? 'api/v/2/collection/' + this.systemKey + '/' + this.name + '/count' : this.endpoint + '/count', systemKey: this.systemKey, systemSecret: this.systemSecret, user: this.user, }, callback ); } else { logger('No callback was defined!'); } }; return collection; }; /** * creates and returns a Query object that can be used in Collection methods or on its own to operate on items on the server * @class ClearBlade.Query * @param {Object} options Object that has configuration values used when instantiating a Query object * @returns {Object} Clearblade.Query the created query */ ClearBlade.prototype.Query = function (options) { var _this = this; var query = {}; if (!options) { options = {}; } if (typeof options === 'string') { query.endpoint = 'api/v/1/data/' + options; options = { collectionID: options }; } else if (options.collectionName && options.collectionName !== '') { query.endpoint = 'api/v/1/collection/' + this.systemKey + '/' + options.collectionName; } else if (options.collectionID && options.collectionID !== '') { query.endpoint = 'api/v/1/data/' + options.collectionID; } query.user = this.user; query.URI = this.URI; query.systemKey = this.systemKey; query.systemSecret = this.systemSecret; query.query = {}; query.OR = []; query.OR.push([query.query]); query.offset = options.offset || 0; query.limit = options.limit || 10; query.addSortToQuery = function (queryObj, direction, column) { if (typeof queryObj.query.SORT === 'undefined') { queryObj.query.SORT = []; } var newSort = {}; newSort[direction] = column; queryObj.query.SORT.push(newSort); }; query.addFilterToQuery = function (queryObj, condition, key, value) { var newObj = {}; newObj[key] = value; var newFilter = {}; newFilter[condition] = [newObj]; if (typeof queryObj.query.FILTERS === 'undefined') { queryObj.query.FILTERS = []; queryObj.query.FILTERS.push([newFilter]); return; } else { for (var i = 0; i < queryObj.query.FILTERS[0].length; i++) { for (var k in queryObj.query.FILTERS[0][i]) { if (queryObj.query.FILTERS[0][i].hasOwnProperty(k)) { if (k === condition) { queryObj.query.FILTERS[0][i][k].push(newObj); return; } } } } queryObj.query.FILTERS[0].push(newFilter); } }; query.ascending = function (field) { this.addSortToQuery(this, 'ASC', field); return this; }; query.descending = function (field) { this.addSortToQuery(this, 'DESC', field); return this; }; /** * Creates an equality clause in the query object * @method ClearBlade.Query.prototype.equalTo * @param {String} field String defining what attribute to compare * @param {String} value String or Number that is used to compare against * @example <caption>Adding an equality clause to a query</caption> * var query = ClearBlade.Query(); * query.equalTo('name', 'John'); * //will only match if an item has an attribute 'name' that is equal to 'John' */ query.equalTo = function (field, value) { this.addFilterToQuery(this, 'EQ', field, value); return this; }; /** * Creates a greater than clause in the query object * @method ClearBlade.Query.prototype.greaterThan * @param {String} field String defining what attribute to compare * @param {String} value String or Number that is used to compare against * @example <caption>Adding a greater than clause to a query</caption> * var query = ClearBlade.Query(); * query.greaterThan('age', 21); * //will only match if an item has an attribute 'age' that is greater than 21 */ query.greaterThan = function (field, value) { this.addFilterToQuery(this, 'GT', field, value); return this; }; /** * Creates a greater than or equality clause in the query object * @method ClearBlade.Query.prototype.greaterThanEqualTo * @param {String} field String defining what attribute to compare * @param {String} value String or Number that is used to compare against * @example <caption>Adding a greater than or equality clause to a query</caption> * var query = ClearBlade.Query(); * query.greaterThanEqualTo('age', 21); * //will only match if an item has an attribute 'age' that is greater than or equal to 21 */ query.greaterThanEqualTo = function (field, value) { this.addFilterToQuery(this, 'GTE', field, value); return this; }; /** * Creates a less than clause in the query object * @method ClearBlade.Query.prototype.lessThan * @param {String} field String defining what attribute to compare * @param {String} value String or Number that is used to compare against * @example <caption>Adding a less than clause to a query</caption> * var query = ClearBlade.Query(); * query.lessThan('age', 50); * //will only match if an item has an attribute 'age' that is less than 50 */ query.lessThan = function (field, value) { this.addFilterToQuery(this, 'LT', field, value); return this; }; /** * Creates a less than or equality clause in the query object * @method ClearBlade.Query.prototype.lessThanEqualTo * @param {String} field String defining what attribute to compare * @param {String} value String or Number that is used to compare against * @example <caption>Adding a less than or equality clause to a query</caption> * var query = ClearBlade.Query(); * query.lessThanEqualTo('age', 50); * //will only match if an item has an attribute 'age' that is less than or equal to 50 */ query.lessThanEqualTo = function (field, value) { this.addFilterToQuery(this, 'LTE', field, value); return this; }; /** * Creates a not equal clause in the query object * @method ClearBlade.Query.prototype.notEqualTo * @param {String} field String defining what attribute to compare * @param {String} value String or Number that is used to compare against * @example <caption>Adding a not equal clause to a query</caption> * var query = ClearBlade.Query(); * query.notEqualTo('name', 'Jim'); * //will only match if an item has an attribute 'name' that is not equal to 'Jim' */ query.notEqualTo = function (field, value) { this.addFilterToQuery(this, 'NEQ', field, value); return this; }; /** * Creates an regular expression matching clause in the query object * @method ClearBlade.Query.prototype.matches * @param {String} field String defining what attribute to compare * @param {String} pattern String or Number that is used to compare against * @example <caption>Adding an regex matching clause to a query</caption> * var query = ClearBlade.Query(); * query.matches('name', 'Smith$'); * //will only match if an item has an attribute 'name' that That ends in 'Smith' */ query.matches = function (field, pattern) { this.addFilterToQuery(this, 'RE', field, pattern); return this; }; /** * chains an existing query object to the Query object in an or * @method ClearBlade.Query.prototype.or * @param {Query} that Query object that will be added in disjunction to this query object * @example <caption>Chaining two queries together in an or</caption> * var query1 = ClearBlade.Query(); * var query2 = ClearBlade.Query(); * query1.equalTo('name', 'John'); * query2.equalTo('name', 'Jim'); * query1.or(query2); * //will match if an item has an attribute 'name' that is equal to 'John' or 'Jim' */ query.or = function (that) { if ( this.query.hasOwnProperty('FILTERS') && that.query.hasOwnProperty('FILTERS') ) { for (var i = 0; i < that.query.FILTERS.length; i++) { this.query.FILTERS.push(that.query.FILTERS[i]); } return this; } else if ( !this.query.hasOwnProperty('FILTERS') && that.query.hasOwnProperty('FILTERS') ) { for (var j = 0; j < that.query.FILTERS.length; j++) { this.query.FILTERS = []; this.query.FILTERS.push(that.query.FILTERS[j]); } return this; } }; /** * Set the pagination options for a Query. * @method ClearBlade.Query.prototype.setPage * @param {int} pageSize Number of items per response page. The default is * 100. * @param {int} pageNum Page number, taking into account the page size. The * default is 1. */ query.setPage = function (pageSize, pageNum) { this.query.PAGESIZE = pageSize; this.query.PAGENUM = pageNum; return this; }; /** * Get the field value for a particular column. * @method ClearBlade.Query.prototype.getFieldValue * @param {string} field Name of column to retrieve the value for */ query.getFieldValue = function (field) { var filters = this.query .FILTERS; /* [[{"EQ":[{"user_id":"f094b5b10bf6ad86f7f7d4b8b9f201"}]}]] */ for (var i = 0, len = filters.length; i < len; i++) { for (var j = 0, jLen = filters[i].length; j < jLen; j++) { for (var key in filters[i][j]) { for (var k = 0, kLen = filters[i][j][key].length; k < kLen; k++) { if (filters[i][j][key][k][field]) { return filters[i][j][key][k]; } } } } } return null; }; /** * Reqests an item or a set of items from the query. Requires that * the Query object was initialized with a collection. * @method ClearBlade.Query.prototype.fetch * @param {function} callback Supplies processing for what to do with the data that is returned from the collection * @return {ClearBlade.Item} An array of ClearBlade Items * @example <caption>The typical callback</caption> * var query = ClearBlade.Query({'collection': 'COLLECTIONID'}); * var callback = function (err, data) { * if (err) { * //error handling * } else { * console.log(data); * } * }; * query.fetch(callback); */ query.fetch = function (callback) { var reqOptions = { method: 'GET', qs: 'query=' + _parseQuery(this.query), user: this.user, endpoint: this.endpoint, URI: this.URI, }; var callCallback = function (err, data) { if (err) { callback(err, data); } else { _createItemList(err, data.DATA, options, callback); } }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callCallback); } else { logger('No callback was defined!'); } }; /** * Updates an existing item or set of items. Requires that a collection was * set when the Query was initialized. * @method ClearBlade.Query.prototype.update * @param {Object} changes Object representing the attributes that you want changed * @param {function} callback Function that handles the response of the server * @example <caption>Updating a set of items</caption> * //This example assumes a collection of items that have the columns name and age. * var query = ClearBlade.Query({'collection': 'COLLECTIONID'}); * query.equalTo('name', 'John'); * var changes = { * age: 23 * }; * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * console.log(data); * } * }; * * query.update(changes, callback); * //sets John's age to 23 */ query.update = function (changes, callback) { var reqOptions = { method: 'PUT', body: { query: this.query.FILTERS, $set: changes }, user: this.user, endpoint: this.endpoint, URI: this.URI, }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callback); } else { logger('No callback was defined!'); } }; /** * Gets rows of the specified columns * @method ClearBlade.Query.prototype.columns * @param {Object} Columns object * @param {function} callback Function that handles the response of the server * @example <caption>Getting values of columns</caption> * //This example assumes a collection of items that have the columns name and age. * var query = ClearBlade.Query({'collectionName': 'COLLECTIONNAME'}); * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * console.log(data); * } * }; * * query.columns(["name","age"]); * query.fetch(callback); * //gets values in columns name and age */ query.columns = function (columnsArray) { this.query.SELECTCOLUMNS = columnsArray; return this; }; query.groupBy = function (columnsArray) { this.query.GROUPBY = columnsArray; return this; }; /** * Removes an item or set of items from the Query * @method ClearBlade.Query.prototype.remove * @param {function} callback Function that handles the response from the server * @example <caption>Removing an item in a collection</caption> * //This example assumes that you have a collection with the item whose 'name' attribute is 'John' * var query = ClearBlade.Query({'collection': 'COLLECTIONID'}); * query.equalTo('name', 'John'); * var callback = function (err, data) { * if (err) { * throw new Error (data); * } else { * console.log(data); * } * }; * * query.remove(callback); * //removes every item whose 'name' attribute is equal to 'John' */ query.remove = function (callback) { var reqOptions = { method: 'DELETE', qs: 'query=' + _parseQuery(this.query), user: this.user, endpoint: this.endpoint, URI: this.URI, }; if (typeof callback === 'function') { ClearBlade.request(reqOptions, callback); } else { logger('No callback was defined!'); } }; return query; }; /** * Note: This class cannot be used with connections * @class ClearBlade.Item * @param {Object} data Object that contains necessary data for an item in a ClearBlade Collection * @param {String} collection Collection ID of the collection the item belongs to */ ClearBlade.prototype.Item = function (data, options) { var item = {}; if (!(data instanceof Object)) { throw new Error('data must be of type Object'); } if (options === undefined || options === null || options === '') { throw new Error('Must supply an options parameter'); } if (typeof options === 'string') { options = { collectionID: options }; } item.data = data; item.save = function (callback) { //do a put or a post to the database to save the item in the db var self = this; var query = ClearBlade.prototype.Query(options); query.equalTo('item_id', this.data.item_id); var callCallback = function (err, data) { if (err) { callback(err, data); } else { self.data = data[0]; callback(err, data); } }; query.update(this.data, callCallback); }; item.re