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