Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using IndexedDB as well as remote server with Backbone.js

I am working on an offline-enabled web app. I am using Backbone.js for client code. I need backbone.js to switch between remote server and the local IndexedDB according to the online/offline state of user. Which of the following ways is the right way to do this:

  1. Use this indexeddb-backbone adapter by superfeedr . But I feel it caters more to just offline storage and not both offline and online.
  2. Override the sync() method in backbone.js and hence create your own needs specific adapter.
like image 631
shreyj Avatar asked Oct 04 '12 07:10

shreyj


1 Answers

Let me give a shoot. I have never use backbone.js. However, I have awesome IndexedDB wrapper YDB-DB and I plan to support backbone.js and angular.js binding frameworks. But it seems not much to do.

As questioner suggested, adapter pattern of overriding Backbone.sync(method, model, options) is possible with few additional logic with the database wrapper library.

Backbone.sync expect return object to be jqXHR object, which implements Promise interface. Backbone.sync is overrided to intersect for caching in client-side database. A data source provider, $.db, is set a schema corresponding to the the given model. (See the YDN-DB for more detail.) I expect the backend server accept Google GData-like model data (Atom Entry), in which each model has etag attribute and optimistic conflict resolution is used.

$.db = new ydn.db.Storage('db_name', schema);

var Backbone_sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
  var df = $.Deferred();
  if (method == 'read') {
    var df_db = $.db.get(model.name, model.cid);
    df_db.done(function(data) {
      if (data) {
        df.resolve(data);
        options['header'].push({'If-Not-Match': data.etag});
        var ajax_df = Backbone_sync(method, model, options);
        ajax_df.done(function(new_data) {
          if (new_data) {
            assert(new_data.cid == model.cid);
            $.db.put(model.name, new_data);
            model.set(new_data).change();
          } // else, no change
        });
      } else {
        var ajax_df = Backbone_sync(method, model, options);
        df.pipe(ajax_df);
        ajax_df.done(function(new_data) {
          $.db.put(model.name, new_data);
        });
      }
    });
    df_db.fail(function(e) {
      throw e; // db connection blocking, or schema mismatch
    });
  } else if (method == 'update') {
    options['header'].push({'If-Match': model.etag});
    var ajax_df = Backbone_sync(method, model, options);
    df.pipe(ajax_df);
    ajax_df.done(function(new_data, status) {
      if (status == 409) { // conflict
        assert(new_data.cid == model.cid);
        $.db.run(function(db) { // run in transaction
          db.get(model.name, model.cid).done(function(data) { // NOTE: not $.db
            if (data) {
              var resolved_data = $.magic.resolve(new_data, data);
              db.put(model.name, resolved_data);
              model.set(resolved_data);            
              model.save(); // send merge result to server                  
            } else {
              db.put(model.name, new_data);
            }
          });
        }, model.name, 'readwrite'); // transaction scope of model object store for read write operations
      } else if (status == 404) { // not found
        $db.clear(model.name, model.cid);
      } else if (status < 300) {
        assert(new_data.cid == model.cid);
        $.db.put(model.name, new_data);
      }
    });
  }

  return df;
};

Remaining methods can be implement in similar way. Collections and query can also intersect and supply from the database cache.

If the server does not implement etag, it is still work, but you will not save server bandwidth, nor resolve conflict.

like image 101
Kyaw Tun Avatar answered Nov 15 '22 13:11

Kyaw Tun