Thursday, March 8, 2012

Route Arguments in Dart

‹prev | My Chain | next›

Let me just get this out of the way. To convert routes such as "page/:num" into equivalent regular expressions in Dart, I finally give up and do:
  _routeToRegExp(matcher) {
    var bits = matcher.split('/').map((chunk) {
      if (chunk.startsWith(':')) return '([^\/]+)';
      return chunk;
    });

    var regex = '';
    for (var bit in bits) {
      regex += bit;
      regex += '/';
    }
    return new RegExp(regex.substring(0, regex.length-1));
  }
That's pretty ugly, but as I found to my dismay last night, it is not possible (yet) to replace regular expressions in Dart strings. There is also no reduce or fold for iterators. Oh, and no join for lists of strings. So that's what I am stuck with.

Now to actually get that into my destination function. My poor man's router currently forces the destination to manually extract the parameter from the URL fragment:
  pageNum(fragment) {
    var el = document.query('body'),
        num = fragment.replaceFirst('page/', '');

    el.innerHTML = _pageNumTemplate(num);
  }
It is easy enough to get the page number out of the fragment myself, but I should not have to—my library has already been told where the argument is ("page/:num").

Instead of supplying the callback directly to the history monitor:
  _initializeRoutes() {
    routes.forEach((route) {
      HipsterHistory.route(_routeToRegExp(route[0]), route[1]);
    });
  }
I can add an intermediate function whose job it is to extract the parameters from the URL fragment. Sadly, there does not seem to be a way to splat a List into function arguments, so I have to invoke different arity functions the hard way:
  _initializeRoutes() {
    routes.forEach((route) {
      HipsterHistory.route(_routeToRegExp(route[0]), (fragment) {
        List args = _routeToRegExp(route[0]).firstMatch(fragment);
        if (args.groupCount() == 0)
          route[1]();
        else if (args.groupCount() == 1)
          route[1](args[1]);
        else if (args.groupCount() == 2)
          route[1](args[1], args[2]);
        else if (args.groupCount() == 3)
          route[1](args[1], args[2], args[3]);
        else
          throw new WrongArgumentCountException();
      });
    });
  }
If anyone is trying to match more than three parameters, they are simply out of luck so I throw a WrongArgumentCountException. Not ideal, but hey, it works for most cases.

After extracting common functionality into a HispterRouter base class, I can then define the router for my simple pagination app as:
class MyRouter extends HipsterRouter {
  List get routes() =>
    [
      ['page/:num', pageNum]
    ];

  pageNum(num) {
    var el = document.query('body');
    el.innerHTML = _pageNumTemplate(num);
  }

  _pageNumTemplate(num) {
    return """
<h1>$num</h1>
<p>Now we're on page <b>$num</b>.</p>""";
  }

}
That is actually quite nice. All of the weird stuff is hidden in my library code, leaving me a nice, clean way to build a router. And, best of all, it still works:


This will do for a stopping point tonight. I am still not quite done with my router as it is not generating events. Given what I know of Dart custom events (they need to declared at compile time), I tend to think routing events like "route:pageNum" may be another less-than-ideal solution, but we shall see. Tomorrow.


Day #319

2 comments:

  1. Do you know if there is an item in the Dart issue tracker for invoking functions with an list of arguments?

    ReplyDelete
    Replies
    1. Not sure if there is a tracker, but it's on the horizon with apply(): http://www.dartlang.org/articles/emulating-functions/

      Delete