UNPKG

grapnel.js

Version:

The first (and smallest!) JavaScript Router with PushState, Middleware, and Named Parameter support

399 lines (318 loc) 10 kB
Grapnel ========== #### The first (started in 2010!) Client/Server-Side JavaScript Router with Named Parameters, HTML5 pushState, and Middleware support. ## Download/Installation **Download Source:** - [Production](https://raw.githubusercontent.com/baseprime/grapnel/master/dist/grapnel.min.js) - [Development](https://raw.githubusercontent.com/baseprime/grapnel/development/dist/grapnel.js) **Install with npm** ```bash npm install grapnel ``` **Or by using bower:** ```bash bower install grapnel ``` **Server only:** (with HTTP methods added, [more info](https://github.com/baseprime/grapnel-server)) ```bash npm install grapnel-server ``` # Grapnel Features - Supports routing using `pushState` or `hashchange` concurrently - Supports Named Parameters similar to Express, Sinatra, and Restify - Middleware Support - Works on the client or server side - RegExp Support - Supports `#` or `#!` for `hashchange` routing - Unobtrusive, supports multiple routers on the same page - No dependencies ## Basic Router ```javascript const router = new Grapnel(); router.get('products/:category/:id?', function(req) { let id = req.params.id; let category = req.params.category; // GET http://mysite.com/#products/widgets/134 console.log(category, id); // => widgets 134 }); ``` ## Using HTML5 pushState ```javascript const router = new Grapnel({ pushState : true }); router.get('/products/:category/:id?', function(req) { let id = req.params.id; let category = req.params.category; console.log(category, id); }); router.navigate('/products/widgets/134'); // => widgets 134 ``` ## Named Parameters Grapnel supports regex style routes similar to Sinatra, Restify, and Express. The properties are mapped to the parameters in the request. ```javascript router.get('products/:id?', function(req) { // GET /file.html#products/134 console.log(req.params.id); // => 134 }); router.get('products/*', function(req) { // The wildcard/asterisk will match anything after that point in the URL // Parameters are provided req.params using req.params[n], where n is the nth capture }); ``` ## Middleware Support Grapnel also supports middleware: ```javascript let auth = function(req, event, next) { user.auth(function(err) { req.user = this; next(); }); } router.get('/*', auth, function(req) { console.log(req.user); }); ``` ## Route Context You can add context to a route and even use it with middleware: ```javascript let usersRoute = router.context('/user/:id', getUser, getFollowers); // Middleware can be used here usersRoute('/', function(req, event) { console.log('Profile', req.params.id); }); usersRoute('/followers', otherMiddleware, function(req, event) { // Middleware can be used here too console.log('Followers', req.params.id); }); router.navigate('/user/13589'); // => Profile 13589 router.navigate('/user/13589/followers'); // => Followers 13589 ``` ## Works as a server-side router ```javascript import { createServer } from 'http'; import Grapnel from 'grapnel'; const app = new Grapnel(); app.get('/', function(req, route) { route.res.end('Hello World!', 200); }); createServer(function(req, res) { app.once('match', function(route) { route.res = res; }).navigate(req.url); }).listen(3000); ``` **This is now simplified as a separate package** ([more info](https://github.com/baseprime/grapnel/tree/server-router)) ```bash npm install grapnel-server ``` ## Declaring Multiple Routes ```javascript let routes = { 'products' : function(req) { // GET /file.html#products }, 'products/:category/:id?' : function(req) { // GET /file.html#products/widgets/35 console.log(req.params.category); // => widgets } } Grapnel.listen(routes); ``` ## Event Handling ```javascript const router = new Grapnel({ pushState : true, root : '/' }); router.on('navigate', function(event){ // GET /foo/bar console.log('URL changed to %s', this.path()); // => URL changed to /foo/bar }); ``` ## RegExp Support Grapnel allows RegEx when defining a route: ```javascript const router = new Grapnel(); let expression = /^food\/tacos\/(.*)$/i; router.get(expression, function(req, event){ // GET http://mysite.com/page#food/tacos/good console.log('I think tacos are %s.', req.params[0]); // => "He thinks tacos are good." }); ```   *** # Usage & Tips ## Basic Configuration ```javascript const router = new Grapnel(); ``` ## Enabling PushState ```javascript const router = new Grapnel({ pushState : true }); ``` You can also specify a root URL by setting it as an option: ```javascript const router = new Grapnel({ root : '/app', pushState : true }); ``` The root may require a beginning slash and a trailing slash depending on how you set up your routes. ## Middleware Grapnel uses middleware similar to how Express uses middleware. Middleware has access to the `req` object, `route` object, and the next middleware in the call stack (commonly denoted as `next`). Middleware must call `next()` to pass control to the next middleware, otherwise the router will stop. For more information about how middleware works, see [Using Middleware](http://expressjs.com/guide/using-middleware.html). ```javascript let user = function(req, route, next) { user.get(function(err) { req.user = this; next(); }); } router.get('/user/*', user, function(req) { console.log(req.user); }); ``` ## Declaring your routes with an object literal: ```javascript Grapnel.listen({ 'products/:id' : function(req) { // Handler } }); ``` When declaring routes with a literal object, router options can be passed as the first parameter: ```javascript let opts = { pushState : true }; Grapnel.listen(opts, routes); ``` ## Navigation If pushState is enabled, you can navigate through your application with `router.navigate`: ```javascript router.navigate('/products/123'); ``` ## Stopping a Route Event ```javascript router.on('match', function(routeEvent) { routeEvent.preventDefault(); // Stops event handler }); ``` ## Stopping Event Propagation ```javascript router.get('/products/:id', function(req, routeEvent) { routeEvent.stopPropagation(); // Stops propagation of the event }); router.get('/products/widgets', function(req, routeEvent) { // This will not be executed }); router.navigate('/products/widgets'); ``` ## 404 Pages You can specify a route that only uses a wildcard `*` as your final route, then use `route.parent()` which returns `false` if the call stack doesn't have any other routes to run. ```javascript let routes = { '/' : function(req, route) { // Handle route }, '/store/products/:id' : function(req, route) { // Handle route }, '/category/:id' : function(req, route) { // Handle route }, '/*' : function(req, route) { if(!route.parent()){ // Handle 404 } } } Grapnel.listen({ pushState : true }, routes); ``` ## Setting window state ```javascript router.navigate('/', { state: { ...windowState } }); ```   *** # Documentation ##### `get` Adds a listeners and middleware for routes ```javascript /** * @param {String|RegExp} path * @param {Function} [[middleware], callback] */ router.get('/store/:category/:id?', function(req, route){ let category = req.params.category; let id = req.params.id; console.log('Product #%s in %s', id, category); }); ``` ##### `navigate` Navigate through application ```javascript /** * @param {String} path relative to root * @param {Object} options navigation options */ router.navigate('/products/123', ...options); ``` ##### `on` Adds a new event listener ```javascript /** * @param {String} event name (multiple events can be called when separated by a space " ") * @param {Function} callback */ router.on('myevent', function(event) { console.log('Grapnel works!'); }); ``` ##### `once` A version of `on` except its handler will only be called once ```javascript /** * @param {String} event name (multiple events can be called when separated by a space " ") * @param {Function} callback */ router.once('init', function() { console.log('This will only be executed once'); }); ``` ##### `emit` Triggers an event ```javascript /** * @param {String} event name * @param {...Mixed} attributes Parameters that will be applied to event handler */ router.emit('event', eventArg1, eventArg2, ...etc); ``` ##### `context` Returns a function that can be called with a specific route in context. Both the `router.context` method and the function it returns can accept middleware. **Note: when calling `route.context`, you should omit the trailing slash.** ```javascript /** * @param {String} Route context (without trailing slash) * @param {[Function]} Middleware (optional) * @return {Function} Adds route to context */ let usersRoute = router.context('/user/:id'); usersRoute('/followers', function(req, route) { console.log('Followers', req.params.id); }); router.navigate('/user/13589/followers'); // => Followers 13589 ``` ##### `path` * `router.path('string')` Sets a new path or hash * `router.path()` Gets path or hash * `router.path(false)` Clears the path or hash ##### `bind` An alias of `on` ##### `trigger` An alias of `emit` ##### `add` An alias of `get` ## Options * `pushState` Enable pushState, allowing manipulation of browser history instead of using the `#` and `hashchange` event * `root` Root of your app, all navigation will be relative to this * `target` Target object where the router will apply its changes (default: `window`) * `hashBang` Enable `#!` as the anchor of a `hashchange` router instead of using just a `#` ## Events * `navigate` Fires when router navigates through history * `match` Fires when a new match is found, but before the handler is called * `hashchange` Fires when hashtag is changed ## License ##### [MIT License](http://opensource.org/licenses/MIT)