Created
April 29, 2013 15:29
-
-
Save crodjer/5482362 to your computer and use it in GitHub Desktop.
Backbone model/collection caching based on the object's url. Uses jStroage for storage.
This file contains 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
/*global Backbone:false */ | |
'use strict'; | |
(function() { | |
var siblingsCache = {}, CacheMixin; | |
window.siblingsCache = siblingsCache; | |
// To extend a model/collection do | |
// _.extend(ModelName, Backbone.CacheMixin); | |
CacheMixin = { | |
// Timeout cached data older then this, will be | |
// re-fetched | |
cacheTimeout: 120, | |
// List of object attributes which should also be cached | |
metaKeys: [], | |
// Set keyPrefix to namespace the data, probably per user | |
keyPrefix: '', | |
cacheKey: function() { | |
var prefix, url; | |
prefix = _.result(this, 'keyPrefix'); | |
url = _.result(this, 'url'); | |
if (prefix) { | |
return prefix + '|' + url; | |
} else { | |
return url; | |
} | |
}, | |
// Pre-fill the object's data from cache if exists. | |
// Executes a fetch if: | |
// - Cache is non-existent | |
// - Cache is older than the timeout for the model | |
// - If options.fetch == true | |
cached: function(options) { | |
var cached, fetch, meta, isStale; | |
var that = this; | |
if (!options) { | |
options = {}; | |
} | |
fetch = options.fetch; | |
cached = $.jStorage.get(this.cacheKey()); | |
isStale = true; | |
if (cached) { | |
/* jshint camelcase: false */ | |
this.sync_status = true; | |
this.set(cached.parsed); | |
meta = cached.meta || {}; | |
if (_.isObject(meta.attrs)) { | |
_.each(this.metaKeys, function (key) { | |
if (!that[key] && meta.attrs[key]) { | |
that[key] = meta.attrs[key]; | |
} | |
}); | |
} | |
if (meta.timestamp) { | |
isStale = (($.now() - meta.timestamp) / 1000) > (this.cacheTimeout || 120); | |
} | |
} | |
if (fetch || isStale) { | |
this.cachedFetch(options); | |
} else if (_.isFunction(options.success)) { | |
options.success(this, {}); | |
} | |
return this; | |
}, | |
// Store the provided or the existing data in the cache. | |
cacheSet: function(data) { | |
var cacheData = {}; | |
var meta; | |
var that = this; | |
if (data === null) { | |
data = null; | |
} | |
if (data) { | |
this.set(data); | |
} | |
cacheData.parsed = that.toJSON(); | |
meta = { | |
attrs: {}, | |
timestamp: $.now() | |
}; | |
if (this.metaKeys.length > 0) { | |
_.each(this.metaKeys, function (key) { | |
meta.attrs[key] = that[key]; | |
}); | |
} | |
cacheData.meta = meta; | |
this.trigger('reset'); | |
return $.jStorage.set(this.cacheKey(), cacheData); | |
}, | |
// All the sibling objects which are listing to the same fetch | |
siblings: function (value) { | |
this.constructor.siblings = this.constructor.siblings || []; | |
if (value) { | |
this.constructor.siblings = value; | |
} | |
return this.constructor.siblings; | |
}, | |
// Add as a sibling and tell if it is not the first in the chain | |
newSibling: function (options) { | |
// Cache the listeners in memory, so that on success or fail | |
// we can trigger corresponding events and callbacks. | |
var oldest = _.first(this.siblings()); | |
var that = this, isNew = false; | |
this.siblings().push({ | |
object: this, | |
options: options | |
}); | |
if (_.isObject(oldest)) { | |
this.listenToOnce(oldest.object, 'all', function (event) { | |
that.trigger(event); | |
}); | |
isNew = true; | |
} | |
// Return true if my older brothers exist. | |
return isNew; | |
}, | |
// Handle the XHR's response on all the siblings | |
handleXHR: function (callbackKind, object, xhr) { | |
var options; | |
_.each(this.siblings(), function (sibling) { | |
options = sibling.options || {}; | |
if (_.isFunction(options[callbackKind])) { | |
options[callbackKind](object, xhr); | |
} | |
if (sibling !== object) { | |
sibling.object.set(object.toJSON()); | |
sibling.object.trigger('reset'); | |
} | |
}); | |
// Remove all siblings | |
this.siblings([]); | |
}, | |
// Do a cached fetch, not overriding fetch to be non-intrusive. | |
cachedFetch: function (originalOptions) { | |
var options; | |
options = _.extend({}, originalOptions); | |
if (this.newSibling(originalOptions)) { | |
// I am new sibling, don't fetch me. My oldest brother will take care | |
return; | |
} | |
options.error = function(object, xhr) { | |
// Run error callbacks for the brotherhood. | |
object.handleXHR('error', object, xhr); | |
// Failed fetch | |
}; | |
options.success = function(object, xhr) { | |
object.cacheSet(); | |
// Run success callbacks for the brotherhood. | |
object.handleXHR('success', object, xhr); | |
// Successful fetch | |
}; | |
delete options.cache; | |
delete options.disableThrobber; | |
this.fetch(options); | |
}, | |
// Flush the cache relating this object, mostly for debugging. | |
flush: function () { | |
$.jStorage.deleteKey(this.cacheKey()); | |
}, | |
}; | |
_.extend(Backbone.Model, CacheMixin); | |
_.extend(Backbone.Collection, CacheMixin); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment