Created
November 25, 2011 12:53
-
-
Save unamashana/1393465 to your computer and use it in GitHub Desktop.
Backbone Cached Model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment