Wednesday, March 7, 2012

MVC Routing with Dart

‹prev | My Chain | next›

One of the many things that I love about Backbone.js is the simplicity of the router. Specifying a list of route / callback pairs is brilliantly easy and still quite powerful. Since I have already shamelessly ripped off most of Backbone.js in my Dart-based Hipster MVC, I might as well go all the way and rip off the router as well...

In fact, I already have a pushState based history mechanism, so the router should be straight-forward. "Should" is always a fun word.

With my history implementation, I can manually specify routes such as:
HipsterHistory.route(new RegExp(@'^page/[-\w]+$'), pageNum);
In Backbone, this might be something like:
var MyRouter = Backbone.Router.extend({
  routes: {
    "page/:num": "pageNum"
  },

  pageNum: function(num) { /* ... */ }
});

new MyRouter();
I don't know that I can get the placeholder stuff working tonight, but I would like to get a minimalist router going. So first up, I replace the manual HipsterHistory.route() call with a new router object:
main() {
  new MyRouter();
  HipsterHistory.startHistory();
}
As for MyRouter, I pull in the existing pageNum() function as a method:
class MyRouter {
  List routes;

  pageNum(num) {
    var el = document.query('body');
    el.innerHTML = _pageNumTemplate(num.replaceFirst('page/', ''));
  }
}
I start by defining my routes in the constructor as:
class MyRouter {
  List routes;

  MyRouter() {
    routes = [
      ['page', this.pageNum]
    ];

    _initializeRoutes();
  }

  pageNum(num) { /* ... */ }
}
It is then the responsibility of the _initializeRoutes() method to assign these to HipsterHistory.route() appropriately:
class MyRouter {
  List routes;

  MyRouter() {
    routes = [
      ['page', this.pageNum]
    ];

    _initializeRoutes();
  }

  _initializeRoutes() {
    routes.forEach((route) {
      HipsterHistory.route(new RegExp(route[0]), route[1]);
    });
  }

  pageNum(num) { /* ... */ }
}
And that still works! Er... I mean of course that works. I had no doubt.

With that working, I decide to push my luck. Instead of matching the page route and forcing pageNum() to extract parameters:
  pageNum(num) {
    var el = document.query('body');
    el.innerHTML = _pageNumTemplate(num.replaceFirst('page/', ''));
  }
I would rather specify the route with a place holder so that I can supply pageNum(num) with an actual num and not the entire URL fragment:
class MyRouter {
  List routes;

  MyRouter() {
    routes = [
      ['page/:num', this.pageNum]
    ];

    _initializeRoutes();
  }
  // ...
}
The first thing that I will need to do is convert the placeholder to a regular expression that extracts the values that would be there. Something like ([^\/]+) (capture one or more characters that are not slashses) ought to do:
// ....
  _initializeRoutes() {
    routes.forEach((route) {
      HipsterHistory.route(_routeToRegExp(route[0]), route[1]);
    });
  }

  _routeToRegExp(matcher) {
    var regex = matcher.replaceAll(new RegExp(@':[^\/]+'), '([^\/]+)');
    return new RegExp(regex);
  }
// ....
Only that throws an error:
Exception: Unimplemented String.replaceAll with RegExp
Stack Trace:  0. Function: 'StringBase.replaceAll' url: 'bootstrap_impl' line:2039 col:7
 1. Function: 'MyRouter._routeToRegExp@676514c' url: 'file:///home/cstrom/repos/dart-book/book/includes/push_state/main.dart' line:26 col:35
 2. Function: 'MyRouter.function' url: 'file:///home/cstrom/repos/dart-book/book/includes/push_state/main.dart' line:21 col:42
 3. Function: 'GrowableObjectArray.forEach' url: 'bootstrap_impl' line:1256 col:8
 4. Function: 'MyRouter._initializeRoutes@676514c' url: 'file:///home/cstrom/repos/dart-book/book/includes/push_state/main.dart' line:20 col:19
 5. Function: 'MyRouter.MyRouter.' url: 'file:///home/cstrom/repos/dart-book/book/includes/push_state/main.dart' line:16 col:22
 6. Function: '::main' url: 'file:///home/cstrom/repos/dart-book/book/includes/push_state/main.dart' line:4 col:3
