Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: cjblomqvist/page.js
base: 4739397466
...
head fork: cjblomqvist/page.js
compare: a8fb22f072
  • 17 commits
  • 13 files changed
  • 0 commit comments
  • 2 contributors
View
8 History.md
@@ -1,4 +1,12 @@
+1.2.0 / 2012-07-05
+==================
+
+ * add `ctx.pathname`
+ * add `ctx.querystring`
+ * add support for passing a query-string through the dispatcher [ovaillancourt]
+ * add `.defaultPrevented` support, ignoring page.js handling [ovaillancourt]
+
1.1.3 / 2012-06-18
==================
View
27 Readme.md
@@ -1,4 +1,3 @@
-
![page router logo](http://f.cl.ly/items/3i3n001d0s1Q031r2q1P/page.png)
Tiny ~1200 byte Express-inspired client-side router.
@@ -31,6 +30,7 @@ page()
- `state` illustrates how the history state may be used to cache data
- `server` illustrates how to use the dispatch option to server initial content
- `chrome` Google Chrome style administration interface
+ - `transitions` Shows off a simple technique for adding transitions between "pages"
__NOTE__: keep in mind these examples do not use jQuery or similar, so
portions of the examples may be relatively verbose, though they're not
@@ -105,6 +105,30 @@ $('.view').click(function(e){
as well as the history "state" `ctx.state` that
the `pushState` API provides.
+## Context#canonicalPath
+
+ Pathname including the "base" (if any) and query string "/admin/login?foo=bar".
+
+## Context#path
+
+ Pathname and query string "/login?foo=bar".
+
+## Context#querystring
+
+ Query string void of leading `?` such as "foo=bar", defaults to "".
+
+## Context#pathname
+
+ The pathname void of query string "/login".
+
+## Context#state
+
+ The `pushState` state object.
+
+## Context#title
+
+ The `pushState` title.
+
## Routing
The router uses the same string-to-regexp conversion
@@ -318,7 +342,6 @@ page('/file/:file(*)', loadUser)
page(/^\/commits\/(\d+)\.\.(\d+)/, loadUser)
```
-
## Plugins
Currently there are no official plugins,
View
14 build/page.js
@@ -88,7 +88,7 @@
if (false !== options.popstate) addEventListener('popstate', onpopstate, false);
if (false !== options.click) addEventListener('click', onclick, false);
if (!dispatch) return;
- page.replace(location.pathname, null, true, dispatch);
+ page.replace(location.pathname + location.search, null, true, dispatch);
};
/**
@@ -183,11 +183,14 @@
function Context(path, state) {
if ('/' == path[0] && 0 != path.indexOf(base)) path = base + path;
+ var i = path.indexOf('?');
this.canonicalPath = path;
this.path = path.replace(base, '') || '/';
this.title = document.title;
this.state = state || {};
this.state.path = path;
+ this.querystring = ~i ? path.slice(i + 1) : '';
+ this.pathname = ~i ? path.slice(0, i) : path;
this.params = [];
}
@@ -264,8 +267,10 @@
Route.prototype.match = function(path, params){
var keys = this.keys
- , m = this.regexp.exec(path);
-
+ , qsIndex = path.indexOf('?')
+ , pathname = ~qsIndex ? path.slice(0, qsIndex) : path
+ , m = this.regexp.exec(pathname);
+
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
@@ -343,11 +348,12 @@
*/
function onclick(e) {
+ if (e.defaultPrevented) return;
var el = e.target;
while (el && 'A' != el.nodeName) el = el.parentNode;
if (!el || 'A' != el.nodeName) return;
var href = el.href;
- var path = el.pathname;
+ var path = el.pathname + el.search;
if (el.hash) return;
if (!sameOrigin(href)) return;
var orig = path;
View
2  build/page.min.js
@@ -1 +1 @@
-(function(){var dispatch=!0,base="",running;function page(path,fn){if("function"==typeof fn){var route=new Route(path);for(var i=1;i<arguments.length;++i)page.callbacks.push(route.middleware(arguments[i]))}else"string"==typeof path?page.show(path,fn):page.start(path)}page.callbacks=[],page.base=function(path){if(0==arguments.length)return base;base=path},page.start=function(options){options=options||{};if(running)return;running=!0,!1===options.dispatch&&(dispatch=!1),!1!==options.popstate&&addEventListener("popstate",onpopstate,!1),!1!==options.click&&addEventListener("click",onclick,!1);if(!dispatch)return;page.replace(location.pathname,null,!0,dispatch)},page.stop=function(){running=!1,removeEventListener("click",onclick,!1),removeEventListener("popstate",onpopstate,!1)},page.show=function(path,state){var ctx=new Context(path,state);return page.dispatch(ctx),ctx.unhandled||ctx.pushState(),ctx},page.replace=function(path,state,init,dispatch){var ctx=new Context(path,state);return ctx.init=init,null==dispatch&&(dispatch=!0),dispatch&&page.dispatch(ctx),ctx.save(),ctx},page.dispatch=function(ctx){var i=0;function next(){var fn=page.callbacks[i++];if(!fn)return unhandled(ctx);fn(ctx,next)}next()};function unhandled(ctx){if(window.location.pathname==ctx.canonicalPath)return;page.stop(),ctx.unhandled=!0,window.location=ctx.canonicalPath}function Context(path,state){"/"==path[0]&&0!=path.indexOf(base)&&(path=base+path),this.canonicalPath=path,this.path=path.replace(base,"")||"/",this.title=document.title,this.state=state||{},this.state.path=path,this.params=[]}Context.prototype.pushState=function(){history.pushState(this.state,this.title,this.canonicalPath)},Context.prototype.save=function(){history.replaceState(this.state,this.title,this.canonicalPath)};function Route(path,options){options=options||{},this.path=path,this.method="GET",this.regexp=pathtoRegexp(path,this.keys=[],options.sensitive,options.strict)}Route.prototype.middleware=function(fn){var self=this;return function(ctx,next){if(self.match(ctx.path,ctx.params))return fn(ctx,next);next()}},Route.prototype.match=function(path,params){var keys=this.keys,m=this.regexp.exec(path);if(!m)return!1;for(var i=1,len=m.length;i<len;++i){var key=keys[i-1],val="string"==typeof m[i]?decodeURIComponent(m[i]):m[i];key?params[key.name]=undefined!==params[key.name]?params[key.name]:val:params.push(val)}return!0};function pathtoRegexp(path,keys,sensitive,strict){return path instanceof RegExp?path:(path instanceof Array&&(path="("+path.join("|")+")"),path=path.concat(strict?"":"/?").replace(/\/\(/g,"(?:/").replace(/\+/g,"__plus__").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,function(_,slash,format,key,capture,optional){return keys.push({name:key,optional:!!optional}),slash=slash||"",""+(optional?"":slash)+"(?:"+(optional?slash:"")+(format||"")+(capture||format&&"([^/.]+?)"||"([^/]+?)")+")"+(optional||"")}).replace(/([\/.])/g,"\\$1").replace(/__plus__/g,"(.+)").replace(/\*/g,"(.*)"),new RegExp("^"+path+"$",sensitive?"":"i"))}function onpopstate(e){if(e.state){var path=e.state.path;page.replace(path,e.state)}}function onclick(e){var el=e.target;while(el&&"A"!=el.nodeName)el=el.parentNode;if(!el||"A"!=el.nodeName)return;var href=el.href,path=el.pathname;if(el.hash)return;if(!sameOrigin(href))return;var orig=path;path=path.replace(base,"");if(base&&orig==path)return;e.preventDefault(),page.show(orig)}function sameOrigin(href){var origin=location.protocol+"//"+location.hostname;return location.port&&(origin+=":"+location.port),0==href.indexOf(origin)}"undefined"==typeof module?window.page=page:module.exports=page})();
+(function(){var dispatch=!0,base="",running;function page(path,fn){if("function"==typeof fn){var route=new Route(path);for(var i=1;i<arguments.length;++i)page.callbacks.push(route.middleware(arguments[i]))}else"string"==typeof path?page.show(path,fn):page.start(path)}page.callbacks=[],page.base=function(path){if(0==arguments.length)return base;base=path},page.start=function(options){options=options||{};if(running)return;running=!0,!1===options.dispatch&&(dispatch=!1),!1!==options.popstate&&addEventListener("popstate",onpopstate,!1),!1!==options.click&&addEventListener("click",onclick,!1);if(!dispatch)return;page.replace(location.pathname+location.search,null,!0,dispatch)},page.stop=function(){running=!1,removeEventListener("click",onclick,!1),removeEventListener("popstate",onpopstate,!1)},page.show=function(path,state){var ctx=new Context(path,state);return page.dispatch(ctx),ctx.unhandled||ctx.pushState(),ctx},page.replace=function(path,state,init,dispatch){var ctx=new Context(path,state);return ctx.init=init,null==dispatch&&(dispatch=!0),dispatch&&page.dispatch(ctx),ctx.save(),ctx},page.dispatch=function(ctx){var i=0;function next(){var fn=page.callbacks[i++];if(!fn)return unhandled(ctx);fn(ctx,next)}next()};function unhandled(ctx){if(window.location.pathname==ctx.canonicalPath)return;page.stop(),ctx.unhandled=!0,window.location=ctx.canonicalPath}function Context(path,state){"/"==path[0]&&0!=path.indexOf(base)&&(path=base+path);var i=path.indexOf("?");this.canonicalPath=path,this.path=path.replace(base,"")||"/",this.title=document.title,this.state=state||{},this.state.path=path,this.querystring=~i?path.slice(i+1):"",this.pathname=~i?path.slice(0,i):path,this.params=[]}Context.prototype.pushState=function(){history.pushState(this.state,this.title,this.canonicalPath)},Context.prototype.save=function(){history.replaceState(this.state,this.title,this.canonicalPath)};function Route(path,options){options=options||{},this.path=path,this.method="GET",this.regexp=pathtoRegexp(path,this.keys=[],options.sensitive,options.strict)}Route.prototype.middleware=function(fn){var self=this;return function(ctx,next){if(self.match(ctx.path,ctx.params))return fn(ctx,next);next()}},Route.prototype.match=function(path,params){var keys=this.keys,qsIndex=path.indexOf("?"),pathname=~qsIndex?path.slice(0,qsIndex):path,m=this.regexp.exec(pathname);if(!m)return!1;for(var i=1,len=m.length;i<len;++i){var key=keys[i-1],val="string"==typeof m[i]?decodeURIComponent(m[i]):m[i];key?params[key.name]=undefined!==params[key.name]?params[key.name]:val:params.push(val)}return!0};function pathtoRegexp(path,keys,sensitive,strict){return path instanceof RegExp?path:(path instanceof Array&&(path="("+path.join("|")+")"),path=path.concat(strict?"":"/?").replace(/\/\(/g,"(?:/").replace(/\+/g,"__plus__").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,function(_,slash,format,key,capture,optional){return keys.push({name:key,optional:!!optional}),slash=slash||"",""+(optional?"":slash)+"(?:"+(optional?slash:"")+(format||"")+(capture||format&&"([^/.]+?)"||"([^/]+?)")+")"+(optional||"")}).replace(/([\/.])/g,"\\$1").replace(/__plus__/g,"(.+)").replace(/\*/g,"(.*)"),new RegExp("^"+path+"$",sensitive?"":"i"))}function onpopstate(e){if(e.state){var path=e.state.path;page.replace(path,e.state)}}function onclick(e){if(e.defaultPrevented)return;var el=e.target;while(el&&"A"!=el.nodeName)el=el.parentNode;if(!el||"A"!=el.nodeName)return;var href=el.href,path=el.pathname+el.search;if(el.hash)return;if(!sameOrigin(href))return;var orig=path;path=path.replace(base,"");if(base&&orig==path)return;e.preventDefault(),page.show(orig)}function sameOrigin(href){var origin=location.protocol+"//"+location.hostname;return location.port&&(origin+=":"+location.port),0==href.indexOf(origin)}"undefined"==typeof module?window.page=page:module.exports=page})();
View
2  examples/state/style.css
@@ -14,4 +14,4 @@ a {
a:hover {
text-decoration: underline;
-}
+}
View
42 examples/transitions/app.js
@@ -0,0 +1,42 @@
+
+// content
+
+var content = document.querySelector('#content');
+
+// current page indicator
+
+var p = document.querySelector('#page');
+
+// "mount" it
+
+page.base('/transitions');
+
+// transition "middleware"
+
+page('*', function(ctx, next){
+ if (ctx.init) {
+ next();
+ } else {
+ content.classList.add('transition');
+ setTimeout(function(){
+ content.classList.remove('transition');
+ next();
+ }, 300);
+ }
+})
+
+// regular pages
+
+page('/', function(){
+ p.textContent = '';
+});
+
+page('/contact', function(){
+ p.textContent = 'contact page';
+});
+
+page('/about', function(){
+ p.textContent = 'about page';
+});
+
+page()
View
BIN  examples/transitions/bg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
22 examples/transitions/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Page.js - transitions</title>
+ <script src="/page.js"></script>
+ <link rel="stylesheet" href="style.css" />
+ </head>
+ <body>
+ <section id="content">
+ <img src="/transitions/logo.png" id="logo" />
+
+ <p id="page"></p>
+
+ <nav>
+ <a href="/transitions">home</a>
+ <a href="/transitions/contact">contact</a>
+ <a href="/transitions/about">about</a>
+ </nav>
+ </section>
+ <script src="app.js"></script>
+ </body>
+</html>
View
BIN  examples/transitions/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
53 examples/transitions/style.css
@@ -0,0 +1,53 @@
+
+body {
+ background: url(/transitions/bg.png);
+ text-align: center;
+ font: 200 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ letter-spacing: .1em;
+}
+
+#content {
+ margin: 100px auto;
+ padding: 100px 50px 80px 50px;
+ width: 500px;
+ background: white;
+ position: relative;
+ opacity: 1;
+ -webkit-border-radius: 2px;
+ -webkit-box-shadow: 0 0 10px black;
+ -webkit-transition: -webkit-transform 300ms ease-out, opacity 300ms ease-out;
+}
+
+#content.transition {
+ -webkit-transform: translateX(100px);
+ opacity: 0;
+}
+
+#page {
+ margin-top: 40px;
+}
+
+nav {
+ position: absolute;
+ bottom: -26px;
+ left: 0;
+}
+
+nav a {
+ text-decoration: none;
+ color: rgba(255,255,255,.3);
+ background: -webkit-linear-gradient(#444, #3a3a3a);
+ display: inline-block;
+ padding: 5px 10px;
+ font-size: 12px;
+ -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.6), inset 0 -1px 0 rgba(255,255,255,.05);
+ -webkit-border-radius: 0 0 2px 2px;
+}
+
+nav a:hover {
+ color: white;
+}
+
+nav a:active {
+ -webkit-box-shadow: 0 0 2px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.05);
+}
View
14 lib/page.js
@@ -88,7 +88,7 @@
if (false !== options.popstate) addEventListener('popstate', onpopstate, false);
if (false !== options.click) addEventListener('click', onclick, false);
if (!dispatch) return;
- page.replace(location.pathname, null, true, dispatch);
+ page.replace(location.pathname + location.search, null, true, dispatch);
};
/**
@@ -183,11 +183,14 @@
function Context(path, state) {
if ('/' == path[0] && 0 != path.indexOf(base)) path = base + path;
+ var i = path.indexOf('?');
this.canonicalPath = path;
this.path = path.replace(base, '') || '/';
this.title = document.title;
this.state = state || {};
this.state.path = path;
+ this.querystring = ~i ? path.slice(i + 1) : '';
+ this.pathname = ~i ? path.slice(0, i) : path;
this.params = [];
}
@@ -264,8 +267,10 @@
Route.prototype.match = function(path, params){
var keys = this.keys
- , m = this.regexp.exec(path);
-
+ , qsIndex = path.indexOf('?')
+ , pathname = ~qsIndex ? path.slice(0, qsIndex) : path
+ , m = this.regexp.exec(pathname);
+
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
@@ -343,11 +348,12 @@
*/
function onclick(e) {
+ if (e.defaultPrevented) return;
var el = e.target;
while (el && 'A' != el.nodeName) el = el.parentNode;
if (!el || 'A' != el.nodeName) return;
var href = el.href;
- var path = el.pathname;
+ var path = el.pathname + el.search;
if (el.hash) return;
if (!sameOrigin(href)) return;
var orig = path;
View
2  package.json
@@ -1,7 +1,7 @@
{
"name": "page",
"description": "Tiny client-side router (~1200 bytes)",
- "version": "1.1.3",
+ "version": "1.2.0",
"repository": {
"type": "git",
"url": "git://github.com/visionmedia/page.js.git"
View
96 test/tests.js
@@ -15,8 +15,65 @@ describe('page', function(){
})
})
- describe('when the route matches', function(){
- it('should invoke the callback', function(done){
+ describe('ctx.querystring', function(){
+ it('should default to ""', function(done){
+ page('/querystring-default', function(ctx){
+ expect(ctx.querystring).to.equal('');
+ done();
+ });
+
+ page('/querystring-default');
+ })
+
+ it('should expose the query string', function(done){
+ page('/querystring', function(ctx){
+ expect(ctx.querystring).to.equal('hello=there');
+ done();
+ });
+
+ page('/querystring?hello=there');
+ })
+ })
+
+ describe('ctx.pathname', function(){
+ it('should default to ctx.path', function(done){
+ page('/pathname-default', function(ctx){
+ expect(ctx.pathname).to.equal('/pathname-default');
+ done();
+ });
+
+ page('/pathname-default');
+ })
+
+ it('should omit the query string', function(done){
+ page('/pathname', function(ctx){
+ expect(ctx.pathname).to.equal('/pathname');
+ done();
+ });
+
+ page('/pathname?hello=there');
+ })
+ })
+
+ describe('dispatcher', function(){
+ it('should ignore query strings', function(done){
+ page('/qs', function(ctx){
+ done();
+ });
+
+ page('/qs?test=true');
+ })
+
+ it('should ignore query strings with params', function(done){
+ page('/qs/:name', function(ctx){
+ expect(ctx.params.name).to.equal('tobi');
+ done();
+ });
+
+ page('/qs/tobi?test=true');
+ })
+
+ it('should invoke the matching callback', function(done){
page('/user/:name', function(ctx){
done();
})
@@ -32,29 +89,32 @@ describe('page', function(){
page('/blog/post/something');
})
- })
- describe('when next() is called', function(){
- it('should invoke the next matching route', function(done){
+ describe('when next() is invoked', function(){
+ it('should invoke subsequent matching middleware', function(done){
+ page('/forum/*', function(ctx, next){
+ ctx.fullPath = ctx.params[0];
+ next();
+ });
- page('/forum/*', function(ctx, next){
- ctx.fullPath = ctx.params[0];
- next();
- });
+ page('/user', function(){
- page('/user', function(){
-
- });
+ });
- page('/forum/:fid/thread/:tid', function(ctx){
- expect(ctx.fullPath).to.equal('1/thread/2');
- expect(ctx.params.tid).to.equal('2');
- done();
- });
+ page('/forum/:fid/thread/:tid', function(ctx){
+ expect(ctx.fullPath).to.equal('1/thread/2');
+ expect(ctx.params.tid).to.equal('2');
+ done();
+ });
- page('/forum/1/thread/2');
+ page('/forum/1/thread/2');
+ })
})
})
+
+ after(function(){
+ page('/');
+ })
})
page();

No commit comments for this range

Something went wrong with that request. Please try again.