Skip to content

Instantly share code, notes, and snippets.

@crodjer
Created April 29, 2013 15:29
Show Gist options
  • Save crodjer/5482362 to your computer and use it in GitHub Desktop.
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.
/*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