| /* | 
|  * Copyright (c) 2011, salesforce.com, inc. | 
|  * All rights reserved. | 
|  * | 
|  * Redistribution and use in source and binary forms, with or without modification, are permitted provided | 
|  * that the following conditions are met: | 
|  * | 
|  * Redistributions of source code must retain the above copyright notice, this list of conditions and the | 
|  * following disclaimer. | 
|  * | 
|  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and | 
|  * the following disclaimer in the documentation and/or other materials provided with the distribution. | 
|  * | 
|  * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or | 
|  * promote products derived from this software without specific prior written permission. | 
|  * | 
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED | 
|  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A | 
|  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | 
|  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | 
|  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | 
|  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  * POSSIBILITY OF SUCH DAMAGE. | 
|  */ | 
|   | 
| /* 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.  | 
|  * https://na1.salesforce.com/ or similar) as a remote site - in the admin | 
|  * console, go to Your Name | Setup | Security Controls | Remote Site Settings | 
|  */ | 
|   | 
| var forcetk = window.forcetk; | 
|   | 
| if (forcetk === undefined) { | 
|     forcetk = {}; | 
| } | 
|   | 
| if (forcetk.Client === undefined) { | 
|   | 
|     // We use $j rather than $ for jQuery so it works in Visualforce | 
|     if (window.$j === undefined) { | 
|         $j = $; | 
|     } | 
|   | 
|     /** | 
|      * 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) { | 
|         this.clientId = clientId; | 
|         this.loginUrl = loginUrl || 'https://login.salesforce.com/'; | 
|         if (typeof proxyUrl === 'undefined' || proxyUrl === null) { | 
|             if (location.protocol === 'file:') { | 
|                 // In PhoneGap | 
|                 this.proxyUrl = null; | 
|             } else { | 
|                 // In Visualforce | 
|                 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.instanceUrl = null; | 
|         this.asyncAjax = true; | 
|     } | 
|   | 
|     /** | 
|      * Set a refresh token in the client. | 
|      * @param refreshToken an OAuth refresh token | 
|      */ | 
|     forcetk.Client.prototype.setRefreshToken = function(refreshToken) { | 
|         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) { | 
|         var that = this; | 
|         var url = this.loginUrl + '/services/oauth2/token'; | 
|         return $j.ajax({ | 
|             type: 'POST', | 
|             url: (this.proxyUrl !== null) ? 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) { | 
|                     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="21.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) { | 
|         this.sessionId = sessionId; | 
|         this.apiVersion = (typeof apiVersion === 'undefined' || apiVersion === null) | 
|         ? 'v27.0': apiVersion; | 
|         if (typeof instanceUrl === 'undefined' || instanceUrl == null) { | 
|             // 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("."); | 
|              | 
|             var 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) { | 
|         var that = this; | 
|         var url = this.instanceUrl + '/services/data' + path; | 
|   | 
|         return $j.ajax({ | 
|             type: method || "GET", | 
|             async: this.asyncAjax, | 
|             url: (this.proxyUrl !== null) ? 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) { | 
|                     xhr.setRequestHeader('SalesforceProxy-Endpoint', url); | 
|                 } | 
|                 xhr.setRequestHeader(that.authzHeader, "OAuth " + 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 rety true if we've already tried refresh token flow once | 
|      **/ | 
|     forcetk.Client.prototype.getChatterFile = function(path,mimeType,callback,error,retry) { | 
|         var that = this; | 
|         var url = this.instanceUrl + path; | 
|   | 
|         var request = new XMLHttpRequest(); | 
|                    | 
|         request.open("GET",  (this.proxyUrl !== null) ? this.proxyUrl: url, true); | 
|         request.responseType = "arraybuffer"; | 
|          | 
|         request.setRequestHeader(that.authzHeader, "OAuth " + that.sessionId); | 
|         request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); | 
|         if (this.proxyUrl !== null) { | 
|             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()); | 
|                     } | 
|                 } | 
|                 //refresh token in 401 | 
|                 else if(request.status == 401 && !retry) { | 
|                     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(); | 
|          | 
|     } | 
|   | 
|     /* | 
|      * 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] payload for POST/PATCH etc | 
|      * @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) { | 
|         var that = this; | 
|         var url = this.instanceUrl + '/services/apexrest' + path; | 
|   | 
|         return $j.ajax({ | 
|             type: method || "GET", | 
|             async: this.asyncAjax, | 
|             url: (this.proxyUrl !== null) ? 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) { | 
|                 if (that.proxyUrl !== null) { | 
|                     xhr.setRequestHeader('SalesforceProxy-Endpoint', url); | 
|                 } | 
|                 //Add any custom headers | 
|                 if (paramMap === null) { | 
|                     paramMap = {}; | 
|                 } | 
|                 for (paramName in paramMap) { | 
|                     xhr.setRequestHeader(paramName, paramMap[paramName]); | 
|                 } | 
|                 xhr.setRequestHeader(that.authzHeader, "OAuth " + 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) { | 
|         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) { | 
|         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) { | 
|         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) { | 
|         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) { | 
|         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) { | 
|         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) { | 
|         if (arguments.length == 4) { | 
|             error = callback; | 
|             callback = fieldlist; | 
|             fieldlist = null; | 
|         } | 
|         var fields = fieldlist ? '?fields=' + fieldlist : ''; | 
|         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) { | 
|         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) { | 
|         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) { | 
|         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) { | 
|         return this.ajax('/' + this.apiVersion + '/query?q=' + escape(soql) | 
|         , callback, error); | 
|     } | 
|      | 
|     /* | 
|      * Queries the next set of records based on pagination. | 
|      * <p>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</p> | 
|      * <p>Ex: forcetkClient.queryMore( successResponse.nextRecordsUrl, successHandler, failureHandler )</p> | 
|      *  | 
|      * @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 ){ | 
|         //-- ajax call adds on services/data to the url call, so only send the url after | 
|         var serviceData = "services/data"; | 
|         var index = url.indexOf( serviceData ); | 
|          | 
|         if( index > -1 ){ | 
|             url = url.substr( index + serviceData.length ); | 
|         } else { | 
|             //-- leave alone | 
|         } | 
|          | 
|         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) { | 
|         return this.ajax('/' + this.apiVersion + '/search?q=' + escape(sosl) | 
|         , callback, error); | 
|     } | 
| } |