Skip to content

Instantly share code, notes, and snippets.

@unamashana
Created November 25, 2011 12:53

Revisions

  1. Prateek Dayal revised this gist Nov 25, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.textile
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    Please refer to [https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js:https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js]
    Please refer to https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js
  2. Prateek Dayal revised this gist Nov 25, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.textile
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    Please refer to [https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js]:https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js
    Please refer to [https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js:https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js]
  3. Prateek Dayal revised this gist Nov 25, 2011. 2 changed files with 1 addition and 1 deletion.
    1 change: 0 additions & 1 deletion gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    Please refer to https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js
    1 change: 1 addition & 0 deletions gistfile1.textile
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Please refer to [https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js]:https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js
  4. Prateek Dayal revised this gist Nov 25, 2011. 1 changed file with 1 addition and 378 deletions.
    379 changes: 1 addition & 378 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -1,378 +1 @@
    Backbone.CacheStore = {

    get: function(key){
    this._store || this.reset();
    return this._store[key];
    },

    set: function(key, object){
    this._store || this.reset();
    this._store[key] = object;
    },

    reset: function(){
    this._store = new Object;
    }

    }

    var getUrl = function(object) {
    if (!(object && object.url)) return null;
    return _.isFunction(object.url) ? object.url() : object.url;
    };

    // Backbone.Model
    // --------------

    // Create a new model, with defined attributes. A client id (`cid`)
    // is automatically generated and assigned for you.
    Backbone.Model = function(attributes, options) {

    var defaults;
    attributes || (attributes = {});
    var foundExisting = false;
    var instance;

    if(attributes.id && this.keepInSync ){

    instance = Backbone.CacheStore.get(this.name + "/" + attributes.id);

    if(instance === undefined){
    instance = this;
    }else{
    foundExisting = true;

    // Update the attributes and fire a change event anyway
    instance.set(attributes)
    return instance;
    }

    }

    if (defaults = this.defaults) {
    if (_.isFunction(defaults)) defaults = defaults();
    attributes = _.extend({}, defaults, attributes);
    }
    this.attributes = {};
    this._escapedAttributes = {};
    this.cid = _.uniqueId('c');
    this.set(attributes, {silent : true});
    this._changed = false;
    this._previousAttributes = _.clone(this.attributes);
    if (options && options.collection) this.collection = options.collection;
    this.initialize.apply(this, arguments);

    if(this.keepInSync && !foundExisting){
    // Store the new instance
    Backbone.CacheStore.set(this.name + "/" + attributes.id, this);
    }
    };

    // Attach all inheritable methods to the Model prototype.
    _.extend(Backbone.Model.prototype, Backbone.Events, {

    // A snapshot of the model's previous attributes, taken immediately
    // after the last `"change"` event was fired.
    _previousAttributes : null,

    // Has the item been changed since the last `"change"` event?
    _changed : false,

    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
    // CouchDB users may want to set this to `"_id"`.
    idAttribute : 'id',

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize : function(){},

    // Return a copy of the model's `attributes` object.
    toJSON : function() {
    return _.clone(this.attributes);
    },

    // Get the value of an attribute.
    get : function(attr) {
    return this.attributes[attr];
    },

    // Get the HTML-escaped value of an attribute.
    escape : function(attr) {
    var html;
    if (html = this._escapedAttributes[attr]) return html;
    var val = this.attributes[attr];
    return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
    },

    // Returns `true` if the attribute contains a value that is not null
    // or undefined.
    has : function(attr) {
    return this.attributes[attr] != null;
    },

    // Set a hash of model attributes on the object, firing `"change"` unless you
    // choose to silence it.
    set : function(attrs, options) {

    // Extract attributes and options.
    options || (options = {});
    if (!attrs) return this;
    if (attrs.attributes) attrs = attrs.attributes;
    var now = this.attributes, escaped = this._escapedAttributes;

    // Run validation.
    if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;

    // Check for changes of `id`.
    if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];

    // We're about to start triggering change events.
    var alreadyChanging = this._changing;
    this._changing = true;

    // Update attributes.
    for (var attr in attrs) {
    var val = attrs[attr];
    if (!_.isEqual(now[attr], val)) {
    now[attr] = val;
    delete escaped[attr];
    this._changed = true;
    if (!options.silent) this.trigger('change:' + attr, this, val, options);
    }
    }

    // Fire the `"change"` event, if the model has been changed.
    if (!alreadyChanging && !options.silent && this._changed) this.change(options);
    this._changing = false;
    return this;
    },

    // Remove an attribute from the model, firing `"change"` unless you choose
    // to silence it. `unset` is a noop if the attribute doesn't exist.
    unset : function(attr, options) {
    if (!(attr in this.attributes)) return this;
    options || (options = {});
    var value = this.attributes[attr];

    // Run validation.
    var validObj = {};
    validObj[attr] = void 0;
    if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;

    // Remove the attribute.
    delete this.attributes[attr];
    delete this._escapedAttributes[attr];
    if (attr == this.idAttribute) delete this.id;
    this._changed = true;
    if (!options.silent) {
    this.trigger('change:' + attr, this, void 0, options);
    this.change(options);
    }
    return this;
    },

    // Clear all attributes on the model, firing `"change"` unless you choose
    // to silence it.
    clear : function(options) {
    options || (options = {});
    var old = this.attributes;

    // Run validation.
    var validObj = {};
    for (var attr in old) validObj[attr] = void 0;
    if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;

    this.attributes = {};
    this._escapedAttributes = {};
    this._changed = true;
    if (!options.silent) {
    for (var attr in old) {
    this.trigger('change:' + attr, this, void 0, options);
    }
    this.change(options);
    }
    return this;
    },

    // Fetch the model from the server. If the server's representation of the
    // model differs from its current attributes, they will be overriden,
    // triggering a `"change"` event.
    fetch : function(options) {
    options || (options = {});
    var model = this;
    var success = options.success;
    options.success = function(resp, status, xhr) {
    if (!model.set(model.parse(resp, xhr), options)) return false;
    if (success) success(model, resp);
    };
    options.error = wrapError(options.error, model, options);
    return (this.sync || Backbone.sync).call(this, 'read', this, options);
    },

    // Set a hash of model attributes, and sync the model to the server.
    // If the server returns an attributes hash that differs, the model's
    // state will be `set` again.
    save : function(attrs, options) {
    options || (options = {});
    if (attrs && !this.set(attrs, options)) return false;
    var model = this;
    var success = options.success;
    options.success = function(resp, status, xhr) {
    if (!model.set(model.parse(resp, xhr), options)) return false;
    if (success) success(model, resp, xhr);
    };
    options.error = wrapError(options.error, model, options);
    var method = this.isNew() ? 'create' : 'update';
    return (this.sync || Backbone.sync).call(this, method, this, options);
    },

    // Destroy this model on the server if it was already persisted. Upon success, the model is removed
    // from its collection, if it has one.
    destroy : function(options) {
    options || (options = {});
    if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
    var model = this;
    var success = options.success;
    options.success = function(resp) {
    model.trigger('destroy', model, model.collection, options);
    if (success) success(model, resp);
    };
    options.error = wrapError(options.error, model, options);
    return (this.sync || Backbone.sync).call(this, 'delete', this, options);
    },

    // Default URL for the model's representation on the server -- if you're
    // using Backbone's restful methods, override this to change the endpoint
    // that will be called.
    url : function() {
    var base = getUrl(this.collection) || this.urlRoot || urlError();
    if (this.isNew()) return base;
    return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
    },

    // **parse** converts a response into the hash of attributes to be `set` on
    // the model. The default implementation is just to pass the response along.
    parse : function(resp, xhr) {
    return resp;
    },

    // Create a new model with identical attributes to this one.
    clone : function() {
    return new this.constructor(this);
    },

    // A model is new if it has never been saved to the server, and lacks an id.
    isNew : function() {
    return this.id == null;
    },

    // Call this method to manually fire a `change` event for this model.
    // Calling this will cause all objects observing the model to update.
    change : function(options) {
    this.trigger('change', this, options);
    this._previousAttributes = _.clone(this.attributes);
    this._changed = false;
    },

    // Determine if the model has changed since the last `"change"` event.
    // If you specify an attribute name, determine if that attribute has changed.
    hasChanged : function(attr) {
    if (attr) return this._previousAttributes[attr] != this.attributes[attr];
    return this._changed;
    },

    // Return an object containing all the attributes that have changed, or false
    // if there are no changed attributes. Useful for determining what parts of a
    // view need to be updated and/or what attributes need to be persisted to
    // the server.
    changedAttributes : function(now) {
    now || (now = this.attributes);
    var old = this._previousAttributes;
    var changed = false;
    for (var attr in now) {
    if (!_.isEqual(old[attr], now[attr])) {
    changed = changed || {};
    changed[attr] = now[attr];
    }
    }
    return changed;
    },

    // Get the previous value of an attribute, recorded at the time the last
    // `"change"` event was fired.
    previous : function(attr) {
    if (!attr || !this._previousAttributes) return null;
    return this._previousAttributes[attr];
    },

    // Get all of the attributes of the model at the time of the previous
    // `"change"` event.
    previousAttributes : function() {
    return _.clone(this._previousAttributes);
    },

    // Run validation against a set of incoming attributes, returning `true`
    // if all is well. If a specific `error` callback has been passed,
    // call that instead of firing the general `"error"` event.
    _performValidation : function(attrs, options) {
    var error = this.validate(attrs);
    if (error) {
    if (options.error) {
    options.error(this, error, options);
    } else {
    this.trigger('error', this, error, options);
    }
    return false;
    }
    return true;
    }

    });

    var extend = function (protoProps, classProps) {
    var child = inherits(this, protoProps, classProps);
    child.extend = this.extend;
    return child;
    };

    // Set up inheritance for the model, collection, and view.
    Backbone.Model.extend = Backbone.Collection.extend =
    Backbone.Router.extend = Backbone.View.extend = extend;

    var ctor = function(){};

    var inherits = function(parent, protoProps, staticProps) {
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call `super()`.
    if (protoProps && protoProps.hasOwnProperty('constructor')) {
    child = protoProps.constructor;
    } else {
    child = function(){ return parent.apply(this, arguments); };
    }

    // Inherit class (static) properties from parent.
    _.extend(child, parent);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function.
    ctor.prototype = parent.prototype;
    child.prototype = new ctor();

    // Add prototype properties (instance properties) to the subclass,
    // if supplied.
    if (protoProps) _.extend(child.prototype, protoProps);

    // Add static properties to the constructor function, if supplied.
    if (staticProps) _.extend(child, staticProps);

    // Correctly set child's `prototype.constructor`.
    child.prototype.constructor = child;

    // Set a convenience property in case the parent's prototype is needed later.
    child.__super__ = parent.prototype;

    return child;
    };
    Please refer to https://github.com/SupportBee/Backbone.js-Identity-Map/blob/master/public/javascripts/backbone_cached_model.js
  5. Prateek Dayal created this gist Nov 25, 2011.
    378 changes: 378 additions & 0 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,378 @@
    Backbone.CacheStore = {

    get: function(key){
    this._store || this.reset();
    return this._store[key];
    },

    set: function(key, object){
    this._store || this.reset();
    this._store[key] = object;
    },

    reset: function(){
    this._store = new Object;
    }

    }

    var getUrl = function(object) {
    if (!(object && object.url)) return null;
    return _.isFunction(object.url) ? object.url() : object.url;
    };

    // Backbone.Model
    // --------------

    // Create a new model, with defined attributes. A client id (`cid`)
    // is automatically generated and assigned for you.
    Backbone.Model = function(attributes, options) {

    var defaults;
    attributes || (attributes = {});
    var foundExisting = false;
    var instance;

    if(attributes.id && this.keepInSync ){

    instance = Backbone.CacheStore.get(this.name + "/" + attributes.id);

    if(instance === undefined){
    instance = this;
    }else{
    foundExisting = true;

    // Update the attributes and fire a change event anyway
    instance.set(attributes)
    return instance;
    }

    }

    if (defaults = this.defaults) {
    if (_.isFunction(defaults)) defaults = defaults();
    attributes = _.extend({}, defaults, attributes);
    }
    this.attributes = {};
    this._escapedAttributes = {};
    this.cid = _.uniqueId('c');
    this.set(attributes, {silent : true});
    this._changed = false;
    this._previousAttributes = _.clone(this.attributes);
    if (options && options.collection) this.collection = options.collection;
    this.initialize.apply(this, arguments);

    if(this.keepInSync && !foundExisting){
    // Store the new instance
    Backbone.CacheStore.set(this.name + "/" + attributes.id, this);
    }
    };

    // Attach all inheritable methods to the Model prototype.
    _.extend(Backbone.Model.prototype, Backbone.Events, {

    // A snapshot of the model's previous attributes, taken immediately
    // after the last `"change"` event was fired.
    _previousAttributes : null,

    // Has the item been changed since the last `"change"` event?
    _changed : false,

    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
    // CouchDB users may want to set this to `"_id"`.
    idAttribute : 'id',

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize : function(){},

    // Return a copy of the model's `attributes` object.
    toJSON : function() {
    return _.clone(this.attributes);
    },

    // Get the value of an attribute.
    get : function(attr) {
    return this.attributes[attr];
    },

    // Get the HTML-escaped value of an attribute.
    escape : function(attr) {
    var html;
    if (html = this._escapedAttributes[attr]) return html;
    var val = this.attributes[attr];
    return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
    },

    // Returns `true` if the attribute contains a value that is not null
    // or undefined.
    has : function(attr) {
    return this.attributes[attr] != null;
    },

    // Set a hash of model attributes on the object, firing `"change"` unless you
    // choose to silence it.
    set : function(attrs, options) {

    // Extract attributes and options.
    options || (options = {});
    if (!attrs) return this;
    if (attrs.attributes) attrs = attrs.attributes;
    var now = this.attributes, escaped = this._escapedAttributes;

    // Run validation.
    if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;

    // Check for changes of `id`.
    if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];

    // We're about to start triggering change events.
    var alreadyChanging = this._changing;
    this._changing = true;

    // Update attributes.
    for (var attr in attrs) {
    var val = attrs[attr];
    if (!_.isEqual(now[attr], val)) {
    now[attr] = val;
    delete escaped[attr];
    this._changed = true;
    if (!options.silent) this.trigger('change:' + attr, this, val, options);
    }
    }

    // Fire the `"change"` event, if the model has been changed.
    if (!alreadyChanging && !options.silent && this._changed) this.change(options);
    this._changing = false;
    return this;
    },

    // Remove an attribute from the model, firing `"change"` unless you choose
    // to silence it. `unset` is a noop if the attribute doesn't exist.
    unset : function(attr, options) {
    if (!(attr in this.attributes)) return this;
    options || (options = {});
    var value = this.attributes[attr];

    // Run validation.
    var validObj = {};
    validObj[attr] = void 0;
    if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;

    // Remove the attribute.
    delete this.attributes[attr];
    delete this._escapedAttributes[attr];
    if (attr == this.idAttribute) delete this.id;
    this._changed = true;
    if (!options.silent) {
    this.trigger('change:' + attr, this, void 0, options);
    this.change(options);
    }
    return this;
    },

    // Clear all attributes on the model, firing `"change"` unless you choose
    // to silence it.
    clear : function(options) {
    options || (options = {});
    var old = this.attributes;

    // Run validation.
    var validObj = {};
    for (var attr in old) validObj[attr] = void 0;
    if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;

    this.attributes = {};
    this._escapedAttributes = {};
    this._changed = true;
    if (!options.silent) {
    for (var attr in old) {
    this.trigger('change:' + attr, this, void 0, options);
    }
    this.change(options);
    }
    return this;
    },

    // Fetch the model from the server. If the server's representation of the
    // model differs from its current attributes, they will be overriden,
    // triggering a `"change"` event.
    fetch : function(options) {
    options || (options = {});
    var model = this;
    var success = options.success;
    options.success = function(resp, status, xhr) {
    if (!model.set(model.parse(resp, xhr), options)) return false;
    if (success) success(model, resp);
    };
    options.error = wrapError(options.error, model, options);
    return (this.sync || Backbone.sync).call(this, 'read', this, options);
    },

    // Set a hash of model attributes, and sync the model to the server.
    // If the server returns an attributes hash that differs, the model's
    // state will be `set` again.
    save : function(attrs, options) {
    options || (options = {});
    if (attrs && !this.set(attrs, options)) return false;
    var model = this;
    var success = options.success;
    options.success = function(resp, status, xhr) {
    if (!model.set(model.parse(resp, xhr), options)) return false;
    if (success) success(model, resp, xhr);
    };
    options.error = wrapError(options.error, model, options);
    var method = this.isNew() ? 'create' : 'update';
    return (this.sync || Backbone.sync).call(this, method, this, options);
    },

    // Destroy this model on the server if it was already persisted. Upon success, the model is removed
    // from its collection, if it has one.
    destroy : function(options) {
    options || (options = {});
    if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
    var model = this;
    var success = options.success;
    options.success = function(resp) {
    model.trigger('destroy', model, model.collection, options);
    if (success) success(model, resp);
    };
    options.error = wrapError(options.error, model, options);
    return (this.sync || Backbone.sync).call(this, 'delete', this, options);
    },

    // Default URL for the model's representation on the server -- if you're
    // using Backbone's restful methods, override this to change the endpoint
    // that will be called.
    url : function() {
    var base = getUrl(this.collection) || this.urlRoot || urlError();
    if (this.isNew()) return base;
    return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
    },

    // **parse** converts a response into the hash of attributes to be `set` on
    // the model. The default implementation is just to pass the response along.
    parse : function(resp, xhr) {
    return resp;
    },

    // Create a new model with identical attributes to this one.
    clone : function() {
    return new this.constructor(this);
    },

    // A model is new if it has never been saved to the server, and lacks an id.
    isNew : function() {
    return this.id == null;
    },

    // Call this method to manually fire a `change` event for this model.
    // Calling this will cause all objects observing the model to update.
    change : function(options) {
    this.trigger('change', this, options);
    this._previousAttributes = _.clone(this.attributes);
    this._changed = false;
    },

    // Determine if the model has changed since the last `"change"` event.
    // If you specify an attribute name, determine if that attribute has changed.
    hasChanged : function(attr) {
    if (attr) return this._previousAttributes[attr] != this.attributes[attr];
    return this._changed;
    },

    // Return an object containing all the attributes that have changed, or false
    // if there are no changed attributes. Useful for determining what parts of a
    // view need to be updated and/or what attributes need to be persisted to
    // the server.
    changedAttributes : function(now) {
    now || (now = this.attributes);
    var old = this._previousAttributes;
    var changed = false;
    for (var attr in now) {
    if (!_.isEqual(old[attr], now[attr])) {
    changed = changed || {};
    changed[attr] = now[attr];
    }
    }
    return changed;
    },

    // Get the previous value of an attribute, recorded at the time the last
    // `"change"` event was fired.
    previous : function(attr) {
    if (!attr || !this._previousAttributes) return null;
    return this._previousAttributes[attr];
    },

    // Get all of the attributes of the model at the time of the previous
    // `"change"` event.
    previousAttributes : function() {
    return _.clone(this._previousAttributes);
    },

    // Run validation against a set of incoming attributes, returning `true`
    // if all is well. If a specific `error` callback has been passed,
    // call that instead of firing the general `"error"` event.
    _performValidation : function(attrs, options) {
    var error = this.validate(attrs);
    if (error) {
    if (options.error) {
    options.error(this, error, options);
    } else {
    this.trigger('error', this, error, options);
    }
    return false;
    }
    return true;
    }

    });

    var extend = function (protoProps, classProps) {
    var child = inherits(this, protoProps, classProps);
    child.extend = this.extend;
    return child;
    };

    // Set up inheritance for the model, collection, and view.
    Backbone.Model.extend = Backbone.Collection.extend =
    Backbone.Router.extend = Backbone.View.extend = extend;

    var ctor = function(){};

    var inherits = function(parent, protoProps, staticProps) {
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call `super()`.
    if (protoProps && protoProps.hasOwnProperty('constructor')) {
    child = protoProps.constructor;
    } else {
    child = function(){ return parent.apply(this, arguments); };
    }

    // Inherit class (static) properties from parent.
    _.extend(child, parent);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function.
    ctor.prototype = parent.prototype;
    child.prototype = new ctor();

    // Add prototype properties (instance properties) to the subclass,
    // if supplied.
    if (protoProps) _.extend(child.prototype, protoProps);

    // Add static properties to the constructor function, if supplied.
    if (staticProps) _.extend(child, staticProps);

    // Correctly set child's `prototype.constructor`.
    child.prototype.constructor = child;

    // Set a convenience property in case the parent's prototype is needed later.
    child.__super__ = parent.prototype;

    return child;
    };