diff --git a/lib/client.dart b/lib/client.dart index ca97d08..186a83e 100644 --- a/lib/client.dart +++ b/lib/client.dart @@ -71,6 +71,11 @@ abstract class Route { */ bool get dontLeaveOnParamChanges; + /** + * Used to set page title when the route [isActive]. + */ + String get pageTitle; + /** * Returns a stream of [RouteEnterEvent] events. The [RouteEnterEvent] event * is fired when route has already been made active, but before subroutes @@ -121,7 +126,7 @@ abstract class Route { void addRoute({String name, Pattern path, bool defaultRoute: false, RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter, RoutePreLeaveEventHandler preLeave, RouteLeaveEventHandler leave, - mount, dontLeaveOnParamChanges: false, + mount, dontLeaveOnParamChanges: false, String pageTitle, List watchQueryParameters}); /** @@ -166,6 +171,8 @@ class RouteImpl extends Route { final UrlMatcher path; @override final RouteImpl parent; + @override + final String pageTitle; /// Child routes map route names to `Route` instances final _routes = {}; @@ -199,7 +206,7 @@ class RouteImpl extends Route { Stream get onEnter => _onEnterController.stream; RouteImpl._new({this.name, this.path, this.parent, - this.dontLeaveOnParamChanges: false, + this.dontLeaveOnParamChanges: false, this.pageTitle, watchQueryParameters}) : _onEnterController = new StreamController.broadcast(sync: true), @@ -215,7 +222,7 @@ class RouteImpl extends Route { void addRoute({String name, Pattern path, bool defaultRoute: false, RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter, RoutePreLeaveEventHandler preLeave, RouteLeaveEventHandler leave, - mount, dontLeaveOnParamChanges: false, + mount, dontLeaveOnParamChanges: false, String pageTitle, List watchQueryParameters}) { if (name == null) { throw new ArgumentError('name is required for all routes'); @@ -230,7 +237,7 @@ class RouteImpl extends Route { var matcher = path is UrlMatcher ? path : new UrlTemplate(path.toString()); var route = new RouteImpl._new(name: name, path: matcher, parent: this, - dontLeaveOnParamChanges: dontLeaveOnParamChanges, + dontLeaveOnParamChanges: dontLeaveOnParamChanges, pageTitle: pageTitle, watchQueryParameters: watchQueryParameters); route..onPreEnter.listen(preEnter) @@ -286,12 +293,8 @@ class RouteImpl extends Route { return tail; } - String _getTailUrl(String routePath, Map parameters) { + String _getTailUrl(Route routeToGo, Map parameters) { var tail = ''; - var routeToGo = findRoute(routePath); - if (routeToGo == null) { - throw new StateError('Invalid route path: $routePath'); - } for (RouteImpl route = routeToGo; route != this; route = route.parent) { tail = route.path.reverse( parameters: _joinParams(parameters == null ? @@ -714,14 +717,15 @@ class Router { Future go(String routePath, Map parameters, {Route startingFrom, bool replace: false, Map queryParameters, bool forceReload: false}) { RouteImpl baseRoute = startingFrom == null ? root : _dehandle(startingFrom); - var newTail = baseRoute._getTailUrl(routePath, parameters) + + var routeToGo = _findRoute(baseRoute, routePath); + var newTail = baseRoute._getTailUrl(routeToGo, parameters) + _buildQuery(queryParameters); String newUrl = baseRoute._getHead(newTail); _logger.finest('go $newUrl'); return route(newTail, startingFrom: baseRoute, forceReload: forceReload) .then((success) { if (success) { - _go(newUrl, null, replace); + _go(newUrl, routeToGo.pageTitle, replace); } return success; }); @@ -732,11 +736,22 @@ class Router { Map queryParameters}) { var baseRoute = startingFrom == null ? root : _dehandle(startingFrom); parameters = parameters == null ? {} : parameters; - var tail = baseRoute._getTailUrl(routePath, parameters); + var routeToGo = _findRoute(baseRoute, routePath); + var tail = baseRoute._getTailUrl(routeToGo, parameters); return (_useFragment ? '#' : '') + baseRoute._getHead(tail) + _buildQuery(queryParameters); } + /// Attempts to find [Route] for the specified [routePath] relative to the + /// [baseRoute]. If nothing is found throws a [StateError]. + Route _findRoute(Route baseRoute, String routePath) { + var route = baseRoute.findRoute(routePath); + if (route == null) { + throw new StateError('Invalid route path: $routePath'); + } + return route; + } + /// Build an query string from a parameter `Map` String _buildQuery(Map queryParams) { if (queryParams == null || queryParams.isEmpty) { @@ -856,9 +871,6 @@ class Router { } else { _window.location.assign('#$path'); } - if (title != null) { - (_window.document as HtmlDocument).title = title; - } } else { if (title == null) { title = (_window.document as HtmlDocument).title; @@ -869,6 +881,9 @@ class Router { _window.history.pushState(null, title, path); } } + if (title != null) { + (_window.document as HtmlDocument).title = title; + } } /** diff --git a/lib/route_handle.dart b/lib/route_handle.dart index e629fe6..2ae8831 100644 --- a/lib/route_handle.dart +++ b/lib/route_handle.dart @@ -67,8 +67,8 @@ class RouteHandle implements Route { @override void addRoute({String name, Pattern path, bool defaultRoute: false, RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter, - RoutePreLeaveEventHandler preLeave,RouteLeaveEventHandler leave, - mount, dontLeaveOnParamChanges: false, + RoutePreLeaveEventHandler preLeave, RouteLeaveEventHandler leave, + mount, dontLeaveOnParamChanges: false, String pageTitle, List watchQueryParameters}) { throw new UnsupportedError('addRoute is not supported in handle'); } @@ -134,6 +134,10 @@ class RouteHandle implements Route { @override bool get dontLeaveOnParamChanges => _route.dontLeaveOnParamChanges; + /// See [Route.title] + @override + String get pageTitle => _route.pageTitle; + @override Map get queryParameters => _route.queryParameters; } diff --git a/test/client_test.dart b/test/client_test.dart index 76f79d1..703fe42 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -1243,6 +1243,32 @@ main() { }); }); + test('should update page title if the title property is set', () { + var mockWindow = new MockWindow(); + var router = new Router(useFragment: false, windowImpl: mockWindow); + router.root.addRoute(name: 'foo', path: '/foo', pageTitle: 'Foo'); + + router.go('foo', {}).then(expectAsync((_) { + var mockHistory = mockWindow.history; + mockWindow.document.getLogs(callsTo('set title')).verify(happenedOnce); + expect(mockHistory.getLogs(callsTo('pushState', anything)).last.args, + [null, 'Foo', '/foo']); + })); + }); + + test('should not change page title if the title property is not set', () { + var mockWindow = new MockWindow(); + mockWindow.document.when(callsTo('get title')).alwaysReturn('page title'); + var router = new Router(useFragment: false, windowImpl: mockWindow); + router.root.addRoute(name: 'foo', path: '/foo'); + + router.go('foo', {}).then(expectAsync((_) { + var mockHistory = mockWindow.history; + expect(mockHistory.getLogs(callsTo('pushState', anything)).last.args, + [null, 'page title', '/foo']); + })); + }); + }); group('url', () {