Code Monkey home page Code Monkey logo

prouter's Introduction

prouter

build status Coverage Status

In rich web applications, without needing to reload the whole pages, we still want to provide linkable, bookmarkable, and shareable URLs to meaningful locations within the app. prouter provides methods for routing client-side pages and connecting them to actions and events. prouter is configurable, you can opt by using pushState and/or hashchange; and if you choose pushState, prouter can handle graceful fallback and transparent translation to the fragment version (#hash) of the URL if you wish.

Why prouter?

  • Unobtrusive: it is designed from the beginning to play well with UI libraries like Polymer, React, Handlebars, etc.
  • No dependencies and still really lightweight: 6kb.
  • Forward-thinking: learns from others Router components like the ones of Express, Backbone and Aurelia. Written in TypeScript for the future and transpiled to ES5 with UMD format for the present... thus it transparently supports almost every modules' style out there: commonJs, AMD. And as global browser variable.
  • KISS principle: unnecessary complexity avoided.
  • Proper JSDoc comments are used in all the source code.
  • Unit tests for every feature are automatically executed in major browsers: Chrome, Firefox and IE9+.

Special features

  • The powerful express's routing-expressions style now available on the client-side.
  • Group of routes. You can group your routes in a modular way, thus for example, you may organize your routes in external files, them import and mount them in the main file.
  • Complete request data is passed as a parameter (object with properties) to the activate callback.
  • Routing cycle (Nested paths): if the current callback want to continue the routing-cycle (i.e. processing next handler in the queue), it must do one of the following things.
    1. Call the next function. Also useful for async-nested callbacks (express's style).
    2. Or return true. Maintained for backwards compatibility.
  • Default handler - you may set a callback function for any routing without a path; thus this function will be executed for any path.

Routing

In client-side apps, routing refers to the declaration of end points (paths) to an application and how it responds to URL changes.

The Router component of prouter is essentially a collection of helpers which operates on a series of handlers. A handler is created in the following way Router.use(path, activate), where Router is the singleton instance provided by prouter, path is a path on the app, and activate is the function (callback) to be executed when the path is matched.

Installation

npm install prouter --save

# or

bower install prouter --save

Examples

basic with default options

var Router = prouter.Router;

// Adds a handler linked to the path 'about'.
Router.use('/about', function (req) {
  console.log(req);
  // {params: {}, query: {}, path: 'about', oldPath: undefined}
});

// Initialize the path-changes handling using default options.
Router.listen();

// Navigate to path 'about'.
Router.navigate('/about');

custom options and several handlers including default

var Router = prouter.Router;

Router.use('/', function(req) {

}).use('/login', function(req) {

}).use('/signup', function(req) {

}).use('/users/:username', function(req) {

}).use('/about', function (req) {
  console.log(req);
  // {params: {}, query: {}, path: 'about', oldPath: undefined}
}).use(function() {

});

// Initialize the Router with custom options (same as default for this example).
Router.listen({
  root: '/', // base path for the handlers.
  usePushState: false, // is pushState of history API desired?
  hashChange: true, // is hashChange desired?
  silent: false // don't try to load handlers for the current path?
});

Router.navigate('/about');

console.log(Router.getCurrent());
// 'about'

leading slashes (/) does not differentiate paths

var Router = prouter.Router;

var counter = 0;

Router.use('docs', function (req) {
  counter++;
}).listen();

Router.navigate('docs');
console.log(Router.getCurrent());
// 'docs'

Router.navigate('/docs');
console.log(Router.getCurrent());
// 'docs'

Router.navigate('/docs/');
console.log(Router.getCurrent());
// 'docs'

Router.navigate('docs/');
console.log(Router.getCurrent());
// 'docs'

console.log(counter);
// 4

default handler

var Router = prouter.Router;

Router.use('other', function (req, next) {
  console.log(req);
  // {params: {}, query: {}, path: 'other', oldPath: undefined}
  req.params.something = 'any';
  next();
}).use(function (req) {
  console.log(req);
  // {params: {something: 'any'}, query: {}, path: 'other', oldPath: undefined}
}).listen();

Router.navigate('other');

parameters (path's tokens)

var Router = prouter.Router;

Router.use('about/:id/:num', function (req) {
  console.log(req);
  // {params: {id: '16', num: '18'}, query: {}, path: 'about/16/18', oldPath: undefined}
}).listen();

Router.navigate('about/16/18');

query (search string)

var Router = prouter.Router;

Router.use('about', function (req) {
  console.log(req);
  // {params: {}, query: {first: '5', second: '6'}, path: 'about', oldPath: undefined}}
}).listen();

Router.navigate('about?first=5&second=6');

parameters and query

var Router = prouter.Router;

Router.use('/about/:id/:num', function (req) {
  console.log(req);
  // {params: {id: '16', num: '18'}, query: {first: '5', second: '6'}, path: 'about/16/18', oldPath: undefined}}
}).listen();

Router.navigate('/about/16/18?first=5&second=6');

optional parameters

var Router = prouter.Router;

var counter = 0;

Router.use('docs/:section/:subsection?', function (req) {
  counter += parseInt(req.params.section);
  if (req.params.subsection) {
    counter += parseInt(req.params.subsection);
  }
}).listen();

Router.navigate('docs/1');
Router.navigate('docs/2/3');

console.log(counter);
// 6

custom match parameters

var Router = prouter.Router;

Router.use(':foo(\\d+)', function(req) {
  console.log(req);
  // {params: {foo: '123'}, query: {}, path: '123', oldPath: undefined}
}).use('some/:other(\\d+)', function(req) {
  console.log(req);
  // {params: {other: '987'}, query: {}, path: 'some/987', oldPath: '123'}
}).listen();

Router.navigate('123');
Router.navigate('some/987');

conditionally end routing cycle (can acts like a filter)

var Router = prouter.Router;

Router.use(function (req, next) {
  // Will enter here first since this is a default handler (any url)
  // and it is at first position in the queue (can acts like a filter).
  if (ifSomeCustomConditionIsMet) {
    // Continue routing-cycle.
    next();
  }
}).use('/about', function () {
  // Will enter here only if the previous callback call 'next' or return true.
});

Router.listen();

Router.navigate('/about');

nested paths - call next

var Router = prouter.Router;

var sequence = '';

Router.use('/about/docs', function (req, next) {
  sequence += '1';
  next();
}).use('about/docs/about', function (req, next) {
  sequence += '2';
  next();
}).use('/about/docs/stub', function (req, next) {
  sequence += '3';
  next();
}).use('/about/*', function () {
  sequence += '*';
}).listen();

Router.navigate('/about/docs');
Router.navigate('/about/docs/about');
Router.navigate('/about/docs/stub');

console.log(sequence);
// '1*2*3*

nested paths - return true

var Router = prouter.Router;

var sequence = '';

Router.use('/about/docs', function () {
  sequence += '1';
  return true;
}).use('about/docs/about', function () {
  sequence += '2';
  return true;
}).use('/about/docs/stub', function () {
  sequence += '3';
  return true;
}).use('/about/*', function () {
  sequence += '*';
}).listen();

Router.navigate('/about/docs');
Router.navigate('/about/docs/about');
Router.navigate('/about/docs/stub');

console.log(sequence);
// '1*2*3*

route group

var Router = prouter.Router;
var RouteGroup = prouter.RouteGroup;

var sequence = '';

// Note that each route-group may be placed in a separate file.
var userGroup = new RouteGroup();
userGroup.use('', function () {
  sequence += '1';
}).use(':id', function () {
  sequence += '2';
});

// Note that each route-group may be placed in a separate file.
var aboutGroup = new RouteGroup();
aboutGroup.use('owner', function () {
  sequence += '3';
}).use('year/:num', function () {
  sequence += '4';
});

Router
  .use('users', userGroup)
  .use('about', aboutGroup)
  .listen();

Router.navigate('users');
Router.navigate('users/9');
Router.navigate('about/owner');
Router.navigate('about/year/2015');

console.log(sequence);
// '1234'

Supported Routing expressions (compatible with the express's routing-expressions style):

Named Parameters

Named parameters are defined by prefixing a colon to the parameter name (:foo). By default, this parameter will match up to the next path segment.

':foo/:bar'
// Given 'any/thing' => newRouteData.params = {foo: 'any', bar: 'thing'}

Suffixed Parameters

Optional

Parameters can be suffixed with a question mark (?) to make the entire parameter optional.

':foo/:bar?'
// Given 'any' => newRouteData.params = {foo: 'any', bar: undefined}
// Given 'any/thing' => newRouteData.params = {foo: 'any', bar: 'thing'}

Zero or more

Parameters can be suffixed with an asterisk (*) to denote a zero or more parameter match.

':foo*'
// Given '' => newRouteData.params = {foo: ''}
// Given 'one/two/three' => newRouteData.params = {foo: 'one/two/three'}

One or more

Parameters can be suffixed with a plus sign (+) to denote a one or more parameters match.

':foo+'
// Given 'one/two/three' => newRouteData.params = {foo: 'one/two/three'}

Custom Match Parameters

All parameters can be provided a custom matching regexp and override the default. Please note: Backslashes need to be escaped in strings.

':foo(\\d+)'
// Given '123' => newRouteData.params = {foo: '123'}

Unnamed Parameters

It is possible to write an unnamed parameter that is only a matching group. It works the same as a named parameter, except it will be numerically indexed.

':foo/(.*)'
// Given 'test/route' => newRouteData.params = {foo: 'test', '0': 'route'}

Asterisk

An asterisk can be used for matching everything. It is equivalent to an unnamed matching group of (.*).

'foo/*'
// Given 'foo/bar/baz' => newRouteData.params = {'0': 'bar/baz'}

':foo/*'
// Given 'foo/bar/baz' => newRouteData.params = {foo: 'foo', '0': 'bar/baz'}

See unit tests for more detailed use cases.

prouter's People

Contributors

rogerpadilla avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.