diff --git a/app.js b/app.js index 7ce5297..9e6fcc0 100644 --- a/app.js +++ b/app.js @@ -80,7 +80,7 @@ } return false; - }, + } }); $('#go').click(function(e) { @@ -156,7 +156,7 @@ && !response.Website.startsWith('http://')) { response.Website = 'http://'+response.Website; } - $dialog.TrimPath.processDOMTemplate("detail_jst" + $dialog.html(TrimPath.processDOMTemplate("detail_jst" ,response)); $dialog.find('#industry').click(function(e) { e.preventDefault(); @@ -240,4 +240,4 @@ client.query(query, queryCallback, errorCallback); } - \ No newline at end of file + diff --git a/blob.js b/blob.js new file mode 100644 index 0000000..6c3a255 --- /dev/null +++ b/blob.js @@ -0,0 +1,211 @@ +/* Blob.js + * A Blob implementation. + * 2014-07-24 + * + * By Eli Grey, http://eligrey.com + * By Devin Samarin, https://github.com/dsamarin + * License: MIT + * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md + */ + +/*global self, unescape */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ + +(function (view) { + "use strict"; + + view.URL = view.URL || view.webkitURL; + + if (view.Blob && view.URL) { + try { + new Blob; + return; + } catch (e) {} + } + + // Internally we use a BlobBuilder implementation to base Blob off of + // in order to support older browsers that only have BlobBuilder + var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { + var + get_class = function(object) { + return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; + } + , FakeBlobBuilder = function BlobBuilder() { + this.data = []; + } + , FakeBlob = function Blob(data, type, encoding) { + this.data = data; + this.size = data.length; + this.type = type; + this.encoding = encoding; + } + , FBB_proto = FakeBlobBuilder.prototype + , FB_proto = FakeBlob.prototype + , FileReaderSync = view.FileReaderSync + , FileException = function(type) { + this.code = this[this.name = type]; + } + , file_ex_codes = ( + "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" + ).split(" ") + , file_ex_code = file_ex_codes.length + , real_URL = view.URL || view.webkitURL || view + , real_create_object_URL = real_URL.createObjectURL + , real_revoke_object_URL = real_URL.revokeObjectURL + , URL = real_URL + , btoa = view.btoa + , atob = view.atob + + , ArrayBuffer = view.ArrayBuffer + , Uint8Array = view.Uint8Array + + , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ + ; + FakeBlob.fake = FB_proto.fake = true; + while (file_ex_code--) { + FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; + } + // Polyfill URL + if (!real_URL.createObjectURL) { + URL = view.URL = function(uri) { + var + uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") + , uri_origin + ; + uri_info.href = uri; + if (!("origin" in uri_info)) { + if (uri_info.protocol.toLowerCase() === "data:") { + uri_info.origin = null; + } else { + uri_origin = uri.match(origin); + uri_info.origin = uri_origin && uri_origin[1]; + } + } + return uri_info; + }; + } + URL.createObjectURL = function(blob) { + var + type = blob.type + , data_URI_header + ; + if (type === null) { + type = "application/octet-stream"; + } + if (blob instanceof FakeBlob) { + data_URI_header = "data:" + type; + if (blob.encoding === "base64") { + return data_URI_header + ";base64," + blob.data; + } else if (blob.encoding === "URI") { + return data_URI_header + "," + decodeURIComponent(blob.data); + } if (btoa) { + return data_URI_header + ";base64," + btoa(blob.data); + } else { + return data_URI_header + "," + encodeURIComponent(blob.data); + } + } else if (real_create_object_URL) { + return real_create_object_URL.call(real_URL, blob); + } + }; + URL.revokeObjectURL = function(object_URL) { + if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { + real_revoke_object_URL.call(real_URL, object_URL); + } + }; + FBB_proto.append = function(data/*, endings*/) { + var bb = this.data; + // decode data to a binary string + if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { + var + str = "" + , buf = new Uint8Array(data) + , i = 0 + , buf_len = buf.length + ; + for (; i < buf_len; i++) { + str += String.fromCharCode(buf[i]); + } + bb.push(str); + } else if (get_class(data) === "Blob" || get_class(data) === "File") { + if (FileReaderSync) { + var fr = new FileReaderSync; + bb.push(fr.readAsBinaryString(data)); + } else { + // async FileReader won't work as BlobBuilder is sync + throw new FileException("NOT_READABLE_ERR"); + } + } else if (data instanceof FakeBlob) { + if (data.encoding === "base64" && atob) { + bb.push(atob(data.data)); + } else if (data.encoding === "URI") { + bb.push(decodeURIComponent(data.data)); + } else if (data.encoding === "raw") { + bb.push(data.data); + } + } else { + if (typeof data !== "string") { + data += ""; // convert unsupported types to strings + } + // decode UTF-16 to binary string + bb.push(unescape(encodeURIComponent(data))); + } + }; + FBB_proto.getBlob = function(type) { + if (!arguments.length) { + type = null; + } + return new FakeBlob(this.data.join(""), type, "raw"); + }; + FBB_proto.toString = function() { + return "[object BlobBuilder]"; + }; + FB_proto.slice = function(start, end, type) { + var args = arguments.length; + if (args < 3) { + type = null; + } + return new FakeBlob( + this.data.slice(start, args > 1 ? end : this.data.length) + , type + , this.encoding + ); + }; + FB_proto.toString = function() { + return "[object Blob]"; + }; + FB_proto.close = function() { + this.size = 0; + delete this.data; + }; + return FakeBlobBuilder; + }(view)); + + view.Blob = function(blobParts, options) { + var type = options ? (options.type || "") : ""; + var builder = new BlobBuilder(); + if (blobParts) { + for (var i = 0, len = blobParts.length; i < len; i++) { + if (Uint8Array && blobParts[i] instanceof Uint8Array) { + builder.append(blobParts[i].buffer); + } + else { + builder.append(blobParts[i]); + } + } + } + var blob = builder.getBlob(type); + if (!blob.slice && blob.webkitSlice) { + blob.slice = blob.webkitSlice; + } + return blob; + }; + + var getPrototypeOf = Object.getPrototypeOf || function(object) { + return object.__proto__; + }; + view.Blob.prototype = getPrototypeOf(new view.Blob()); +}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); diff --git a/forcetk.js b/forcetk.js index 7fd2918..8c12875 100644 --- a/forcetk.js +++ b/forcetk.js @@ -27,7 +27,7 @@ /* JavaScript library to wrap REST API on Visualforce. Leverages Ajax Proxy * (see http://bit.ly/sforce_ajax_proxy for details). * - * Note that you must add the REST endpoint hostname for your instance (i.e. + * Note that you must add the REST endpoint hostname for your instance (i.e. * https://na1.salesforce.com/ or similar) as a remote site - in the admin * console, go to Your Name | Setup | Security Controls | Remote Site Settings */ @@ -38,605 +38,615 @@ var forcetk = window.forcetk; if (forcetk === undefined) { - forcetk = {}; + forcetk = {}; } if (forcetk.Client === undefined) { - /** - * The Client provides a convenient wrapper for the Force.com REST API, - * allowing JavaScript in Visualforce pages to use the API via the Ajax - * Proxy. - * @param [clientId=null] 'Consumer Key' in the Remote Access app settings - * @param [loginUrl='https://login.salesforce.com/'] Login endpoint - * @param [proxyUrl=null] Proxy URL. Omit if running on Visualforce or - * PhoneGap etc - * @constructor - */ - forcetk.Client = function (clientId, loginUrl, proxyUrl) { - 'use strict'; - this.clientId = clientId; - this.loginUrl = loginUrl || 'https://login.salesforce.com/'; - if (proxyUrl === undefined || proxyUrl === null) { - if (location.protocol === 'file:' || location.protocol === 'ms-appx:') { - // In PhoneGap - this.proxyUrl = null; - } else { - // In Visualforce - still need proxyUrl for Apex REST methods - this.proxyUrl = location.protocol + "//" + location.hostname - + "/services/proxy"; - } - this.authzHeader = "Authorization"; - } else { - // On a server outside VF - this.proxyUrl = proxyUrl; - this.authzHeader = "X-Authorization"; - } - this.refreshToken = null; - this.sessionId = null; - this.apiVersion = null; - this.visualforce = false; - this.instanceUrl = null; - this.asyncAjax = true; - }; - - /** - * Set a refresh token in the client. - * @param refreshToken an OAuth refresh token - */ - forcetk.Client.prototype.setRefreshToken = function (refreshToken) { - 'use strict'; - this.refreshToken = refreshToken; - }; - - /** - * Refresh the access token. - * @param callback function to call on success - * @param error function to call on failure - */ - forcetk.Client.prototype.refreshAccessToken = function (callback, error) { - 'use strict'; - var that = this, - url = this.loginUrl + '/services/oauth2/token'; - return $.ajax({ - type: 'POST', - url: (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, - cache: false, - processData: false, - data: 'grant_type=refresh_token&client_id=' + this.clientId + '&refresh_token=' + this.refreshToken, - success: callback, - error: error, - dataType: "json", - beforeSend: function (xhr) { - if (that.proxyUrl !== null && !this.visualforce) { - xhr.setRequestHeader('SalesforceProxy-Endpoint', url); - } - } - }); - }; - - /** - * Set a session token and the associated metadata in the client. - * @param sessionId a salesforce.com session ID. In a Visualforce page, - * use '{!$Api.sessionId}' to obtain a session ID. - * @param [apiVersion="v29.0"] Force.com API version - * @param [instanceUrl] Omit this if running on Visualforce; otherwise - * use the value from the OAuth token. - */ - forcetk.Client.prototype.setSessionToken = function (sessionId, apiVersion, instanceUrl) { - 'use strict'; - this.sessionId = sessionId; - this.apiVersion = (apiVersion === undefined || apiVersion === null) - ? 'v29.0' : apiVersion; - if (instanceUrl === undefined || instanceUrl === null) { - this.visualforce = true; - - // location.hostname can be of the form 'abc.na1.visual.force.com', - // 'na1.salesforce.com' or 'abc.my.salesforce.com' (custom domains). - // Split on '.', and take the [1] or [0] element as appropriate - var elements = location.hostname.split("."), - instance = null; - if (elements.length === 4 && elements[1] === 'my') { - instance = elements[0] + '.' + elements[1]; - } else if (elements.length === 3) { - instance = elements[0]; - } else { - instance = elements[1]; - } - - this.instanceUrl = "https://" + instance + ".salesforce.com"; - } else { - this.instanceUrl = instanceUrl; - } - }; - - /* - * Low level utility function to call the Salesforce endpoint. - * @param path resource path relative to /services/data - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - * @param [method="GET"] HTTP method for call - * @param [payload=null] payload for POST/PATCH etc - */ - forcetk.Client.prototype.ajax = function (path, callback, error, method, payload, retry) { - 'use strict'; - var that = this, - url = (this.visualforce ? '' : this.instanceUrl) + '/services/data' + path; - - return $.ajax({ - type: method || "GET", - async: this.asyncAjax, - url: (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, - contentType: method === "DELETE" ? null : 'application/json', - cache: false, - processData: false, - data: payload, - success: callback, - error: (!this.refreshToken || retry) ? error : function (jqXHR, textStatus, errorThrown) { - if (jqXHR.status === 401) { - that.refreshAccessToken(function (oauthResponse) { - that.setSessionToken(oauthResponse.access_token, null, - oauthResponse.instance_url); - that.ajax(path, callback, error, method, payload, true); - }, - error); - } else { - error(jqXHR, textStatus, errorThrown); - } - }, - dataType: "json", - beforeSend: function (xhr) { - if (that.proxyUrl !== null && !that.visualforce) { - xhr.setRequestHeader('SalesforceProxy-Endpoint', url); - } - xhr.setRequestHeader(that.authzHeader, "Bearer " + that.sessionId); - xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); - } - }); - }; - - /** - * Utility function to query the Chatter API and download a file - * Note, raw XMLHttpRequest because JQuery mangles the arraybuffer - * This should work on any browser that supports XMLHttpRequest 2 because arraybuffer is required. - * For mobile, that means iOS >= 5 and Android >= Honeycomb - * @author Tom Gersic - * @param path resource path relative to /services/data - * @param mimetype of the file - * @param callback function to which response will be passed - * @param [error=null] function to which request will be passed in case of error - * @param retry true if we've already tried refresh token flow once - */ - forcetk.Client.prototype.getChatterFile = function (path, mimeType, callback, error, retry) { - 'use strict'; - var that = this, - url = (this.visualforce ? '' : this.instanceUrl) + path, - request = new XMLHttpRequest(); - - request.open("GET", (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, true); - request.responseType = "arraybuffer"; - - request.setRequestHeader(this.authzHeader, "Bearer " + this.sessionId); - request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + this.apiVersion); - if (this.proxyUrl !== null && !this.visualforce) { - request.setRequestHeader('SalesforceProxy-Endpoint', url); - } - - request.onreadystatechange = function () { - // continue if the process is completed - if (request.readyState === 4) { - // continue only if HTTP status is "OK" - if (request.status === 200) { - try { - // retrieve the response - callback(request.response); - } catch (e) { - // display error message - alert("Error reading the response: " + e.toString()); - } - } else if (request.status === 401 && !retry) { - //refresh token in 401 - that.refreshAccessToken(function (oauthResponse) { - that.setSessionToken(oauthResponse.access_token, null, oauthResponse.instance_url); - that.getChatterFile(path, mimeType, callback, error, true); - }, error); - } else { - // display status message - error(request, request.statusText, request.response); - } - } - }; - - request.send(); - - }; - - // Local utility to create a random string for multipart boundary - var randomString = function () { - 'use strict'; - var str = '', - i; - for (i = 0; i < 4; i += 1) { - str += (Math.random().toString(16) + "000000000").substr(2, 8); - } - return str; - }; - - /* Low level function to create/update records with blob data - * @param path resource path relative to /services/data - * @param fields an object containing initial field names and values for - * the record, e.g. {ContentDocumentId: "069D00000000so2", - * PathOnClient: "Q1 Sales Brochure.pdf"} - * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf" - * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document - * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload - * @param callback function to which response will be passed - * @param [error=null] function to which response will be passed in case of error - * @param retry true if we've already tried refresh token flow once - */ - forcetk.Client.prototype.blob = function (path, fields, filename, payloadField, payload, callback, error, retry) { - 'use strict'; - var that = this, - url = (this.visualforce ? '' : this.instanceUrl) + '/services/data' + path, - boundary = randomString(), - blob = new Blob([ - "--boundary_" + boundary + '\n' - + "Content-Disposition: form-data; name=\"entity_content\";" + "\n" - + "Content-Type: application/json" + "\n\n" - + JSON.stringify(fields) - + "\n\n" - + "--boundary_" + boundary + "\n" - + "Content-Type: application/octet-stream" + "\n" - + "Content-Disposition: form-data; name=\"" + payloadField - + "\"; filename=\"" + filename + "\"\n\n", - payload, - "\n\n" - + "--boundary_" + boundary + "--" - ], {type : 'multipart/form-data; boundary=\"boundary_' + boundary + '\"'}), - request = new XMLHttpRequest(); - - request.open("POST", (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, this.asyncAjax); - - request.setRequestHeader('Accept', 'application/json'); - request.setRequestHeader(this.authzHeader, "Bearer " + this.sessionId); - request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + this.apiVersion); - request.setRequestHeader('Content-Type', 'multipart/form-data; boundary=\"boundary_' + boundary + '\"'); - if (this.proxyUrl !== null && !this.visualforce) { - request.setRequestHeader('SalesforceProxy-Endpoint', url); - } - - if (this.asyncAjax) { - request.onreadystatechange = function () { - // continue if the process is completed - if (request.readyState === 4) { - // continue only if HTTP status is good - if (request.status >= 200 && request.status < 300) { - // retrieve the response - callback(request.response ? JSON.parse(request.response) : null); - } else if (request.status === 401 && !retry) { - that.refreshAccessToken(function (oauthResponse) { - that.setSessionToken(oauthResponse.access_token, null, oauthResponse.instance_url); - that.blob(path, fields, filename, payloadField, payload, callback, error, true); - }, error); - } else { - // return status message - error(request, request.statusText, request.response); - } - } - }; - } - - request.send(blob); - - return this.asyncAjax ? null : JSON.parse(request.response); - }; - - /* - * Create a record with blob data - * @param objtype object type; e.g. "ContentVersion" - * @param fields an object containing initial field names and values for - * the record, e.g. {ContentDocumentId: "069D00000000so2", - * PathOnClient: "Q1 Sales Brochure.pdf"} - * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf" - * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document - * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload - * @param callback function to which response will be passed - * @param [error=null] function to which response will be passed in case of error - * @param retry true if we've already tried refresh token flow once - */ - forcetk.Client.prototype.createBlob = function (objtype, fields, filename, - payloadField, payload, callback, - error, retry) { - 'use strict'; - return this.blob('/' + this.apiVersion + '/sobjects/' + objtype + '/', - fields, filename, payloadField, payload, callback, error, retry); - }; - - /* - * Update a record with blob data - * @param objtype object type; e.g. "ContentVersion" - * @param id the record's object ID - * @param fields an object containing initial field names and values for - * the record, e.g. {ContentDocumentId: "069D00000000so2", - * PathOnClient: "Q1 Sales Brochure.pdf"} - * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf" - * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document - * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload - * @param callback function to which response will be passed - * @param [error=null] function to which response will be passed in case of error - * @param retry true if we've already tried refresh token flow once - */ - forcetk.Client.prototype.updateBlob = function (objtype, id, fields, filename, - payloadField, payload, callback, - error, retry) { - 'use strict'; - return this.blob('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id + - '?_HttpMethod=PATCH', fields, filename, payloadField, payload, callback, error, retry); - }; - - /* - * Low level utility function to call the Salesforce endpoint specific for Apex REST API. - * @param path resource path relative to /services/apexrest - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - * @param [method="GET"] HTTP method for call - * @param [payload=null] string or object with payload for POST/PATCH etc or params for GET - * @param [paramMap={}] parameters to send as header values for POST/PATCH etc - * @param [retry] specifies whether to retry on error - */ - forcetk.Client.prototype.apexrest = function (path, callback, error, method, payload, paramMap, retry) { - 'use strict'; - var that = this, - url = this.instanceUrl + '/services/apexrest' + path; - - method = method || "GET"; - - if (method === "GET") { - // Handle proxied query params correctly - if (this.proxyUrl && payload) { - if (typeof payload !== 'string') { - payload = $.param(payload); - } - url += "?" + payload; - payload = null; - } - } else { - // Allow object payload for POST etc - if (payload && typeof payload !== 'string') { - payload = JSON.stringify(payload); - } - } - - return $.ajax({ - type: method, - async: this.asyncAjax, - url: this.proxyUrl || url, - contentType: 'application/json', - cache: false, - processData: false, - data: payload, - success: callback, - error: (!this.refreshToken || retry) ? error : function (jqXHR, textStatus, errorThrown) { - if (jqXHR.status === 401) { - that.refreshAccessToken(function (oauthResponse) { - that.setSessionToken(oauthResponse.access_token, null, - oauthResponse.instance_url); - that.apexrest(path, callback, error, method, payload, paramMap, true); - }, error); - } else { - error(jqXHR, textStatus, errorThrown); - } - }, - dataType: "json", - beforeSend: function (xhr) { - var paramName; - if (that.proxyUrl !== null) { - xhr.setRequestHeader('SalesforceProxy-Endpoint', url); - } - //Add any custom headers - if (paramMap === null) { - paramMap = {}; - } - for (paramName in paramMap) { - if (paramMap.hasOwnProperty(paramName)) { - xhr.setRequestHeader(paramName, paramMap[paramName]); - } - } - xhr.setRequestHeader(that.authzHeader, "Bearer " + that.sessionId); - xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); - } - }); - }; - - /* - * Lists summary information about each Salesforce.com version currently - * available, including the version, label, and a link to each version's - * root. - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.versions = function (callback, error) { - 'use strict'; - return this.ajax('/', callback, error); - }; - - /* - * Lists available resources for the client's API version, including - * resource name and URI. - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.resources = function (callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/', callback, error); - }; - - /* - * Lists the available objects and their metadata for your organization's - * data. - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.describeGlobal = function (callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/', callback, error); - }; - - /* - * Describes the individual metadata for the specified object. - * @param objtype object type; e.g. "Account" - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.metadata = function (objtype, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/', - callback, error); - }; - - /* - * Completely describes the individual metadata at all levels for the - * specified object. - * @param objtype object type; e.g. "Account" - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.describe = function (objtype, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype - + '/describe/', callback, error); - }; - - /* - * Creates a new record of the given type. - * @param objtype object type; e.g. "Account" - * @param fields an object containing initial field names and values for - * the record, e.g. {:Name "salesforce.com", :TickerSymbol - * "CRM"} - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.create = function (objtype, fields, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/', - callback, error, "POST", JSON.stringify(fields)); - }; - - /* - * Retrieves field values for a record of the given type. - * @param objtype object type; e.g. "Account" - * @param id the record's object ID - * @param [fields=null] optional comma-separated list of fields for which - * to return values; e.g. Name,Industry,TickerSymbol - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.retrieve = function (objtype, id, fieldlist, callback, error) { - 'use strict'; - if (arguments.length === 4) { - error = callback; - callback = fieldlist; - fieldlist = null; - } - var fields = fieldlist ? '?fields=' + fieldlist : ''; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id - + fields, callback, error); - }; - - /* - * Upsert - creates or updates record of the given type, based on the - * given external Id. - * @param objtype object type; e.g. "Account" - * @param externalIdField external ID field name; e.g. "accountMaster__c" - * @param externalId the record's external ID value - * @param fields an object containing field names and values for - * the record, e.g. {:Name "salesforce.com", :TickerSymbol - * "CRM"} - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.upsert = function (objtype, externalIdField, externalId, fields, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + externalIdField + '/' + externalId - + '?_HttpMethod=PATCH', callback, error, "POST", JSON.stringify(fields)); - }; - - /* - * Updates field values on a record of the given type. - * @param objtype object type; e.g. "Account" - * @param id the record's object ID - * @param fields an object containing initial field names and values for - * the record, e.g. {:Name "salesforce.com", :TickerSymbol - * "CRM"} - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.update = function (objtype, id, fields, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id - + '?_HttpMethod=PATCH', callback, error, "POST", JSON.stringify(fields)); - }; - - /* - * Deletes a record of the given type. Unfortunately, 'delete' is a - * reserved word in JavaScript. - * @param objtype object type; e.g. "Account" - * @param id the record's object ID - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.del = function (objtype, id, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id, - callback, error, "DELETE"); - }; - - /* - * Executes the specified SOQL query. - * @param soql a string containing the query to execute - e.g. "SELECT Id, - * Name from Account ORDER BY Name LIMIT 20" - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.query = function (soql, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/query?q=' + encodeURIComponent(soql), - callback, error); - }; - - /* - * Queries the next set of records based on pagination. - *
This should be used if performing a query that retrieves more than can be returned - * in accordance with http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_query.htm
- *Ex: forcetkClient.queryMore( successResponse.nextRecordsUrl, successHandler, failureHandler )
- * - * @param url - the url retrieved from nextRecordsUrl or prevRecordsUrl - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.queryMore = function (url, callback, error) { - 'use strict'; - //-- ajax call adds on services/data to the url call, so only send the url after - var serviceData = "services/data", - index = url.indexOf(serviceData); - - if (index > -1) { - url = url.substr(index + serviceData.length); - } - - return this.ajax(url, callback, error); - }; - - /* - * Executes the specified SOSL search. - * @param sosl a string containing the search to execute - e.g. "FIND - * {needle}" - * @param callback function to which response will be passed - * @param [error=null] function to which jqXHR will be passed in case of error - */ - forcetk.Client.prototype.search = function (sosl, callback, error) { - 'use strict'; - return this.ajax('/' + this.apiVersion + '/search?q=' + encodeURIComponent(sosl), - callback, error); - }; + /** + * The Client provides a convenient wrapper for the Force.com REST API, + * allowing JavaScript in Visualforce pages to use the API via the Ajax + * Proxy. + * @param [clientId=null] 'Consumer Key' in the Remote Access app settings + * @param [loginUrl='https://login.salesforce.com/'] Login endpoint + * @param [proxyUrl=null] Proxy URL. Omit if running on Visualforce or + * PhoneGap etc + * @constructor + */ + forcetk.Client = function (clientId, loginUrl, proxyUrl) { + 'use strict'; + this.clientId = clientId; + this.loginUrl = loginUrl || 'https://login.salesforce.com/'; + if (proxyUrl === undefined || proxyUrl === null) { + if (location.protocol === 'file:' || location.protocol === 'ms-appx:') { + // In PhoneGap + this.proxyUrl = null; + } else { + // In Visualforce - still need proxyUrl for Apex REST methods + this.proxyUrl = location.protocol + "//" + location.hostname + + "/services/proxy"; + } + this.authzHeader = "Authorization"; + } else { + // On a server outside VF + this.proxyUrl = proxyUrl; + this.authzHeader = "X-Authorization"; + } + this.refreshToken = null; + this.sessionId = null; + this.apiVersion = null; + this.visualforce = false; + this.instanceUrl = null; + this.asyncAjax = true; + }; + + /** + * Set a refresh token in the client. + * @param refreshToken an OAuth refresh token + */ + forcetk.Client.prototype.setRefreshToken = function (refreshToken) { + 'use strict'; + this.refreshToken = refreshToken; + }; + + /** + * Refresh the access token. + * @param callback function to call on success + * @param error function to call on failure + */ + forcetk.Client.prototype.refreshAccessToken = function (callback, error) { + 'use strict'; + var that = this, + url = this.loginUrl + '/services/oauth2/token'; + return jQuery.ajax({ + type: 'POST', + url: (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, + cache: false, + processData: false, + data: 'grant_type=refresh_token&client_id=' + this.clientId + '&refresh_token=' + this.refreshToken, + success: callback, + error: error, + dataType: "json", + beforeSend: function (xhr) { + if (that.proxyUrl !== null && !this.visualforce) { + xhr.setRequestHeader('SalesforceProxy-Endpoint', url); + } + } + }); + }; + + /** + * Set a session token and the associated metadata in the client. + * @param sessionId a salesforce.com session ID. In a Visualforce page, + * use '{!$Api.sessionId}' to obtain a session ID. + * @param [apiVersion="v31.0"] Force.com API version + * @param [instanceUrl] Omit this if running on Visualforce; otherwise + * use the value from the OAuth token. + */ + forcetk.Client.prototype.setSessionToken = function (sessionId, apiVersion, instanceUrl) { + 'use strict'; + this.sessionId = sessionId; + this.apiVersion = (apiVersion === undefined || apiVersion === null) + ? 'v31.0' : apiVersion; + if (instanceUrl === undefined || instanceUrl === null) { + this.visualforce = true; + + // location.hostname can be of the form 'abc.na1.visual.force.com', + // 'na1.salesforce.com' or 'abc.my.salesforce.com' (custom domains). + // Split on '.', and take the [1] or [0] element as appropriate + var elements = location.hostname.split("."), + instance = null; + if (elements.length === 4 && elements[1] === 'my') { + instance = elements[0] + '.' + elements[1]; + } else if (elements.length === 3) { + instance = elements[0]; + } else { + instance = elements[1]; + } + + this.instanceUrl = "https://" + instance + ".salesforce.com"; + } else { + this.instanceUrl = instanceUrl; + } + }; + + /* + * Low level utility function to call the Salesforce endpoint. + * @param path resource path relative to /services/data + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + * @param [method="GET"] HTTP method for call + * @param [payload=null] payload for POST/PATCH etc + */ + forcetk.Client.prototype.ajax = function (path, callback, error, method, payload, retry, progressCallback) { + 'use strict'; + var that = this, + url = (this.visualforce ? '' : this.instanceUrl) + '/services/data' + path; + + return jQuery.ajax({ + type: method || "GET", + async: this.asyncAjax, + url: (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, + contentType: method === "DELETE" ? null : 'application/json', + cache: false, + processData: false, + data: payload, + success: callback, + error: (!this.refreshToken || retry) ? error : function (jqXHR, textStatus, errorThrown) { + if (jqXHR.status === 401) { + that.refreshAccessToken(function (oauthResponse) { + that.setSessionToken(oauthResponse.access_token, null, + oauthResponse.instance_url); + that.ajax(path, callback, error, method, payload, true); + }, + error); + } else { + error(jqXHR, textStatus, errorThrown); + } + }, + dataType: "json", + beforeSend: function (xhr) { + if (that.proxyUrl !== null && !that.visualforce) { + xhr.setRequestHeader('SalesforceProxy-Endpoint', url); + } + xhr.setRequestHeader(that.authzHeader, "Bearer " + that.sessionId); + xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); + if(progressCallback){ + xhr.upload.addEventListener("progress", progressCallback); + } + } + }); + }; + + /** + * Utility function to query the Chatter API and download a file + * Note, raw XMLHttpRequest because JQuery mangles the arraybuffer + * This should work on any browser that supports XMLHttpRequest 2 because arraybuffer is required. + * For mobile, that means iOS >= 5 and Android >= Honeycomb + * @author Tom Gersic + * @param path resource path relative to /services/data + * @param mimetype of the file + * @param callback function to which response will be passed + * @param [error=null] function to which request will be passed in case of error + * @param retry true if we've already tried refresh token flow once + */ + forcetk.Client.prototype.getChatterFile = function (path, mimeType, callback, error, retry) { + 'use strict'; + var that = this, + url = (this.visualforce ? '' : this.instanceUrl) + path, + request = new XMLHttpRequest(); + + request.open("GET", (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, true); + request.responseType = "arraybuffer"; + + request.setRequestHeader(this.authzHeader, "Bearer " + this.sessionId); + request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + this.apiVersion); + if (this.proxyUrl !== null && !this.visualforce) { + request.setRequestHeader('SalesforceProxy-Endpoint', url); + } + + request.onreadystatechange = function () { + // continue if the process is completed + if (request.readyState === 4) { + // continue only if HTTP status is "OK" + if (request.status === 200) { + try { + // retrieve the response + callback(request.response); + } catch (e) { + // display error message + alert("Error reading the response: " + e.toString()); + } + } else if (request.status === 401 && !retry) { + //refresh token in 401 + that.refreshAccessToken(function (oauthResponse) { + that.setSessionToken(oauthResponse.access_token, null, oauthResponse.instance_url); + that.getChatterFile(path, mimeType, callback, error, true); + }, error); + } else { + // display status message + error(request, request.statusText, request.response); + } + } + }; + + request.send(); + + }; + + // Local utility to create a random string for multipart boundary + var randomString = function () { + 'use strict'; + var str = '', + i; + for (i = 0; i < 4; i += 1) { + str += (Math.random().toString(16) + "000000000").substr(2, 8); + } + return str; + }; + + /* Low level function to create/update records with blob data + * @param path resource path relative to /services/data + * @param fields an object containing initial field names and values for + * the record, e.g. {ContentDocumentId: "069D00000000so2", + * PathOnClient: "Q1 Sales Brochure.pdf"} + * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf" + * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document + * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload + * @param callback function to which response will be passed + * @param [error=null] function to which response will be passed in case of error + * @param retry true if we've already tried refresh token flow once + */ + forcetk.Client.prototype.blob = function (path, fields, filename, payloadField, payload, callback, error, retry, progressCallback) { + 'use strict'; + var that = this, + url = (this.visualforce ? '' : this.instanceUrl) + '/services/data' + path, + boundary = randomString(), + blob = new Blob([ + "--boundary_" + boundary + '\n' + + "Content-Disposition: form-data; name=\"entity_content\";" + "\n" + + "Content-Type: application/json" + "\n\n" + + JSON.stringify(fields) + + "\n\n" + + "--boundary_" + boundary + "\n" + + "Content-Type: application/octet-stream" + "\n" + + "Content-Disposition: form-data; name=\"" + payloadField + + "\"; filename=\"" + filename + "\"\n\n", + payload, + "\n" + + "--boundary_" + boundary + "--" + ], {type : 'multipart/form-data; boundary=\"boundary_' + boundary + '\"'}), + request = new XMLHttpRequest(); + + request.open("POST", (this.proxyUrl !== null && !this.visualforce) ? this.proxyUrl : url, this.asyncAjax); + + request.setRequestHeader('Accept', 'application/json'); + request.setRequestHeader(this.authzHeader, "Bearer " + this.sessionId); + request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + this.apiVersion); + request.setRequestHeader('Content-Type', 'multipart/form-data; boundary=\"boundary_' + boundary + '\"'); + if (this.proxyUrl !== null && !this.visualforce) { + request.setRequestHeader('SalesforceProxy-Endpoint', url); + } + + if (this.asyncAjax) { + request.onreadystatechange = function () { + // continue if the process is completed + if (request.readyState === 4) { + // continue only if HTTP status is good (1223) + if ((request.status >= 200 && request.status < 300) || (request.status == 1223)) { + // retrieve the response + callback(request.response ? JSON.parse(request.response) : null); + } else if (request.status === 401 && !retry) { + that.refreshAccessToken(function (oauthResponse) { + that.setSessionToken(oauthResponse.access_token, null, oauthResponse.instance_url); + that.blob(path, fields, filename, payloadField, payload, callback, error, true); + }, error); + } else { + // return status message + error(request, request.statusText, request.response); + } + } + }; + } + if(progressCallback){ + request.upload.addEventListener("progress", progressCallback); + } + if(blob.fake) { + request.send(blob.data); + } else { + request.send(blob); + } + + + return this.asyncAjax ? null : JSON.parse(request.response); + }; + + /* + * Create a record with blob data + * @param objtype object type; e.g. "ContentVersion" + * @param fields an object containing initial field names and values for + * the record, e.g. {ContentDocumentId: "069D00000000so2", + * PathOnClient: "Q1 Sales Brochure.pdf"} + * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf" + * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document + * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload + * @param callback function to which response will be passed + * @param [error=null] function to which response will be passed in case of error + * @param retry true if we've already tried refresh token flow once + */ + forcetk.Client.prototype.createBlob = function (objtype, fields, filename, + payloadField, payload, callback, + error, retry, progressCallback) { + 'use strict'; + return this.blob('/' + this.apiVersion + '/sobjects/' + objtype + '/', + fields, filename, payloadField, payload, callback, error, retry, progressCallback); + }; + + /* + * Update a record with blob data + * @param objtype object type; e.g. "ContentVersion" + * @param id the record's object ID + * @param fields an object containing initial field names and values for + * the record, e.g. {ContentDocumentId: "069D00000000so2", + * PathOnClient: "Q1 Sales Brochure.pdf"} + * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf" + * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document + * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload + * @param callback function to which response will be passed + * @param [error=null] function to which response will be passed in case of error + * @param retry true if we've already tried refresh token flow once + */ + forcetk.Client.prototype.updateBlob = function (objtype, id, fields, filename, + payloadField, payload, callback, + error, retry) { + 'use strict'; + return this.blob('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id + + '?_HttpMethod=PATCH', fields, filename, payloadField, payload, callback, error, retry); + }; + + /* + * Low level utility function to call the Salesforce endpoint specific for Apex REST API. + * @param path resource path relative to /services/apexrest + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + * @param [method="GET"] HTTP method for call + * @param [payload=null] string or object with payload for POST/PATCH etc or params for GET + * @param [paramMap={}] parameters to send as header values for POST/PATCH etc + * @param [retry] specifies whether to retry on error + */ + forcetk.Client.prototype.apexrest = function (path, callback, error, method, payload, paramMap, retry) { + 'use strict'; + var that = this, + url = this.instanceUrl + '/services/apexrest' + path; + + method = method || "GET"; + + if (method === "GET") { + // Handle proxied query params correctly + if (this.proxyUrl && payload) { + if (typeof payload !== 'string') { + payload = jQuery.param(payload); + } + url += "?" + payload; + payload = null; + } + } else { + // Allow object payload for POST etc + if (payload && typeof payload !== 'string') { + payload = JSON.stringify(payload); + } + } + + return jQuery.ajax({ + type: method, + async: this.asyncAjax, + url: this.proxyUrl || url, + contentType: 'application/json', + cache: false, + processData: false, + data: payload, + success: callback, + error: (!this.refreshToken || retry) ? error : function (jqXHR, textStatus, errorThrown) { + if (jqXHR.status === 401) { + that.refreshAccessToken(function (oauthResponse) { + that.setSessionToken(oauthResponse.access_token, null, + oauthResponse.instance_url); + that.apexrest(path, callback, error, method, payload, paramMap, true); + }, error); + } else { + error(jqXHR, textStatus, errorThrown); + } + }, + dataType: "json", + beforeSend: function (xhr) { + var paramName; + if (that.proxyUrl !== null) { + xhr.setRequestHeader('SalesforceProxy-Endpoint', url); + } + //Add any custom headers + if (paramMap === null) { + paramMap = {}; + } + for (paramName in paramMap) { + if (paramMap.hasOwnProperty(paramName)) { + xhr.setRequestHeader(paramName, paramMap[paramName]); + } + } + xhr.setRequestHeader(that.authzHeader, "Bearer " + that.sessionId); + xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); + } + }); + }; + + /* + * Lists summary information about each Salesforce.com version currently + * available, including the version, label, and a link to each version's + * root. + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.versions = function (callback, error) { + 'use strict'; + return this.ajax('/', callback, error); + }; + + /* + * Lists available resources for the client's API version, including + * resource name and URI. + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.resources = function (callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/', callback, error); + }; + + /* + * Lists the available objects and their metadata for your organization's + * data. + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.describeGlobal = function (callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/', callback, error); + }; + + /* + * Describes the individual metadata for the specified object. + * @param objtype object type; e.g. "Account" + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.metadata = function (objtype, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/', + callback, error); + }; + + /* + * Completely describes the individual metadata at all levels for the + * specified object. + * @param objtype object type; e.g. "Account" + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.describe = function (objtype, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + + '/describe/', callback, error); + }; + + /* + * Creates a new record of the given type. + * @param objtype object type; e.g. "Account" + * @param fields an object containing initial field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.create = function (objtype, fields, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/', + callback, error, "POST", JSON.stringify(fields)); + }; + + /* + * Retrieves field values for a record of the given type. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param [fields=null] optional comma-separated list of fields for which + * to return values; e.g. Name,Industry,TickerSymbol + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.retrieve = function (objtype, id, fieldlist, callback, error) { + 'use strict'; + if (arguments.length === 4) { + error = callback; + callback = fieldlist; + fieldlist = null; + } + var fields = fieldlist ? '?fields=' + fieldlist : ''; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id + + fields, callback, error); + }; + + /* + * Upsert - creates or updates record of the given type, based on the + * given external Id. + * @param objtype object type; e.g. "Account" + * @param externalIdField external ID field name; e.g. "accountMaster__c" + * @param externalId the record's external ID value + * @param fields an object containing field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.upsert = function (objtype, externalIdField, externalId, fields, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + externalIdField + '/' + externalId + + '?_HttpMethod=PATCH', callback, error, "POST", JSON.stringify(fields)); + }; + + /* + * Updates field values on a record of the given type. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param fields an object containing initial field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.update = function (objtype, id, fields, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id + + '?_HttpMethod=PATCH', callback, error, "POST", JSON.stringify(fields)); + }; + + /* + * Deletes a record of the given type. Unfortunately, 'delete' is a + * reserved word in JavaScript. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.del = function (objtype, id, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id, + callback, error, "DELETE"); + }; + + /* + * Executes the specified SOQL query. + * @param soql a string containing the query to execute - e.g. "SELECT Id, + * Name from Account ORDER BY Name LIMIT 20" + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.query = function (soql, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/query?q=' + encodeURIComponent(soql), + callback, error); + }; + + /* + * Queries the next set of records based on pagination. + *This should be used if performing a query that retrieves more than can be returned + * in accordance with http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_query.htm
+ *Ex: forcetkClient.queryMore( successResponse.nextRecordsUrl, successHandler, failureHandler )
+ * + * @param url - the url retrieved from nextRecordsUrl or prevRecordsUrl + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.queryMore = function (url, callback, error) { + 'use strict'; + //-- ajax call adds on services/data to the url call, so only send the url after + var serviceData = "services/data", + index = url.indexOf(serviceData); + + if (index > -1) { + url = url.substr(index + serviceData.length); + } + + return this.ajax(url, callback, error); + }; + + /* + * Executes the specified SOSL search. + * @param sosl a string containing the search to execute - e.g. "FIND + * {needle}" + * @param callback function to which response will be passed + * @param [error=null] function to which jqXHR will be passed in case of error + */ + forcetk.Client.prototype.search = function (sosl, callback, error) { + 'use strict'; + return this.ajax('/' + this.apiVersion + '/search?q=' + encodeURIComponent(sosl), + callback, error); + }; } diff --git a/typedarray.js b/typedarray.js new file mode 100644 index 0000000..af64d3f --- /dev/null +++ b/typedarray.js @@ -0,0 +1,1048 @@ +/* + Copyright (c) 2010, Linden Research, Inc. + Copyright (c) 2014, Joshua Bell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + $/LicenseInfo$ + */ + +// Original can be found at: +// https://bitbucket.org/lindenlab/llsd +// Modifications by Joshua Bell inexorabletash@gmail.com +// https://github.com/inexorabletash/polyfill + +// ES3/ES5 implementation of the Krhonos Typed Array Specification +// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ +// Date: 2011-02-01 +// +// Variations: +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) +// * Gradually migrating structure from Khronos spec to ES2015 spec +// +// Caveats: +// * Beyond 10000 or so entries, polyfilled array accessors (ta[0], +// etc) become memory-prohibitive, so array creation will fail. Set +// self.TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS=true to disable +// creation of accessors. Your code will need to use the +// non-standard get()/set() instead, and will need to add those to +// native arrays for interop. +(function(global) { + 'use strict'; + var undefined = (void 0); // Paranoia + + // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to + // create, and consume so much memory, that the browser appears frozen. + var MAX_ARRAY_LENGTH = 1e5; + + // Approximations of internal ECMAScript conversion functions + function Type(v) { + switch(typeof v) { + case 'undefined': return 'undefined'; + case 'boolean': return 'boolean'; + case 'number': return 'number'; + case 'string': return 'string'; + default: return v === null ? 'null' : 'object'; + } + } + + // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: + function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); } + function IsCallable(o) { return typeof o === 'function'; } + function ToObject(v) { + if (v === null || v === undefined) throw TypeError(); + return Object(v); + } + function ToInt32(v) { return v >> 0; } + function ToUint32(v) { return v >>> 0; } + + // Snapshot intrinsics + var LN2 = Math.LN2, + abs = Math.abs, + floor = Math.floor, + log = Math.log, + max = Math.max, + min = Math.min, + pow = Math.pow, + round = Math.round; + + // emulate ES5 getter/setter API using legacy APIs + // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx + // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but + // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) + + (function() { + var orig = Object.defineProperty; + var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}()); + + if (!orig || dom_only) { + Object.defineProperty = function (o, prop, desc) { + // In IE8 try built-in implementation for defining properties on DOM prototypes. + if (orig) + try { return orig(o, prop, desc); } catch (_) {} + if (o !== Object(o)) + throw TypeError('Object.defineProperty called on non-object'); + if (Object.prototype.__defineGetter__ && ('get' in desc)) + Object.prototype.__defineGetter__.call(o, prop, desc.get); + if (Object.prototype.__defineSetter__ && ('set' in desc)) + Object.prototype.__defineSetter__.call(o, prop, desc.set); + if ('value' in desc) + o[prop] = desc.value; + return o; + }; + } + }()); + + // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) + // for index in 0 ... obj.length + function makeArrayAccessors(obj) { + if ('TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS' in global) + return; + + if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill'); + + function makeArrayAccessor(index) { + Object.defineProperty(obj, index, { + 'get': function() { return obj._getter(index); }, + 'set': function(v) { obj._setter(index, v); }, + enumerable: true, + configurable: false + }); + } + + var i; + for (i = 0; i < obj.length; i += 1) { + makeArrayAccessor(i); + } + } + + // Internal conversion functions: + // pack