/pancake-web/pancake/web/static/js/models/placecollection.js
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
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- define([
- 'config',
- 'underscore',
- 'models/sitecollection',
- 'models/thumbnails',
- 'lib/template',
- 'lib/urlhelper',
- 'lib/selectmodel',
- 'logger',
- 'lib/assert',
- 'lib/errors',
- 'lib/promise',
- 'lib/bridge',
- 'lib/lazily'
- ], function(
- config,
- util,
- SiteCollection,
- thumbnails,
- template,
- URLHelper,
- selectModel,
- logger,
- assert,
- errors,
- Promise,
- Bridge,
- lazily
- ){
- var flagError = function(msg){
- return function(err) {
- logger.error.apply(logger, [msg, err]);
- };
- };
- var bridge = new Bridge();
- var __SiteCollection = SiteCollection.prototype;
- // Define a constructor function for place collections.
- // This function is wrapped by an outer constructor function
- // below.
- var PlaceCollectionConstructor = SiteCollection.extend({
- // override as we need different url behavior
- initialize: function (models, options) {
- this.urlHelper = new URLHelper({
- template: config.latticeUrl,
- tokens: {
- latticeRoot: config.latticeRoot,
- username: config.username,
- service: 'stack'
- },
- query: { v: 2 }
- });
- __SiteCollection.initialize.apply(this, arguments);
- },
- url: function (tokens, query) {
- // Calculate stack ID at URL run-time. This is a deliberate decision. It
- // allows the parent stack to be changed during the duration of this
- // object's lifetime, and also allows you to construct PlaceCollections
- // without a stack.
- tokens = util.extend({ method: this.stack().id + '/nodes' }, tokens);
- return this.urlHelper.url(tokens, query);
- },
- // Return a reference to the parent stack.
- stack: function () {
- // `Era.EraCollection.Stack`
- return this.parent();
- },
- promisePlaces: lazily(function () {
- return this.fetch({ diff: true });
- }),
- // Fetch an individual place by id. Will refetch places from server
- // if ID is not found in current collection. Returns a Promise or value.
- //
- // TODO: this method should return a memoized promise object if the promise
- // resolution is in-flight.
- fetchPlace: function (place_id) {
- var places = this;
- var placesWithPlace = places.get(place_id) ?
- places : this.fetch({ diff: true});
- var placePromise = new Promise();
- Promise.when(placesWithPlace, function (places) {
- var place = places.get(place_id);
- // Resolve the promise if we found the place. Reject it if the
- // place still isn't found.
- placePromise[place ? 'resolve' : 'reject'](place);
- }, flagError("PlaceCollection fetchPlace resulted in an error"));
- return placePromise;
- },
- // Set a particular place in this collection as "selected".
- // De-select any previously selected place (modal).
- //
- // By default, retrieves the place with `get`. You can override the
- // getter used by passing a function via `options.with`. `with` function
- // may return a promise or value.
- selectPlace: function (place_id, options) {
- var place = this.get(place_id);
- // This could be decoupled, I suppose. -GB
- this.move(place, { at: 0 });
- selectModel.select.call(this, place.id);
- // This could potentially move to a method on the stack, I suppose. -GB
- this.stack().set({
- place_id: place.id,
- // TODO: we should avoid storing this information on the stack.
- // I know it comes back from the server, but perhaps we should
- // throw it away and defer to the place in the place collection.
- place_title: place.get('place_title'),
- place_url: place.get('place_url')
- });
- return this;
- },
- isSelected: selectModel.isSelected,
- selected: selectModel.selected,
- // Looks at the collection instance, the `stack` property and config to
- // piece together:
- //
- // * place_id
- // * place_url
- // * stack_id
- // * session_id
- // * origin
- //
- // ...and send a Bridge `changePlace` function.
- changePlace: function (place_id, options) {
- var stack = this.stack();
- var stack_id = stack.get('stack_id');
- var session_id = stack.get('session_id');
- var place = this.get(place_id);
- // Send a notification of a place change while I'm at it.
- var msg = util.extend(place.toJSON(), {
- stack_id: stack_id,
- session_id: session_id,
- origin: config.appName
- });
- bridge.changePlace(msg);
- return this;
- },
- // Decorate `SiteModels` on the way in
- // with `session_id` and `stack_id` from stack.
- //
- // These are used to issue `PUT lattice/:username/link` requests
- // via `siteModel.save()`. These should probably never change, so
- // don't worry about data binding for now.
- decorateModel: function (model) {
- var stack = this.stack();
- var decoration = {
- stack_id: stack.get('stack_id'),
- session_id: stack.get('session_id')
- };
- var set = model.set;
- // If model has a `set` method, assume they are model instances rather
- // than vanilla objects.
- return (set && 'function' === typeof set) ?
- model.set(decoration) : util.extend(model, decoration);
- },
- // Customize the `add` method.
- add: function (models, options) {
- // Decorate `SiteModels` on the way in
- // with `session_id` and `stack_id` from stack.
- //
- // There is one case where stack is `undefined` -- when instantiating
- // a `PlaceCollection`, `Backbone.Collection` calls
- // `Backbone.collection._reset`, which in turn calls `add`.
- if (this.stack()) models = util.isArray(models) ?
- util.map(models, this.decorateModel, this) :
- this.decorateModel(models);
- // Hand off to default `add` method.
- return __SiteCollection.add.call(this, models, options);
- },
- // Define a comparator function, used by Backbone to keep the
- // collection in sorted order.
- comparator: function (model) {
- // Sort models by reverse chronological order (last-accessed timestamp).
- return -1 * model.get('accessed');
- },
- // Backbone.Collection's default parse method passes sync (xhr) results
- // directly to Collection.set. We're defining a custom parse
- // implementation that massages data from our JSON API into
- // a Backbone.Model-compatible format.
- //
- // Sample return data:
- //
- // {
- // "d": [
- // {
- // stack: {
- // "title": "Join.me",
- // "id": "..."
- // },
- // place: {
- // "title": "Join.me",
- // "url": "https://join.me/",
- // "thumbnail_key": "...",
- // "id": "..."
- // },
- // "session_id": "..."
- // },
- // ...
- // ],
- // "thumbnails_job": "..."
- // }
- parse: function (resp, xhr) {
- resp = thumbnails.parse.call(this, resp, xhr);
- // throw a KeysMissingError error if the data isn't in the format we expect
- assert
- .contains(resp, ['d'])
- .orThrow(errors.KeysMissingError, "Results array not found in PlaceCollection fetch response data");
-
- var results = resp.d;
-
- // nothing to do here
- if(!results.length) return [];
- var model = this.model,
- stack = this.stack(),
- stackAttrs = {
- session_id: stack.get('session_id'),
- stack_id: stack.id
- };
- // Translate response array using curried translator function.
- return util.map(results, function (place) {
- // Decorate with `stack_id` and `session_id`, if not passed in.
- // These are used to issue `PUT lattice/:username/link` requests
- // via `siteModel.save()`. These should probably never change, so
- // don't worry about data binding for now.
- util.defaults(place, stackAttrs);
- place = model.prototype.parse.call(this, place);
- return place;
- });
- }
- });
- // Wrap `PlaceCollectionConstructor` in an "outer"
- // constructor function.
- // This allows us to get in as early as possible and overshadow `stack`
- // on the prototype if we need to.
- //
- // Resolves the construction race condition described in
- // <https://bugzilla.mozilla.org/show_bug.cgi?id=758019#c5>.
- var PlaceCollection = function (models, options) {
- // If a stack was passed in via options, use it.
- // Overshadow the standard `stack` accessor on the prototype
- // with a new one.
- if (options && options.stack) this.stack = function () {
- return options.stack;
- };
- return PlaceCollectionConstructor.call(this, models, options);
- };
- // Give PlaceCollection the configured prototype.
- PlaceCollection.prototype = PlaceCollectionConstructor.prototype;
- // ...and make sure it can self-extend, just like a Backbone Collection.
- PlaceCollection.extend = PlaceCollectionConstructor.extend;
- return PlaceCollection;
- });