Wednesday, February 15, 2012

HipsterSync: Swapping MVC Storage Behavior in Dart

‹prev | My Chain | next›

Last night I figured out how to inject shared behavior in Dart. Today I need to use that knowledge to support an injectable sync in my HipsterMVC framework.

First up, I convert yesterday's proof of concept to something resembling what I envision as the final product. The HipsterSync class exposes a setter (sync=) allowing application code to inject behavior, and a method for internal libraries to invoke the appropriate behavior. I start with:
#library('Sync layer for HipsterMVC');

class HipsterSync {
  // private class variable to hold an application injected sync behavior
  static var _injected_sync;

  // setter for the injected sync behavior
  static set sync(fn) {
    _injected_sync = fn;
  }

  // static method for HipsterModel and HipsterCollection to invoke -- will
  // forward the call to the appropriate behavior (injected or default)
  static call(method, model, [options]) {
    if (_injected_sync == null) {
      return _default_sync(method, model, options:options);
    }
    else {
      return _injected_sync(method, model, options:options);
    }
  }

  // default sync behavior
  static _default_sync(method, model, [options]) {
    print("[_default_sync]");
  }
}
In my HipsterCollection base class, I then remove the local storage that I had been doing, replacing it with HipsterSync.call():
class HipsterCollection implements Collection {
  // ...
  fetch() {
    HipsterSync.call('get', this);
  }
  // ...
}
When I load my app, I see the print() tracer bullets from the current _default_sync() implementation:


Then, in my main.dart entry point, I inject a different sync behavior:
main() {
  HipsterSync.sync = (method, model, [options]) {
    print("[new_sync] $method");
  };
  // ...
}
When I load my app now, I see the injected behavior:


Nice. My proof of concept is closer to being reality. Next, I need to do something real with the default implementation. I remove the injected behavior. Then, back in HipsterSync, I add the Ajax that used to be in HipsterCollection#fetch():
#import('dart:html');

class HipsterSync {
  // ...
  // default sync behavior
  static _default_sync(method, model, [options]) {
    print("[_default_sync]");

    var req = new XMLHttpRequest();

    req.on.load.add(_handleOnLoad);
    req.open('get', model.url, true);
    req.send();
  }
}
Ah. That _handleOnLoad callback method is defined back in the collection class. I need a mechanism to pass that into HipsterSync. Those options look like a good place to start. In HipsterCollection, pass the on-load callback via an 'onLoad' key:
  fetch() {
    HipsterSync.call('get', this, options: {
      'onLoad': _handleOnLoad
    });
  }
Then in HipsterSync, I add the value (_handleOnLoad) to the list of on.load event handlers for the Ajax request:
  // default sync behavior
  static _default_sync(method, model, [options]) {
    print("[_default_sync]");

    if (options == null) options = {};

    var req = new XMLHttpRequest();

    if (options.containsKey('onLoad')) {
      req.on.load.add(options['onLoad']);
    }
    req.open('get', model.url, true);
    req.send();
  }
Trying this out, it works and I have my application again loading my comic book collection over Ajax:


Now for the moment of truth—I define a local storage sync behavior in main.dart:
main() {
  HipsterSync.sync = (method, model, [options]) {
    print("[local_sync] $method");

    if (method == 'get') {
      var json =  window.localStorage.getItem(model.url),
          data = (json == null) ? {} : JSON.parse(json);

      if (options is Map && options.containsKey('onLoad')) {
        options['onLoad'](data.getValues());
      }
    }
  };
  // ...
}
In here, I have adapted my local storage solution. In order for both local storage and Ajax to submit data to the on-load callback, I had to alter the callback to expect data rather than an event from which the data could be extracted. Once that is done, the injected behavior of local storage is working:


Cool! I can switch back and forth between local storage and the default Ajax implementation by adding and removing the HipsterSync.sync local behavior. I still need to get all of this working with my model class, but hopefully that will prove relatively easy now that I have my collection working properly.


Day #297

No comments:

Post a Comment