I’ve finally got around to tackling the offline portion of the project I’m working on and I’ve come up with a hacky/ingenious workaround (delete as appropriate) that simplifies things quite a bit. I’ll try to write the approach up in more detail and with examples when I have some time but here is a quick(ish!) description because I’m wondering if there is a better way to do some of it…
I’m using the appcache to make my files available offline. All my data is loaded from a /api/syncs
URL - the syncs model has a bit of meta data in it but all of the other records that I need in my app are included in the payload and so get side-loaded into the store. And I’ve added a fallback for my sync URL which returns:
{
"syncs": {
"isUnavailable": true
}
}
Then I made a custom app adapter (extending DS.RESTAdapter
) and overrode the (private) ajax
method:
ajax: function(url, type, hash) {
var adapter = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
adapter._super.apply(adapter, [url, type, hash]).then(function(data) {
switch(url) {
case '/api/syncs':
if (data.syncs.isUnavailable) {
resolve(JSON.parse(window.localStorage.getItem('syncCache')));
} else {
window.localStorage.setItem('syncCache', JSON.stringify(data));
resolve(data);
}
break;
default:
resolve(data);
}
}, function(jqXHR) {
// Handle server failures
});
});
}
As you can see, when the /api/syncs
URL loads correctly we store the raw JSON returned in the browser’s localStorage
. When we are offline and the appcache returns the fallback then we load the “last known good” data from localStorage
and the rest of the app works fine and doesn’t need to be aware that the app was offline.
This works great so far. The problems start when my app needs to create or update models. Doing so causes POST
or PUT
queries to the server which don’t get caught by the appcache fallback. They do trigger the error handler of the ajax promise (where the “// Handle server failures” comment is in the above code) and I am catching the errors here and saving the URL, type and hash to localStorage (as a QueuedRequestModel
).
I then need to resolve the outer promise. In doing so I need to make sure I create a representation of the object that matches what the server would have returned. This means converting dates to the expected format (they are sent to the server as a string e.g. “Fri, 17 Jan 2014 07:42:22 GMT” but are expected back as an integer e.g. 1389944542000). And making sure information about all relationships is present. And adding IDs to newly generated records.
This also works and when the user is back online they can trigger a sync where all of the pending requests from localStorage
get sent to the server.
The problem that I still have is if a user reloads the page while still offline. Then the cached data for the /url/syncs
endpoint doesn’t include the changes and additions that are recorded in the QueuedRequestModels
. I need to find a way to replay these changes so that the app is in a consistent and up-to-date state.
So my questions are:
- What do you think of this approach? Can you think of a simpler alternative or do you think it’s worth pursuing?
- Is there any way around manually transforming the models in the
POST
/ PUT
callbacks to the structure that ember seems to expect? Is it correct behaviour of ember-data that the data sent to the server is formatted differently to what it expects to be returned? Is there a way to tell ember-data not to update the related model?
- Can you think of a clean way to replay the queued changes when the app re-starts with
QueuedRequestModels
in the localStorage
?
Thanks!