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

No comments:
Post a Comment