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

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

Issue 1086713003: Remove everything but markdown from third_party (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 7 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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library route.client;
6
7 import 'dart:async';
8 import 'dart:html';
9
10 import 'package:logging/logging.dart';
11
12 import 'src/utils.dart';
13
14 import 'url_matcher.dart';
15 export 'url_matcher.dart';
16 import 'url_template.dart';
17
18 part 'route_handle.dart';
19
20
21 final _logger = new Logger('route');
22 const _PATH_SEPARATOR = '.';
23
24 typedef void RoutePreEnterEventHandler(RoutePreEnterEvent event);
25 typedef void RouteEnterEventHandler(RouteEnterEvent event);
26 typedef void RouteLeaveEventHandler(RouteLeaveEvent event);
27
28 /**
29 * [Route] represents a node in the route tree.
30 */
31 abstract class Route {
32 /**
33 * Name of the route. Used when querying routes.
34 */
35 String get name;
36
37 /**
38 * A path fragment [UrlMatcher] for this route.
39 */
40 UrlMatcher get path;
41
42 /**
43 * Parent route in the route tree.
44 */
45 Route get parent;
46
47 /**
48 * Indicates whether this route is currently active. Root route is always
49 * active.
50 */
51 bool get isActive;
52
53 /**
54 * Returns parameters for the currently active route. If the route is not
55 * active the getter returns null.
56 */
57 Map get parameters;
58
59 /**
60 * Whether to trigger the leave event when only the parameters change.
61 */
62 bool get dontLeaveOnParamChanges;
63
64 /**
65 * Returns a stream of [RouteEnterEvent] events. The [RouteEnterEvent] event
66 * is fired when route has already been made active, but before subroutes
67 * are entered. The event starts at the root and propagates from parent to
68 * child routes.
69 */
70 @Deprecated("use [onEnter] instead.")
71 Stream<RouteEnterEvent> get onRoute;
72
73 /**
74 * Returns a stream of [RoutePreEnterEvent] events. The [RoutePreEnterEvent]
75 * event is fired when the route is matched during the routing, but before
76 * any previous routes were left, or any new routes were entered. The event
77 * starts at the root and propagates from parent to child routes.
78 *
79 * At this stage it's possible to veto entering of the route by calling
80 * [RoutePreEnterEvent.allowEnter] with a [Future] returns a boolean value
81 * indicating whether enter is permitted (true) or not (false).
82 */
83 Stream<RoutePreEnterEvent> get onPreEnter;
84
85 /**
86 * Returns a stream of [RouteLeaveEvent] events. The [RouteLeaveEvent]
87 * event is fired when the route is being left. The event starts at the leaf
88 * route and propagates from child to parent routes.
89 *
90 * At this stage it's possible to veto leaving of the route by calling
91 * [RouteLeaveEvent.allowLeave] with a [Future] returns a boolean value
92 * indicating whether leave is permitted (true) or not (false).
93 *
94 * Note: that once child routes have been notified of the leave they will not
95 * be notified of the subsequent veto by any parent route. See:
96 * https://github.com/angular/route.dart/issues/28
97 */
98 Stream<RouteLeaveEvent> get onLeave;
99
100 /**
101 * Returns a stream of [RouteEnterEvent] events. The [RouteEnterEvent] event
102 * is fired when route has already been made active, but before subroutes
103 * are entered. The event starts at the root and propagates from parent
104 * to child routes.
105 */
106 Stream<RouteEnterEvent> get onEnter;
107
108 void addRoute({String name, Pattern path, bool defaultRoute: false,
109 RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter,
110 RouteLeaveEventHandler leave, mount, dontLeaveOnParamChanges: false});
111
112 /**
113 * Queries sub-routes using the [routePath] and returns the matching [Route].
114 *
115 * [routePath] is a dot-separated list of route names. Ex: foo.bar.baz, which
116 * means that current route should contain route named 'foo', the 'foo' route
117 * should contain route named 'bar', and so on.
118 *
119 * If no match is found then [:null:] is returned.
120 */
121 @Deprecated("use [findRoute] instead.")
122 Route getRoute(String routePath);
123
124 /**
125 * Queries sub-routes using the [routePath] and returns the matching [Route].
126 *
127 * [routePath] is a dot-separated list of route names. Ex: foo.bar.baz, which
128 * means that current route should contain route named 'foo', the 'foo' route
129 * should contain route named 'bar', and so on.
130 *
131 * If no match is found then [:null:] is returned.
132 */
133 Route findRoute(String routePath);
134
135 /**
136 * Create an return a new [RouteHandle] for this route.
137 */
138 RouteHandle newHandle();
139
140 String toString() => '[Route: $name]';
141 }
142
143 /**
144 * Route is a node in the tree of routes. The edge leading to the route is
145 * defined by path.
146 */
147 class RouteImpl extends Route {
148 @override
149 final String name;
150 @override
151 final UrlMatcher path;
152 @override
153 final RouteImpl parent;
154
155 final _routes = <String, RouteImpl>{};
156 final StreamController<RouteEnterEvent> _onEnterController;
157 final StreamController<RoutePreEnterEvent> _onPreEnterController;
158 final StreamController<RouteLeaveEvent> _onLeaveController;
159 RouteImpl _defaultRoute;
160 RouteImpl _currentRoute;
161 RouteEvent _lastEvent;
162 @override
163 final bool dontLeaveOnParamChanges;
164
165 @override
166 @Deprecated("use [onEnter] instead.")
167 Stream<RouteEvent> get onRoute => onEnter;
168 @override
169 Stream<RouteEvent> get onPreEnter => _onPreEnterController.stream;
170 @override
171 Stream<RouteEvent> get onLeave => _onLeaveController.stream;
172 @override
173 Stream<RouteEvent> get onEnter => _onEnterController.stream;
174
175 RouteImpl._new({this.name, this.path, this.parent,
176 this.dontLeaveOnParamChanges: false})
177 : _onEnterController =
178 new StreamController<RouteEnterEvent>.broadcast(sync: true),
179 _onPreEnterController =
180 new StreamController<RoutePreEnterEvent>.broadcast(sync: true),
181 _onLeaveController =
182 new StreamController<RouteLeaveEvent>.broadcast(sync: true);
183
184 @override
185 void addRoute({String name, Pattern path, bool defaultRoute: false,
186 RouteEnterEventHandler enter, RoutePreEnterEventHandler preEnter,
187 RouteLeaveEventHandler leave, mount, dontLeaveOnParamChanges: false}) {
188 if (name == null) {
189 throw new ArgumentError('name is required for all routes');
190 }
191 if (name.contains(_PATH_SEPARATOR)) {
192 throw new ArgumentError('name cannot contain dot.');
193 }
194 if (_routes.containsKey(name)) {
195 throw new ArgumentError('Route $name already exists');
196 }
197
198 var matcher = path is UrlMatcher ? path : new UrlTemplate(path.toString());
199
200 var route = new RouteImpl._new(name: name, path: matcher, parent: this,
201 dontLeaveOnParamChanges: dontLeaveOnParamChanges);
202
203 route..onPreEnter.listen(preEnter)
204 ..onEnter.listen(enter)
205 ..onLeave.listen(leave);
206
207 if (mount != null) {
208 if (mount is Function) {
209 mount(route);
210 } else if (mount is Routable) {
211 mount.configureRoute(route);
212 }
213 }
214
215 if (defaultRoute) {
216 if (_defaultRoute != null) {
217 throw new StateError('Only one default route can be added.');
218 }
219 _defaultRoute = route;
220 }
221 _routes[name] = route;
222 }
223
224 @override
225 Route getRoute(String routePath) => findRoute(routePath);
226
227 @override
228 Route findRoute(String routePath) {
229 var routeName = routePath.split(_PATH_SEPARATOR).first;
230 if (!_routes.containsKey(routeName)) {
231 _logger.warning('Invalid route name: $routeName $_routes');
232 return null;
233 }
234 var routeToGo = _routes[routeName];
235 var childPath = routePath.substring(routeName.length);
236 return childPath.isEmpty ? routeToGo :
237 routeToGo.getRoute(childPath.substring(1));
238 }
239
240 String _getHead(String tail, Map queryParams) {
241 if (parent == null) return tail;
242 if (parent._currentRoute == null) {
243 throw new StateError('Router $parent has no current router.');
244 }
245 _populateQueryParams(parent._currentRoute._lastEvent.parameters,
246 parent._currentRoute, queryParams);
247 return parent._getHead(parent._currentRoute._reverse(tail), queryParams);
248 }
249
250 String _getTailUrl(String routePath, Map parameters, Map queryParams) {
251 var routeName = routePath.split('.').first;
252 if (!_routes.containsKey(routeName)) {
253 throw new StateError('Invalid route name: $routeName');
254 }
255 var routeToGo = _routes[routeName];
256 var tail = '';
257 var childPath = routePath.substring(routeName.length);
258 if (childPath.isNotEmpty) {
259 tail = routeToGo._getTailUrl(
260 childPath.substring(1), parameters, queryParams);
261 }
262 _populateQueryParams(parameters, routeToGo, queryParams);
263 return routeToGo.path.reverse(
264 parameters: _joinParams(parameters, routeToGo._lastEvent), tail: tail);
265 }
266
267 void _populateQueryParams(Map parameters, Route route, Map queryParams) {
268 parameters.keys.forEach((String prefixedKey) {
269 if (prefixedKey.startsWith('${route.name}.')) {
270 var key = prefixedKey.substring('${route.name}.'.length);
271 if (!route.path.urlParameterNames().contains(key)) {
272 queryParams[prefixedKey] = parameters[prefixedKey];
273 }
274 }
275 });
276 }
277
278 Map _joinParams(Map parameters, RouteEvent lastEvent) => lastEvent == null
279 ? parameters
280 : new Map.from(lastEvent.parameters)..addAll(parameters);
281
282 /**
283 * Returns a URL for this route. The tail (url generated by the child path)
284 * will be passes to the UrlMatcher to be properly appended in the
285 * right place.
286 */
287 String _reverse(String tail) =>
288 path.reverse(parameters: _lastEvent.parameters, tail: tail);
289
290 /**
291 * Create an return a new [RouteHandle] for this route.
292 */
293 @override
294 RouteHandle newHandle() {
295 _logger.finest('newHandle for $this');
296 return new RouteHandle._new(this);
297 }
298
299 /**
300 * Indicates whether this route is currently active. Root route is always
301 * active.
302 */
303 @override
304 bool get isActive =>
305 parent == null ? true : identical(parent._currentRoute, this);
306
307 /**
308 * Returns parameters for the currently active route. If the route is not
309 * active the getter returns null.
310 */
311 @override
312 Map get parameters {
313 if (isActive) {
314 return _lastEvent == null ? {} : new Map.from(_lastEvent.parameters);
315 }
316 return null;
317 }
318 }
319
320 /**
321 * Route enter or leave event.
322 */
323 abstract class RouteEvent {
324 final String path;
325 final Map parameters;
326 final Route route;
327
328 RouteEvent(this.path, this.parameters, this.route);
329 }
330
331 class RoutePreEnterEvent extends RouteEvent {
332 final _allowEnterFutures = <Future<bool>>[];
333
334 RoutePreEnterEvent(path, parameters, route) : super(path, parameters, route);
335
336 RoutePreEnterEvent._fromMatch(_Match m)
337 : this(m.urlMatch.tail, m.urlMatch.parameters, m.route);
338
339 /**
340 * Can be called on enter with the future which will complete with a boolean
341 * value allowing ([:true:]) or disallowing ([:false:]) the current
342 * navigation.
343 */
344 void allowEnter(Future<bool> allow) {
345 _allowEnterFutures.add(allow);
346 }
347 }
348
349 class RouteEnterEvent extends RouteEvent {
350
351 RouteEnterEvent(path, parameters, route) : super(path, parameters, route);
352
353 RouteEnterEvent._fromMatch(_Match m)
354 : this(m.urlMatch.match, m.urlMatch.parameters, m.route);
355 }
356
357 class RouteLeaveEvent extends RouteEvent {
358 final _allowLeaveFutures = <Future<bool>>[];
359
360 RouteLeaveEvent(path, parameters, route) : super(path, parameters, route);
361
362 /**
363 * Can be called on enter with the future which will complete with a boolean
364 * value allowing ([:true:]) or disallowing ([:false:]) the current
365 * navigation.
366 */
367 void allowLeave(Future<bool> allow) {
368 _allowLeaveFutures.add(allow);
369 }
370
371 RouteLeaveEvent _clone() => new RouteLeaveEvent(path, parameters, route);
372 }
373
374 /**
375 * Event emitted when routing starts.
376 */
377 class RouteStartEvent {
378 /**
379 * URI that was passed to [Router.route].
380 */
381 final String uri;
382
383 /**
384 * Future that completes to a boolean value of whether the routing was
385 * successful.
386 */
387 final Future<bool> completed;
388
389 RouteStartEvent._new(this.uri, this.completed);
390 }
391
392 abstract class Routable {
393 void configureRoute(Route router);
394 }
395
396 /**
397 * Stores a set of [UrlPattern] to [Handler] associations and provides methods
398 * for calling a handler for a URL path, listening to [Window] history events,
399 * and creating HTML event handlers that navigate to a URL.
400 */
401 class Router {
402 final bool _useFragment;
403 final Window _window;
404 final Route root;
405 final _onRouteStart =
406 new StreamController<RouteStartEvent>.broadcast(sync: true);
407 final bool sortRoutes;
408 bool _listen = false;
409
410 /**
411 * [useFragment] determines whether this Router uses pure paths with
412 * [History.pushState] or paths + fragments and [Location.assign]. The default
413 * value is null which then determines the behavior based on
414 * [History.supportsState].
415 */
416 Router({bool useFragment, Window windowImpl, bool sortRoutes: true})
417 : this._init(null, useFragment: useFragment, windowImpl: windowImpl,
418 sortRoutes: sortRoutes);
419
420
421 Router._init(Router parent, {bool useFragment, Window windowImpl,
422 this.sortRoutes})
423 : _useFragment = (useFragment == null)
424 ? !History.supportsState
425 : useFragment,
426 _window = (windowImpl == null) ? window : windowImpl,
427 root = new RouteImpl._new();
428
429 /**
430 * A stream of route calls.
431 */
432 Stream<RouteStartEvent> get onRouteStart => _onRouteStart.stream;
433
434 /**
435 * Finds a matching [Route] added with [addRoute], parses the path
436 * and invokes the associated callback.
437 *
438 * This method does not perform any navigation, [go] should be used for that.
439 * This method is used to invoke a handler after some other code navigates the
440 * window, such as [listen].
441 */
442 Future<bool> route(String path, {Route startingFrom}) {
443 var future = _route(path, startingFrom);
444 _onRouteStart.add(new RouteStartEvent._new(path, future));
445 return future;
446 }
447
448 Future<bool> _route(String path, Route startingFrom) {
449 var baseRoute = startingFrom == null ? root : _dehandle(startingFrom);
450 _logger.finest('route $path $baseRoute');
451 var treePath = _matchingTreePath(path, baseRoute);
452 var cmpBase = baseRoute;
453 var tail = path;
454 // Skip all routes that are unaffected by this path.
455 treePath = treePath.skipWhile((_Match matchedRoute) {
456 var skip = cmpBase._currentRoute == matchedRoute.route &&
457 !_paramsChanged(cmpBase, matchedRoute.urlMatch);
458 if (skip) {
459 cmpBase = matchedRoute.route;
460 tail = matchedRoute.urlMatch.tail;
461 }
462 return skip;
463 }).toList();
464
465 if (treePath.isEmpty) return new Future.value(true);
466
467 var preEnterFutures = _preEnter(tail, treePath);
468
469 return Future.wait(preEnterFutures).then((List<bool> results) {
470 return results.any((v) => v == false)
471 ? false
472 : _processNewRoute(cmpBase, treePath, tail);
473 });
474 }
475
476 List<Future<bool>> _preEnter(String tail, List<_Match> treePath) {
477 var preEnterFutures = <Future<bool>>[];
478 treePath.forEach((_Match matchedRoute) {
479 var preEnterEvent = new RoutePreEnterEvent._fromMatch(matchedRoute);
480 matchedRoute.route._onPreEnterController.add(preEnterEvent);
481 preEnterFutures.addAll(preEnterEvent._allowEnterFutures);
482 });
483 return preEnterFutures;
484 }
485
486 Future<bool> _processNewRoute(Route startingFrom, List<_Match> treePath,
487 String path) {
488 return _leaveOldRoutes(startingFrom, treePath).then((bool allowed) {
489 if (allowed) {
490 var base = startingFrom;
491 treePath.forEach((_Match matchedRoute) {
492 var event = new RouteEnterEvent._fromMatch(matchedRoute);
493 _unsetAllCurrentRoutes(base);
494 base._currentRoute = matchedRoute.route;
495 base._currentRoute._lastEvent = event;
496 matchedRoute.route._onEnterController.add(event);
497 base = matchedRoute.route;
498 });
499 return true;
500 }
501 return false;
502 });
503 }
504
505 Future<bool> _leaveOldRoutes(RouteImpl startingFrom, List<_Match> treePath) {
506 if (treePath.isEmpty) return new Future.value(true);
507
508 var currentRoute = startingFrom._currentRoute;
509 if (currentRoute != null &&
510 currentRoute.dontLeaveOnParamChanges &&
511 identical(currentRoute, treePath.last.route)) {
512 return new Future.value(true);
513 }
514
515 var event = new RouteLeaveEvent('', {}, startingFrom);
516 return _leaveCurrentRoute(startingFrom, event);
517 }
518
519 List _matchingRoutes(String path, RouteImpl baseRoute) {
520 var routes = baseRoute._routes.values.toList();
521 if (sortRoutes) {
522 routes.sort((r1, r2) => r1.path.compareTo(r2.path));
523 }
524 return routes.where((r) => r.path.match(path) != null).toList();
525 }
526
527 List<_Match> _matchingTreePath(String path, RouteImpl baseRoute) {
528 final treePath = <_Match>[];
529 Route matchedRoute;
530 do {
531 matchedRoute = null;
532 List matchingRoutes = _matchingRoutes(path, baseRoute);
533 if (matchingRoutes.isNotEmpty) {
534 if (matchingRoutes.length > 1) {
535 _logger.warning("More than one route matches $path $matchingRoutes");
536 }
537 matchedRoute = matchingRoutes.first;
538 } else {
539 if (baseRoute._defaultRoute != null) {
540 matchedRoute = baseRoute._defaultRoute;
541 }
542 }
543 if (matchedRoute != null) {
544 var match = _getMatch(matchedRoute, path);
545 treePath.add(new _Match(matchedRoute, match));
546 baseRoute = matchedRoute;
547 path = match.tail;
548 }
549 } while (matchedRoute != null);
550 return treePath;
551 }
552
553 bool _paramsChanged(RouteImpl baseRoute, UrlMatch match) {
554 var lastEvent = baseRoute._currentRoute._lastEvent;
555 return lastEvent.path != match.match ||
556 !mapsShallowEqual(lastEvent.parameters, match.parameters);
557 }
558
559 /// Navigates to a given relative route path, and parameters.
560 Future go(String routePath, Map parameters,
561 {Route startingFrom, bool replace: false}) {
562 var queryParams = {};
563 var baseRoute = startingFrom == null ? this.root : _dehandle(startingFrom);
564 var newTail = baseRoute._getTailUrl(routePath, parameters, queryParams) +
565 _buildQuery(queryParams);
566 String newUrl = baseRoute._getHead(newTail, queryParams);
567 _logger.finest('go $newUrl');
568 return route(newTail, startingFrom: baseRoute).then((success) {
569 if (success) _go(newUrl, null, replace);
570 return success;
571 });
572 }
573
574 /// Returns an absolute URL for a given relative route path and parameters.
575 String url(String routePath, {Route startingFrom, Map parameters}) {
576 var baseRoute = startingFrom == null ? this.root : _dehandle(startingFrom);
577 parameters = parameters == null ? {} : parameters;
578 var queryParams = {};
579 var tail = baseRoute._getTailUrl(routePath, parameters, queryParams);
580 return (_useFragment ? '#' : '') + baseRoute._getHead(tail, queryParams) +
581 _buildQuery(queryParams);
582 }
583
584 String _buildQuery(Map queryParams) {
585 if (queryParams.isEmpty) return '';
586 var query = queryParams.keys.map((key) =>
587 '$key=${Uri.encodeComponent(queryParams[key])}').join('&');
588 return '?$query';
589 }
590
591 Route _dehandle(Route r) => r is RouteHandle ? r._getHost(r): r;
592
593 UrlMatch _getMatch(Route route, String path) {
594 var match = route.path.match(path);
595 // default route
596 if (match == null) return new UrlMatch('', '', {});
597 match.parameters.addAll(_parseQuery(route, path));
598 return match;
599 }
600
601 Map _parseQuery(Route route, String path) {
602 var params = {};
603 if (path.indexOf('?') == -1) return params;
604 var queryStr = path.substring(path.indexOf('?') + 1);
605 queryStr.split('&').forEach((String keyValPair) {
606 List<String> keyVal = _parseKeyVal(keyValPair);
607 if (keyVal[0].startsWith('${route.name}.')) {
608 var key = keyVal[0].substring('${route.name}.'.length);
609 if (key.isNotEmpty) params[key] = Uri.decodeComponent(keyVal[1]);
610 }
611 });
612 return params;
613 }
614
615 List<String> _parseKeyVal(keyValPair) {
616 if (keyValPair.isEmpty) return const ['', ''];
617 var splitPoint = keyValPair.indexOf('=') == -1 ?
618 keyValPair.length : keyValPair.indexOf('=') + 1;
619 var key = keyValPair.substring(0, splitPoint +
620 (keyValPair.indexOf('=') == -1 ? 0 : -1));
621 var value = keyValPair.substring(splitPoint);
622 return [key, value];
623 }
624
625 void _unsetAllCurrentRoutes(RouteImpl r) {
626 if (r._currentRoute != null) {
627 _unsetAllCurrentRoutes(r._currentRoute);
628 r._currentRoute = null;
629 }
630 }
631
632 Future<bool> _leaveCurrentRoute(RouteImpl base, RouteLeaveEvent e) =>
633 Future
634 .wait(_leaveCurrentRouteHelper(base, e))
635 .then((values) => values.fold(true, (c, v) => c && v));
636
637 List<Future<bool>> _leaveCurrentRouteHelper(RouteImpl base, RouteLeaveEvent e) {
638 var futures = [];
639 if (base._currentRoute != null) {
640 List<Future<bool>> pendingResponses = <Future<bool>>[];
641 // We create a copy of the route event
642 var event = e._clone();
643 base._currentRoute._onLeaveController.add(event);
644 futures..addAll(event._allowLeaveFutures)
645 ..addAll(_leaveCurrentRouteHelper(base._currentRoute, event));
646 }
647 return futures;
648 }
649
650 /**
651 * Listens for window history events and invokes the router. On older
652 * browsers the hashChange event is used instead.
653 */
654 void listen({bool ignoreClick: false, Element appRoot}) {
655 _logger.finest('listen ignoreClick=$ignoreClick');
656 if (_listen) throw new StateError('listen can only be called once');
657 _listen = true;
658 if (_useFragment) {
659 _window.onHashChange.listen((_) {
660 route(_normalizeHash(_window.location.hash)).then((allowed) {
661 // if not allowed, we need to restore the browser location
662 if (!allowed) _window.history.back();
663 });
664 });
665 route(_normalizeHash(_window.location.hash));
666 } else {
667 String getPath() =>
668 '${_window.location.pathname}${_window.location.hash}';
669
670 _window.onPopState.listen((_) {
671 route(getPath()).then((allowed) {
672 // if not allowed, we need to restore the browser location
673 if (!allowed) _window.history.back();
674 });
675 });
676 route(getPath());
677 }
678 if (!ignoreClick) {
679 if (appRoot == null) appRoot = _window.document.documentElement;
680 _logger.finest('listen on win');
681 appRoot.onClick
682 .where((MouseEvent e) => !(e.ctrlKey || e.metaKey || e.shiftKey))
683 .where((MouseEvent e) => e.target is AnchorElement)
684 .listen((MouseEvent e) {
685 AnchorElement anchor = e.target;
686 if (anchor.host == _window.location.host) {
687 _logger.finest('clicked ${anchor.pathname}${anchor.hash}');
688 e.preventDefault();
689 var path = _useFragment
690 ? _normalizeHash(anchor.hash)
691 : '${anchor.pathname}';
692 route(path).then((allowed) {
693 if (allowed) _go(path, null, false);
694 });
695 }
696 });
697 }
698 }
699
700 String _normalizeHash(String hash) => hash.isEmpty ? '' : hash.substring(1);
701
702 /**
703 * Navigates the browser to the path produced by [url] with [args] by calling
704 * [History.pushState], then invokes the handler associated with [url].
705 *
706 * On older browsers [Location.assign] is used instead with the fragment
707 * version of the UrlPattern.
708 */
709 Future<bool> gotoUrl(String url) =>
710 route(url).then((success) {
711 if (success) _go(url, null, false);
712 });
713
714 void _go(String path, String title, bool replace) {
715 if (title == null) title = '';
716 if (_useFragment) {
717 if (replace) {
718 _window.location.replace('#$path');
719 } else {
720 _window.location.assign('#$path');
721 }
722 (_window.document as HtmlDocument).title = title;
723 } else {
724 if (replace) {
725 _window.history.replaceState(null, title, path);
726 } else {
727 _window.history.pushState(null, title, path);
728 }
729 }
730 }
731
732 /**
733 * Returns the current active route path in the route tree.
734 * Excludes the root path.
735 */
736 List<Route> get activePath {
737 var res = <RouteImpl>[];
738 var route = root;
739 while (route._currentRoute != null) {
740 route = route._currentRoute;
741 res.add(route);
742 }
743 return res;
744 }
745
746 /**
747 * A shortcut for router.root.findRoute().
748 */
749 Route findRoute(String routePath) => root.findRoute(routePath);
750 }
751
752 class _Match {
753 final RouteImpl route;
754 final UrlMatch urlMatch;
755
756 _Match(this.route, this.urlMatch);
757 }
OLDNEW
« no previous file with comments | « third_party/pkg/route_hierarchical/karma.conf.js ('k') | third_party/pkg/route_hierarchical/lib/pattern.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698