PageRenderTime 46ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/mozillapancake/pancake
JavaScript | 288 lines | 145 code | 37 blank | 106 comment | 5 complexity | d759f8e9f1d4b882ffd07b5c3613b758 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. define([
  5. 'config',
  6. 'underscore',
  7. 'models/sitecollection',
  8. 'models/thumbnails',
  9. 'lib/template',
  10. 'lib/urlhelper',
  11. 'lib/selectmodel',
  12. 'logger',
  13. 'lib/assert',
  14. 'lib/errors',
  15. 'lib/promise',
  16. 'lib/bridge',
  17. 'lib/lazily'
  18. ], function(
  19. config,
  20. util,
  21. SiteCollection,
  22. thumbnails,
  23. template,
  24. URLHelper,
  25. selectModel,
  26. logger,
  27. assert,
  28. errors,
  29. Promise,
  30. Bridge,
  31. lazily
  32. ){
  33. var flagError = function(msg){
  34. return function(err) {
  35. logger.error.apply(logger, [msg, err]);
  36. };
  37. };
  38. var bridge = new Bridge();
  39. var __SiteCollection = SiteCollection.prototype;
  40. // Define a constructor function for place collections.
  41. // This function is wrapped by an outer constructor function
  42. // below.
  43. var PlaceCollectionConstructor = SiteCollection.extend({
  44. // override as we need different url behavior
  45. initialize: function (models, options) {
  46. this.urlHelper = new URLHelper({
  47. template: config.latticeUrl,
  48. tokens: {
  49. latticeRoot: config.latticeRoot,
  50. username: config.username,
  51. service: 'stack'
  52. },
  53. query: { v: 2 }
  54. });
  55. __SiteCollection.initialize.apply(this, arguments);
  56. },
  57. url: function (tokens, query) {
  58. // Calculate stack ID at URL run-time. This is a deliberate decision. It
  59. // allows the parent stack to be changed during the duration of this
  60. // object's lifetime, and also allows you to construct PlaceCollections
  61. // without a stack.
  62. tokens = util.extend({ method: this.stack().id + '/nodes' }, tokens);
  63. return this.urlHelper.url(tokens, query);
  64. },
  65. // Return a reference to the parent stack.
  66. stack: function () {
  67. // `Era.EraCollection.Stack`
  68. return this.parent();
  69. },
  70. promisePlaces: lazily(function () {
  71. return this.fetch({ diff: true });
  72. }),
  73. // Fetch an individual place by id. Will refetch places from server
  74. // if ID is not found in current collection. Returns a Promise or value.
  75. //
  76. // TODO: this method should return a memoized promise object if the promise
  77. // resolution is in-flight.
  78. fetchPlace: function (place_id) {
  79. var places = this;
  80. var placesWithPlace = places.get(place_id) ?
  81. places : this.fetch({ diff: true});
  82. var placePromise = new Promise();
  83. Promise.when(placesWithPlace, function (places) {
  84. var place = places.get(place_id);
  85. // Resolve the promise if we found the place. Reject it if the
  86. // place still isn't found.
  87. placePromise[place ? 'resolve' : 'reject'](place);
  88. }, flagError("PlaceCollection fetchPlace resulted in an error"));
  89. return placePromise;
  90. },
  91. // Set a particular place in this collection as "selected".
  92. // De-select any previously selected place (modal).
  93. //
  94. // By default, retrieves the place with `get`. You can override the
  95. // getter used by passing a function via `options.with`. `with` function
  96. // may return a promise or value.
  97. selectPlace: function (place_id, options) {
  98. var place = this.get(place_id);
  99. // This could be decoupled, I suppose. -GB
  100. this.move(place, { at: 0 });
  101. selectModel.select.call(this, place.id);
  102. // This could potentially move to a method on the stack, I suppose. -GB
  103. this.stack().set({
  104. place_id: place.id,
  105. // TODO: we should avoid storing this information on the stack.
  106. // I know it comes back from the server, but perhaps we should
  107. // throw it away and defer to the place in the place collection.
  108. place_title: place.get('place_title'),
  109. place_url: place.get('place_url')
  110. });
  111. return this;
  112. },
  113. isSelected: selectModel.isSelected,
  114. selected: selectModel.selected,
  115. // Looks at the collection instance, the `stack` property and config to
  116. // piece together:
  117. //
  118. // * place_id
  119. // * place_url
  120. // * stack_id
  121. // * session_id
  122. // * origin
  123. //
  124. // ...and send a Bridge `changePlace` function.
  125. changePlace: function (place_id, options) {
  126. var stack = this.stack();
  127. var stack_id = stack.get('stack_id');
  128. var session_id = stack.get('session_id');
  129. var place = this.get(place_id);
  130. // Send a notification of a place change while I'm at it.
  131. var msg = util.extend(place.toJSON(), {
  132. stack_id: stack_id,
  133. session_id: session_id,
  134. origin: config.appName
  135. });
  136. bridge.changePlace(msg);
  137. return this;
  138. },
  139. // Decorate `SiteModels` on the way in
  140. // with `session_id` and `stack_id` from stack.
  141. //
  142. // These are used to issue `PUT lattice/:username/link` requests
  143. // via `siteModel.save()`. These should probably never change, so
  144. // don't worry about data binding for now.
  145. decorateModel: function (model) {
  146. var stack = this.stack();
  147. var decoration = {
  148. stack_id: stack.get('stack_id'),
  149. session_id: stack.get('session_id')
  150. };
  151. var set = model.set;
  152. // If model has a `set` method, assume they are model instances rather
  153. // than vanilla objects.
  154. return (set && 'function' === typeof set) ?
  155. model.set(decoration) : util.extend(model, decoration);
  156. },
  157. // Customize the `add` method.
  158. add: function (models, options) {
  159. // Decorate `SiteModels` on the way in
  160. // with `session_id` and `stack_id` from stack.
  161. //
  162. // There is one case where stack is `undefined` -- when instantiating
  163. // a `PlaceCollection`, `Backbone.Collection` calls
  164. // `Backbone.collection._reset`, which in turn calls `add`.
  165. if (this.stack()) models = util.isArray(models) ?
  166. util.map(models, this.decorateModel, this) :
  167. this.decorateModel(models);
  168. // Hand off to default `add` method.
  169. return __SiteCollection.add.call(this, models, options);
  170. },
  171. // Define a comparator function, used by Backbone to keep the
  172. // collection in sorted order.
  173. comparator: function (model) {
  174. // Sort models by reverse chronological order (last-accessed timestamp).
  175. return -1 * model.get('accessed');
  176. },
  177. // Backbone.Collection's default parse method passes sync (xhr) results
  178. // directly to Collection.set. We're defining a custom parse
  179. // implementation that massages data from our JSON API into
  180. // a Backbone.Model-compatible format.
  181. //
  182. // Sample return data:
  183. //
  184. // {
  185. // "d": [
  186. // {
  187. // stack: {
  188. // "title": "Join.me",
  189. // "id": "..."
  190. // },
  191. // place: {
  192. // "title": "Join.me",
  193. // "url": "https://join.me/",
  194. // "thumbnail_key": "...",
  195. // "id": "..."
  196. // },
  197. // "session_id": "..."
  198. // },
  199. // ...
  200. // ],
  201. // "thumbnails_job": "..."
  202. // }
  203. parse: function (resp, xhr) {
  204. resp = thumbnails.parse.call(this, resp, xhr);
  205. // throw a KeysMissingError error if the data isn't in the format we expect
  206. assert
  207. .contains(resp, ['d'])
  208. .orThrow(errors.KeysMissingError, "Results array not found in PlaceCollection fetch response data");
  209. var results = resp.d;
  210. // nothing to do here
  211. if(!results.length) return [];
  212. var model = this.model,
  213. stack = this.stack(),
  214. stackAttrs = {
  215. session_id: stack.get('session_id'),
  216. stack_id: stack.id
  217. };
  218. // Translate response array using curried translator function.
  219. return util.map(results, function (place) {
  220. // Decorate with `stack_id` and `session_id`, if not passed in.
  221. // These are used to issue `PUT lattice/:username/link` requests
  222. // via `siteModel.save()`. These should probably never change, so
  223. // don't worry about data binding for now.
  224. util.defaults(place, stackAttrs);
  225. place = model.prototype.parse.call(this, place);
  226. return place;
  227. });
  228. }
  229. });
  230. // Wrap `PlaceCollectionConstructor` in an "outer"
  231. // constructor function.
  232. // This allows us to get in as early as possible and overshadow `stack`
  233. // on the prototype if we need to.
  234. //
  235. // Resolves the construction race condition described in
  236. // <https://bugzilla.mozilla.org/show_bug.cgi?id=758019#c5>.
  237. var PlaceCollection = function (models, options) {
  238. // If a stack was passed in via options, use it.
  239. // Overshadow the standard `stack` accessor on the prototype
  240. // with a new one.
  241. if (options && options.stack) this.stack = function () {
  242. return options.stack;
  243. };
  244. return PlaceCollectionConstructor.call(this, models, options);
  245. };
  246. // Give PlaceCollection the configured prototype.
  247. PlaceCollection.prototype = PlaceCollectionConstructor.prototype;
  248. // ...and make sure it can self-extend, just like a Backbone Collection.
  249. PlaceCollection.extend = PlaceCollectionConstructor.extend;
  250. return PlaceCollection;
  251. });