This year, at the
Full Frontal conference, offline apps have been a popular topic.
Paul Kinlan made an excellent report “Building Web Applications for the Future Tomorrow, today and yesterday ”(
here are his slides), in which he compared the user experience of working with 50 popular mobile apps for iOS and Android with the sensations of websites and apps.
Needless to say, native applications have proven to be much better when the Internet connection was unavailable. Offline mode is a very important thing, and you should think about it from the very beginning of work on the application, and not expect to add it later when there is time. Working on the
Rareloop site, we remembered about offline mode from day one.
FormAgent mobile clients
were also originally designed to work offline, so that the user could continue working in the absence of the Internet and synchronize transparently when the connection appears. In this article, I describe the principles and practices that, in my opinion, are very helpful in developing such applications.
Note! I do not consider the issues of caching application resources - you can use App Cache or a hybrid solution (like
PhoneGap ), it does not matter [
From the translator: there is a detailed article on Habré about how to work with the Application Cache API ]. This tutorial focuses more on how to design your web application architecture to work offline, rather than what mechanisms to use to implement it.
Basic principles
Maximum untie the application from the server
Historically, most of the work on the web page was taken over by the server. The data was stored in the database, accessed through a thick layer of code in the server language like PHP or Ruby, the data were processed and rendered in HTML using templates. Most modern frameworks use MVC architecture to separate these tasks, but all the hard work is still done on the server. Storing, processing and displaying information requires constant communication with the server.
')
The offline first approach involves moving the entire MVC stack to the client side. On the server side there is only a lightweight JSON API for accessing the database. This makes server code much smaller, simpler, and easier to test.
James Pierce also talked about this on Full Frontal (
slides ), in a bit of a joking way:
No corner brackets on the line - just curly!
Summary:
- Make sure that the client application can do without the server, providing minimal functionality. As a last resort - at least a message that the data is not available.
- Use JSON.
Create a wrapper object for the client-side server API
Do not pollute application code with AJAX calls with nested callbacks. Create an object that will represent the functionality of the server within the application. This contributes to the separation of code and facilitates testing and debugging, allows the use of convenient stubs in place of server functions that have not yet been implemented. Inside this object can use AJAX, but from the point of view of the rest of the application it should not be seen exactly how it communicates with the server.
Summary:
- Abstract the JSON API in a separate object.
- Do not litter the application code with AJAX calls.
Unlink data update from data warehouse
Do not be tempted to simply request data directly from the object that abstracts the server API, and immediately use it for rendering templates. Better create a data object that serves as a proxy between the API object and the rest of the application. This data object will be responsible for requests for data updates and handle situations when the connection is broken - to synchronize data that was changed while working offline.
The data object can query the server for updates, when the user clicks the “refresh” button, or by timer, or by the “
online
” browser event, as desired, and the lack of direct access to the server makes it easier to manage data caching.
The data object must also be responsible for serializing and saving its state in the persistent storage, in Local Storage or WebSQL / IndexedDB, and be able to recover this data.
Summary:
- Use a separate data object to store and synchronize state.
- All work with data should go through this proxy object.
Example
As a simple example, take the contact management application. First, we will create a server API that will allow us to receive raw data of contacts. Suppose we create a RESTful API where the URI
/contacts
returns a list of all contact entries. Each entry has the
id
,
firstName
,
lastName
and
email
fields.
Then we write a wrapper object over this API:
var API = function() { }; API.prototype.getContacts = function(success, failure) { var win = function(data) { if(success) success(data); }; var fail = function() { if(failure) failure() }; $.ajax('http://myserver.com/contacts', { success: win, failure: fail }); };
Now we need a data object that will become the interface between our application and the data warehouse. It might look like this:
var Data = function() { this.api = new API(); this.contacts = this.readFromStorage(); this.indexData(); }; Data.prototype.indexData = function() {
Now we have a data object through which we can request an update from the server API, but by default it will return data stored locally. The rest of the application can use this code:
var App = function() { this.data = new Data(); this.template = '...'; this.render(); this.setupListeners(); }; App.prototype.render = function() {
This is a very simple example, in a real application, you probably want to implement the data object as a singleton, but to illustrate how to structure the code for working in offline mode is enough.