I have myself an intriguing Backbone.js collection caching solution that I have built up over the past few days. It relies on a master collection that holds the models that are ultimately displayed in views. Actually fetching the data from the server is delegated to sub-collections.
Tonight, I hope to get things set up such that once the cached data is displayed, my application makes a call to the server and replaces the displayed cached content in my calendar:
The overall structure that accomplishes this is a nested set of fetch methods:
var Appointments = Backbone.Collection.extend({
model: Models.Appointment,
url: '/appointments',
cached: {},
initialize: function(models, options) { /* ... */ },
fetch: function(options) { /* ... */ },
fetchFromCache: function(options) { /* ... */ },
fetchFromServer: function(options) { /* ... */ },
// ...
});
The fetch()
method calls fetchFromCache()
. The fetchFromCache()
method calls fetchFromServer()
(if the data has not already been fetched). The
fetchFromCache()
is where I will focus tonight. If the appointment data for the currently displayed month is already in the collection, it simply fires the "cached"
event:var Appointments = Backbone.Collection.extend({ model: Models.Appointment, url: '/appointments', cached: {}, initialize: function(models, options) { /* ... */ }, fetch: function(options) { /* ... */ }, fetchFromCache: function(options) { var success = options.success; options.success = _.bind(function() { this.triggerCacheHits(options.date) success(); }, this); if (this.cached[options.date]) { options.success(); } else { this.fetchFromServer(options); } }, fetchFromServer: function(options) { /* ... */ }, // ... });This seems a fine place to add a
refreshCache()
call:// ...
fetchFromCache: function(options) {
var success = options.success;
options.success = _.bind(function() {
this.triggerCacheHits(options.date)
success();
}, this);
if (this.cached[options.date]) {
options.success();
this.refreshCache(options.date);
}
else {
this.fetchFromServer(options);
}
},
//...
When the data is fetched from the server, my primary appointments collection squirrels away a reference to the sub-collection://...
fetchFromServer: function(options) {
var subcollection =
this.cached[options.date] =
(new MonthAppointments());
// ...
}
//...
So the easiest thing that could possibly work is to reset that sub-collection inside refreshCache()
: refreshCache: function(date) {
console.log("[refreshCache] " + date);
this.cached[date].fetch();
}
If I load November's appointments, then navigate to the previous month, I should see a cache refresh of October's data:October is refreshed even though November was previously displayed because November's adjoining months (October and December) were pre-fetched. In fact November and September are pre-fetched now that I have navigated to October. This caching and pre-fetching is starting to get confusing.
Anyhow, I think I still have a handle on things. The re-
fetch()
of the October sub-collection is definitely going out because there is an XHR in Chrome's Javascript console. Nothing happens in the UI, though. For that to happen, I need to actually replace the existing records. I already have a
addFromSubCollection
method. If I invoke that in my success callback, it ought to do something: refreshCache: function(date) {
console.log("[refreshCache] " + date);
this.cached[date].fetch({
success: _.bind(function(collection, resp) {
this.addFromSubCollection(collection);
}, this)
});
},
But, of course, that simply adds the appointments to the calendar, duplicating the appointments already in place:No, I need to replace each model in the collection. Unfortunately,
replace()
is not a method on a Backbone collection. So I have to make my own: replace: function(models) {
if (_.isArray(models)) {
for (var i = 0, l = models.length; i < l; i++) {
this._replace(models[i]);
}
} else {
this._replace(models);
}
return this;
},
_replace: function(model) {
var existing = this.get(model);
existing.set(model.toJSON());
return existing;
},
// ...
This replace
/ _replace
method pair mimics similar pairs in Backbone itself (e.g. add
/ _add
). For each model in the newly fetched sub-collection, _replace
gets the existing one and sets the attributes based on what was fetched from the server.The
refreshCache()
method then becomes: refreshCache: function(date) {
console.log("[refreshCache] " + date);
this.cached[date].fetch({
success: _.bind(function(collection, resp) {
this.replace(collection.models);
}, this)
});
},
And that actually works. Of course, I am not handling deletes from cache or additions. I will pick back up there tomorrow. Ugh. Caching.
Day #176
No comments:
Post a Comment