Sunday, February 12, 2012

Getting Started with Dart Local Storage

‹prev | My Chain | next›

I am in a happy place with my Dart-based "Hipster" MVC framework. To be sure, there are a few things that I miss from Javascript, but I can do without. At least for now. So today, I would like to explore saving and loading records from a client-side data store rather than over Ajax.

As can be seen from the Dart Window reference, Dart supports both sessionStorage and localStorage from the DOM Storage specification. The persistence of localStorage makes it a little more fun so that is where I will start.

I am not going to try to support Backbone.js's sync just yet. For now I will content myself with replacing the Ajax calls directly in code (that's why God created source code control).

First up, I add an instance variable to hold the data in the local store and I populate it with data from the localStorage of my app:
class HipsterCollection implements Collection<HipsterModel> {
  // ...
  List<HipsterModel> models;
  Map<String,Map> data;
  // ...
  fetch() {
    var json =  window.localStorage.getItem(url);
    data = (json == null) ? {} : JSON.parse(json);
    _handleStoreInit();
  }
}
I am using the url attribute as the name of my local stores "table" to minimize the number of initial changes from the calling context and concrete sub-classes. In the case of my comic book collection application, the url is "/comics":
class Comics extends HipsterCollection {
  get url() => '/comics';
  modelMaker(attrs) => new ComicBook(attrs);
}
The first time that I fetch() the local store, I expect that it will be null and thus set the local data with an empty Map.

In the future, I expect to parse JSON from the local store. So, next up is a post-data lookup method. For the Ajax version, I had parsed the JSON and created model objects from the data:
  _handleOnLoad(event) {
    var request = event.target
      , list = JSON.parse(request.responseText);

    list.forEach((attrs) {
      models.add(modelMaker(attrs));
    });

    on.load.dispatch(new CollectionEvent('load', this));
  }
In response to the local data store being ready, I need to do the same:
  _handleStoreInit() {
    data.forEach((k,attrs) {
      models.add(modelMaker(attrs));
    });

    on.load.dispatch(new CollectionEvent('load', this));
  }
After reloading the application in Dartium, I find that nothing is broken, but that could very well be thanks to the empty data initializer. The next step is to swap out the save() method on the model class. The Ajax version had been:
  save([callback]) {
    var req = new XMLHttpRequest()
      , json = JSON.stringify(attributes);

    req.on.load.add((event) {
      attributes = JSON.parse(req.responseText);
      on.save.dispatch(event);
      if (callback != null) callback(event);
    });

    req.open('post', '/comics', true);
    req.setRequestHeader('Content-type', 'application/json');
    req.send(json);
  }
I need to retain the actual store (req.send(json)) and must remember to dispatch the save event as well as invoke any supplied callbacks. Additionally, I now have to assign an ID attribute to the records if they do not already have one assigned (I can no longer rely on the backend data store to assign IDs). What I end up with is:
  save([callback]) {
    var id, event;

    if (attributes['id'] == null) {
      attributes['id'] = hash();
    }

    id = attributes['id'];
    collection.data[id] = attributes;
    window.localStorage.setItem(collection.url, JSON.stringify(collection.data));

    event = new Event("Save");
    on.save.dispatch(event);
    if (callback != null) callback(event);
  }
The hash() method used to assign an ID is just the value of Date.now() (milliseconds since the epoch). I make use of the fact that collection is an attribute of model to gain access to the collection's data for updating. Similarly, I grab the url from the collection to retrieve the local "table" to be updated. Since local storage is synchronous, I can then perform event dispatching and callback handling immediately following the save.

And, amazingly, it actually works. I can create two dummy records and, checking local storage in the Javascript console, I see that the records are created:


Since I only have create and delete working in the Ajax version, all that remains for me is to implement the model delete, which looks very similar to the create:
  delete([callback]) {
    collection.data.remove(attributes['id']);
    window.localStorage.setItem(collection.url, JSON.stringify(collection.data));

    var event = new Event("Delete");
    on.delete.dispatch(event);
    if (callback != null) callback(event);
  }
Using the UI, I remove the test records from the data store, then write to local storage some real comic books:


Nice. That worked surprisingly well and was quite easy to implement. I think that is due more to following the Backbone way of things that to Dart. Still, it nice to see that Dart is at least not getting in the way. Up tomorrow, I might take a shot at implementing a Backbone.sync method. That should prove... different in Dart.


Day #294

No comments:

Post a Comment