summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarren Smith <garren.smith@gmail.com>2014-02-04 16:47:54 +0200
committerGarren Smith <garren.smith@gmail.com>2014-02-04 16:47:54 +0200
commited89f34020a10259344ded418785a4492159c23f (patch)
treea2b6caf51b8eff5426d09454db5a9a1091c9fd45
parent34aabbfb58ca4f0ed60a20c466b6bf544b7dc28f (diff)
downloadcouchdb-ed89f34020a10259344ded418785a4492159c23f.tar.gz
Add backbone.fetch-cache
-rw-r--r--src/fauxton/app/addons/databases/resources.js8
-rw-r--r--src/fauxton/app/addons/documents/resources.js14
-rw-r--r--src/fauxton/app/addons/documents/routes.js2
-rw-r--r--src/fauxton/app/addons/documents/views.js22
-rw-r--r--src/fauxton/app/config.js3
-rw-r--r--src/fauxton/app/core/base.js27
-rw-r--r--src/fauxton/assets/js/plugins/backbone.fetch-cache.js311
7 files changed, 365 insertions, 22 deletions
diff --git a/src/fauxton/app/addons/databases/resources.js b/src/fauxton/app/addons/databases/resources.js
index 1b55f8885..5fa931b00 100644
--- a/src/fauxton/app/addons/databases/resources.js
+++ b/src/fauxton/app/addons/databases/resources.js
@@ -24,7 +24,7 @@ function(app, FauxtonAPI, Documents) {
Databases.DocLimit = 20;
- Databases.Model = Backbone.Model.extend({
+ Databases.Model = FauxtonAPI.Model.extend({
initialize: function(options) {
this.status = new Databases.Status({
database: this
@@ -82,7 +82,7 @@ function(app, FauxtonAPI, Documents) {
}
});
- Databases.Changes = Backbone.Collection.extend({
+ Databases.Changes = FauxtonAPI.Collection.extend({
initialize: function(options) {
this.database = options.database;
@@ -110,7 +110,7 @@ function(app, FauxtonAPI, Documents) {
}
});
- Databases.Status = Backbone.Model.extend({
+ Databases.Status = FauxtonAPI.Model.extend({
url: function() {
return app.host + "/" + this.database.safeID();
},
@@ -166,7 +166,7 @@ function(app, FauxtonAPI, Documents) {
});
// TODO: shared databases - read from the user doc
- Databases.List = Backbone.Collection.extend({
+ Databases.List = FauxtonAPI.Collection.extend({
model: Databases.Model,
documentation: function(){
return "all_dbs";
diff --git a/src/fauxton/app/addons/documents/resources.js b/src/fauxton/app/addons/documents/resources.js
index 831d5e3d0..adfee1f16 100644
--- a/src/fauxton/app/addons/documents/resources.js
+++ b/src/fauxton/app/addons/documents/resources.js
@@ -18,7 +18,7 @@ define([
function(app, FauxtonAPI) {
var Documents = FauxtonAPI.addon();
- Documents.Doc = Backbone.Model.extend({
+ Documents.Doc = FauxtonAPI.Model.extend({
idAttribute: "_id",
documentation: function(){
return "docs";
@@ -194,7 +194,7 @@ function(app, FauxtonAPI) {
}
});
- Documents.DdocInfo = Backbone.Model.extend({
+ Documents.DdocInfo = FauxtonAPI.Model.extend({
idAttribute: "_id",
documentation: function(){
return "docs";
@@ -224,7 +224,7 @@ function(app, FauxtonAPI) {
});
- Documents.ViewRow = Backbone.Model.extend({
+ Documents.ViewRow = FauxtonAPI.Model.extend({
// this is a hack so that backbone.collections doesn't group
// these by id and reduce the number of items returned.
idAttribute: "_id",
@@ -272,7 +272,7 @@ function(app, FauxtonAPI) {
});
- Documents.AllDocs = Backbone.Collection.extend({
+ Documents.AllDocs = FauxtonAPI.Collection.extend({
model: Documents.Doc,
documentation: function(){
return "docs";
@@ -397,7 +397,7 @@ function(app, FauxtonAPI) {
}
});
- Documents.IndexCollection = Backbone.Collection.extend({
+ Documents.IndexCollection = FauxtonAPI.Collection.extend({
model: Documents.ViewRow,
documentation: function(){
return "docs";
@@ -527,7 +527,7 @@ function(app, FauxtonAPI) {
// we can get the request duration
fetch: function () {
this.startTime = new Date().getTime();
- return Backbone.Collection.prototype.fetch.call(this);
+ return FauxtonAPI.Collection.prototype.fetch.call(this);
},
allDocs: function(){
@@ -568,7 +568,7 @@ function(app, FauxtonAPI) {
});
- Documents.PouchIndexCollection = Backbone.Collection.extend({
+ Documents.PouchIndexCollection = FauxtonAPI.Collection.extend({
model: Documents.ViewRow,
documentation: function(){
return "docs";
diff --git a/src/fauxton/app/addons/documents/routes.js b/src/fauxton/app/addons/documents/routes.js
index 1510485ee..be9ce2f7c 100644
--- a/src/fauxton/app/addons/documents/routes.js
+++ b/src/fauxton/app/addons/documents/routes.js
@@ -182,7 +182,7 @@ function(app, FauxtonAPI, Documents, Databases) {
},
establish: function () {
- return this.data.designDocs.fetch();
+ return this.data.designDocs.fetchOnce();
},
allDocs: function(databaseName, options) {
diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js
index 298cfb4d7..547620330 100644
--- a/src/fauxton/app/addons/documents/views.js
+++ b/src/fauxton/app/addons/documents/views.js
@@ -1803,11 +1803,31 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
Views.Changes = FauxtonAPI.View.extend({
template: "addons/documents/templates/changes",
+ initialize: function () {
+ var that = this;
+ this.listenTo( this.model.changes, 'change', function () {
+ console.log('render on change');
+ that.render();
+ });
+ this.listenTo( this.model.changes, 'cachesync', function () {
+ console.log('render on cachesync');
+ that.render();
+ });
+ },
+
establish: function() {
- return [ this.model.changes.fetch()];
+ return [ this.model.changes.fetchOnce({prefill: true,
+ success: function () {
+ console.log('hi ajax success');
+ },
+ prefillSuccess: function () {
+ console.log('hi prefill success');
+ }
+ })];
},
serialize: function () {
+ console.log('ss');
return {
changes: this.model.changes.toJSON(),
database: this.model
diff --git a/src/fauxton/app/config.js b/src/fauxton/app/config.js
index 98be9c6a8..a5d764f8c 100644
--- a/src/fauxton/app/config.js
+++ b/src/fauxton/app/config.js
@@ -30,7 +30,8 @@ require.config({
spin: "../assets/js/libs/spin.min",
d3: "../assets/js/libs/d3",
"nv.d3": "../assets/js/libs/nv.d3",
- "ace":"../assets/js/libs/ace"
+ "ace":"../assets/js/libs/ace",
+ "backbone.fetch-cache": "../assets/js/plugins/backbone.fetch-cache"
},
baseUrl: '/',
diff --git a/src/fauxton/app/core/base.js b/src/fauxton/app/core/base.js
index 53316bccf..6fe32618f 100644
--- a/src/fauxton/app/core/base.js
+++ b/src/fauxton/app/core/base.js
@@ -12,10 +12,12 @@
define([
"backbone",
- "plugins/backbone.layoutmanager"
+ "plugins/backbone.layoutmanager",
+ "backbone.fetch-cache"
],
-function(Backbone) {
+function(Backbone, LayoutManager, BackboneCache) {
+ console.log(BackboneCache);
var FauxtonAPI = {
//add default objects
router: {
@@ -67,16 +69,25 @@ function(Backbone) {
}
});
+
FauxtonAPI.Model = Backbone.Model.extend({
- fetchOnce: function (opt) {
- var options = _.extend({}, opt);
- if (!this._deferred || this._deferred.state() === "rejected" || options.forceFetch ) {
- this._deferred = this.fetch();
- }
+ });
+
+ FauxtonAPI.Collection = Backbone.Collection.extend({
- return this._deferred;
+ });
+
+ var caching = {
+ fetchOnce: function (opts) {
+ var options = _.defaults(opts || {}, this.cache, {cache: true});
+ console.log('opts', options);
+ return this.fetch(options);
}
+ };
+
+ _.each([FauxtonAPI.Collection, FauxtonAPI.Model], function (ctor) {
+ _.extend(ctor.prototype, caching);
});
var extensions = _.extend({}, Backbone.Events);
diff --git a/src/fauxton/assets/js/plugins/backbone.fetch-cache.js b/src/fauxton/assets/js/plugins/backbone.fetch-cache.js
new file mode 100644
index 000000000..c86a8b96f
--- /dev/null
+++ b/src/fauxton/assets/js/plugins/backbone.fetch-cache.js
@@ -0,0 +1,311 @@
+/*!
+ backbone.fetch-cache v1.3.0
+ by Andy Appleton - https://github.com/mrappleton/backbone-fetch-cache.git
+ */
+
+// AMD wrapper from https://github.com/umdjs/umd/blob/master/amdWebGlobal.js
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module and set browser global
+ define(['underscore', 'backbone', 'jquery'], function (_, Backbone, $) {
+ return (root.Backbone = factory(_, Backbone, $));
+ });
+ } else {
+ // Browser globals
+ root.Backbone = factory(root._, root.Backbone, root.jQuery);
+ }
+}(this, function (_, Backbone, $) {
+
+ // Setup
+ var superMethods = {
+ modelFetch: Backbone.Model.prototype.fetch,
+ modelSync: Backbone.Model.prototype.sync,
+ collectionFetch: Backbone.Collection.prototype.fetch
+ },
+ supportLocalStorage = (function() {
+ var supported = typeof window.localStorage !== 'undefined';
+ if (supported) {
+ try {
+ // impossible to write on some platforms when private browsing is on and
+ // throws an exception = local storage not supported.
+ localStorage.setItem("test_support", "test_support");
+ localStorage.removeItem("test_support");
+ } catch (e) {
+ supported = false;
+ }
+ }
+ return supported;
+ })();
+
+ Backbone.fetchCache = (Backbone.fetchCache || {});
+ Backbone.fetchCache._cache = (Backbone.fetchCache._cache || {});
+
+ Backbone.fetchCache.priorityFn = function(a, b) {
+ if (!a || !a.expires || !b || !b.expires) {
+ return a;
+ }
+
+ return a.expires - b.expires;
+ };
+
+ Backbone.fetchCache._prioritize = function() {
+ var sorted = _.values(this._cache).sort(this.priorityFn);
+ var index = _.indexOf(_.values(this._cache), sorted[0]);
+ return _.keys(this._cache)[index];
+ };
+
+ Backbone.fetchCache._deleteCacheWithPriority = function() {
+ Backbone.fetchCache._cache[this._prioritize()] = null;
+ delete Backbone.fetchCache._cache[this._prioritize()];
+ Backbone.fetchCache.setLocalStorage();
+ };
+
+ Backbone.fetchCache.getLocalStorageKey = function() {
+ return 'backboneCache';
+ };
+
+ if (typeof Backbone.fetchCache.localStorage === 'undefined') {
+ Backbone.fetchCache.localStorage = true;
+ }
+
+ // Shared methods
+ function getCacheKey(instance, opts) {
+ var url;
+
+ if(opts && opts.url) {
+ url = opts.url;
+ } else {
+ url = _.isFunction(instance.url) ? instance.url() : instance.url;
+ }
+
+ // Need url to use as cache key so return if we can't get it
+ if(!url) { return; }
+
+ if(opts && opts.data) {
+ return url + "?" + $.param(opts.data);
+ }
+ return url;
+ }
+
+ function setCache(instance, opts, attrs) {
+ opts = (opts || {});
+ var key = Backbone.fetchCache.getCacheKey(instance, opts),
+ expires = false;
+
+ // Need url to use as cache key so return if we can't get it
+ if (!key) { return; }
+
+ // Never set the cache if user has explicitly said not to
+ if (opts.cache === false) { return; }
+
+ // Don't set the cache unless cache: true or prefill: true option is passed
+ if (!(opts.cache || opts.prefill)) { return; }
+
+ if (opts.expires !== false) {
+ expires = (new Date()).getTime() + ((opts.expires || 5 * 60) * 1000);
+ }
+
+ Backbone.fetchCache._cache[key] = {
+ expires: expires,
+ value: attrs
+ };
+
+ Backbone.fetchCache.setLocalStorage();
+ }
+
+ function clearItem(key) {
+ if (_.isFunction(key)) { key = key(); }
+ delete Backbone.fetchCache._cache[key];
+ Backbone.fetchCache.setLocalStorage();
+ }
+
+ function setLocalStorage() {
+ if (!supportLocalStorage || !Backbone.fetchCache.localStorage) { return; }
+ try {
+ localStorage.setItem(Backbone.fetchCache.getLocalStorageKey(), JSON.stringify(Backbone.fetchCache._cache));
+ } catch (err) {
+ var code = err.code || err.number || err.message;
+ if (code === 22) {
+ this._deleteCacheWithPriority();
+ } else {
+ throw(err);
+ }
+ }
+ }
+
+ function getLocalStorage() {
+ if (!supportLocalStorage || !Backbone.fetchCache.localStorage) { return; }
+ var json = localStorage.getItem(Backbone.fetchCache.getLocalStorageKey()) || '{}';
+ Backbone.fetchCache._cache = JSON.parse(json);
+ }
+
+ function nextTick(fn) {
+ return window.setTimeout(fn, 0);
+ }
+
+ // Instance methods
+ Backbone.Model.prototype.fetch = function(opts) {
+ opts = _.defaults(opts || {}, { parse: true });
+ var key = Backbone.fetchCache.getCacheKey(this, opts),
+ data = Backbone.fetchCache._cache[key],
+ expired = false,
+ attributes = false,
+ deferred = new $.Deferred(),
+ self = this;
+
+ function setData() {
+ if (opts.parse) {
+ attributes = self.parse(attributes, opts);
+ }
+
+ self.set(attributes, opts);
+ if (_.isFunction(opts.prefillSuccess)) { opts.prefillSuccess(self, attributes, opts); }
+
+ // Trigger sync events
+ self.trigger('cachesync', self, attributes, opts);
+ self.trigger('sync', self, attributes, opts);
+
+ // Notify progress if we're still waiting for an AJAX call to happen...
+ if (opts.prefill) { deferred.notify(self); }
+ // ...finish and return if we're not
+ else {
+ if (_.isFunction(opts.success)) { opts.success(self, attributes, opts); }
+ deferred.resolve(self);
+ }
+ }
+
+ if (data) {
+ expired = data.expires;
+ expired = expired && data.expires < (new Date()).getTime();
+ attributes = data.value;
+ }
+
+ if (!expired && (opts.cache || opts.prefill) && attributes) {
+ // Ensure that cache resolution adhers to async option, defaults to true.
+ if (opts.async == null) { opts.async = true; }
+
+ if (opts.async) {
+ nextTick(setData);
+ } else {
+ setData();
+ }
+
+ if (!opts.prefill) {
+ return deferred.promise();
+ }
+ }
+
+ // Delegate to the actual fetch method and store the attributes in the cache
+ superMethods.modelFetch.apply(this, arguments)
+ // resolve the returned promise when the AJAX call completes
+ .done( _.bind(deferred.resolve, this, this) )
+ // Set the new data in the cache
+ .done( _.bind(Backbone.fetchCache.setCache, null, this, opts) )
+ // Reject the promise on fail
+ .fail( _.bind(deferred.reject, this, this) );
+
+ // return a promise which provides the same methods as a jqXHR object
+ return deferred.promise();
+ };
+
+ // Override Model.prototype.sync and try to clear cache items if it looks
+ // like they are being updated.
+ Backbone.Model.prototype.sync = function(method, model, options) {
+ // Only empty the cache if we're doing a create, update, patch or delete.
+ if (method === 'read') {
+ return superMethods.modelSync.apply(this, arguments);
+ }
+
+ var collection = model.collection,
+ keys = [],
+ i, len;
+
+ // Build up a list of keys to delete from the cache, starting with this
+ keys.push(Backbone.fetchCache.getCacheKey(model, options));
+
+ // If this model has a collection, also try to delete the cache for that
+ if (!!collection) {
+ keys.push(Backbone.fetchCache.getCacheKey(collection));
+ }
+
+ // Empty cache for all found keys
+ for (i = 0, len = keys.length; i < len; i++) { clearItem(keys[i]); }
+
+ return superMethods.modelSync.apply(this, arguments);
+ };
+
+ Backbone.Collection.prototype.fetch = function(opts) {
+ opts = _.defaults(opts || {}, { parse: true });
+ var key = Backbone.fetchCache.getCacheKey(this, opts),
+ data = Backbone.fetchCache._cache[key],
+ expired = false,
+ attributes = false,
+ deferred = new $.Deferred(),
+ self = this;
+
+ function setData() {
+ self[opts.reset ? 'reset' : 'set'](attributes, opts);
+ if (_.isFunction(opts.prefillSuccess)) { opts.prefillSuccess(self); }
+
+ // Trigger sync events
+ self.trigger('cachesync', self, attributes, opts);
+ self.trigger('sync', self, attributes, opts);
+
+ // Notify progress if we're still waiting for an AJAX call to happen...
+ if (opts.prefill) { deferred.notify(self); }
+ // ...finish and return if we're not
+ else {
+ if (_.isFunction(opts.success)) { opts.success(self, attributes, opts); }
+ deferred.resolve(self);
+ }
+ }
+
+ if (data) {
+ expired = data.expires;
+ expired = expired && data.expires < (new Date()).getTime();
+ attributes = data.value;
+ }
+
+ if (!expired && (opts.cache || opts.prefill) && attributes) {
+ // Ensure that cache resolution adhers to async option, defaults to true.
+ if (opts.async == null) { opts.async = true; }
+
+ if (opts.async) {
+ nextTick(setData);
+ } else {
+ setData();
+ }
+
+ if (!opts.prefill) {
+ return deferred.promise();
+ }
+ }
+
+ // Delegate to the actual fetch method and store the attributes in the cache
+ superMethods.collectionFetch.apply(this, arguments)
+ // resolve the returned promise when the AJAX call completes
+ .done( _.bind(deferred.resolve, this, this) )
+ // Set the new data in the cache
+ .done( _.bind(Backbone.fetchCache.setCache, null, this, opts) )
+ // Reject the promise on fail
+ .fail( _.bind(deferred.reject, this, this) );
+
+ // return a promise which provides the same methods as a jqXHR object
+ return deferred.promise();
+ };
+
+ // Prime the cache from localStorage on initialization
+ getLocalStorage();
+
+ // Exports
+
+ Backbone.fetchCache._superMethods = superMethods;
+ Backbone.fetchCache.setCache = setCache;
+ Backbone.fetchCache.getCacheKey = getCacheKey;
+ Backbone.fetchCache.clearItem = clearItem;
+ Backbone.fetchCache.setLocalStorage = setLocalStorage;
+ Backbone.fetchCache.getLocalStorage = getLocalStorage;
+
+ return Backbone;
+}));