PageRenderTime 46ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js

https://github.com/grepory/flapjack
JavaScript | 303 lines | 220 code | 59 blank | 24 comment | 37 complexity | e1c65ba3e1b280b12090fa94870cb863 MD5 | raw file
  1. var toolbox = {};
  2. toolbox.getMainCollection = function (response) {
  3. return _.without(_.keys(response), 'links', 'linked', 'meta')[0];
  4. };
  5. // from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
  6. toolbox.generateUUID = function() {
  7. var d = new Date().getTime();
  8. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  9. var r = (d + Math.random()*16)%16 | 0;
  10. d = Math.floor(d/16);
  11. return (c=='x' ? r : (r&0x7|0x8)).toString(16);
  12. });
  13. return uuid;
  14. };
  15. toolbox.batchRequest = function(klass, ids, amount, success) {
  16. // batch requests to avoid GET length limits
  17. var grouped;
  18. var batch_size = 75;
  19. if (ids.length <= batch_size) {
  20. grouped = _.groupBy(ids, function(element, index){
  21. return Math.floor(index/batch_size);
  22. });
  23. } else {
  24. grouped = {1: ids};
  25. }
  26. return( _.map(grouped, function(id_group, index) {
  27. var linkedClass = klass;
  28. if (id_group.length > 0) {
  29. linkedClass = linkedClass.extend({
  30. url: function() { return(klass.prototype.url.call() + "/" + id_group.join(',')); }
  31. });
  32. }
  33. var linkedCollection = new linkedClass();
  34. return linkedCollection.fetch({'reset' : true, 'success' : success});
  35. }));
  36. };
  37. Backbone.JSONAPIModel = Backbone.Model.extend({
  38. // the following two methods, and batchRequest, should be folded into
  39. // Collection.fetch (and Model.fetch?)
  40. resolveLink: function(name, klass, superset) {
  41. if ( _.isUndefined(this.get('links')[name]) ) {
  42. this.linked[name] = new klass();
  43. } else {
  44. context = this;
  45. var records = superset.filter(function(obj) {
  46. return(context.get('links')[name].indexOf(obj.get('id')) > -1);
  47. });
  48. if ( _.isUndefined(this.linked[name]) ) {
  49. this.linked[name] = new klass();
  50. }
  51. this.linked[name].add(records);
  52. }
  53. },
  54. resolveLinks: function(name_klass_h) {
  55. var context = this;
  56. return _.flatten( _.map(name_klass_h, function(klass, name) {
  57. if ( _.isUndefined(context.linked) ) {
  58. context.linked = {};
  59. }
  60. var links_name = context.get('links')[name];
  61. if ( !_.isUndefined(links_name) && !_.isEmpty(links_name) ) {
  62. var success = function(resultCollection, response, options) {
  63. context.resolveLink(name, klass, resultCollection);
  64. };
  65. return toolbox.batchRequest(klass, links_name, 75, success);
  66. } else {
  67. return($.when(context.resolveLink(name, klass, new Array())));
  68. }
  69. }));
  70. },
  71. parse: function (response) {
  72. if (response === undefined) {
  73. return;
  74. }
  75. if (response._alreadyBBJSONAPIParsed) {
  76. delete response._alreadyBBJSONAPIParsed;
  77. return response;
  78. }
  79. var mainCollection = toolbox.getMainCollection(response);
  80. var obj = response[mainCollection][0];
  81. return obj;
  82. },
  83. // post: function(urlType, attrs, options) {
  84. // if ( _.isUndefined(options) || _.isNull(options) ) {
  85. // options = {};
  86. // }
  87. // postData = {}
  88. // postData[urlType] = [attrs];
  89. // return(this.save({}, _.extend(options, {
  90. // contentType: 'application/vnd.api+json',
  91. // data: JSON.stringify(postData)
  92. // })));
  93. // },
  94. // NB: should not be exported
  95. savePatch: function(attrs, patch, options) {
  96. if ( _.isUndefined(options) || _.isNull(options) ) {
  97. options = {};
  98. }
  99. return(this.save(attrs, _.extend(options, {
  100. data: JSON.stringify(patch),
  101. patch: true,
  102. contentType: 'application/json-patch+json'
  103. })));
  104. },
  105. patch: function(urlType, attrs, options) {
  106. if (attrs == null) {
  107. attrs = {};
  108. }
  109. var context = this;
  110. var patch = _.inject(attrs, function(memo, val, key) {
  111. // skip if not a simple attribute value
  112. if ( (key == 'links') || _.isObject(val) || _.isArray(val) ) {
  113. return memo;
  114. }
  115. memo.push({
  116. op: 'replace',
  117. path: '/' + urlType + '/0/' + key,
  118. value: val
  119. });
  120. return memo;
  121. }, new Array());
  122. this.savePatch(attrs, patch, options);
  123. },
  124. // singular operation only -- TODO batch up and submit en masse
  125. addLinked: function(urlType, type, obj, options) {
  126. var id = obj.get('id');
  127. var patch = [{
  128. op: 'add',
  129. path: '/' + urlType + '/0/links/' + type + '/-',
  130. value: id
  131. }];
  132. this.savePatch({}, patch, {}, options);
  133. if ( _.isUndefined(this.get('links')[type]) ) {
  134. this.get('links')[type] = new Array();
  135. }
  136. this.get('links')[type].push(id);
  137. this.linked[type].add(obj);
  138. },
  139. // singular operation only -- TODO batch up and submit en masse
  140. removeLinked: function(urlType, type, obj, options) {
  141. var id = obj.get('id');
  142. var patch = [{
  143. op: 'remove',
  144. path: '/' + urlType + '/0/links/' + type + '/' + id,
  145. }];
  146. this.savePatch({}, patch, {}, options);
  147. if ( _.isUndefined(this.get('links')[type]) ) {
  148. this.get('links')[type] = new Array();
  149. }
  150. this.get('links')[type] = _.without(this.get('links')[type], id);
  151. this.linked[type].remove(obj);
  152. },
  153. urlRoot: function() { return(flapjack.api_url + "/" + this.name); },
  154. // can only be called from inside a 'change' event
  155. setDirty: function() {
  156. if ( !this.hasChanged() ) {
  157. return;
  158. }
  159. this.dirty = true;
  160. if ( _.isUndefined(this.clean) ) {
  161. this.clean = {};
  162. }
  163. var context = this;
  164. // foreach changed attribute, merge previous value into a 'clean' hash
  165. // (only if the key isn't already in there)
  166. _.each(_.without(_.keys(this.changedAttributes()), _.keys(this.clean)), function(key) {
  167. context.clean[key] = context.previous(key);
  168. });
  169. },
  170. // can only be called from outside a change event
  171. revertClean: function() {
  172. if ( !_.isUndefined(this.clean) ) {
  173. this.set(this.clean, {silent: true});
  174. }
  175. this.clean = {};
  176. this.dirty = false;
  177. },
  178. sync: function(method, model, options) {
  179. if ( method == 'create' ) {
  180. // patch sets its own content type, get and delete don't have body content
  181. data = {};
  182. data[model.name] = [options.attrs || model.toJSON(options)];
  183. options['data'] = JSON.stringify(data)
  184. options['contentType'] = 'application/vnd.api+json';
  185. }
  186. var succ = function(data, response, opts) {
  187. model.clean = {};
  188. model.dirty = false;
  189. };
  190. if ( _.isUndefined(options['success']) ) {
  191. options['success'] = succ;
  192. } else {
  193. options['success'] = [options['success'], succ];
  194. }
  195. Backbone.sync(method, model, options);
  196. }
  197. });
  198. Backbone.JSONAPICollection = Backbone.Collection.extend({
  199. resolveLinks: function(name_klass_h) {
  200. if ( _.isUndefined(this.linked) ) {
  201. this.linked = {};
  202. }
  203. var context = this;
  204. _.each(name_klass_h, function(klass, name) {
  205. if ( !_.isUndefined(context.links[name]) && !_.isEmpty(context.links[name]) ) {
  206. context.linked[name] = new klass();
  207. var success = function(resultCollection, response, options) {
  208. context.forEach(function(obj, index) {
  209. if ( _.isUndefined(obj.linked) ) {
  210. obj.linked = {};
  211. }
  212. obj.resolveLink(name, klass, resultCollection);
  213. });
  214. }
  215. toolbox.batchRequest(klass, context.links[name], 75, success);
  216. }
  217. });
  218. },
  219. parse: function (response) {
  220. if (response === undefined) {
  221. return;
  222. }
  223. this.links = {};
  224. var context = this;
  225. var mainCollection = toolbox.getMainCollection(response);
  226. var objects = _.map(response[mainCollection], function (obj) {
  227. _.each(obj.links, function(ids, name) {
  228. if ( _.isUndefined(context.links[name]) ) {
  229. context.links[name] = new Array();
  230. }
  231. if ( _.isArray(ids) ) {
  232. context.links[name] = context.links[name].concat(ids);
  233. }
  234. });
  235. obj._alreadyBBJSONAPIParsed = true;
  236. return obj;
  237. });
  238. this.links = _.reduce(this.links, function(memo, ids, name) {
  239. memo[name] = _.uniq(ids);
  240. return(memo);
  241. }, {});
  242. return objects;
  243. }
  244. });