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

/pancake-web/pancake/web/static/js/models/basemodel.js

https://bitbucket.org/mozillapancake/pancake
JavaScript | 180 lines | 101 code | 30 blank | 49 comment | 16 complexity | 3580760721f647236326a25d48c37a82 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, MIT, Apache-2.0
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. // This is a base class for models -- a thick and delicious layer of frosting
  5. // spread all over Backbone's Model cake.
  6. define([
  7. 'underscore',
  8. 'backbone',
  9. 'lib/promise',
  10. 'lib/lazily',
  11. 'models/statemixin'
  12. ],
  13. function (
  14. util,
  15. Backbone,
  16. Promise,
  17. lazily,
  18. stateMixin
  19. ) {
  20. var __Array = Array.prototype;
  21. var __BaseModel = util.extend(stateMixin, {
  22. parent: function () {
  23. return this.collection;
  24. },
  25. // A specialized fetch that decouples processing response objects from
  26. // fetching.
  27. //
  28. // Returns a `Promise` for the response value.
  29. //
  30. // This implementation of fetch completely bypasses Backbone's fetch
  31. // because we want to be able to provide the response, without parsing,
  32. // to the success event. It does use `Backbone.sync`, though.
  33. fetch: function (options) {
  34. options = options || {};
  35. var model = this;
  36. var cachedFetch = this._promisedFetch;
  37. // Check for an unresolved (in-flight) cached fetch promise on this
  38. // instance. If we find one, return it instead of issuing an
  39. // additional fetch.
  40. //
  41. // TODO: this is naive and assumes there is no real difference between
  42. // kinds of fetch determined by `options`. If we need to improve this
  43. // we could key the cache by doing a JSON.stringify on `options` and
  44. // using that as a promise cache key. For now, I want to avoid the
  45. // perf penalty until we know we need this. -GB
  46. if (
  47. cachedFetch &&
  48. !cachedFetch.resolved &&
  49. !cachedFetch.rejected
  50. ) {
  51. return cachedFetch;
  52. }
  53. var cleanup = util.bind(function(){
  54. this._promisedFetch = null;
  55. }, this);
  56. this._promisedFetch = new Promise();
  57. var promisedFetch = this._promisedFetch;
  58. // If success and error callbacks were provided, add them to the
  59. // `then` queue.
  60. promisedFetch
  61. .then(options.success, options.error)
  62. .then(cleanup, cleanup);
  63. // Trigger a fetch event -- useful for adding, then removing
  64. // loaders.
  65. if (!options.silent) this.trigger('fetch');
  66. // Redefine success/error handlers to manage promise.
  67. options.success = function (resp) {
  68. model = model.process(resp, options);
  69. promisedFetch.resolve(model);
  70. };
  71. options.error = function (resp) {
  72. if (!options.silent) model.trigger('error', resp, resp, options);
  73. promisedFetch.reject(resp, Boolean("dont throw on error"));
  74. };
  75. (this.sync || Backbone.sync).call(this, 'read', this, options);
  76. return promisedFetch;
  77. },
  78. // Process a response object -- the object is usually the result of a
  79. // call to `fetch`, but may be passed to `process` directly.
  80. // Either way, the logical path for processing a response object should
  81. // be the same.
  82. process: function (resp, options) {
  83. options = options || {};
  84. var model = this;
  85. // Use any passed `parse` option, or fall back to `this.parse`.
  86. model.set((options.parse || this.parse).call(this, resp), options);
  87. // Notify other interested parties that we've had a value come
  88. // back from the server. Provide the value unparsed.
  89. if (!options.silent) model.trigger('success', resp, options);
  90. return model;
  91. },
  92. addTo: function (collection) {
  93. collection.add(this);
  94. return this;
  95. }
  96. });
  97. // Extend Backbone Model with __BaseModel object.
  98. var BaseModel = Backbone.Model.extend(__BaseModel);
  99. // This method can be used to create subcollections for Models.
  100. // Pass a modifier key to republish events under, and a callback that
  101. // returns a collection. The resulting method will return a memoized
  102. // collection with `parent` method and event bindings.
  103. BaseModel.subcollection = function (key, callback) {
  104. if (!key) throw new Error('No key provided for subcollection');
  105. callback = callback || function () { return new Backbone.Collection(); };
  106. // This is the list of events from a collection that seem worth republishing
  107. // to me.
  108. var republish = [
  109. 'add',
  110. 'remove',
  111. 'move',
  112. 'reset',
  113. 'state'
  114. ];
  115. return lazily(function () {
  116. if (this === window) throw new Error(
  117. 'this function should be invoked as a method'
  118. );
  119. var self = this;
  120. var subcollection = callback.call(this);
  121. subcollection.parent = function () {
  122. return self;
  123. };
  124. // Republish subCollection events on the model.
  125. subcollection.bind('all', function (ev, model, collection) {
  126. var args = arguments,
  127. trigger = this.trigger;
  128. // If we get one of the events above from our collection, let's
  129. // add the key prefix and republish it.
  130. if (collection === subcollection && util.indexOf(republish, ev) !== -1) {
  131. args = __Array.slice.call(arguments);
  132. args.shift();
  133. args.unshift(key + ' ' + ev);
  134. return trigger.apply(self, args);
  135. }
  136. // Otherwise, if we got the event from elsewhere, let's check if
  137. // the event contains any of the words above IN ADDITION to. If it does, we can
  138. // republish it.
  139. // This will only publish events that look like `foo add`,
  140. // `foobar remove`, `foobar baz remove`.
  141. var iOf;
  142. for (var i = 0; i < republish.length; i++) {
  143. if (ev.indexOf(' ' + republish[i]) > 0) return trigger.apply(self, args);
  144. }
  145. }, this);
  146. return subcollection;
  147. });
  148. };
  149. return BaseModel;
  150. });