Tiny client-side router
npm install pageTiny Express-inspired client-side router.



``js`
page('/', index)
page('/user/:user', show)
page('/user/:user/edit', edit)
page('/user/:user/album', album)
page('/user/:user/album/sort', sort)
page('*', notfound)
page()
There are multiple ways to install page.js.
With package managers:
`bash`
$ npm install page # for browserify
$ component install visionmedia/page.js
$ bower install visionmedia/page.js
Or use with a CDN. We support:
Using with global script tags:
`html`
Or with modules, in modern browsers:
`html`
To run examples do the following to install dev dependencies and run the example server:
$ git clone git://github.com/visionmedia/page.js
$ cd page.js
$ npm install
$ node examples
$ open http://localhost:4000
Currently we have examples for:
- basic minimal application showing basic routingnotfound
- similar to basic with single-page 404 supportalbum
- showing pagination and external linksprofile
- simple user profilesquery-string
- shows how you can integrate plugins using the routerstate
- illustrates how the history state may be used to cache dataserver
- illustrates how to use the dispatch option to server initial contentchrome
- Google Chrome style administration interfacetransitions
- Shows off a simple technique for adding transitions between "pages"partials
- using hogan.js to render mustache partials client side
__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
directly related to page.js in any way.
Defines a route mapping path to the given callback(s).next
Each callback is invoked with two arguments, context and . Much like Express invoking next will call the next registered callback with the given path.
`js`
page('/', user.list)
page('/user/:id', user.load, user.show)
page('/user/:id/edit', user.load, user.edit)
page('*', notfound)
Under certain conditions, links will be disregarded
and will not be dispatched, such as:
- Links that are not of the same origin
- Links with the download attributetarget
- Links with the attributerel="external"
- Links with the attribute
This is equivalent to page('*', callback) for generic "middleware".
Navigate to the given path.
`js`
$('.view').click(function(e){
page('/user/12')
e.preventDefault()
})
Setup redirect from one path to another.
Identical to page(fromPath, toPath)
`js
page('/default', function(){
// some logic to decide which route to redirect to
if(admin) {
page.redirect('/admin');
} else {
page.redirect('/guest');
}
});
page('/default');
`
Identical to page(path) above.
Register page's popstate / click bindings. If you're{ click: false }
doing selective binding you'll like want to pass
to specify this yourself. The following options are available:
- click bind to click events [__true__]popstate
- bind to popstate [__true__]dispatch
- perform initial dispatch [__true__]hashbang
- add #! before urls [__false__]decodeURLComponents
- remove URL encoding from path components (query string, pathname, hash) [__true__]window
- provide a window to control (by default it will control the main window)
If you wish to load serve initial content
from the server you likely will want to
set dispatch to __false__.
Identical to page([options]) above.
Unbind both the popstate and click handlers.
Get or set the base path. For example if page.js/blog/*
is operating within set the base path to "/blog".
Get or set the strict path matching mode to enable. If enabled/blog
will not match "/blog/" and /blog/ will not match "/blog".
Defines an exit route mapping path to the given callback(s).
Exit routes are called when a page changes, using the context
from the previous change. For example:
`js
page('/sidebar', function(ctx, next) {
sidebar.open = true
next()
})
page.exit('/sidebar', function(ctx, next) {
sidebar.open = false
next()
})
`
Equivalent to page.exit('*', callback).
Create a new page instance with the given options. Options provided
are the same as provided in page([options]) above. Use this if you need
to control multiple windows (like iframes or popups) in addition
to the main window.
`js`
var otherPage = page.create({ window: iframe.contentWindow });
otherPage('/', main);
This is the click handler used by page to handle routing when a user clicks an anchor like . This is exported for those who want to disable the click handling behavior with page.start({ click: false }), but still might want to dispatch based on the click handler's logic in some scenarios.
Routes are passed Context objects, these mayctx.user =
be used to share state, for example ,ctx.state
as well as the history "state" thatpushState
the API provides.
#### Context#save()
Saves the context using replaceState(). For example
this is useful for caching HTML or other resources
that were loaded for when a user presses "back".
#### Context#handled
If true, marks the context as handled to prevent [default 404 behaviour][404].
For example this is useful for the routes with interminate quantity of the
callbacks.
[404]: https://github.com/visionmedia/page.js#default-404-behaviour
#### 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.
The router uses the same string-to-regexp conversion
that Express does, so things like ":id", ":id?", and "*" work
as you might expect.
Another aspect that is much like Express is the ability to
pass multiple callbacks. You can use this to your advantage
to flatten nested callbacks, or simply to abstract components.
For example suppose you have a route to _edit_ users, and a
route to _view_ users. In both cases you need to load the user.
One way to achieve this is with several callbacks as shown here:
`js`
page('/user/:user', load, show)
page('/user/:user/edit', load, edit)
Using the * character we can alter this to match all
routes prefixed with "/user" to achieve the same result:
`js`
page('/user/*', load)
page('/user/:user', show)
page('/user/:user/edit', edit)
Likewise * can be used as catch-alls after all routes
acting as a 404 handler, before all routes, in-between and
so on. For example:
`js`
page('/user/:user', load, show)
page('*', function(){
$('body').text('Not found!')
})
By default when a route is not matched,
page.js invokes page.stop() to unbind
itself, and proceed with redirecting to the
location requested. This means you may use
page.js with a multi-page application _without_
explicitly binding to certain links.
Much like request and response objects areload
passed around in Express, page.js has a single
"Context" object. Using the previous examples
of and show for a user, we can assignctx
arbitrary properties to to maintain state
between callbacks.
To build a load function that will loadctx.params.NAME
the user for subsequent routes you'll need to
access the ":id" passed. You can do this with
much like Express:
`js`
function load(ctx, next){
var id = ctx.params.id
}
Then perform some kind of action against the server,
assigning the user to ctx.user for other routes tonext()
utilize. is then invoked to pass control to
the following matching route in sequence, if any.
`js`
function load(ctx, next){
var id = ctx.params.id
$.getJSON('/user/' + id + '.json', function(user){
ctx.user = user
next()
})
}
The "show" function might look something like this,
however you may render templates or do anything you
want. Note that here next() is _not_ invoked, becausepage(path)
this is considered the "end point", and no routes
will be matched until another link is clicked or
is called.
`js`
function show(ctx){
$('body')
.empty()
.append('' + ctx.user.name + '
');
}
Finally using them like so:
`js`
page('/user/:id', load, show)
NOTE: The value of ctx.params.NAME is decoded via decodeURIComponent(sliceOfUrl). One exception though is the use of the plus sign (+) in the url, e.g. /user/john+doe, which is decoded to a space: ctx.params.id == 'john doe'. Also an encoded plus sign (%2B) is decoded to a space.
When working with the pushState API,
and page.js you may optionally provide
state objects available when the user navigates
the history.
For example if you had a photo application
and you performed a relatively extensive
search to populate a list of images,
normally when a user clicks "back" in
the browser the route would be invoked
and the query would be made yet-again.
An example implementation might look as follows:
`js`
function show(ctx){
$.getJSON('/photos', function(images){
displayImages(images)
})
}
You may utilize the history's state
object to cache this result, or any
other values you wish. This makes it
possible to completely omit the query
when a user presses back, providing
a much nicer experience.
`js`
function show(ctx){
if (ctx.state.images) {
displayImages(ctx.state.images)
} else {
$.getJSON('/photos', function(images){
ctx.state.images = images
ctx.save()
displayImages(images)
})
}
}
__NOTE__: ctx.save() must be used
if the state changes _after_ the first
tick (xhr, setTimeout, etc), otherwise
it is optional and the state will be
saved after dispatching.
Here are some examples of what's possible
with the string to RegExp conversion.
Match an explicit path:
`js`
page('/about', callback)
Match with required parameter accessed via ctx.params.name:
`js`
page('/user/:name', callback)
Match with several params, for example /user/tj/edit or/user/tj/view
.
`js`
page('/user/:name/:operation', callback)
Match with one optional and one required, now /user/tj/user/tj/show
will match the same route as etc:
`js`
page('/user/:name/:operation?', callback)
Use the wildcard char * to match across segments,ctx.params[N]
available via where __N__ is the*
index of since you may use several. For example/user/12/edit
the following will match , /user/12/albums/2/admin
and so on.
`js`
page('/user/*', loadUser)
Named wildcard accessed, for example /file/javascripts/jquery.jsctx.params.file
would provide "/javascripts/jquery.js" as :
`js`
page('/file/:file(.*)', loadUser)
And of course RegExp literals, where the capturectx.params[N]
groups are available via where __N__
is the index of the capture group.
`js`
page(/^\/commits\/(\d+)\.\.(\d+)/, loadUser)
An example plugin _examples/query-string/query.js_
demonstrates how to make plugins. It will provide a parsed ctx.query object
derived from node-querystring.
Usage by using "*" to match any path
in order to parse the query-string:
`js
page('*', parse)
page('/', show)
page()
function parse(ctx, next) {
ctx.query = qs.parse(location.search.slice(1));
next();
}
function show(ctx) {
if (Object.keys(ctx.query).length) {
document
.querySelector('pre')
.textContent = JSON.stringify(ctx.query, null, 2);
}
}
`
- querystring: provides a parsed ctx.query object derived from node-querystring.req.body
- body-parser: provides a object for routes derived from body-parser.
- express-mapper: provides a direct imitation of the Express API so you can share controller code on the client and the server with your Express application without modification.
Please submit pull requests to add more to this list.
In the console:
``
$ npm install
$ npm test
In the browser:
``
$ npm install
$ npm run serve
$ open http://localhost:3000/
If you want the router to work in older version of Internet Explorer that don't support pushState, you can use the HTML5-History-API polyfill:
`bash`
npm install html5-history-api
##### How to use a Polyfill together with router (OPTIONAL):
If your web app is located within a nested basepath, you will need to specify the basepath for the HTML5-History-API polyfill.page.base()
Before calling use: history.redirect([prefixType], [basepath]) - Translation link if required.prefixType
* : [string|null] - Substitute the string after the anchor (#) by default "/".basepath
* : [string|null] - Set the base path. See page.base() by default "/". (Note: Slash after pathname required)
* Break commits into a single objective.
* An objective should be a chunk of code that is related but requires explanation.
* Commits should be in the form of what-it-is: how-it-does-it and or why-it's-needed or what-it-is for trivial changes
* Pull requests and commits should be a guide to the code.
In order to load and update any URL managed by page.js, you need to configure your environment to point to your project's main file (index.html, for example) for each non-existent URL. Below you will find examples for most common server scenarios.
If using Nginx, add this to the .conf file related to your project (inside the "server" part), and reload your Nginx server:
`nginx`
location / {
try_files $uri $uri/ /index.html?$args;
}
If using Apache, create (or add to) the .htaccess file in the root of your public folder, with the code:
`apache
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^.*$ ./index.html
`
For development and/or production, using Express, you need to use express-history-api-fallback package. An example:
`js
import { join } from 'path';
import express from 'express';
import history from 'express-history-api-fallback';
const app = express();
const root = join(__dirname, '../public');
app.use(express.static(root));
app.use(history('index.html', { root }));
const server = app.listen(process.env.PORT || 3000);
export default server;
`
For development using Browsersync, you need to use history-api-fallback package. An example:
`js
var browserSync = require("browser-sync").create();
var historyApiFallback = require('connect-history-api-fallback');
browserSync.init({
files: [".html", "css/.css", "js/*.js"],
server: {
baseDir: ".",
middleware: [ historyApiFallback() ]
},
port: 3030
});
``
(The MIT License)
Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.