UNPKG

20.5 kBMarkdownView Raw
1# Director [![Build Status](https://secure.travis-ci.org/flatiron/director.png)](http://travis-ci.org/flatiron/director)
2
3# Overview
4Director is a router. Routing is the process of determining what code to run when a URL is requested. Director works on the client and the server. Director is dependency free, on the client it does not require any other libraries (such as jQuery).
5
6* [Client-Side Routing](#client-side)
7* [Server-Side HTTP Routing](#http-routing)
8* [Server-Side CLI Routing](#cli-routing)
9* [API Documentation](#api-documentation)
10* [Frequently Asked Questions](#faq)
11
12<a name="client-side"></a>
13## Client-side Routing
14It simply watches the hash of the URL to determine what to do, for example:
15
16```
17http://foo.com/#/bar
18```
19
20Client-side routing (aka hash-routing) allows you to specify some information about the state of the application using the URL. So that when the user visits a specific URL, the application can be transformed accordingly.
21
22<img src="https://github.com/flatiron/director/raw/master/img/hashRoute.png" />
23
24Here is a simple example:
25
26```html
27 <!html>
28 <html>
29 <head>
30 <script src="/director.js"></script>
31 <script>
32
33 var author = function () { /* ... */ },
34 books = function () { /* ... */ },
35 viewBook = function(bookId) { /* bookId is populated. */ };
36
37 var routes = {
38 '/author': author,
39 '/books': [books, function() { /* An inline route handler. */ }],
40 '/books/view/:bookId': viewBook
41 };
42
43 var router = Router(routes);
44 router.init();
45
46 </script>
47 </head>
48 <body>
49 </body>
50 </html>
51```
52
53Director works great with your favorite DOM library, such as jQuery.
54
55```html
56 <!html>
57 <html>
58 <head>
59 <script src="/director.js"></script>
60 <script>
61
62 //
63 // create some functions to be executed when
64 // the correct route is issued by the user.
65 //
66 var author = function () { /* ... */ },
67 books = function () { /* ... */ },
68 allroutes = function(route) {
69 var sections = $('section');
70 sections.hide();
71 sections.find('data-route[' + route + ']').show();
72 };
73
74 //
75 // define the routing table.
76 //
77 var routes = {
78 '/author': showAuthorInfo,
79 '/books': [showAuthorInfo, listBooks]
80 };
81
82 //
83 // instantiate the router.
84 //
85 var router = Router(routes);
86
87 //
88 // a global configuration setting.
89 //
90 router.configure({
91 on: allroutes
92 });
93 router.init();
94
95 </script>
96 </head>
97 <body>
98 <section data-route="author">Author Name</section>
99 <section data-route="books">Book1, Book2, Book3</section>
100 </body>
101 </html>
102```
103
104You can find a browser-specific build of `director` [here][1] which has all of the server code stripped away.
105
106<a name="http-routing"></a>
107## Server-Side HTTP Routing
108
109Director handles routing for HTTP requests similar to `journey` or `express`:
110
111```js
112 //
113 // require the native http module, as well as director.
114 //
115 var http = require('http'),
116 director = require('director');
117
118 //
119 // create some logic to be routed to.
120 //
121 function helloWorld(route) {
122 this.res.writeHead(200, { 'Content-Type': 'text/plain' })
123 this.res.end('hello world from (' + route + ')');
124 }
125
126 //
127 // define a routing table.
128 //
129 var router = new director.http.Router({
130 '/hello': {
131 get: helloWorld
132 }
133 });
134
135 //
136 // setup a server and when there is a request, dispatch the
137 // route that was requestd in the request object.
138 //
139 var server = http.createServer(function (req, res) {
140 router.dispatch(req, res, function (err) {
141 if (err) {
142 res.writeHead(404);
143 res.end();
144 }
145 });
146 });
147
148 //
149 // You can also do ad-hoc routing, similar to `journey` or `express`.
150 // This can be done with a string or a regexp.
151 //
152 router.get('/bonjour', helloWorld);
153 router.get(/hola/, helloWorld);
154
155 //
156 // set the server to listen on port `8080`.
157 //
158 server.listen(8080);
159```
160
161<a name="cli-routing"></a>
162## CLI Routing
163
164Director supports Command Line Interface routing. Routes for cli options are based on command line input (i.e. `process.argv`) instead of a URL.
165
166``` js
167 var director = require('director');
168
169 var router = new director.cli.Router();
170
171 router.on('create', function () {
172 console.log('create something');
173 });
174
175 router.on(/destroy/, function () {
176 console.log('destroy something');
177 });
178
179 // You will need to dispatch the cli arguments yourself
180 router.dispatch('on', process.argv.slice(2).join(' '));
181```
182
183Using the cli router, you can dispatch commands by passing them as a string. For example, if this example is in a file called `foo.js`:
184
185``` bash
186$ node foo.js create
187create something
188$ node foo.js destroy
189destroy something
190```
191
192<a name="api-documentation"></a>
193# API Documentation
194
195* [Constructor](#constructor)
196* [Routing Table](#routing-table)
197* [Adhoc Routing](#adhoc-routing)
198* [Scoped Routing](#scoped-routing)
199* [Routing Events](#routing-events)
200* [Configuration](#configuration)
201* [URL Matching](#url-matching)
202* [URL Params](#url-params)
203* [Route Recursion](#route-recursion)
204* [Async Routing](#async-routing)
205* [Resources](#resources)
206* [Instance Methods](#instance-methods)
207
208<a name="constructor"></a>
209## Constructor
210
211``` js
212 var router = Router(routes);
213```
214
215<a name="routing-table"></a>
216## Routing Table
217
218An object literal that contains nested route definitions. A potentially nested set of key/value pairs. The keys in the object literal represent each potential part of the URL. The values in the object literal contain references to the functions that should be associated with them. *bark* and *meow* are two functions that you have defined in your code.
219
220``` js
221 //
222 // Assign routes to an object literal.
223 //
224 var routes = {
225 //
226 // a route which assigns the function `bark`.
227 //
228 '/dog': bark,
229 //
230 // a route which assigns the functions `meow` and `scratch`.
231 //
232 '/cat': [meow, scratch]
233 };
234
235 //
236 // Instantiate the router.
237 //
238 var router = Router(routes);
239```
240
241<a name="adhoc-routing"></a>
242## Adhoc Routing
243
244When developing large client-side or server-side applications it is not always possible to define routes in one location. Usually individual decoupled components register their own routes with the application router. We refer to this as _Adhoc Routing._ Lets take a look at the API `director` exposes for adhoc routing:
245
246**Client-side Routing**
247
248``` js
249 var router = new Router().init();
250
251 router.on('/some/resource', function () {
252 //
253 // Do something on `/#/some/resource`
254 //
255 });
256```
257
258**HTTP Routing**
259
260``` js
261 var router = new director.http.Router();
262
263 router.get(/\/some\/resource/, function () {
264 //
265 // Do something on an GET to `/some/resource`
266 //
267 });
268```
269
270<a name="scoped-routing"></a>
271## Scoped Routing
272
273In large web appliations, both [Client-side](#client-side) and [Server-side](#server-side), routes are often scoped within a few individual resources. Director exposes a simple way to do this for [Adhoc Routing](#adhoc-routing) scenarios:
274
275``` js
276 var router = new director.http.Router();
277
278 //
279 // Create routes inside the `/users` scope.
280 //
281 router.path(/\/users\/(\w+)/, function () {
282 //
283 // The `this` context of the function passed to `.path()`
284 // is the Router itself.
285 //
286
287 this.post(function (id) {
288 //
289 // Create the user with the specified `id`.
290 //
291 });
292
293 this.get(function (id) {
294 //
295 // Retreive the user with the specified `id`.
296 //
297 });
298
299 this.get(/\/friends/, function (id) {
300 //
301 // Get the friends for the user with the specified `id`.
302 //
303 });
304 });
305```
306
307<a name="routing-events"></a>
308## Routing Events
309
310In `director`, a "routing event" is a named property in the [Routing Table](#routing-table) which can be assigned to a function or an Array of functions to be called when a route is matched in a call to `router.dispatch()`.
311
312* **on:** A function or Array of functions to execute when the route is matched.
313* **before:** A function or Array of functions to execute before calling the `on` method(s).
314
315**Client-side only**
316
317* **after:** A function or Array of functions to execute when leaving a particular route.
318* **once:** A function or Array of functions to execute only once for a particular route.
319
320<a name="configuration"></a>
321## Configuration
322
323Given the flexible nature of `director` there are several options available for both the [Client-side](#client-side) and [Server-side](#server-side). These options can be set using the `.configure()` method:
324
325``` js
326 var router = new director.Router(routes).configure(options);
327```
328
329The `options` are:
330
331* **recurse:** Controls [route recursion](#route-recursion). Use `forward`, `backward`, or `false`. Default is `false` Client-side, and `backward` Server-side.
332* **strict:** If set to `false`, then trailing slashes (or other delimiters) are allowed in routes. Default is `true`.
333* **async:** Controls [async routing](#async-routing). Use `true` or `false`. Default is `false`.
334* **delimiter:** Character separator between route fragments. Default is `/`.
335* **notfound:** A function to call if no route is found on a call to `router.dispatch()`.
336* **on:** A function (or list of functions) to call on every call to `router.dispatch()` when a route is found.
337* **before:** A function (or list of functions) to call before every call to `router.dispatch()` when a route is found.
338
339**Client-side only**
340
341* **resource:** An object to which string-based routes will be bound. This can be especially useful for late-binding to route functions (such as async client-side requires).
342* **after:** A function (or list of functions) to call when a given route is no longer the active route.
343
344<a name="url-matching"></a>
345## URL Matching
346
347``` js
348 var router = Router({
349 //
350 // given the route '/dog/yella'.
351 //
352 '/dog': {
353 '/:color': {
354 //
355 // this function will return the value 'yella'.
356 //
357 on: function (color) { console.log(color) }
358 }
359 }
360 });
361```
362
363Routes can sometimes become very complex, `simple/:tokens` don't always suffice. Director supports regular expressions inside the route names. The values captured from the regular expressions are passed to your listener function.
364
365``` js
366 var router = Router({
367 //
368 // given the route '/hello/world'.
369 //
370 '/hello': {
371 '/(\\w+)': {
372 //
373 // this function will return the value 'world'.
374 //
375 on: function (who) { console.log(who) }
376 }
377 }
378 });
379```
380
381``` js
382 var router = Router({
383 //
384 // given the route '/hello/world/johny/appleseed'.
385 //
386 '/hello': {
387 '/world/?([^\/]*)\/([^\/]*)/?': function (a, b) {
388 console.log(a, b);
389 }
390 }
391 });
392```
393
394<a name="url-params"></a>
395## URL Parameters
396
397When you are using the same route fragments it is more descriptive to define these fragments by name and then use them in your [Routing Table](#routing-table) or [Adhoc Routes](#adhoc-routing). Consider a simple example where a `userId` is used repeatedly.
398
399``` js
400 //
401 // Create a router. This could also be director.cli.Router() or
402 // director.http.Router().
403 //
404 var router = new director.Router();
405
406 //
407 // A route could be defined using the `userId` explicitly.
408 //
409 router.on(/([\w-_]+)/, function (userId) { });
410
411 //
412 // Define a shorthand for this fragment called `userId`.
413 //
414 router.param('userId', /([\\w\\-]+)/);
415
416 //
417 // Now multiple routes can be defined with the same
418 // regular expression.
419 //
420 router.on('/anything/:userId', function (userId) { });
421 router.on('/something-else/:userId', function (userId) { });
422```
423
424<a name="route-recursion"></a>
425## Route Recursion
426
427Can be assigned the value of `forward` or `backward`. The recurse option will determine the order in which to fire the listeners that are associated with your routes. If this option is NOT specified or set to null, then only the listeners associated with an exact match will be fired.
428
429### No recursion, with the URL /dog/angry
430
431``` js
432 var routes = {
433 '/dog': {
434 '/angry': {
435 //
436 // Only this method will be fired.
437 //
438 on: growl
439 },
440 on: bark
441 }
442 };
443
444 var router = Router(routes);
445```
446
447### Recursion set to `backward`, with the URL /dog/angry
448
449``` js
450 var routes = {
451 '/dog': {
452 '/angry': {
453 //
454 // This method will be fired first.
455 //
456 on: growl
457 },
458 //
459 // This method will be fired second.
460 //
461 on: bark
462 }
463 };
464
465 var router = Router(routes).configure({ recurse: 'backward' });
466```
467
468### Recursion set to `forward`, with the URL /dog/angry
469
470``` js
471 var routes = {
472 '/dog': {
473 '/angry': {
474 //
475 // This method will be fired second.
476 //
477 on: growl
478 },
479 //
480 // This method will be fired first.
481 //
482 on: bark
483 }
484 };
485
486 var router = Router(routes).configure({ recurse: 'forward' });
487```
488
489### Breaking out of recursion, with the URL /dog/angry
490
491``` js
492 var routes = {
493 '/dog': {
494 '/angry': {
495 //
496 // This method will be fired first.
497 //
498 on: function() { return false; }
499 },
500 //
501 // This method will not be fired.
502 //
503 on: bark
504 }
505 };
506
507 //
508 // This feature works in reverse with recursion set to true.
509 //
510 var router = Router(routes).configure({ recurse: 'backward' });
511```
512
513<a name="async-routing"></a>
514## Async Routing
515
516Before diving into how Director exposes async routing, you should understand [Route Recursion](#route-recursion). At it's core route recursion is about evaluating a series of functions gathered when traversing the [Routing Table](#routing-table).
517
518Normally this series of functions is evaluated synchronously. In async routing, these functions are evaluated asynchronously. Async routing can be extremely useful both on the client-side and the server-side:
519
520* **Client-side:** To ensure an animation or other async operations (such as HTTP requests for authentication) have completed before continuing evaluation of a route.
521* **Server-side:** To ensure arbitrary async operations (such as performing authentication) have completed before continuing the evaluation of a route.
522
523The method signatures for route functions in synchronous and asynchronous evaluation are different: async route functions take an additional `next()` callback.
524
525### Synchronous route functions
526
527``` js
528 var router = new director.Router();
529
530 router.on('/:foo/:bar/:bazz', function (foo, bar, bazz) {
531 //
532 // Do something asynchronous with `foo`, `bar`, and `bazz`.
533 //
534 });
535```
536
537### Asynchronous route functions
538
539``` js
540 var router = new director.http.Router().configure({ async: true });
541
542 router.on('/:foo/:bar/:bazz', function (foo, bar, bazz, next) {
543 //
544 // Go do something async, and determine that routing should stop
545 //
546 next(false);
547 });
548```
549
550<a name="resources"></a>
551## Resources
552
553**Available on the Client-side only.** An object literal containing functions. If a host object is specified, your route definitions can provide string literals that represent the function names inside the host object. A host object can provide the means for better encapsulation and design.
554
555``` js
556
557 var router = Router({
558
559 '/hello': {
560 '/usa': 'americas',
561 '/china': 'asia'
562 }
563
564 }).configure({ resource: container }).init();
565
566 var container = {
567 americas: function() { return true; },
568 china: function() { return true; }
569 };
570
571```
572
573<a name="instance-methods"></a>
574## Instance methods
575
576### configure(options)
577* `options` {Object}: Options to configure this instance with.
578
579Configures the Router instance with the specified `options`. See [Configuration](#configuration) for more documentation.
580
581### param(token, matcher)
582* token {string}: Named parameter token to set to the specified `matcher`
583* matcher {string|Regexp}: Matcher for the specified `token`.
584
585Adds a route fragment for the given string `token` to the specified regex `matcher` to this Router instance. See [URL Parameters](#url-parameters) for more documentation.
586
587### on(method, path, route)
588* `method` {string}: Method to insert within the Routing Table (e.g. `on`, `get`, etc.).
589* `path` {string}: Path within the Routing Table to set the `route` to.
590* `route` {function|Array}: Route handler to invoke for the `method` and `path`.
591
592Adds the `route` handler for the specified `method` and `path` within the [Routing Table](#routing-table).
593
594### path(path, routesFn)
595* `path` {string|Regexp}: Scope within the Routing Table to invoke the `routesFn` within.
596* `routesFn` {function}: Adhoc Routing function with calls to `this.on()`, `this.get()` etc.
597
598Invokes the `routesFn` within the scope of the specified `path` for this Router instance.
599
600### dispatch(method, path[, callback])
601* method {string}: Method to invoke handlers for within the Routing Table
602* path {string}: Path within the Routing Table to match
603* callback {function}: Invoked once all route handlers have been called.
604
605Dispatches the route handlers matched within the [Routing Table](#routing-table) for this instance for the specified `method` and `path`.
606
607### mount(routes, path)
608* routes {object}: Partial routing table to insert into this instance.
609* path {string|Regexp}: Path within the Routing Table to insert the `routes` into.
610
611Inserts the partial [Routing Table](#routing-table), `routes`, into the Routing Table for this Router instance at the specified `path`.
612
613## Instance methods (Client-side only)
614
615### init()
616Initialize the router, start listening for changes to the URL.
617
618### getState()
619Returns the state object that is relative to the current route.
620
621### getRoute([index])
622* `index` {Number}: The hash value is divided by forward slashes, each section then has an index, if this is provided, only that section of the route will be returned.
623
624Returns the entire route or just a section of it.
625
626### setRoute(route)
627* `route` {String}: Supply a route value, such as `home/stats`.
628
629Set the current route.
630
631### setRoute(start, length)
632* `start` {Number} - The position at which to start removing items.
633* `length` {Number} - The number of items to remove from the route.
634
635Remove a segment from the current route.
636
637### setRoute(index, value)
638* `index` {Number} - The hash value is divided by forward slashes, each section then has an index.
639* `value` {String} - The new value to assign the the position indicated by the first parameter.
640
641Set a segment of the current route.
642
643<a name="faq"></a>
644# Frequently Asked Questions
645
646## What About SEO?
647
648Is using a Client-side router a problem for SEO? Yes. If advertising is a requirement, you are probably building a "Web Page" and not a "Web Application". Director on the client is meant for script-heavy Web Applications.
649
650## Is Director compatible with X?
651
652Director is known to be Ender.js compatible. However, the project still needs solid cross-browser testing.
653
654# Licence
655
656(The MIT License)
657
658Copyright (c) 2010 Nodejitsu Inc. <http://www.twitter.com/nodejitsu>
659
660Permission 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:
661
662The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
663
664THE 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.
665
666[0]: http://github.com/flatiron/director
667[1]: https://github.com/flatiron/director/blob/master/build/director-1.0.7.min.js