/pancake-web/pancake/web/static/js/models/basemodel.js
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
- /* 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/. */
- // This is a base class for models -- a thick and delicious layer of frosting
- // spread all over Backbone's Model cake.
- define([
- 'underscore',
- 'backbone',
- 'lib/promise',
- 'lib/lazily',
- 'models/statemixin'
- ],
- function (
- util,
- Backbone,
- Promise,
- lazily,
- stateMixin
- ) {
- var __Array = Array.prototype;
- var __BaseModel = util.extend(stateMixin, {
- parent: function () {
- return this.collection;
- },
- // A specialized fetch that decouples processing response objects from
- // fetching.
- //
- // Returns a `Promise` for the response value.
- //
- // This implementation of fetch completely bypasses Backbone's fetch
- // because we want to be able to provide the response, without parsing,
- // to the success event. It does use `Backbone.sync`, though.
- fetch: function (options) {
- options = options || {};
- var model = this;
- var cachedFetch = this._promisedFetch;
- // Check for an unresolved (in-flight) cached fetch promise on this
- // instance. If we find one, return it instead of issuing an
- // additional fetch.
- //
- // TODO: this is naive and assumes there is no real difference between
- // kinds of fetch determined by `options`. If we need to improve this
- // we could key the cache by doing a JSON.stringify on `options` and
- // using that as a promise cache key. For now, I want to avoid the
- // perf penalty until we know we need this. -GB
- if (
- cachedFetch &&
- !cachedFetch.resolved &&
- !cachedFetch.rejected
- ) {
- return cachedFetch;
- }
- var cleanup = util.bind(function(){
- this._promisedFetch = null;
- }, this);
- this._promisedFetch = new Promise();
- var promisedFetch = this._promisedFetch;
- // If success and error callbacks were provided, add them to the
- // `then` queue.
- promisedFetch
- .then(options.success, options.error)
- .then(cleanup, cleanup);
-
- // Trigger a fetch event -- useful for adding, then removing
- // loaders.
- if (!options.silent) this.trigger('fetch');
- // Redefine success/error handlers to manage promise.
- options.success = function (resp) {
- model = model.process(resp, options);
- promisedFetch.resolve(model);
- };
- options.error = function (resp) {
- if (!options.silent) model.trigger('error', resp, resp, options);
- promisedFetch.reject(resp, Boolean("dont throw on error"));
- };
- (this.sync || Backbone.sync).call(this, 'read', this, options);
-
- return promisedFetch;
- },
- // Process a response object -- the object is usually the result of a
- // call to `fetch`, but may be passed to `process` directly.
- // Either way, the logical path for processing a response object should
- // be the same.
- process: function (resp, options) {
- options = options || {};
- var model = this;
- // Use any passed `parse` option, or fall back to `this.parse`.
- model.set((options.parse || this.parse).call(this, resp), options);
- // Notify other interested parties that we've had a value come
- // back from the server. Provide the value unparsed.
- if (!options.silent) model.trigger('success', resp, options);
- return model;
- },
- addTo: function (collection) {
- collection.add(this);
- return this;
- }
- });
-
- // Extend Backbone Model with __BaseModel object.
- var BaseModel = Backbone.Model.extend(__BaseModel);
- // This method can be used to create subcollections for Models.
- // Pass a modifier key to republish events under, and a callback that
- // returns a collection. The resulting method will return a memoized
- // collection with `parent` method and event bindings.
- BaseModel.subcollection = function (key, callback) {
- if (!key) throw new Error('No key provided for subcollection');
- callback = callback || function () { return new Backbone.Collection(); };
- // This is the list of events from a collection that seem worth republishing
- // to me.
- var republish = [
- 'add',
- 'remove',
- 'move',
- 'reset',
- 'state'
- ];
- return lazily(function () {
- if (this === window) throw new Error(
- 'this function should be invoked as a method'
- );
- var self = this;
- var subcollection = callback.call(this);
- subcollection.parent = function () {
- return self;
- };
- // Republish subCollection events on the model.
- subcollection.bind('all', function (ev, model, collection) {
- var args = arguments,
- trigger = this.trigger;
- // If we get one of the events above from our collection, let's
- // add the key prefix and republish it.
- if (collection === subcollection && util.indexOf(republish, ev) !== -1) {
- args = __Array.slice.call(arguments);
- args.shift();
- args.unshift(key + ' ' + ev);
- return trigger.apply(self, args);
- }
- // Otherwise, if we got the event from elsewhere, let's check if
- // the event contains any of the words above IN ADDITION to. If it does, we can
- // republish it.
- // This will only publish events that look like `foo add`,
- // `foobar remove`, `foobar baz remove`.
- var iOf;
- for (var i = 0; i < republish.length; i++) {
- if (ev.indexOf(' ' + republish[i]) > 0) return trigger.apply(self, args);
- }
- }, this);
- return subcollection;
- });
- };
- return BaseModel;
- });