Eke! Dart has not implemented replacing regular expressions in strings?! Say it ain't so!

In fact it looks as though it is so. I get the same error when I try replaceFirst(). Dang. That'll teach me to quit when I'm ahead.

With a working, if somewhat limited Router in place, I call it a night here. I will come up with a workaround for this tomorrow.


Day #318

Tuesday, March 6, 2012

MVC History in Dart

‹prev | My Chain | next›

At this point, I have pushState and popState in Dart working well enough that, if I clear the browser history and navigate to a page, the correct page is routed and rendered:


This works entirely because of a call to startHistory(), which adds a listener to the popState event:
startHistory() {
  window.
    on.
    popState.
    add((event) {
      var page_num = window.location.hash.replaceFirst('#', '');
      render(page_num);
    });
}
Even when the page first loads, the popState event fires, at which point my page handler kicks in to extract the page number parameter from the URL hash (i.e. "Fourty-Two" in "http://example.com/page#Fourty-Two"). I can then route to the appropriate resource in my application.

The problem here is my handling of history is hopelessly coupled with my application routing. Worse yet, the routing is hard-coded inside the handling of the browser history. If I wanted to add a greetings route (e.g. #howdy/Bob), then I would have to add it directly to the history handler.

No matter what, the history mechanism is going to need to know what to call on popState. Instead of hard-coding it though, it should be injected. But first, the startHistory() function is going to need to be a class that can encapsulate the list of routes:
class HipsterHistory {
  static startHistory() {
    window.on.popState.add(_checkUrl);
  }

  static _checkUrl(_) {
    var page_num = window.location.hash.replaceFirst('#', '');
    render(page_num);
  }
}
If I then change the start-up call to HipsterHistory.startHistory(), then everything continues to work.

Still, _checkUrl() is hard-coded to render the numbered page route. Since I will want to be able to route to multiple destinations, I will want a list of routes. I can then iterate over on each popState:
class HipsterHistory {
  static List routes;

  static startHistory() {
    routes = [[new RegExp(@'^[A-Z][-\w]+$'), render]];

    window.on.popState.add(_checkUrl);
  }

  static _checkUrl(_) {
    var fragment = window.location.hash.replaceFirst('#', '');

    var matching_handlers = routes.
      filter((r) => r[0].hasMatch(fragment));

    if (matching_handlers.isEmpty()) return;

    var handler = matching_handlers[0];
    handler[1](fragment);
  }
}
Here, I am using a regular expression (@'^[A-Z][-\w]+$') to only route when the URL matches something like "One", "Three", or "Forty-Two". The _checkUrl() no longer hard codes the route, but iterates over the know routes. If it finds a matching route, the handler is invoked.

Everything still works at this point, but I still need to inject that route into the list of handlers known to the History mechanism. A class method route() to add an individual route and callback pair should do:
class HipsterHistory {
  static List _routes;

  static get routes() {
    if (_routes == null) _routes = [];
    return _routes;
  }

  static route(route, fn) {
    routes.add([route, fn]);
  }

  static startHistory() {
    window.on.popState.add(_checkUrl);
  }

  static _checkUrl(_) {
    var fragment = window.location.hash.replaceFirst('#', '');

    var matching_handlers = routes.
      filter((r) => r[0].hasMatch(fragment));

    if (matching_handlers.isEmpty()) return;

    var handler = matching_handlers[0];
    handler[1](fragment);
  }
}
With that, my start code becomes:
main() {
  HipsterHistory.route(new RegExp(@'^[A-Z][-\w]+$'), render);
  HipsterHistory.startHistory();
}
And everything still works. I could do with an easier API to add multiple routes, but this is a pretty decent start on a history mechanism for my MVC library.

Day #317

Monday, March 5, 2012

Simple PushState in Dart

‹prev | My Chain | next›

Tonight, I try to figure out push state in Dart. Any client-side MVC framework worth its salt needs push state routing and, dammit, Hipster MVC is worth salt.

So I start with an empty page that loads in a Dart script:
<html>
<head>
  <title>Hipster Push State</title>
  <script type="application/dart"
     src="main.dart"></script>

  <script type="text/javascript">
    // start Dart
    navigator.webkitStartDart();
  </script>
</head>

<body>
<h1>Push that state!</h1>

</body>
</html>
In that main.dart entry point, I define render() and template() functions:
render(num) {
  var el = document.query('body');
  el.innerHTML = template(num);
}

template(num) {
  return """
<h1>$num</h1>
<p>Now we're on page <b>$num</b>.</p>""";
}
The render() function draws the result of the template in the page so that render('One'); results in:


Next, I define a method that will navigate to a particular page after a given number of seconds:
navAfter(page_number, delay) {
  window.setTimeout(() {
    render(page_number);
  }, delay * 1000);
}
If I then define main() to be:
main() {
  navAfter('One', 1);
  navAfter('Two', 2);
  navAfter('Three', 3);
}
Then, I see page "One", page "Two" and finally page "Three". But the URL remains the same throughout and, if I click back, then I leave this sample app entirely.

So, instead of rendering the page when I nav-after, I route to the correct page:
navAfter(page_number, delay) {
  window.setTimeout(() {
    route(page_number);
  }, delay * 1000);
}
This route function then renders the appropriate HTML snippet, but it is also responsible for pushing the current state into History:
route(num) {
  window.history.pushState(null, num, window.location.pathname + "#$num");
  render(num);
}
With that, I have three different pages in history, but, when I click back to page "Two", I still see page three rendered:


To fix that, I need to start a history listener at the end of my main() entry point:
main() {
  navAfter('One', 1);
  navAfter('Two', 2);
  navAfter('Three', 3);

  startHistory();
}
That history listener is responsible for listening to the window's popState events:
startHistory() {
  window.
    on.
    popState.
    add((event) {
      var page_num = window.location.hash.replaceFirst('#', '');
      print(page_num);
      render(page_num);
    });
}
With that, I have the proper page displaying for the current route:


This is not quite ready to be popped into Hipster MVC, but it was a nice way to ease into it.


Day #316

Sunday, March 4, 2012

Complete is not Broken

‹prev | My Chain | next›

I ran into a weird behavior in exceptional Dart Futures yesterday. In the simplest case, the following works:
main() {
  var completer = new Completer();
  var future = completer.future;

  future.handleException((e) {
    print("Handled: $e");
    return true;
  });

  completer.completeException("That ain't gonna work");
}
(try.dartlang.org)

In this case, the completer completes with an exception, not with the normal successful complete(). But, since the future associated with the completer injects a handleException() method, the exception is handled. More specifically since the injected handleException() returns true, the exception is handled.

If I remove the true return, I see both the "Handled" print() statement as well as the exception bubbling all the way up:
Handled: That ain't gonna work
Exception: That ain't gonna work
Stack Trace:  0. Function: 'FutureImpl._complete@924b4b8' url: 'bootstrap_impl' line:3124 col:9
 1. Function: 'FutureImpl._setException@924b4b8' url: 'bootstrap_impl' line:3146 col:14
 2. Function: 'CompleterImpl.completeException' url: 'bootstrap_impl' line:3207 col:30
 3. Function: '::main' url: 'file:///home/cstrom/repos/dart-book/book/includes/no_more_callback_hell/main.dart' line:12 col:30
(try.dartlang does not seem to care about the return value—it always considers handleException() to have handled the exception)

My problem is that, although this behaves as expected in a simple case, it does not seem to work as expected in a more complex case. In my Hipster MVC library, my syncing layer that is responsible for coordinating in-browser data with permanent storage can have different behavior injected. So the HipsterSync.call() class method first needs a conditional to return a Future from the appropriate behavior (_defultSync in this case):
class HipsterSync {
  // ...
  static Future<Dynamic> call(method, model) {
    if (_injected_sync == null) {
      return _defaultSync(method, model);
    }
    else { /* ... */ }
  }
  // ...
}
The default syncing behavior then creates the completer and invokes completeException() when a non-200 response is received from the backend:
class HipsterSync {
  // ...
  static Future<Dynamic> _defaultSync(method, model) {
    var request = new XMLHttpRequest(),
        completer = new Completer();

    request.
      on.
      load.
      add((event) {
        var req = event.target;

        if (req.status > 299) {
          completer.
            completeException("That ain't gonna work: ${req.status}");
        }
        else { /* ... */ }
      });
    // ...

    return completer.future;
  }
}
Finally, the HipsterModel class is invoking HipsterSync.call() and trying to do the right thing with handleException():
class HipsterCollection implements Collection {
  // ...
  create(attrs) {
    Future after_save = _buildModel(attrs).save();

    after_save.
      then((saved_model) {
        this.add(saved_model);
      });

    after_save.
      handleException(bool (e) {
        print("Exception handled: ${e.type}");
        return true;
      });
  }
  // ...
}
When I generate a 409, however, that handleException() does not kick in. Instead, I see the exception bubbling all the way up to the Dart console:
POST http://localhost:3000/comics 409 (Conflict)
Exception: That ain't gonna work: 409
Stack Trace:  0. Function: 'FutureImpl._complete@924b4b8' url: 'bootstrap_impl' line:3124 col:9
 1. Function: 'FutureImpl._setException@924b4b8' url: 'bootstrap_impl' line:3146 col:14
 2. Function: 'CompleterImpl.completeException' url: 'bootstrap_impl' line:3207 col:30
 3. Function: 'HipsterSync.function' url: 'http://localhost:3000/scripts/HipsterSync.dart' line:44 col:30
 4. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23163 col:35
Seemingly, the problem lies somewhere in between the simplest use-case and my complex Hipster MVC. So I start expanding from this simple case to more and more complex cases. After a time, I have worked through no less than six test cases on increasing complexity—all of them working. In my last test case, I have a static method from one class produce a completer exception that another class handles. Both are in separate libraries.

They say you can't prove a negative, but I seem to have just done so.

So I go back to my code and, indeed, the bug was mine. I eventually realize that the Future in HipsterCollection is not coming from HipsterSync as I thought. Rather it comes from HipsterModel:
class HipsterCollection implements Collection {
  // ...
  create(attrs) {
    Future after_save = _buildModel(attrs).save();
    // ...
  }
}
In hindsight this seems perfectly obvious.

When I first refactored into Completers and Futures, I did take into account that HipsterModel required changes as well:
class HipsterModel {
  // ...
  Future<HipsterModel> save() {
    Completer completer = new Completer();

    HipsterSync.
      call('post', this).
      then((attrs) {
        this.attributes = attrs;
        on.load.dispatch(new ModelEvent('save', this));
        completer.complete(this);
      });

    return completer.future;
  }
}
What is missing from that implementation is concern about an exception from HipsterSync.call(). The only thing that I do is wait patiently for a then() that never comes. So, just as I do in the HipsterCollection, I grab the Future returned by HipsterSync.call() and inject then() and handleException() behavior:
class HipsterModel {
  // ...
  Future<HipsterModel> save() {
    Completer completer = new Completer();

    Future after_call = HipsterSync.call('post', this);

    after_call.
      then((attrs) {
        this.attributes = attrs;
        on.load.dispatch(new ModelEvent('save', this));
        completer.complete(this);
      });

    after_call.
      handleException((e) {
        completer.completeException(e);
        return true;
      });

    return completer.future;
  }
}
It feels a bit much having to completeException() / handleException() all the way from HipsterSync, through HipsterModel and on up to HipsterCollection. Then again, the model has need to perform error handling before notifying the collection that something went wrong, so I see no real way around this.

At any rate, the Future exception is now captured in the model, re-raisd to the collection in such a way that the collection can handle it:


It is difficult when working with new languages to judge whether a mistake like this lies with the language (e.g. too much complexity) or myself. In this case, my understanding of the technique was solid, but I muffed the implementation—was that Dart's fault or my own? I think in this case, it was my own. I think the architecture that I have chosen necessitates this type of propagation on occasion and I need to be cognizant of this. Re-examining my ultimate solution, I do not see anywhere that Dart could have eased my burden. Then again, perhaps I simply lack imagination.


Day #315

Saturday, March 3, 2012

Errors from the Future (of Dart)

‹prev | My Chain | next›

Last night I cleaned up a bit of callback hell in my Dart-based Hipster MVC library. Today, I am going to strike out off of the happy path. I had not begun to deal with errors in my callback version of the MVC library. Let's see how easy it is to handle the unexpected with Futures and Completers.

So I modify the express.js backend to always throw an error when POSTing:
app.post('/comics', function(req, res) {
  res.send(409);
}
Then I modify the frontend code to handle errors:
class HipsterSync {
  // ...
  static Future<Dynamic> _defaultSync(method, model) {
    var request = new XMLHttpRequest(),
        completer = new Completer();

    request.
      on.
      error.
      add((event) {
        print("Something bad happened");
      });

    request.
      on.
      load.
      add((event) {
        var req = event.target,
            json = JSON.parse(req.responseText);
        completer.complete(json);
      });

    request.open(method, model.url, true);

    // POST and PUT HTTP request bodies if necessary
    if (method == 'post' || method == 'put') {
      request.setRequestHeader('Content-type', 'application/json');
      request.send(JSON.stringify(model.attributes));
    }
    else {
      request.send();
    }

    return completer.future;
  }
}
I may need to break that up -- possibly extracting the Ajax callbacks into their own methods. For now, I will focus on getting it to work. When I try to POST from my comic book application, however, I get:


So Dart seemingly does not see this as an error. The stacktrace is because my normal "load" listener is handling this case. Bah!

I remove my "error" listener and add the error handling directly in the normal "load" handler:
  static Future<Dynamic> _defaultSync(method, model) {
    // ...
    request.
      on.
      load.
      add((event) {
        var req = event.target;

        if (req.status > 299) _handleError(req, completer);

        var json = JSON.parse(req.responseText);
        completer.complete(json);
      });
    // ...
  }

  static void _handleError(request, completer) {
    completer.completeException(new Exception("That ain't gonna work: ${request.status}"));
  }
That does the trick, as I now see my "That ain't gonna work error" at the top of the stacktrace in the Dart console:


To get rid of this, I ought to be able to define a handleException method on the Future returned from save(). So I change this:
  create(attrs) {
    _buildModel(attrs).
      save().
      then((saved_model) {
        this.add(saved_model);
      });
  }
To This:
  create(attrs) {
    Future after_save = _buildModel(attrs).save();

    after_save.
      then((saved_model) {
        this.add(saved_model);
      });

    after_save.
      handleException((e) {
        print("Exception handled: ${e.type}");
        return true;
      });
  }
The return true statement at the end of my handleException function is supposed to mark this exception as handled. But reality is not matching the spec in this case. Instead, I continue to see the exception bubbling all the way up to the user.


Ugh. Straying from the happy path in Dart is not working well for me tonight. So, in the end, I have to settle for catching the HipsterSync exception directly. That is not a huge loss, but the exception types could become a bit tricky (depending on if the sync is for a collection or a model). And really, that handleException() method should work properly. Well, I'm off to the Dart bug tracking system....

Update: this seems to work in simpler cases. That also works in Dartium. So somewhere between my complex multi-library completer/future and this simple case, things break down. I will keep investigating tomorrow.


Day #314

Friday, March 2, 2012

From the Future: Hipster MVC

‹prev | My Chain | next›

Yesterday, I failed miserably to get Dart to work with IndexedDB in Dartium. It was a pretty disappointing outing all around, but not a complete waste. I eventually came across a nice example of Dart IndexedDB written by Seth Ladd. It only works when compiled into Javascript, so it was not directly useful to me. But it did get me to thinking about my callback ways.

Coming from a very functional Javascript / node.js background, I am perfectly comfortable slinging callbacks about. In my Hipster MVC library, for example, I supply a callback when I save individual models:
  create(attrs) {
    var new_model = modelMaker(attrs);
    new_model.collection = this;
    new_model.save(callback:(event) {
      this.add(new_model);
    });
  }
When I read through Seth's IndexedDB code, it struck me how much he relies on Futures. This, naturally enough, made me feel all inadequate in my Dart coding. Well today, I shall be inadequate no more! Well... at least in this area.

Looking into HipsterModel, I see that my save is awash in callback... stuff:
class HipsterModel {
  // ...
  save([callback]) {
    HipsterSync.call('post', this, options: {
      'onLoad': (attrs) {
        attributes = attrs;

        var event = new ModelEvent('save', this);
        on.load.dispatch(event);
        if (callback != null) callback(event);
      }
    });
  }
  // ...
}
Instead of this approach, I declare that save() returns a Future. In the method body, I need a Completer, whose future I can return:

  Future<HipsterModel> save() {
    Completer completer = new Completer();

    HipsterSync.call('post', this, options: {
      'onLoad': (attrs) {
        attributes = attrs;

        var event = new ModelEvent('save', this);
        on.load.dispatch(event);

        completer.complete(this);
      }
    });

    return completer.future;
  }
The future is not invoked until the Completer completes, which happens inside the onLoad callback (say, I can probably get rid of that too). Back in the Collection class, I now need a then() method for the returned Future:
class HipsterModel {
  // ...
  create(attrs) {
    var new_model = modelMaker(attrs);
    new_model.collection = this;
    new_model.
      save().
      then((saved_model) {
        this.add(new_model);
      });
  }
  // ...
}
Ooh! I like that. It reads a heck of a lot better than my callback parameter. Here, I can read that I save my new model, then add it to the current collection.

A quick sanity check verifies everything still works:


Back in HipsterModel#save, I am still invoking HipsterSync.call() with a callback:
class HipsterModel {
  // ...
  Future<HipsterModel> save() {
    Completer completer = new Completer();

    HipsterSync.call('post', this, options: {
      'onLoad': (attrs) {
        attributes = attrs;

        var event = new ModelEvent('save', this);
        on.load.dispatch(event);

        completer.complete(this);
      }
    });

    return completer.future;
  }
  // ...
}
If I replace the callback in HipsterSync with a Future, the callback becomes:
class HipsterModel {
  // ...
  Future<HipsterModel> save() {
    Completer completer = new Completer();

    HipsterSync.
      call('post', this).
      then((attrs) {
        this.attributes = attrs;
        on.load.dispatch(new ModelEvent('save', this));
        completer.complete(this);
      });

    return completer.future;
  }
  // ...
}
That is so much nicer to read. I am calling HipsterSync, telling it to POST the model. Then, I take the attributes returned from the sync and assign them to the model's attributes, dispatch any events, and, finally, complete the save() completer from earlier.

That is much cleaner than the callback approach. I may have to start using custom-built Futures in Javascript. One thing missing from this is exceptions—I have a lot of happy path going on. Fortunately, there are exception facilities built into Futures. I will take a look at those tomorrow.


Day #313

Thursday, March 1, 2012

Dart IndexedDB: FIXME

‹prev | My Chain | next›

Thanks to my work on the varying the data sync method in Hipster MVC, I have a pretty good handle on how to do localStorage() in Dart. I do not have much experience with the other HTML5-ish client store, IndexedDB, so today I set out to learn it and how to use it in Dart. What could go wrong?

So I begin with the simplest use-case I can find in the MDN documentation—opening a request:
#import('dart:html');

main() {
  var request = window.webkitIndexedDB.open('asdf');
  print(request);
}
Loading this up in Dartium, I find:
Exception: NoSuchMethodException : method not found: 'get:webkitIndexedDB'
Receiver: Instance of 'WindowWrappingImplementation'
Arguments: []
Stack Trace:  0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:669 col:3
 1. Function: '::main' url: 'file:///home/cstrom/repos/dart-book/book/includes/client_storage/indexeddb/main.dart' line:4 col:39
Greeeeeat.

If it is not working in dart:html, perhaps the lower-level dart:dom has a working implementation:
#import('dart:dom');

main() {
  var request = window.webkitIndexedDB.open('asdf');
  print(request);
}
Checking the console, I now see:
Instance of 'IDBRequestImplementation'
So that's progress.

The next thing to do with IndexedDB is add event listeners. Since this is dart:dom instead of dart:html, the API omits the now familiar on getter. Instead, I invoke addEventListener directly on the request object:
  var request = window.webkitIndexedDB.open('asdf');

  request.
    addEventListener('error', (event) {
      print("Whoa! Something bad happened: ${event}");
    });

  request.
    addEventListener('success', (event) {
      print("Niiice! Something good happened: ${event}");
    });
Loading this in Dartium, I see:
Niiice! Something good happened: Instance of 'EventImplementation'
Hrm... I am not quite sure why I am getting a success event, but I suppose it beats the alternative.

Ah, it seems that success means that I have access to do IndexedDB stuff. So I'm ready to try grabbing a DB object out of the event target (which is the request):
  request.
    addEventListener('success', (event) {
      print("Niiice! Something good happened: ${event.target}");

      db = event.target.result;
      print("the db is: ${db}");
    });
Only this fails when I try to access the result property:
Exception: NotImplementedException
Stack Trace:  0. Function: 'IDBRequestImplementation.get:result' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/out/Release/obj/gen/webkit/bindings/dart/generated/dart/IDBRequestImplementation.dart' line:18 col:3
 1. Function: '::function' url: 'file:///home/cstrom/repos/dart-book/book/includes/client_storage/indexeddb/main.dart' line:17 col:31
That is a not-implemented exception, not a no-such-method exception. In other words, it is supposed to be implemented, but seemingly is not.

I give this a try in type checked mode and find:
Exception: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/out/Release/obj/gen/webkit/bindings/dart/generated/dart/EventImplementation.dart': Failed type check: line 36 pos 3: type 'IDBRequestImplementation' is not assignable to type 'EventTarget' of 'function result'.
Stack Trace:  0. Function: 'EventImplementation.get:target' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/out/Release/obj/gen/webkit/bindings/dart/generated/dart/EventImplementation.dart' line:36 col:3
 1. Function: '::function' url: 'file:///home/cstrom/repos/dart-book/book/includes/client_storage/indexeddb/main.dart' line:9 col:61
Hrm... that failed type check starts in my code, but actually generated in Dart itself. At first glance, it seems that IDBRequestImplementation might not implement EventTarget. Regardless, that is not much help in explaining why this is not working for my in non-checked mode.

I look through the generated source code, but it looks to be a lot of native code. Ugh. Seemingly at a dead end, I call it a night here. I will recompile the latest version of Dartium to see if that helps. But at this point, it seems that the client storage chapter in Dart for Hipsters is destined to be quite short.

Update: I eventually found Seth Ladd's post on Dart IndexDB. Unfortunately, I ended up with the same results.


Day #312