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

/src/js/cilantro/models/base.js

https://github.com/cbmi/cilantro
JavaScript | 250 lines | 177 code | 52 blank | 21 comment | 31 complexity | 384338fd55e7765ca2e82f15e3678af3 MD5 | raw file
  1. /* global define */
  2. define([
  3. 'underscore',
  4. 'backbone',
  5. 'loglevel',
  6. '../utils'
  7. ], function(_, Backbone, loglevel, utils) {
  8. // Base model for Cilantro.
  9. var Model = Backbone.Model.extend({
  10. constructor: function(attrs, options) {
  11. options = _.defaults({parse: true}, options);
  12. this.links = {};
  13. Backbone.Model.prototype.constructor.call(this, attrs, options);
  14. // TODO: Should this be limited to listening to those attributes
  15. // that are in link templates on the collection?
  16. if (this.collection) {
  17. this.on('change', function() {
  18. // TODO: Common method for this since it's similar to what is
  19. // in parse()?
  20. if (this.collection) {
  21. _.extend(
  22. this.links,
  23. utils.getLinksFromTemplates(
  24. this, this.collection.linkTemplates)
  25. );
  26. }
  27. }, this);
  28. }
  29. },
  30. url: function() {
  31. if (this.isNew()) {
  32. return Backbone.Model.prototype.url.call(this);
  33. }
  34. return this.links.self;
  35. },
  36. _parseLinks: function(model, xhr) {
  37. model.links = utils.getLinks(xhr);
  38. },
  39. parse: function(attrs, options) {
  40. if (this.collection) {
  41. _.extend(
  42. this.links,
  43. utils.getLinksFromTemplates(attrs, this.collection.linkTemplates)
  44. );
  45. }
  46. // 2.3.x Backwards compatibility for resources that were not ported
  47. // to using the Link header.
  48. if (attrs && attrs._links) {
  49. _.each(attrs._links, function(link, name) {
  50. this.links[name] = link.href;
  51. }, this);
  52. }
  53. return Backbone.Model.prototype.parse.call(this, attrs, options);
  54. },
  55. sync: function(method, model, options) {
  56. var success = options.success,
  57. _this = this;
  58. options.success = function(resp, status, xhr) {
  59. _this._parseLinks(model, xhr);
  60. if (success) success(resp, status, xhr);
  61. };
  62. return Backbone.Model.prototype.sync.call(
  63. this, method, model, options);
  64. }
  65. });
  66. var StatModel = Model.extend({
  67. constructor: function(attrs, options) {
  68. Model.prototype.constructor.call(this, attrs, options);
  69. this.parent = {};
  70. if (!(this.parent = attrs.parent)) {
  71. throw new Error('parent model required');
  72. }
  73. this.listenTo(this.parent, 'request', this.onParentRequest);
  74. this.listenTo(this.parent, 'sync', this.onParentSync);
  75. },
  76. onParentRequest: function() {
  77. // if the parent make a request and we are
  78. // waiting for a stat response, it will be
  79. // stale when it arrives
  80. if (this.xhr) {
  81. this.xhr.abort();
  82. this.xhr = null;
  83. }
  84. },
  85. onParentReset: function() {
  86. this.xhr = this.fetch();
  87. },
  88. onParentSync: function() {
  89. this.xhr = this.fetch();
  90. },
  91. url: function() {
  92. if (!this.parent.id && this.parent.collection) {
  93. return this.parent.collection.links.self;
  94. }
  95. else if (this.parent.links.stats) {
  96. return this.parent.links.stats;
  97. }
  98. else {
  99. throw new Error('Stat supported model has no stats URL defined.');
  100. }
  101. },
  102. manualFetch: function() {
  103. this.xhr = this.fetch();
  104. return this.xhr;
  105. }
  106. });
  107. var StatsSupportedModel = Model.extend({
  108. statModel: StatModel,
  109. constructor: function(attrs, options) {
  110. if (!this.statModel) {
  111. throw new Error('statModel must be defined');
  112. }
  113. this.stats = new this.statModel({parent: this});
  114. Model.prototype.constructor.call(this, attrs, options);
  115. if (this.collection) {
  116. this.stats.listenTo(this.collection, 'reset', this.stats.onParentReset);
  117. }
  118. }
  119. });
  120. var Collection = Backbone.Collection.extend({
  121. model: Model,
  122. constructor: function(attrs, options) {
  123. options = _.defaults({parse: true}, options);
  124. this.links = {};
  125. Backbone.Collection.prototype.constructor.call(this, attrs, options);
  126. },
  127. _parseLinks: function(collection, xhr) {
  128. collection.links = utils.getLinks(xhr);
  129. collection.linkTemplates = utils.getLinkTemplates(xhr);
  130. },
  131. sync: function(method, collection, options) {
  132. var success = options.success,
  133. _this = this;
  134. options.success = function(resp, status, xhr) {
  135. _this._parseLinks(collection, xhr);
  136. if (success) success(resp, status, xhr);
  137. };
  138. return Backbone.Collection.prototype.sync.call(
  139. this, method, collection, options);
  140. }
  141. });
  142. // Base collection class that is session-aware. A session is always
  143. // created on initialization which enables immediately binding to the
  144. // session object, as well transparency when switching between session
  145. // objects. This is used for Context, View and Query collections.
  146. var SessionCollection = Collection.extend({
  147. initialize: function() {
  148. this.session = this.add({session: true});
  149. },
  150. // Prevent deferencing the session.
  151. reset: function(models, options) {
  152. options = options || {};
  153. models = models || [];
  154. // Search for session model, merge into existing and remove it.
  155. var model, match;
  156. for (var i = 0; i < models.length; i++) {
  157. model = models[i];
  158. if (model instanceof Backbone.Model) {
  159. if (model.get('session') === true) match = model.toJSON();
  160. } else if (model && model.session === true) {
  161. match = model;
  162. }
  163. if (match) {
  164. this.session.set(match, options);
  165. models.splice(i, 1);
  166. break;
  167. }
  168. }
  169. models.push(this.session);
  170. return Collection.prototype.reset.call(this, models, options);
  171. },
  172. // Extend `get` to lookup by session if passed. The session model
  173. // may change over time which is independent of the model id.
  174. // Furthermore, it guarantees views will have something to bind to
  175. // prior to it being fetched from the server.
  176. get: function(attrs) {
  177. var session = false;
  178. if (attrs instanceof Backbone.Model) {
  179. session = attrs.get('session');
  180. }
  181. if (attrs === 'session' || (typeof attrs === 'object' && attrs.session)) {
  182. session = true;
  183. }
  184. if (session) return this.findWhere({session: true});
  185. return Collection.prototype.get.call(this, attrs);
  186. },
  187. getSession: function() {
  188. return this.session;
  189. }
  190. });
  191. return {
  192. Model: Model,
  193. StatModel: StatModel,
  194. StatsSupportedModel: StatsSupportedModel,
  195. Collection: Collection,
  196. SessionCollection: SessionCollection
  197. };
  198. });