123 lines
3.3 KiB
Dart
123 lines
3.3 KiB
Dart
import 'dart:async' show Stream, StreamController;
|
|
import 'dart:convert' show JSON;
|
|
import 'dart:html' show AnchorElement, window;
|
|
import 'angel_route.dart';
|
|
|
|
final RegExp _hash = new RegExp(r'^#/');
|
|
|
|
abstract class BrowserRouter extends Router {
|
|
Stream<Route> get onRoute;
|
|
|
|
factory BrowserRouter({bool hash: false, bool listen: true, Route root}) {
|
|
return hash
|
|
? new _HashRouter(listen: listen, root: root)
|
|
: new _PushStateRouter(listen: listen, root: root);
|
|
}
|
|
|
|
BrowserRouter._([Route root]) : super(root);
|
|
|
|
void go(String path, [Map params]);
|
|
void goTo(Route route, [Map params]);
|
|
void listen();
|
|
}
|
|
|
|
class _BrowserRouterImpl extends Router implements BrowserRouter {
|
|
Route _current;
|
|
StreamController<Route> _onRoute = new StreamController<Route>();
|
|
Route get currentRoute => _current;
|
|
|
|
@override
|
|
Stream<Route> get onRoute => _onRoute.stream;
|
|
|
|
_BrowserRouterImpl({bool listen, Route root}) : super(root) {
|
|
if (listen) this.listen();
|
|
prepareAnchors();
|
|
}
|
|
|
|
@override
|
|
void go(String path, [Map params]) {
|
|
final resolved = resolve(path);
|
|
|
|
if (resolved != null)
|
|
goTo(resolved, params);
|
|
else
|
|
throw new RoutingException.noSuchRoute(path);
|
|
}
|
|
|
|
void prepareAnchors() {
|
|
final anchors = window.document.querySelectorAll('a:not([dynamic])');
|
|
|
|
for (final AnchorElement $a in anchors) {
|
|
if ($a.attributes.containsKey('href') &&
|
|
!$a.attributes.containsKey('download') &&
|
|
!$a.attributes.containsKey('target') &&
|
|
$a.attributes['rel'] != 'external') {
|
|
$a.onClick.listen((e) {
|
|
e.preventDefault();
|
|
go($a.attributes['href']);
|
|
});
|
|
}
|
|
|
|
$a.attributes['dynamic'] = 'true';
|
|
}
|
|
}
|
|
}
|
|
|
|
class _HashRouter extends _BrowserRouterImpl {
|
|
_HashRouter({bool listen, Route root}) : super(listen: listen, root: root) {
|
|
if (listen) this.listen();
|
|
}
|
|
|
|
@override
|
|
void goTo(Route route, [Map params]) {
|
|
route.state.properties.addAll(params ?? {});
|
|
window.location.hash = '#/${route.makeUri(params)}';
|
|
_onRoute.add(route);
|
|
}
|
|
|
|
@override
|
|
void listen() {
|
|
window.onHashChange.listen((_) {
|
|
final path = window.location.hash.replaceAll(_hash, '');
|
|
final resolved = resolve(path);
|
|
|
|
if (resolved == null || (path.isEmpty && resolved == root)) {
|
|
_onRoute.add(_current = null);
|
|
} else if (resolved != null && resolved != _current) {
|
|
goTo(resolved);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class _PushStateRouter extends _BrowserRouterImpl {
|
|
_PushStateRouter({bool listen, Route root})
|
|
: super(listen: listen, root: root) {
|
|
if (listen) this.listen();
|
|
}
|
|
|
|
@override
|
|
void goTo(Route route, [Map params]) {
|
|
window.history.pushState(
|
|
{'path': route.path, 'params': params ?? {}, 'properties': properties},
|
|
route.name ?? route.path,
|
|
route.makeUri(params));
|
|
_onRoute.add(_current = route..state.properties.addAll(params ?? {}));
|
|
}
|
|
|
|
@override
|
|
void listen() {
|
|
window.onPopState.listen((e) {
|
|
if (e.state is Map && e.state.containsKey('path')) {
|
|
final resolved = resolve(e.state['path']);
|
|
|
|
if (resolved != _current) {
|
|
properties.addAll(e.state['properties'] ?? {});
|
|
_onRoute.add(_current = resolved
|
|
..state.properties.addAll(e.state['params'] ?? {}));
|
|
}
|
|
} else
|
|
_onRoute.add(_current = null);
|
|
});
|
|
}
|
|
}
|