Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(254)

Side by Side Diff: third_party/pkg/route_hierarchical/lib/client.dart

Issue 176943008: Update the Angular/DI tests to latest from github. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library route.client; 5 library route.client;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 import 'dart:html'; 9 import 'dart:html';
10 10
11 import 'package:logging/logging.dart'; 11 import 'package:logging/logging.dart';
12 12
13 import 'url_matcher.dart'; 13 import 'url_matcher.dart';
14 export 'url_matcher.dart'; 14 export 'url_matcher.dart';
15 import 'url_template.dart'; 15 import 'url_template.dart';
16 16
17 17
18 final _logger = new Logger('route'); 18 final _logger = new Logger('route');
19 19
20 typedef RouteEventHandler(RouteEvent path); 20 typedef RoutePreEnterEventHandler(RoutePreEnterEvent path);
21 typedef RouteEnterEventHandler(RouteEnterEvent path);
22 typedef RouteLeaveEventHandler(RouteLeaveEvent path);
21 23
22 /** 24 /**
23 * A helper Router handle that scopes all route event subsriptions to it's 25 * A helper Router handle that scopes all route event subsriptions to it's
24 * instance and provides an convinience [discard] method. 26 * instance and provides an convinience [discard] method.
25 */ 27 */
26 class RouteHandle implements Route { 28 class RouteHandle implements Route {
27 Route _route; 29 Route _route;
28 final StreamController<RouteEvent> _onRouteController; 30 final StreamController<RoutePreEnterEvent> _onPreEnterController;
29 final StreamController<RouteEvent> _onLeaveController; 31 final StreamController<RouteEnterEvent> _onEnterController;
30 Stream<RouteEvent> get onRoute => _onRouteController.stream; 32 final StreamController<RouteLeaveEvent> _onLeaveController;
31 Stream<RouteEvent> get onLeave => _onLeaveController.stream; 33
32 StreamSubscription _onRouteSubscription; 34 @deprecated
35 Stream<RouteEnterEvent> get onRoute => onEnter;
36 Stream<RoutePreEnterEvent> get onPreEnter => _onPreEnterController.stream;
37 Stream<RouteEnterEvent> get onEnter => _onEnterController.stream;
38 Stream<RouteLeaveEvent> get onLeave => _onLeaveController.stream;
39
40 StreamSubscription _onPreEnterSubscription;
41 StreamSubscription _onEnterSubscription;
33 StreamSubscription _onLeaveSubscription; 42 StreamSubscription _onLeaveSubscription;
34 List<RouteHandle> _childHandles = <RouteHandle>[]; 43 List<RouteHandle> _childHandles = <RouteHandle>[];
35 44
36 RouteHandle._new(Route this._route) 45 RouteHandle._new(Route this._route)
37 : _onRouteController = 46 : _onEnterController =
38 new StreamController<RouteEvent>.broadcast(sync: true), 47 new StreamController<RouteEnterEvent>.broadcast(sync: true),
48 _onPreEnterController =
49 new StreamController<RoutePreEnterEvent>.broadcast(sync: true),
39 _onLeaveController = 50 _onLeaveController =
40 new StreamController<RouteEvent>.broadcast(sync: true) { 51 new StreamController<RouteLeaveEvent>.broadcast(sync: true) {
41 _onRouteSubscription = _route.onRoute.listen(_onRouteController.add); 52 _onEnterSubscription = _route.onEnter.listen(_onEnterController.add);
53 _onPreEnterSubscription =
54 _route.onPreEnter.listen(_onPreEnterController.add);
42 _onLeaveSubscription = _route.onLeave.listen(_onLeaveController.add); 55 _onLeaveSubscription = _route.onLeave.listen(_onLeaveController.add);
43 } 56 }
44 57
45 /// discards this handle. 58 /// discards this handle.
46 void discard() { 59 void discard() {
47 _logger.finest('discarding handle for $_route'); 60 _logger.finest('discarding handle for $_route');
48 _onRouteSubscription.cancel(); 61 _onPreEnterSubscription.cancel();
62 _onEnterSubscription.cancel();
49 _onLeaveSubscription.cancel(); 63 _onLeaveSubscription.cancel();
50 _onRouteController.close(); 64 _onEnterController.close();
51 _onLeaveController.close(); 65 _onLeaveController.close();
52 _childHandles.forEach((RouteHandle c) => c.discard()); 66 _childHandles.forEach((RouteHandle c) => c.discard());
53 _childHandles.clear(); 67 _childHandles.clear();
54 _route = null; 68 _route = null;
55 } 69 }
56 70
57 /// Not supported. Overridden to throw an error. 71 /// Not supported. Overridden to throw an error.
58 void addRoute({String name, Pattern path, bool defaultRoute: false, 72 void addRoute({String name, Pattern path, bool defaultRoute: false,
59 RouteEventHandler enter, RouteEventHandler leave, mount}) => 73 RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter,
74 RouteLeaveEventHandler leave, mount}) =>
60 throw new UnsupportedError('addRoute is not supported in handle'); 75 throw new UnsupportedError('addRoute is not supported in handle');
61 76
62 /// See [Route.getRoute] 77 /// See [Route.getRoute]
63 Route getRoute(String routePath) { 78 Route getRoute(String routePath) {
64 Route r = _assertState(() => _getHost(_route).getRoute(routePath)); 79 Route r = _assertState(() => _getHost(_route).getRoute(routePath));
65 if (r == null) return null; 80 if (r == null) return null;
66 var handle = r.newHandle(); 81 var handle = r.newHandle();
67 if (handle != null) { 82 if (handle != null) {
68 _childHandles.add(handle); 83 _childHandles.add(handle);
69 } 84 }
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
110 /// See [Route.path] 125 /// See [Route.path]
111 UrlMatcher get path => _route.path; 126 UrlMatcher get path => _route.path;
112 127
113 /// See [Route.name] 128 /// See [Route.name]
114 String get name => _route.name; 129 String get name => _route.name;
115 130
116 /// See [Route.parent] 131 /// See [Route.parent]
117 Route get parent => _route.parent; 132 Route get parent => _route.parent;
118 } 133 }
119 134
135 childRoute({String name, Pattern path, bool defaultRoute: false,
136 RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter,
137 RouteLeaveEventHandler leave, mount}) => (Route route) =>
138 route.addRoute(name: name, path: path, defaultRoute: defaultRoute,
139 enter: enter, preEnter: preEnter, leave: leave, mount: leave);
140
120 /** 141 /**
121 * Route is a node in the tree of routes. The edge leading to the route is 142 * Route is a node in the tree of routes. The edge leading to the route is
122 * defined by path. 143 * defined by path.
123 */ 144 */
124 class Route { 145 class Route {
125 final String name; 146 final String name;
126 final Map<String, Route> _routes = new LinkedHashMap<String, Route>(); 147 final Map<String, Route> _routes = new LinkedHashMap<String, Route>();
127 final UrlMatcher path; 148 final UrlMatcher path;
128 final StreamController<RouteEvent> _onRouteController; 149 final StreamController<RouteEnterEvent> _onEnterController;
129 final StreamController<RouteEvent> _onLeaveController; 150 final StreamController<RoutePreEnterEvent> _onPreEnterController;
151 final StreamController<RouteLeaveEvent> _onLeaveController;
130 final Route parent; 152 final Route parent;
131 Route _defaultRoute; 153 Route _defaultRoute;
132 Route _currentRoute; 154 Route _currentRoute;
133 RouteEvent _lastEvent; 155 RouteEvent _lastEvent;
134 156
135 Stream<RouteEvent> get onRoute => _onRouteController.stream; 157 @deprecated
158 Stream<RouteEvent> get onRoute => onEnter;
159
160 Stream<RouteEvent> get onPreEnter => _onPreEnterController.stream;
136 Stream<RouteEvent> get onLeave => _onLeaveController.stream; 161 Stream<RouteEvent> get onLeave => _onLeaveController.stream;
162 Stream<RouteEvent> get onEnter => _onEnterController.stream;
137 163
138 Route._new({this.name, this.path, this.parent}) 164 Route._new({this.name, this.path, this.parent})
139 : _onRouteController = 165 : _onEnterController =
140 new StreamController<RouteEvent>.broadcast(sync: true), 166 new StreamController<RouteEnterEvent>.broadcast(sync: true),
167 _onPreEnterController =
168 new StreamController<RoutePreEnterEvent>.broadcast(sync: true),
141 _onLeaveController = 169 _onLeaveController =
142 new StreamController<RouteEvent>.broadcast(sync: true); 170 new StreamController<RouteLeaveEvent>.broadcast(sync: true);
143 171
144 void addRoute({String name, Pattern path, bool defaultRoute: false, 172 void addRoute({String name, Pattern path, bool defaultRoute: false,
145 RouteEventHandler enter, RouteEventHandler leave, mount}) { 173 RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter,
174 RouteLeaveEventHandler leave, mount}) {
146 if (name == null) { 175 if (name == null) {
147 throw new ArgumentError('name is required for all routes'); 176 throw new ArgumentError('name is required for all routes');
148 } 177 }
149 if (_routes.containsKey(name)) { 178 if (_routes.containsKey(name)) {
150 throw new ArgumentError('Route $name already exists'); 179 throw new ArgumentError('Route $name already exists');
151 } 180 }
152 181
153 var matcher; 182 var matcher;
154 if (!(path is UrlMatcher)) { 183 if (!(path is UrlMatcher)) {
155 matcher = new UrlTemplate(path.toString()); 184 matcher = new UrlTemplate(path.toString());
156 } else { 185 } else {
157 matcher = path; 186 matcher = path;
158 } 187 }
159 var route = new Route._new(name: name, path: matcher, parent: this); 188 var route = new Route._new(name: name, path: matcher, parent: this);
160 189
190 if (preEnter != null) {
191 route.onPreEnter.listen(preEnter);
192 }
161 if (enter != null) { 193 if (enter != null) {
162 route.onRoute.listen(enter); 194 route.onEnter.listen(enter);
163 } 195 }
164 if (leave != null) { 196 if (leave != null) {
165 route.onLeave.listen(leave); 197 route.onLeave.listen(leave);
166 } 198 }
167 199
168 if (mount != null) { 200 if (mount != null) {
169 if (mount is Function) { 201 if (mount is Function) {
170 mount(route); 202 mount(route);
171 } else if (mount is Routable) { 203 } else if (mount is Routable) {
172 mount.configureRoute(route); 204 mount.configureRoute(route);
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
286 if (_lastEvent == null) return {}; 318 if (_lastEvent == null) return {};
287 return new Map.from(_lastEvent.parameters); 319 return new Map.from(_lastEvent.parameters);
288 } 320 }
289 return null; 321 return null;
290 } 322 }
291 } 323 }
292 324
293 /** 325 /**
294 * Route enter or leave event. 326 * Route enter or leave event.
295 */ 327 */
296 class RouteEvent { 328 abstract class RouteEvent {
297 final String path; 329 final String path;
298 final Map parameters; 330 final Map parameters;
299 final Route route; 331 final Route route;
332
333 RouteEvent(this.path, this.parameters, this.route);
334 }
335
336 class RoutePreEnterEvent extends RouteEvent {
337
338 var _allowEnterFutures = <Future<bool>>[];
339
340 RoutePreEnterEvent(path, parameters, route) : super(path, parameters, route);
341
342 /**
343 * Can be called on enter with the future which will complete with a boolean
344 * value allowing (true) or disallowing (false) the current navigation.
345 */
346 void allowEnter(Future<bool> allow) {
347 _allowEnterFutures.add(allow);
348 }
349 }
350
351 class RouteEnterEvent extends RouteEvent {
352
353 RouteEnterEvent(path, parameters, route) : super(path, parameters, route);
354 }
355
356 class RouteLeaveEvent extends RouteEvent {
357
300 var _allowLeaveFutures = <Future<bool>>[]; 358 var _allowLeaveFutures = <Future<bool>>[];
301 359
302 RouteEvent(this.path, this.parameters, this.route); 360 RouteLeaveEvent(path, parameters, route) : super(path, parameters, route);
303 361
304 /** 362 /**
305 * Can be called on leave with the future which will complete with a boolean 363 * Can be called on enter with the future which will complete with a boolean
306 * value allowing (true) or disallowing (false) the current navigation. 364 * value allowing (true) or disallowing (false) the current navigation.
307 */ 365 */
308 void allowLeave(Future<bool> allow) { 366 void allowLeave(Future<bool> allow) {
309 _allowLeaveFutures.add(allow); 367 _allowLeaveFutures.add(allow);
310 } 368 }
311 369
312 RouteEvent _clone() => new RouteEvent(path, parameters, route); 370 RouteLeaveEvent _clone() => new RouteLeaveEvent(path, parameters, route);
313 } 371 }
314 372
315 /** 373 /**
316 * Event emitted when routing starts. 374 * Event emitted when routing starts.
317 */ 375 */
318 class RouteStartEvent { 376 class RouteStartEvent {
319 377
320 /** 378 /**
321 * URI that was passed to [Router.route]. 379 * URI that was passed to [Router.route].
322 */ 380 */
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
372 430
373 /** 431 /**
374 * Finds a matching [Route] added with [addRoute], parses the path 432 * Finds a matching [Route] added with [addRoute], parses the path
375 * and invokes the associated callback. 433 * and invokes the associated callback.
376 * 434 *
377 * This method does not perform any navigation, [go] should be used for that. 435 * This method does not perform any navigation, [go] should be used for that.
378 * This method is used to invoke a handler after some other code navigates the 436 * This method is used to invoke a handler after some other code navigates the
379 * window, such as [listen]. 437 * window, such as [listen].
380 */ 438 */
381 Future<bool> route(String path, {Route startingFrom}) { 439 Future<bool> route(String path, {Route startingFrom}) {
382 var future = _route(path, startingFrom: startingFrom); 440 var future = _route(path, startingFrom);
383 _onRouteStart.add(new RouteStartEvent._new(path, future)); 441 _onRouteStart.add(new RouteStartEvent._new(path, future));
384 return future; 442 return future;
385 } 443 }
386 444
387 Future<bool> _route(String path, {Route startingFrom}) { 445 Future<bool> _route(String path, Route startingFrom) {
388 var baseRoute = startingFrom == null ? this.root : _dehandle(startingFrom); 446 var baseRoute = startingFrom == null ? root : _dehandle(startingFrom);
389 _logger.finest('route $path $baseRoute'); 447 _logger.finest('route $path $baseRoute');
448 var treePath = _matchingTreePath(path, baseRoute);
449 Route cmpBase = baseRoute;
450 var tail = path;
451 // Skip all routes that are unaffected by this path.
452 treePath = treePath.skipWhile((_Match matchedRoute) {
453 var skip = cmpBase._currentRoute == matchedRoute.route &&
454 !_paramsChanged(cmpBase, matchedRoute.urlMatch);
455 if (skip) {
456 cmpBase = matchedRoute.route;
457 tail = matchedRoute.urlMatch.tail;
458 }
459 return skip;
460 });
461 // TODO(pavelgj): weird things happen without this line...
462 treePath = treePath.toList();
463 if (treePath.isEmpty) {
464 return new Future.value(true);
465 }
466 var preEnterFutures = _preEnter(tail, treePath);
467 return Future.wait(preEnterFutures).then((List<bool> results) {
468 if (results.fold(true, (a, b) => a && b)) {
469 return _processNewRoute(cmpBase, treePath, tail);
470 }
471 return false;
472 });
473 }
474
475 List<Future<bool>> _preEnter(String tail, Iterable<_Match> treePath) {
476 List<Future<bool>> preEnterFutures = <Future<bool>>[];
477 treePath.forEach((_Match matchedRoute) {
478 tail = matchedRoute.urlMatch.tail;
479 var preEnterEvent = new RoutePreEnterEvent(tail, matchedRoute.urlMatch.par ameters, matchedRoute.route);
480 matchedRoute.route._onPreEnterController.add(preEnterEvent);
481 preEnterFutures.addAll(preEnterEvent._allowEnterFutures);
482 });
483 return preEnterFutures;
484 }
485
486 Future<bool> _processNewRoute(Route startingFrom, Iterable<_Match> treePath, S tring path) {
487 return _leaveOldRoutes(startingFrom, treePath).then((bool allowed) {
488 if (allowed) {
489 var base = startingFrom;
490 var tail = path;
491 treePath.forEach((_Match matchedRoute) {
492 tail = matchedRoute.urlMatch.tail;
493 var event = new RouteEnterEvent(matchedRoute.urlMatch.match,
494 matchedRoute.urlMatch.parameters, matchedRoute.route);
495 _unsetAllCurrentRoutes(base);
496 base._currentRoute = matchedRoute.route;
497 base._currentRoute._lastEvent = event;
498 matchedRoute.route._onEnterController.add(event);
499 base = matchedRoute.route;
500 });
501 return true;
502 }
503 return false;
504 });
505 }
506
507 Future<bool> _leaveOldRoutes(Route startingFrom, Iterable<_Match> treePath) {
508 if (treePath.isEmpty) {
509 return new Future.value(true);
510 }
511 var event = new RouteLeaveEvent('', {}, startingFrom);
512 return _leaveCurrentRoute(startingFrom, event);
513 }
514
515 Iterable<_Match> _matchingTreePath(String path, Route baseRoute) {
516 List<_Match> treePath = <_Match>[];
390 Route matchedRoute; 517 Route matchedRoute;
391 List matchingRoutes = baseRoute._routes.values.where( 518 do {
392 (r) => r.path.match(path) != null).toList(); 519 matchedRoute = null;
393 if (!matchingRoutes.isEmpty) { 520 List matchingRoutes = baseRoute._routes.values.where(
394 if (matchingRoutes.length > 1) { 521 (r) => r.path.match(path) != null).toList();
395 _logger.warning("More than one route matches $path $matchingRoutes"); 522 if (!matchingRoutes.isEmpty) {
523 if (matchingRoutes.length > 1) {
524 _logger.warning("More than one route matches $path $matchingRoutes");
525 }
526 matchedRoute = matchingRoutes.first;
527 } else {
528 if (baseRoute._defaultRoute != null) {
529 matchedRoute = baseRoute._defaultRoute;
530 }
396 } 531 }
397 matchedRoute = matchingRoutes.first; 532 if (matchedRoute != null) {
398 } else { 533 var match = _getMatch(matchedRoute, path);
399 if (baseRoute._defaultRoute != null) { 534 treePath.add(new _Match(matchedRoute, match));
400 matchedRoute = baseRoute._defaultRoute; 535 baseRoute = matchedRoute;
536 path = match.tail;
401 } 537 }
402 } 538 } while (matchedRoute != null);
403 if (matchedRoute != null) { 539 return treePath;
404 var match = _getMatch(matchedRoute, path);
405 if (matchedRoute != baseRoute._currentRoute ||
406 _paramsChanged(baseRoute, match)) {
407 return _processNewRoute(baseRoute, path, match, matchedRoute);
408 } else {
409 baseRoute._currentRoute._lastEvent =
410 new RouteEvent(match.match, match.parameters,
411 baseRoute._currentRoute);
412 return _route(match.tail, startingFrom: matchedRoute);
413 }
414 } else if (baseRoute._currentRoute != null) {
415 var event = new RouteEvent('', {}, baseRoute);
416 return _leaveCurrentRoute(baseRoute, event).then((success) {
417 if (success) {
418 baseRoute._currentRoute = null;
419 }
420 return success;
421 });
422 }
423 return new Future.value(true);
424 } 540 }
425 541
426 bool _paramsChanged(Route baseRoute, UrlMatch match) { 542 bool _paramsChanged(Route baseRoute, UrlMatch match) {
427 return baseRoute._currentRoute._lastEvent.path != match.match || 543 return baseRoute._currentRoute._lastEvent.path != match.match ||
428 !_mapsEqual(baseRoute._currentRoute._lastEvent.parameters, 544 !_mapsEqual(baseRoute._currentRoute._lastEvent.parameters,
429 match.parameters); 545 match.parameters);
430 } 546 }
431 547
432 bool _mapsEqual(Map a, Map b) { 548 bool _mapsEqual(Map a, Map b) {
433 if (a.keys.length != b.keys.length) { 549 if (a.keys.length != b.keys.length) {
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
470 586
471 String _buildQuery(Map queryParams) { 587 String _buildQuery(Map queryParams) {
472 var query = queryParams.keys.map((key) => 588 var query = queryParams.keys.map((key) =>
473 '$key=${Uri.encodeComponent(queryParams[key])}').join('&'); 589 '$key=${Uri.encodeComponent(queryParams[key])}').join('&');
474 if (query.isEmpty) { 590 if (query.isEmpty) {
475 return ''; 591 return '';
476 } 592 }
477 return '?$query'; 593 return '?$query';
478 } 594 }
479 595
480 Route _dehandle(Route r) { 596 Route _dehandle(Route r) => r is RouteHandle ? r._getHost(r): r;
481 if (r is RouteHandle) {
482 return (r as RouteHandle)._getHost(r);
483 }
484 return r;
485 }
486 597
487 UrlMatch _getMatch(Route route, String path) { 598 UrlMatch _getMatch(Route route, String path) {
488 var match = route.path.match(path); 599 var match = route.path.match(path);
489 if (match == null) { // default route 600 if (match == null) { // default route
490 return new UrlMatch('', '', {}); 601 return new UrlMatch('', '', {});
491 } 602 }
492 _parseQuery(route, path).forEach((k, v) { match.parameters[k] = v; }); 603 _parseQuery(route, path).forEach((k, v) { match.parameters[k] = v; });
493 return match; 604 return match;
494 } 605 }
495 606
(...skipping 20 matching lines...) Expand all
516 return ['', '']; 627 return ['', ''];
517 } 628 }
518 var splitPoint = keyValPair.indexOf('=') == -1 ? 629 var splitPoint = keyValPair.indexOf('=') == -1 ?
519 keyValPair.length : keyValPair.indexOf('=') + 1; 630 keyValPair.length : keyValPair.indexOf('=') + 1;
520 var key = keyValPair.substring(0, splitPoint + 631 var key = keyValPair.substring(0, splitPoint +
521 (keyValPair.indexOf('=') == -1 ? 0 : -1)); 632 (keyValPair.indexOf('=') == -1 ? 0 : -1));
522 var value = keyValPair.substring(splitPoint); 633 var value = keyValPair.substring(splitPoint);
523 return [key, value]; 634 return [key, value];
524 } 635 }
525 636
526 Future<bool> _processNewRoute(Route base, String path, UrlMatch match,
527 Route newRoute) {
528 _logger.finest('_processNewRoute $path');
529 var event = new RouteEvent(match.match, match.parameters, newRoute);
530 // before we make this a new current route, leave the old
531 return _leaveCurrentRoute(base, event).then((bool allowNavigation) {
532 if (allowNavigation) {
533 _unsetAllCurrentRoutes(base);
534 base._currentRoute = newRoute;
535 base._currentRoute._lastEvent = event;
536 newRoute._onRouteController.add(event);
537 return _route(match.tail, startingFrom: newRoute);
538 }
539 return false;
540 });
541 }
542
543 void _unsetAllCurrentRoutes(Route r) { 637 void _unsetAllCurrentRoutes(Route r) {
544 if (r._currentRoute != null) { 638 if (r._currentRoute != null) {
545 _unsetAllCurrentRoutes(r._currentRoute); 639 _unsetAllCurrentRoutes(r._currentRoute);
546 r._currentRoute = null; 640 r._currentRoute = null;
547 } 641 }
548 } 642 }
549 643
550 Future<bool> _leaveCurrentRoute(Route base, RouteEvent e) => 644 Future<bool> _leaveCurrentRoute(Route base, RouteLeaveEvent e) =>
551 Future.wait(_leaveCurrentRouteHelper(base, e)) 645 Future.wait(_leaveCurrentRouteHelper(base, e))
552 .then((values) => values.fold(true, (c, v) => c && v)); 646 .then((values) => values.fold(true, (c, v) => c && v));
553 647
554 List<Future<bool>> _leaveCurrentRouteHelper(Route base, RouteEvent e) { 648 List<Future<bool>> _leaveCurrentRouteHelper(Route base, RouteLeaveEvent e) {
555 var futures = []; 649 var futures = [];
556 if (base._currentRoute != null) { 650 if (base._currentRoute != null) {
557 List<Future<bool>> pendingResponses = <Future<bool>>[]; 651 List<Future<bool>> pendingResponses = <Future<bool>>[];
558 // We create a copy of the route event 652 // We create a copy of the route event
559 var event = e._clone(); 653 var event = e._clone();
560 base._currentRoute._onLeaveController.add(event); 654 base._currentRoute._onLeaveController.add(event);
561 futures.addAll(event._allowLeaveFutures); 655 futures.addAll(event._allowLeaveFutures);
562 futures.addAll(_leaveCurrentRouteHelper(base._currentRoute, event)); 656 futures.addAll(_leaveCurrentRouteHelper(base._currentRoute, event));
563 } 657 }
564 return futures; 658 return futures;
565 } 659 }
566 660
567 /** 661 /**
568 * Listens for window history events and invokes the router. On older 662 * Listens for window history events and invokes the router. On older
569 * browsers the hashChange event is used instead. 663 * browsers the hashChange event is used instead.
570 */ 664 */
571 void listen({bool ignoreClick: false}) { 665 void listen({bool ignoreClick: false, Element appRoot}) {
572 _logger.finest('listen ignoreClick=$ignoreClick'); 666 _logger.finest('listen ignoreClick=$ignoreClick');
573 if (_listen) { 667 if (_listen) {
574 throw new StateError('listen can only be called once'); 668 throw new StateError('listen can only be called once');
575 } 669 }
576 _listen = true; 670 _listen = true;
577 if (_useFragment) { 671 if (_useFragment) {
578 _window.onHashChange.listen((_) { 672 _window.onHashChange.listen((_) {
579 route(_normalizeHash(_window.location.hash)).then((allowed) { 673 route(_normalizeHash(_window.location.hash)).then((allowed) {
580 // if not allowed, we need to restore the browser location 674 // if not allowed, we need to restore the browser location
581 if (!allowed) { 675 if (!allowed) {
582 _window.history.back(); 676 _window.history.back();
583 } 677 }
584 }); 678 });
585 }); 679 });
586 route(_normalizeHash(_window.location.hash)); 680 route(_normalizeHash(_window.location.hash));
587 } else { 681 } else {
588 _window.onPopState.listen((_) { 682 _window.onPopState.listen((_) {
589 var path = '${_window.location.pathname}${_window.location.hash}'; 683 var path = '${_window.location.pathname}${_window.location.hash}';
590 route(path).then((allowed) { 684 route(path).then((allowed) {
591 // if not allowed, we need to restore the browser location 685 // if not allowed, we need to restore the browser location
592 if (!allowed) { 686 if (!allowed) {
593 _window.history.back(); 687 _window.history.back();
594 } 688 }
595 }); 689 });
596 }); 690 });
597 } 691 }
598 if (!ignoreClick) { 692 if (!ignoreClick) {
693 if (appRoot == null) {
694 appRoot = _window.document.documentElement;
695 }
599 _logger.finest('listen on win'); 696 _logger.finest('listen on win');
600 _window.onClick.listen((Event e) { 697 appRoot.onClick.listen((MouseEvent e) {
601 if (e.target is AnchorElement) { 698 if (!e.ctrlKey && !e.metaKey && !e.shiftKey && e.target is AnchorElement ) {
602 AnchorElement anchor = e.target; 699 AnchorElement anchor = e.target;
603 if (anchor.host == _window.location.host) { 700 if (anchor.host == _window.location.host) {
604 _logger.finest('clicked ${anchor.pathname}${anchor.hash}'); 701 _logger.finest('clicked ${anchor.pathname}${anchor.hash}');
605 e.preventDefault(); 702 e.preventDefault();
606 var path; 703 var path;
607 if (_useFragment) { 704 if (_useFragment) {
608 path = _normalizeHash(anchor.hash); 705 path = _normalizeHash(anchor.hash);
609 } else { 706 } else {
610 path = '${anchor.pathname}'; 707 path = '${anchor.pathname}';
611 } 708 }
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
667 List<Route> get activePath { 764 List<Route> get activePath {
668 var res = <Route>[]; 765 var res = <Route>[];
669 var current = root; 766 var current = root;
670 while (current._currentRoute != null) { 767 while (current._currentRoute != null) {
671 current = current._currentRoute; 768 current = current._currentRoute;
672 res.add(current); 769 res.add(current);
673 } 770 }
674 return res; 771 return res;
675 } 772 }
676 } 773 }
774
775 class _Match {
776 final Route route;
777 final UrlMatch urlMatch;
778
779 _Match(this.route, this.urlMatch);
780 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698