PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/scripts/libs/backbone/backbone.force.js

https://gitlab.com/patrickmwanga/appboil-force.com
JavaScript | 295 lines | 162 code | 67 blank | 66 comment | 46 complexity | d131f938e1c732d5880be446b426d661 MD5 | raw file
  1. //////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2012 Piotr Walczyszyn (http://outof.me | @pwalczyszyn)
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. //////////////////////////////////////////////////////////////////////////////////////
  18. (function (root, factory) {
  19. if (typeof define === 'function' && define.amd) {
  20. define(['underscore', 'Backbone'], factory);
  21. } else {
  22. // Browser globals
  23. factory(root._, root.Backbone);
  24. }
  25. }(this, function (_, Backbone) {
  26. var methodMap = {
  27. 'create':'POST',
  28. 'update':'PATCH',
  29. 'delete':'DELETE',
  30. 'read':'GET'
  31. };
  32. var Force = Backbone.Force = {
  33. initialize:function (forctkClient) {
  34. this.client = forctkClient;
  35. },
  36. sync:function (method, model, options) {
  37. // Setting options if were not set
  38. options || (options = {});
  39. var that = this,
  40. client = Force.client,
  41. error = options.error;
  42. // Extending options with Salesforce specific settings
  43. _.extend(options, {
  44. cache:false,
  45. dataType:'json',
  46. processData:false,
  47. type:methodMap[method],
  48. async:client.asyncAjax,
  49. contentType:'application/json',
  50. beforeSend:function (xhr) {
  51. if (client.proxyUrl !== null) {
  52. xhr.setRequestHeader('SalesforceProxy-Endpoint', options.url);
  53. }
  54. xhr.setRequestHeader(client.authzHeader, "OAuth " + client.sessionId);
  55. xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + client.apiVersion);
  56. },
  57. error:function (jqXHR, textStatus, errorThrown) {
  58. if (client.refreshToken && jqXHR.status === 401) {
  59. client.refreshAccessToken(function (oauthResponse) {
  60. client.setSessionToken(oauthResponse.access_token, null, oauthResponse.instance_url);
  61. that.sync.call(that, method, model, options);
  62. });
  63. } else if (error) {
  64. error(jqXHR, textStatus, errorThrown);
  65. }
  66. }
  67. });
  68. // In case of update it has to follow custom logic because Salesforce uses PATCH method and accepts only
  69. // changed attributes
  70. if (method === 'update') {
  71. // Getting updates
  72. var changes = _.clone(model.changesToUpdate) || [],
  73. updates = _.pick(model.toJSON(), changes);
  74. // Making sure that Id attribute is not part of an update
  75. delete updates.Id;
  76. // Handling error
  77. var error = options.error;
  78. options.error = function () {
  79. // In case of error reverting back changes to update
  80. model.changesToUpdate = _.union(model.changesToUpdate, changes);
  81. // Calling original error function
  82. if (error) error.apply(this, Array.prototype.slice.call(arguments));
  83. };
  84. // Clearing current changes
  85. model.changesToUpdate.length = 0;
  86. // Setting options data property with updates
  87. options.data = JSON.stringify(updates);
  88. }
  89. // Calling original sync function
  90. Backbone.sync(method, model, options)
  91. },
  92. _getServiceURL:function () {
  93. return this.client.instanceUrl
  94. + '/services/data/'
  95. + this.client.apiVersion;
  96. }
  97. };
  98. var Model = Force.Model = Backbone.Model.extend({
  99. // Salesforce Id attribute
  100. idAttribute:'Id',
  101. // Type of Salesforce object e.g. Opportunity, Account...
  102. type:null,
  103. // Fields to be loaded from Salesforce in fetch function
  104. fields:null,
  105. // Array of fields to be updated with next save funciton call
  106. changesToUpdate:null,
  107. // Setting Salesforce specific sync implementation
  108. sync:Force.sync,
  109. fetch:function (options) {
  110. // Setting options if it wasn't passed to the function
  111. options || (options = {});
  112. // Setting flag that indicates that this is fetch change
  113. options.addToUpdates = false;
  114. // Getting fields to fetch
  115. var fields = this.fields ? '?fields=' + this.fields.join(',') : '';
  116. // Setting options url property
  117. _.extend(options, {
  118. url:(Force._getServiceURL() + '/sobjects/' + this.type + '/' + this.id + fields)
  119. });
  120. // Calling Backbone's fetch function
  121. return Backbone.Model.prototype.fetch.call(this, options);
  122. },
  123. save:function (key, value, options) {
  124. // Getting options property
  125. if (_.isObject(key) || key == null) options = value;
  126. // Setting options if it wasn't passed to the function
  127. options || (options = {});
  128. // Setting url option
  129. _.extend(options, {
  130. url:(Force._getServiceURL() + '/sobjects/' + this.type + '/' + (!this.isNew() ? this.id : ''))
  131. });
  132. // Calling Backbone's save function
  133. return Backbone.Model.prototype.save.call(this, key, value, options);
  134. },
  135. set:function (key, value, options) {
  136. var attrs;
  137. // Handle both `"key", value` and `{key: value}` -style arguments.
  138. if (_.isObject(key) || key == null) {
  139. attrs = key;
  140. options = value;
  141. } else {
  142. attrs = {};
  143. attrs[key] = value;
  144. }
  145. // If attrs are set and this is not a fetch update
  146. if (attrs && !this.isNew() && (!options || options.addToUpdates !== false)) {
  147. // Setting changesToUpdate if were not set previously
  148. this.changesToUpdate || (this.changesToUpdate = []);
  149. // Adding current updates to this.changesToUpdate
  150. this.changesToUpdate = _.union(this.changesToUpdate, Object.keys(attrs));
  151. }
  152. // Calling Backbone's set function
  153. return Backbone.Model.prototype.set.call(this, key, value, options);
  154. },
  155. parse:function (resp, xhr) {
  156. var result = resp;
  157. if (resp != null) {
  158. // Cloning resp
  159. result = _.clone(resp);
  160. // Renaming id to Id
  161. if (result.hasOwnProperty('id')) {
  162. result.Id = result.id;
  163. delete result.id;
  164. }
  165. if (result.hasOwnProperty('attributes')) {
  166. // Checking if type is set, if not using the one from resp
  167. if (this.type == null) this.type = result.attributes.type;
  168. // deleting attributes property
  169. delete result.attributes;
  170. }
  171. // Removing property
  172. delete result.success;
  173. // Removing errors array property
  174. if (result.errors && result.errors.length > 0)
  175. delete result.errors;
  176. }
  177. return result;
  178. }
  179. });
  180. var Collection = Force.Collection = Backbone.Collection.extend({
  181. // Query string, either full query with SELECT part or only WHERE part
  182. query:null,
  183. // Model type that should extend Force.Model
  184. model:Model,
  185. // Setting Salesforce specific sync implementation
  186. sync:Force.sync,
  187. fetch:function (options) {
  188. // Throwing an error if query is null
  189. if (this.query == null) throw new Error('Force.Collection.query property is required!');
  190. var query = this.query;
  191. // Checking if this is just a WHERE query
  192. if (this.query.toLowerCase().indexOf('where') == 0) {
  193. var model = new this.model();
  194. if (model.fields == null)
  195. throw new Error('With WHERE queries Model.fields property needs to be set!');
  196. if (model.type == null)
  197. throw new Error('With WHERE queries Model.type property needs to be set!');
  198. query = 'SELECT ' + model.fields.join(',') + ' FROM ' + model.type + ' ' + this.query;
  199. }
  200. // Setting options if it wasn't passed to the function
  201. options = options ? _.clone(options) : {};
  202. // Setting options url property
  203. options.url = Force._getServiceURL() + '/query/?q=' + encodeURIComponent(query);
  204. if (options.parse === undefined) options.parse = true;
  205. var collection = this,
  206. success = options.success,
  207. records = [];
  208. options.success = function (resp, status, xhr) {
  209. // Adding result to the records array
  210. records.push.apply(records, resp.records);
  211. // Checking if the result is not paged
  212. if (resp.nextRecordsUrl !== undefined) {
  213. var _options = _.clone(options);
  214. // Setting url to next records batch
  215. _options.url = Force._getServiceURL() + resp.nextRecordsUrl;
  216. // Making another request for next records batch
  217. collection.sync.call(collection, 'read', collection, _options);
  218. } else {
  219. collection[options.add ? 'add' : 'reset'](collection.parse(records, xhr), options);
  220. if (success) success(collection, resp);
  221. }
  222. };
  223. options.error = Backbone.wrapError(options.error, collection, options);
  224. return this.sync.call(this, 'read', this, options);
  225. }
  226. });
  227. return Force;
  228. }));