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:3Eke! 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
No comments:
Post a Comment