Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routing on page load #28

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions lib/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef void EventHandler(Event e);
class Router {
final Map<UrlPattern, Handler> _handlers;
final bool useFragment;
String _currentPath;

/**
* [useFragment] determines whether this Router uses pure paths with
Expand All @@ -47,7 +48,7 @@ class Router {
}
return matches.first;
}

/**
* Finds a matching [UrlPattern] added with [addHandler], parses the path
* and invokes the associated callback.
Expand All @@ -59,12 +60,15 @@ class Router {
* If the UrlPattern contains a fragment (#), the handler is always called
* with the path version of the URL by converting the # to a /.
*/
void handle(String path) {
void handle(String path, {bool force: false}) {
var url = _getUrl(path);
if (url != null) {
// always give handlers a non-fragment path
var fixedPath = url.reverse(url.parse(path));
_handlers[url](fixedPath);
if (force || fixedPath != _currentPath) {
_currentPath = fixedPath;
_handlers[url](fixedPath);
}
} else {
_logger.info("Unhandled path: $path");
}
Expand All @@ -73,28 +77,43 @@ class Router {
/**
* Listens for window history events and invokes the router. On older
* browsers the hashChange event is used instead.
*
* Handle routing for the current path which leads to consitent behavior
* for all browsers.
*/
void listen({bool ignoreClick: false}) {
if (useFragment) {
window.onHashChange.listen((_) {
print("location: ${window.location}");
return handle('${window.location.pathname}#${window.location.hash}');
});
} else {
window.onPopState.listen((_) => handle(window.location.pathname));
}
bool listen({bool ignoreClick: false}) {
var currentPathMatched = true;
if (!ignoreClick) {
window.onClick.listen((e) {
if (e.target is AnchorElement) {
AnchorElement anchor = e.target;
if (anchor.host == window.location.host) {
var fragment = (anchor.hash == '') ? '' : '${anchor.hash}';
var fragment = (anchor.hash == '') ? '' : '${anchor.hash}';
gotoPath("${anchor.pathname}$fragment", anchor.title);
e.preventDefault();
}
}
});
}
if (useFragment) {
try {
handle('${window.location.pathname}${window.location.hash}');
} on ArgumentError catch (e) {
currentPathMatched = false;
}
window.onHashChange.listen((_) {
print("location: ${window.location}");
handle('${window.location.pathname}${window.location.hash}');
});
} else {
try {
handle(window.location.pathname);
} on ArgumentError catch (e) {
currentPathMatched = false;
}
window.onPopState.listen((_) => handle(window.location.pathname));
}
return currentPathMatched;
}

/**
Expand All @@ -106,17 +125,20 @@ class Router {
*/
void gotoUrl(UrlPattern url, List args, String title) {
if (_handlers.containsKey(url)) {
_go(url.reverse(args, useFragment: useFragment), title);
_handlers[url](url.reverse(args, useFragment: useFragment));
var fixedPath = url.reverse(args, useFragment: useFragment);
_go(fixedPath, title);
_currentPath = fixedPath;
_handlers[url](fixedPath);
} else {
throw new ArgumentError('Unknown URL pattern: $url');
}
}

void gotoPath(String path, String title) {
var url = _getUrl(path);
if (url != null) {
_go(path, title);
_currentPath = path;
_handlers[url](path);
}
}
Expand All @@ -130,7 +152,7 @@ class Router {
window.history.pushState(null, title, path);
}
}

/**
* Returns an [Event] handler suitable for use as a click handler on [:<a>;]
* elements. The handler reverses [ur] with [args] and uses [window.pushState]
Expand Down
37 changes: 37 additions & 0 deletions test/client_listen_fragment.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<!--
Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
for details. All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
-->
<html lang="en">
<head>
<title>client_test</title>
<div><a id="a_with_path" href="/path">/path</a></div>
<div><a id="a_with_fragment" href="#fragment">#fragment</a></div>
</head>
<body>
<script type="application/javascript">
if (navigator.webkitStartDart) {
navigator.webkitStartDart();
}
</script>
<script type="application/dart">
import 'dart:html';
import 'package:unittest/unittest.dart';
import 'package:route/client.dart';

main() {
test('route on hash changed when useFragment == true', () {
var router = new Router(useFragment: true);
var urlWithFragment = new UrlPattern(r'(.*)#fragment');
router.addHandler(urlWithFragment, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('fragment')));
}));
router.listen();
window.location.hash = "fragment";
});
}
</script>
</body>
</html>
37 changes: 37 additions & 0 deletions test/client_listen_fragment_start.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<!--
Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
for details. All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
-->
<html lang="en">
<head>
<title>client_test</title>
<div><a id="a_with_path" href="/path">/path</a></div>
<div><a id="a_with_fragment" href="#fragment">#fragment</a></div>
</head>
<body>
<script type="application/javascript">
if (navigator.webkitStartDart) {
navigator.webkitStartDart();
}
</script>
<script type="application/dart">
import 'dart:html';
import 'package:unittest/unittest.dart';
import 'package:route/client.dart';

main() {
test('route after calling listen when useFragment == true', () {
var router = new Router(useFragment: true);
var urlWithFragment = new UrlPattern(r'(.*)#fragment');
router.addHandler(urlWithFragment, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('fragment')));
}));
window.location.hash = "fragment";
expect(router.listen(), true);
});
}
</script>
</body>
</html>
41 changes: 41 additions & 0 deletions test/client_listen_popstate.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<!--
Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
for details. All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
-->
<html lang="en">
<head>
<title>client_test</title>
<div><a id="a_with_path" href="/path">/path</a></div>
<div><a id="a_with_fragment" href="#fragment">#fragment</a></div>
</head>
<body>
<script type="application/javascript">
if (navigator.webkitStartDart) {
navigator.webkitStartDart();
}
</script>
<script type="application/dart">
import 'dart:html';
import 'package:unittest/unittest.dart';
import 'package:route/client.dart';

main() {
test('route on url changed', () {
var router = new Router();
var urlBar = new UrlPattern(r'(.*)/bar');
var urlFoo = new UrlPattern(r'(.*)/foo');
router.addHandler(urlBar, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('bar')));
}));
router.addHandler(urlFoo, (String path) {});
window.history.pushState(null, '', '${window.location}/bar');
window.history.pushState(null, '', '${window.location}/foo');
router.listen();
window.history.back();
});
}
</script>
</body>
</html>
37 changes: 37 additions & 0 deletions test/client_listen_popstate_start.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<!--
Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
for details. All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
-->
<html lang="en">
<head>
<title>client_test</title>
<div><a id="a_with_path" href="/path">/path</a></div>
<div><a id="a_with_fragment" href="#fragment">#fragment</a></div>
</head>
<body>
<script type="application/javascript">
if (navigator.webkitStartDart) {
navigator.webkitStartDart();
}
</script>
<script type="application/dart">
import 'dart:html';
import 'package:unittest/unittest.dart';
import 'package:route/client.dart';

main() {
test('route after calling listen', () {
var router = new Router();
var url = new UrlPattern(r'(.*)/bar');
router.addHandler(url, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('bar')));
}));
window.history.pushState(null, '', '${window.location}/bar');
expect(router.listen(), true);
});
}
</script>
</body>
</html>
30 changes: 30 additions & 0 deletions test/client_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,36 @@
router.handle(testPathFragment);
});

group('prevent handling the same path twice with pushState after', () {
var router;
var url = new UrlPattern(r'/foo#(\d+)');
var testPath = '/foo/123';
setUp(() {
router = new Router();
});
test('handle', () {
router.addHandler(url, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('/foo/123')));
}));
router.handle(testPath);
router.handle(testPath);
});
test('gotoPath', () {
router.addHandler(url, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('/foo/123')));
}));
router.gotoPath(testPath, '');
router.handle(testPath);
});
test('gotoUrl', () {
router.addHandler(url, expectAsync1((String path) {
expect(path, predicate((p) => p.endsWith('/foo/123')));
}));
router.gotoUrl(url, ['123'], '');
router.handle(testPath);
});
});

test('click handler with fragment is routed when useFragment == true', () {
var router = new Router(useFragment: true);
var urlWithFragment = new UrlPattern(r'(.*)#fragment');
Expand Down