Code Monkey home page Code Monkey logo

director's Introduction

Logo

Synopsis

Director is a router. Routing is the process of determining what code to run when a URL is requested.

Motivation

A routing library that works in both the browser and node.js environments with as few differences as possible. Simplifies the development of Single Page Apps and Node.js applications. Dependency free (doesn't require jQuery or Express, etc).

Status

Build Status

Features

Usage

Building client-side script

Run the provided CLI script.

./bin/build

Client-side Routing

It simply watches the hash of the URL to determine what to do, for example:

http://foo.com/#/bar

Client-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.

Hash route

Here is a simple example:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>A Gentle Introduction</title>

    <script
      src="https://rawgit.com/flatiron/director/master/build/director.min.js">
    </script>

    <script>
      var author = function () { console.log("author"); };
      var books = function () { console.log("books"); };
      var viewBook = function (bookId) {
        console.log("viewBook: bookId is populated: " + bookId);
      };

      var routes = {
        '/author': author,
        '/books': [books, function() {
          console.log("An inline route handler.");
        }],
        '/books/view/:bookId': viewBook
      };

      var router = Router(routes);

      router.init();
    </script>
  </head>

  <body>
    <ul>
      <li><a href="#/author">#/author</a></li>
      <li><a href="#/books">#/books</a></li>
      <li><a href="#/books/view/1">#/books/view/1</a></li>
    </ul>
  </body>
</html>

Director works great with your favorite DOM library, such as jQuery.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>A Gentle Introduction 2</title>

    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js">
    </script>

    <script
      src="https://rawgit.com/flatiron/director/master/build/director.min.js">
    </script>

    <script>
    $('document').ready(function() {
      //
      // create some functions to be executed when
      // the correct route is issued by the user.
      //
      var showAuthorInfo = function () { console.log("showAuthorInfo"); };
      var listBooks = function () { console.log("listBooks"); };

      var allroutes = function() {
        var route = window.location.hash.slice(2);
        var sections = $('section');
        var section;

        section = sections.filter('[data-route=' + route + ']');

        if (section.length) {
          sections.hide(250);
          section.show(250);
        }
      };

      //
      // define the routing table.
      //
      var routes = {
        '/author': showAuthorInfo,
        '/books': listBooks
      };

      //
      // instantiate the router.
      //
      var router = Router(routes);

      //
      // a global configuration setting.
      //
      router.configure({
        on: allroutes
      });

      router.init();
    });
    </script>
  </head>

  <body>
    <section data-route="author">Author Name</section>
    <section data-route="books">Book1, Book2, Book3</section>
    <ul>
      <li><a href="#/author">#/author</a></li>
      <li><a href="#/books">#/books</a></li>
    </ul>
  </body>
</html>

You can find a browser-specific build of director here which has all of the server code stripped away.

Server-Side HTTP Routing

Director handles routing for HTTP requests similar to journey or express:

  //
  // require the native http module, as well as director.
  //
  var http = require('http'),
      director = require('director');

  //
  // create some logic to be routed to.
  //
  function helloWorld() {
    this.res.writeHead(200, { 'Content-Type': 'text/plain' })
    this.res.end('hello world');
  }

  //
  // define a routing table.
  //
  var router = new director.http.Router({
    '/hello': {
      get: helloWorld
    }
  });

  //
  // setup a server and when there is a request, dispatch the
  // route that was requested in the request object.
  //
  var server = http.createServer(function (req, res) {
    router.dispatch(req, res, function (err) {
      if (err) {
        res.writeHead(404);
        res.end();
      }
    });
  });

  //
  // You can also do ad-hoc routing, similar to `journey` or `express`.
  // This can be done with a string or a regexp.
  //
  router.get('/bonjour', helloWorld);
  router.get(/hola/, helloWorld);

  //
  // set the server to listen on port `8080`.
  //
  server.listen(8080);

See Also:

Server-Side CLI Routing

Director supports Command Line Interface routing. Routes for cli options are based on command line input (i.e. process.argv) instead of a URL.

  var director = require('director');

  var router = new director.cli.Router();

  router.on('create', function () {
    console.log('create something');
  });

  router.on(/destroy/, function () {
    console.log('destroy something');
  });

  // You will need to dispatch the cli arguments yourself
  router.dispatch('on', process.argv.slice(2).join(' '));

Using 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:

$ node foo.js create
create something
$ node foo.js destroy
destroy something

API Documentation

Constructor

  var router = Router(routes);

Routing Table

An 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.

  //
  // Assign routes to an object literal.
  //
  var routes = {
    //
    // a route which assigns the function `bark`.
    //
    '/dog': bark,
    //
    // a route which assigns the functions `meow` and `scratch`.
    //
    '/cat': [meow, scratch]
  };

  //
  // Instantiate the router.
  //
  var router = Router(routes);

Adhoc Routing

When 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:

Client-side Routing

  var router = new Router().init();

  router.on('/some/resource', function () {
    //
    // Do something on `/#/some/resource`
    //
  });

HTTP Routing

  var router = new director.http.Router();

  router.get(/\/some\/resource/, function () {
    //
    // Do something on an GET to `/some/resource`
    //
  });

Scoped Routing

In large web appliations, both Client-side and Server-side, routes are often scoped within a few individual resources. Director exposes a simple way to do this for Adhoc Routing scenarios:

  var router = new director.http.Router();

  //
  // Create routes inside the `/users` scope.
  //
  router.path(/\/users\/(\w+)/, function () {
    //
    // The `this` context of the function passed to `.path()`
    // is the Router itself.
    //

    this.post(function (id) {
      //
      // Create the user with the specified `id`.
      //
    });

    this.get(function (id) {
      //
      // Retreive the user with the specified `id`.
      //
    });

    this.get(/\/friends/, function (id) {
      //
      // Get the friends for the user with the specified `id`.
      //
    });
  });

Routing Events

In director, a "routing event" is a named property in the 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().

  • on: A function or Array of functions to execute when the route is matched.
  • before: A function or Array of functions to execute before calling the on method(s).

Client-side only

  • after: A function or Array of functions to execute when leaving a particular route.
  • once: A function or Array of functions to execute only once for a particular route.

Configuration

Given the flexible nature of director there are several options available for both the Client-side and Server-side. These options can be set using the .configure() method:

  var router = new director.Router(routes).configure(options);

The options are:

  • recurse: Controls route recursion. Use forward, backward, or false. Default is false Client-side, and backward Server-side.
  • strict: If set to false, then trailing slashes (or other delimiters) are allowed in routes. Default is true.
  • async: Controls async routing. Use true or false. Default is false.
  • delimiter: Character separator between route fragments. Default is /.
  • notfound: A function to call if no route is found on a call to router.dispatch().
  • on: A function (or list of functions) to call on every call to router.dispatch() when a route is found.
  • before: A function (or list of functions) to call before every call to router.dispatch() when a route is found.

Client-side only

  • 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).
  • after: A function (or list of functions) to call when a given route is no longer the active route.
  • html5history: If set to true and client supports pushState(), then uses HTML5 History API instead of hash fragments. See History API for more information.
  • run_handler_in_init: If html5history is enabled, the route handler by default is executed upon Router.init() since with real URIs the router can not know if it should call a route handler or not. Setting this to false disables the route handler initial execution.
  • convert_hash_in_init: If html5history is enabled, the window.location hash by default is converted to a route upon Router.init() since with canonical URIs the router can not know if it should convert the hash to a route or not. Setting this to false disables the hash conversion on router initialisation.

URL Matching

  var router = Router({
    //
    // given the route '/dog/yella'.
    //
    '/dog': {
      '/:color': {
        //
        // this function will return the value 'yella'.
        //
        on: function (color) { console.log(color) }
      }
    }
  });

Routes 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.

  var router = Router({
    //
    // given the route '/hello/world'.
    //
    '/hello': {
      '/(\\w+)': {
        //
        // this function will return the value 'world'.
        //
        on: function (who) { console.log(who) }
      }
    }
  });
  var router = Router({
    //
    // given the route '/hello/world/johny/appleseed'.
    //
    '/hello': {
      '/world/?([^\/]*)\/([^\/]*)/?': function (a, b) {
        console.log(a, b);
      }
    }
  });

URL Parameters

When 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 or Adhoc Routes. Consider a simple example where a userId is used repeatedly.

  //
  // Create a router. This could also be director.cli.Router() or
  // director.http.Router().
  //
  var router = new director.Router();

  //
  // A route could be defined using the `userId` explicitly.
  //
  router.on(/([\w-_]+)/, function (userId) { });

  //
  // Define a shorthand for this fragment called `userId`.
  //
  router.param('userId', /([\\w\\-]+)/);

  //
  // Now multiple routes can be defined with the same
  // regular expression.
  //
  router.on('/anything/:userId', function (userId) { });
  router.on('/something-else/:userId', function (userId) { });

Wildcard routes

It is possible to define wildcard routes, so that /foo and /foo/a/b/c routes to the same handler, and gets passed "" and "a/b/c" respectively.

  router.on("/foo/?((\w|.)*)"), function (path) { });

Route Recursion

Can 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.

No recursion, with the URL /dog/angry

  var routes = {
    '/dog': {
      '/angry': {
        //
        // Only this method will be fired.
        //
        on: growl
      },
      on: bark
    }
  };

  var router = Router(routes);

Recursion set to backward, with the URL /dog/angry

  var routes = {
    '/dog': {
      '/angry': {
        //
        // This method will be fired first.
        //
        on: growl
      },
      //
      // This method will be fired second.
      //
      on: bark
    }
  };

  var router = Router(routes).configure({ recurse: 'backward' });

Recursion set to forward, with the URL /dog/angry

  var routes = {
    '/dog': {
      '/angry': {
        //
        // This method will be fired second.
        //
        on: growl
      },
      //
      // This method will be fired first.
      //
      on: bark
    }
  };

  var router = Router(routes).configure({ recurse: 'forward' });

Breaking out of recursion, with the URL /dog/angry

  var routes = {
    '/dog': {
      '/angry': {
        //
        // This method will be fired first.
        //
        on: function() { return false; }
      },
      //
      // This method will not be fired.
      //
      on: bark
    }
  };

  //
  // This feature works in reverse with recursion set to true.
  //
  var router = Router(routes).configure({ recurse: 'backward' });

Async Routing

Before diving into how Director exposes async routing, you should understand Route Recursion. At it's core route recursion is about evaluating a series of functions gathered when traversing the Routing Table.

Normally 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:

  • Client-side: To ensure an animation or other async operations (such as HTTP requests for authentication) have completed before continuing evaluation of a route.
  • Server-side: To ensure arbitrary async operations (such as performing authentication) have completed before continuing the evaluation of a route.

The method signatures for route functions in synchronous and asynchronous evaluation are different: async route functions take an additional next() callback.

Synchronous route functions

  var router = new director.Router();

  router.on('/:foo/:bar/:bazz', function (foo, bar, bazz) {
    //
    // Do something asynchronous with `foo`, `bar`, and `bazz`.
    //
  });

Asynchronous route functions

  var router = new director.http.Router().configure({ async: true });

  router.on('/:foo/:bar/:bazz', function (foo, bar, bazz, next) {
    //
    // Go do something async, and determine that routing should stop
    //
    next(false);
  });

Resources

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.

  var router = Router({

    '/hello': {
      '/usa': 'americas',
      '/china': 'asia'
    }

  }).configure({ resource: container }).init();

  var container = {
    americas: function() { return true; },
    china: function() { return true; }
  };

History API

Available on the Client-side only. Director supports using HTML5 History API instead of hash fragments for navigation. To use the API, pass {html5history: true} to configure(). Use of the API is enabled only if the client supports pushState().

Using the API gives you cleaner URIs but they come with a cost. Unlike with hash fragments your route URIs must exist. When the client enters a page, say http://foo.com/bar/baz, the web server must respond with something meaningful. Usually this means that your web server checks the URI points to something that, in a sense, exists, and then serves the client the JavaScript application.

If you're after a single-page application you can not use plain old <a href="/bar/baz"> tags for navigation anymore. When such link is clicked, web browsers try to ask for the resource from server which is not of course desired for a single-page application. Instead you need to use e.g. click handlers and call the setRoute() method yourself.

Attach Properties To this

Available in the http router only. Generally, the this object bound to route handlers, will contain the request in this.req and the response in this.res. One may attach additional properties to this with the router.attach method:

  var director = require('director');

  var router = new director.http.Router().configure(options);

  //
  // Attach properties to `this`
  //
  router.attach(function () {
    this.data = [1,2,3];
  });

  //
  // Access properties attached to `this` in your routes!
  //
  router.get('/hello', function () {
    this.res.writeHead(200, { 'content-type': 'text/plain' });

    //
    // Response will be `[1,2,3]`!
    //
    this.res.end(this.data);
  });

This API may be used to attach convenience methods to the this context of route handlers.

HTTP Streaming and Body Parsing

When you are performing HTTP routing there are two common scenarios:

  • Buffer the request body and parse it according to the Content-Type header (usually application/json or application/x-www-form-urlencoded).
  • Stream the request body by manually calling .pipe or listening to the data and end events.

By default director.http.Router() will attempt to parse either the .chunks or .body properties set on the request parameter passed to router.dispatch(request, response, callback). The router instance will also wait for the end event before firing any routes.

Default Behavior

  var director = require('director');

  var router = new director.http.Router();

  router.get('/', function () {
    //
    // This will not work, because all of the data
    // events and the end event have already fired.
    //
    this.req.on('data', function (chunk) {
      console.log(chunk)
    });
  });

In flatiron, director is used in conjunction with union which uses a BufferedStream proxy to the raw http.Request instance. union will set the req.chunks property for you and director will automatically parse the body. If you wish to perform this buffering yourself directly with director you can use a simple request handler in your http server:

  var http = require('http'),
      director = require('director');

  var router = new director.http.Router();

  var server = http.createServer(function (req, res) {
    req.chunks = [];
    req.on('data', function (chunk) {
      req.chunks.push(chunk.toString());
    });

    router.dispatch(req, res, function (err) {
      if (err) {
        res.writeHead(404);
        res.end();
      }

      console.log('Served ' + req.url);
    });
  });

  router.post('/', function () {
    this.res.writeHead(200, { 'Content-Type': 'application/json' })
    this.res.end(JSON.stringify(this.req.body));
  });

Streaming Support

If you wish to get access to the request stream before the end event is fired, you can pass the { stream: true } options to the route.

  var director = require('director');

  var router = new director.http.Router();

  router.get('/', { stream: true }, function () {
    //
    // This will work because the route handler is invoked
    // immediately without waiting for the `end` event.
    //
    this.req.on('data', function (chunk) {
      console.log(chunk);
    });
  });

Instance methods

configure(options)

  • options {Object}: Options to configure this instance with.

Configures the Router instance with the specified options. See Configuration for more documentation.

param(token, matcher)

  • token {string}: Named parameter token to set to the specified matcher
  • matcher {string|Regexp}: Matcher for the specified token.

Adds a route fragment for the given string token to the specified regex matcher to this Router instance. See URL Parameters for more documentation.

on(method, path, route)

  • method {string}: Method to insert within the Routing Table (e.g. on, get, etc.).
  • path {string}: Path within the Routing Table to set the route to.
  • route {function|Array}: Route handler to invoke for the method and path.

Adds the route handler for the specified method and path within the Routing Table.

path(path, routesFn)

  • path {string|Regexp}: Scope within the Routing Table to invoke the routesFn within.
  • routesFn {function}: Adhoc Routing function with calls to this.on(), this.get() etc.

Invokes the routesFn within the scope of the specified path for this Router instance.

dispatch(method, path[, callback])

  • method {string}: Method to invoke handlers for within the Routing Table
  • path {string}: Path within the Routing Table to match
  • callback {function}: Invoked once all route handlers have been called.

Dispatches the route handlers matched within the Routing Table for this instance for the specified method and path.

mount(routes, path)

  • routes {object}: Partial routing table to insert into this instance.
  • path {string|Regexp}: Path within the Routing Table to insert the routes into.

Inserts the partial Routing Table, routes, into the Routing Table for this Router instance at the specified path.

Instance methods (Client-side only)

init([redirect])

  • redirect {String}: This value will be used if '/#/' is not found in the URL. (e.g., init('/') will resolve to '/#/', init('foo') will resolve to '/#foo').

Initialize the router, start listening for changes to the URL.

getRoute([index])

  • 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.

Returns the entire route or just a section of it.

setRoute(route)

  • route {String}: Supply a route value, such as home/stats.

Set the current route.

setRoute(start, length)

  • start {Number} - The position at which to start removing items.
  • length {Number} - The number of items to remove from the route.

Remove a segment from the current route.

setRoute(index, value)

  • index {Number} - The hash value is divided by forward slashes, each section then has an index.
  • value {String} - The new value to assign the the position indicated by the first parameter.

Set a segment of the current route.

Frequently Asked Questions

What About SEO?

Is 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.

LICENSE: MIT
Contributors: Paolo Fragomeni

director's People

Contributors

3rd-eden avatar alfred-nsh avatar avianflu avatar beaugunderson avatar blakmatrix avatar bmeck avatar coen-hyde avatar dcneiner avatar domenic avatar dscape avatar ferlores avatar frekw avatar geddski avatar indexzero avatar indutny avatar jcrugzz avatar jfhbrook avatar julianduque avatar kevinswiber avatar longlho avatar luk- avatar marak avatar mmalecki avatar pksunkara avatar rvagg avatar southern avatar stolsma avatar tblobaum avatar vesln avatar yojimbo87 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

director's Issues

Routing event support for request/response

Wanted to implement http basic auth checking as a filter and I'm not able to using routing events.

Request and response objects are not available to routing events and according to the comments in the routing events code, we should not return anything from a routing event function.

Multiple Routers and URL params

Looks like if I try to make multiple routers (like in Sammy.js) the second one overrides/disables the first. Any chance of adding support for multiple concurrent routers?

HTTP routing functions getting passed the route?

Either this is a bug in the code or in the docs. Should the route name be available to the registered function? Right now it's coming out as undefined.

From the docs:

function helloWorld(route) {
  this.res.writeHead(200, { 'Content-Type': 'text/plain' })
  this.res.end('hello world from (' + route + ')');
}

When running the demo code and hitting curl http://localhost:8080/hello, the print statement comes out as:
hello world from (undefined)

Initialization always fires 'notfound' handler

The Router init() method executes a call to route() without passing a parameter. This causes the route to be 'undefined' which ultimately causes a call to the notfound handler upon initialization.

// init method
this.init = function() {
listener.init(route);
route();
return this;
};

//in parse method
if(route === undefined && path.length === 0) {
self.noroute(partialpath);
return false;
};

Is the initial route() call necessary? Should the 'notfound' handler be added only initialization? That seems clunky.

Thanks!

router.get(/\//, handler) breaks

What I'm trying to do is match against the root directory ("/").

It's entirely possible that this shouldn't work, but if so the way to match against a root directory (using .get and friends) is non-obvious.

Bump current master version and publish ??

First of all: A happy and good coding new year! :-)

And 2nd, is there a possibility for someone to bump the version on master and publish that new version to NPM? Need the query parameter fix thats already committed two weeks ago but not published yet.... ;-)

Thanks !!

[build 1.0.7] Adhoc routing in browser broken

I wanted to use this adhoc routing but it seems te be broken... How is this possible btw? We're creating builds that are not working correctly :S

See commit ef3b3bf.

Adhoc routing results in Uncaught TypeError: Cannot call method 'concat' of undefined

Use director programmatically ( without CLI or HTTP or browser )

Is it currently possible to use director entirely in memory? Without needing any external interface?

We were attempting to use an API that looked like this:

var router = new director.Router();

router.on(/foo/, function () {
  console.log('sup');
});

router.dispatch('GET', '/foo', function (err) {
  console.log('sup', err);
});

Can't match ";" or "/" with a wildcard in routes

Director can not match these characters because regifyString will replace route wildcards with another regexp which does not match these characters.

The following server responds to /foo/bar with "hello world", but will 404 for /foo/bar;baz or /foo/bar/baz.

var union = require('union');
var director = require('director');
var router = new director.http.Router();

router.get(/\/foo\/(.*)/, function () {
  this.res.writeHead(200, { 'Content-Type': 'text/plain' })
  this.res.end('hello world\n');
});

var server = union.createServer({
  before: [
    function (req, res) {
      var found = router.dispatch(req, res);
      if (!found) {
        res.emit('next');
      }
    }
  ]
});

server.listen(9090);

Routing table created incorrectly when passed to the constructor

I've copied the sample code from the docs and added a little logging.

https://gist.github.com/1367702

After starting the server, http://localhost:8080/hello, the route passed to the constructor on lines 20 and 21, returns a 404.

The logging statement on line 47 prints the routing table which is broken for the routes passed to the constructor. For a method it seems to have "et" instead of "get".

data:   {
data:       bonjour: { get: [Function] },
data:       hello: {
data:           et: { on: [Function] }
data:       },
data:       hola: { get: [Function] }
data:   }

This is using Node 0.6.1 and SugarSkull version 1.0.1 from npm.

Nested syntax with params

Would like to be able to define nested routes that support simple-tokens or regexps, pass all values forward.

'/a': {
   '/(\d+)': {
       on: function(b) { ... },
       '/(\d+)': {
           on: function(b, c) { ... }
       }
   }
}

Stack overflow / event firing order

My mini-router here:
https://gist.github.com/1248332

Having 2 issues:

  1. my global 'after' event seems to be firing before 'on'
  • this was happening with the sample code from the SugarSkull dl too
  • what exactly does "after this route" mean? i.e. does this mean when another hashchange happens when you are already on the specified route?
  1. Getting a stack overflow error with regify on line 38 of SS.js when I try to use the resource param and define a methods object
  • this was happening for me on both Chrome and FF

Maybe I'm just defining something wrong?

ps. can you also specify what SS.min.js is? doesn't seem to just be a minified version of SS.js, or am I wrong?

Global Routes

The ability to add global routes to be fired whenever any part of the hash contains the fragment

e.g.

router = Router({

    '*/global/*: {
        on: killDog
    },
    '/home': {
        on: goHome,
        '/users': {
            on: loadUser
        }
    }

}).init();

would match...

http://someapp.com/#/global/

http://someapp.com/#/home/users/global/

http://someapp.com/#/home/global/users/

similarly, if you could define it as global part of a subsequent route

router = Router({

    '/home': {
        on: goHome,
        '/users': {
            on: loadUser
        },
        '*/global/*: {
            on: killDog
        }
    }

}).init();

Would match

http://someapp.com/#/home/users/global/

http://someapp.com/#/home/global/users/

http://someapp.com/#/home/users/:token/global

but not

http://someapp.com/#/global/

http://someapp.com/#/some-other-route/global/

It would be beneficial to pass the before and after fragments via arrays

e.g.

function killDog([ before, match, fragments], [after, match, fragments]  ){

}

Not sure if I'm missing something but I have tried to achieve a similar result using regex and recurse, to no avail.

Not totally clear how to use state from README

I think I'd like to use state for something I'm working on, but it's not very clear how to do this from the documentation. Basically I have a small app with two routes:

'/builds': {
  '/:buildid': {
    on: function(buildid) { ... }
    '/jobs': {
      '/:jobid': {
        on: function(buildid, jobid) { ... }
      }
    }
  }
}

The second is a superset of the first (that is, the first one should be loaded before the second). I can make this happen by recursing forward. So far so good. The problem is that when going backward (e.g. transitioning from /builds/foo/jobs/bar to /builds/foo), SugarSkull think it needs to call the "on" function for /builds/foo (when in fact it's already loaded). I'm gathering that this is something that I can use state for, but it's not clear to me how from the documentation.

Problem 1: How do I access the state dictionary from inside my router functions?
Problem 2: How do I assign route-specific information (e.g. a buildid in the example above) to a state dictionary?

Http Router doesn't work with URL parameters

When testing with: http://development:9090/test/foo?test=test the attached route function to /test/foo isn't executed. But if I change the request to http://development:9090/test/foo it is.

I debugged the code and found that the whole URL (+ the parameters after ?) is fed into the traverse function by the dispatch function. If I change that to only the part before ? then it works. Can make a patch for it but don't know if the current behavior is the correct one. If this is the correct behavior then what do I need to do to get URLs with parameters working with Director?

Error when router.on() (Client-side routing)

using: director-1.0.7.min.js

In chrome v16:

var router = new Router().init();
router.on('/:some', function () {...}); //=> TypeError: Cannot call method 'concat' of undefined

In FF v9

var router = new Router().init();
router.on('/:some', function () {...}); //=> TypeError: this.scope is undefined

In IE v9 does not work.

Overlapping simple tokens issue

If you have overlapping route that include simple tokens, only the routes that END in a simple token will be routed. No errors are displayed or callbacks are hit.

The routes are listed below:

'/foo/:id' : function(){
            console.log("foo2 ", arguments);
        },

        '/foo/:id/bar' : function(){
            console.log("bar", arguments);
        },
        '/foo/:id/bar/:id2' : function(){
            console.log("bar2", arguments);
        },

        '/foo/:id/bar/:id2/baz' : function(){
            console.log("baz", arguments);
        },
        '/foo/:id/bar/:id2/baz/:id3' : function(){
            console.log("baz2", arguments);
        }

Routing for a non-match

I have the following in my application:

new Router({
    '/(\\w+)\\/' : selectRepository 
}).init();

function selectRepository(repo) {
    ...
}

I would like to match #/AAA, or #/BBB,.... However, if I put no hash, to hit the main page originally, as in http://localhost:8080/context/ it goes through the router, and in the parse method of SS.js, the first line (parse.shift) returns undefined. Then the method continues on and undefined becomes /undefined/ in leftovers, which matches my regular expression, but of course the routing will not work. I could of course check for undefined in my selectRepository function, but I really think this case should be handled internally and /undefined should not be handled as an acceptable case.

To fix this locally, I just added:

  if (partialpath === undefined) {
      return false;
  }

after

 var partialpath = path.shift(); 

of the parse method.

That said, this is probably just treating the symptom. I have not really delved in to understand all of the code, so, maybe something like:

leftovers = (partialpath ? ('/' + partialpath + '/') : '/') + path.join('/');

would be more correct.

Anyway, I am not a regex guru and so I could just have something wrong with my regex, or just be missing something in the usage entirely, as I just barely started using SugarSkull, so if there is a better way, please let me know.

http routing recurse doesn't work

Either settings 'backword' or 'forward' don't work in http routing.

My env:

$ node -v
v0.6.6
$ npm list
├── [email protected]

Example code:

  var http = require('http'),
      director = require('director');

  var router = new director.http.Router(
  {
    '/hello':
    {
      '/hello2':
      {
        get: function(route)
        {
            this.res.write('hello2\n');
        }
      },
      get: function(route)
      {
        this.res.write('hello\n');
      }
    }
  }).configure(
  {
    recurse: 'backward',
    before: function(){ this.res.writeHead(200, { 'Content-Type': 'text/plain' }); },
    on: function(){ this.res.end(); }
  });

  var server = http.createServer(function (req, res) {
    router.dispatch(req, res, function (err) {
      if (err) {
        res.writeHead(404);
        res.end();
      }
    });
  });

  server.listen(8080);

Result:

$ curl http://localhost:8080/hello/hello2
hello2

Addition of query string parameters causes existing/previously working routes to no longer fire.

Original URL that worked:
http://localhost:3201/games/PS3/MostPopular/All%20PS3%20Games

'/:platform/:filter/([^.]*)': {
get: function (platform, filter, category) {
renderer.getGameslist(filter, platform, category.replace(/&/g, '%26'), 1, 20, this.req, this.res);
}
},

When I add query string parameters like in the following URL , the above route no longer is triggered.
http://localhost:3201/games/PS3/MostPopular/All%20PS3%20Games?pageNumber=2&pageSize=20

IE8 regex routing

so the following two routes work in FF, Chrome, IE9, etc.

'/test\\/': function(){console.log('test hit')},
'/test\\/([a-z0-9-]+)': function(x){console.log('test hit with '+x)}

but only the first one works when my browser is put into "Document Mode: IE8 Standards"
i am partially suspecting regex differences ( http://stevenlevithan.com/demo/split.cfm )

my current project has an IE8+ requirement. is this something you would be willing to look into? i do understand that coding for old quirks is sometimes more hassle than its worth.

SIDENOTE: as i was testing different patterns i learned that this route works in all browsers:

'/foo': { '/bar': function(){console.log('FUBAR')} }

but this one does not work in any browsers:

'/foo/bar': function(){console.log('FUBAR')}

is this the designed/desired behavior?

example code doesn't work.

encountered many problems, trying what was in the readme.

Router({})
TypeError: Object #<Object> has no method 'configure'
    at /home/dominic/source/dev/experiement/node_modules/sugarskull/lib/sugarskull/router.js:124:8
    at /home/dominic/source/dev/experiement/node_modules/sugarskull/lib/sugarskull/http/index.js:14:21
    at Object.<anonymous> (/home/dominic/source/dev/experiement/ss.js:4:9)
    at Module._compile (module.js:411:26)
    at Object..js (module.js:417:10)
    at Module.load (module.js:343:31)
    at Function._load (module.js:302:12)
    at Array.<anonymous> (module.js:430:10)
    at EventEmitter._tickCallback (node.js:126:26)

new Router({}).init()
TypeError: Object [object Object] has no method 'init'
    at Object.<anonymous> (/home/dominic/source/dev/experiement/ss.js:9:4)
    at Module._compile (module.js:411:26)
    at Object..js (module.js:417:10)
    at Module.load (module.js:343:31)
    at Function._load (module.js:302:12)
    at Array.<anonymous> (module.js:430:10)
    at EventEmitter._tickCallback (node.js:126:26)
var Router = require('sugarskull').http.Router
  , http = require('http')

function respond () {
  console.error('it worked')
  this.res.writeHeader(200); this.res.end('aoneuhaoenuhoneiurkpborkbropkbr')   
}

var r = new Router({
  on: respond,
  '/\//': respond,
  '/': respond,

})

http.createServer(function (req, res) {
  r.dispatch(req, res, function (err) { throw err })
}).listen(8080)
                                        ^
Error: Could not find path: /
    at [object Object].dispatch (/home/dominic/source/dev/experiement/node_modules/sugarskull/lib/sugarskull/http/index.js:60:16)
    at Server.<anonymous> (/home/dominic/source/dev/experiement/ss.js:21:5)
    at Server.emit (events.js:67:17)
    at HTTPParser.onIncoming (http.js:1134:12)
    at HTTPParser.onHeadersComplete (http.js:108:31)
    at Socket.ondata (http.js:1029:22)
    at Socket._onReadable (net.js:677:27)
    at IOWatcher.onReadable [as callback] (net.js:177:10)

this needs working examples!

HTML5 History API Support

Feature Request: Support for HTML5 History API on an opt-in basis, with graceful fallback to hash version in lame browsers. Similar to the backbone and sammy routers. The main use case is for web apps that also want SEO (think twitter) and that build a server side solution for it. With Node being able to run your front end's same backbone models and templating it's not much more work. Having the option to handle the routing in sugarskull would be nice.

with recurse on After is fired on On

for the router setup

router =Router( {
    'foo':{
        on: function(){},
        after: function(){}
    }
}).use({ recourse: forward }).init();

and the url:

#/foo

After will fired when the url is navigated to, and once the url has left.

Problem with regex matching "\/"

Code example:

router.get(/sup/meh/, function () {
this.res.writeHead(200, { 'Content-Type': 'text/plain' })
this.res.end('hello world\n');
});

If I try to access: /sup/meh, I get this error:
/usr/local/lib/node_modules/director/lib/director/router.js:420
match = path.match(new RegExp('^' + exact));
^
SyntaxError: Invalid regular expression: /^/sup/: \ at end of pattern
at new RegExp (unknown source)
at [object Object].traverse (/usr/local/lib/node_modules/director/lib/director/router.js:420:26)

I've debugged a little bit and came to the conclusion that the problem is:
if (path.source) { // ~ line 200
path = path.source
}

The problem is when we split based on the '/' delimiter, the parts become:
'sup', 'meh'.. and when later on it tries to match 'sup' it obviously says there's a regex error.

My current quick fix: (Replacing / by /)
if (path.source) {
path = path.source.replace('/', '/');
}

Hope it helps

Routes recurse

Say I have two routes: /blogpost and /blogpost/edit. Currently, if /blogpost/edit is hit, the "on" function for both the routes are called. There might be use-cases for both to be called, but the majority of use-cases will be entirely different functionality, so recursing the child urls is not expected. /blogpost will show the blog post, and /edit will show a form, for example.

If there already is a way to control if the recursion will happen, I'm not aware of it. If there isn't, there should be. :)

Let me know if I wasn't clear enough.

Multiple hash parameters

Is there a way to handle multiple hash parameters?

eg.
...index.html#/roadmap/7/2010

I tried to define it this way:
'/roadmap': {
   '/(\d+)': {
       on: displayRoadmap,
       '/(\d+)': {
           on: displayRoadmap
       }
   }
}

Hoping that it would call my function with one or two parameters based on matches:
function displayRoadmap(id, year) {
   ...
}

Thanks,
tvalletta

Parameter dictionary support

So as I mentioned (too briefly) on twitter the other day, I think it
would be really neat if SugarSkull supported parameters to its URLs.
The main motivation for this is that the application I'm currently
working on (http://brasstacks.mozilla.com/gofaster/#/) has some views
that can take many parameters, for example:

http://brasstacks.mozilla.com/gofaster/#/executiontime/build/30
http://brasstacks.mozilla.com/gofaster/#/executiontime/test/60

In this example, only really the first part of the path
(executiontime) is really modelling a resource in my application. The
other two are more like parameters to the request. Obviously putting
them as fully fledged paths works (as that's what I'm doing right
now), but has the following disadvantages:

  1. It complicates the links I make on the main page (instead of just
    using defaults)
  2. More importantly, if I want to add an additional parameter (which
    I'm actually about to do), any previous links I made using the old
    structure will be invalidated.

I can think of a few ways around the above but they seem excessively
complicated. What I'm thinking of is adding the option of setting
parameters to sugar skull "URLs", so you could make queries like this:

http://brasstacks.mozilla.com/gofaster/#/executiontime
http://brasstacks.mozilla.com/gofaster/#/executiontime?type=build
http://brasstacks.mozilla.com/gofaster/#/executiontime?type=build&range=30

and the router would only parse the section up to the '?' character,
passing the set of parameters to the function as a dictionary called
params (if there are no parameters, the dictionary would obviously be
empty).

I guess this would break backwards compatibility since we'd now be
expecting the parameters variable to appear in everybody's functions
(but at least all people would have to do is update their code:
existing URLs would still work on the client side).

If I were to add something like that to a fork of your project, would
you accept it back into mainline?

Allow routing without the hash

Currently client-side director only supports hash-routing. It would be a nice sugar feature if it could support regular routing too.

Readme link missing

You can find a browser-specific build of director here which has all of the server code stripped away.

The text here links to the front page of the repo and not to the alluded to browser-specific version

this when using a resource

Hi,

I've got a question on how to handle this in the following (browser) scenario.

var MyApp = function(){
}

MyApp.prototype.start = function() {
console.log(this);
};

var app = new MyApplication();

routes = {
'/': 'start',
}

Router(routes).configure({resource:app}).init();

When I point my browser to "#/" start gets called, but the console.log(this) prints the a.Router object.

Is there a way to get this' set to the resource?

Thanks

Trailing slashes not possible in director routes?

In theory, these two URLs are equally valid, and will generally mean the same thing:

  1. http://localhost:7000/api/
  2. http://localhost:7000/api

However, it seems that number 1 is not currently possible with Director.

I have this setup in my Flatiron app:

// In app.js
var flatiron = require('flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.http);

app.router.path('/api', require('./api').router);


// In api/index.js
var router = function () {
  this.get('/', function () {
    this.res.writeHead(200, { 'Content-Type': 'text/plain' });
    this.res.end('This is the entry point for the Ardon API.');
  });
};

module.exports = {
  router: router
};

I've tried different variations of that with slash or not slash in the app.router.path() and this.get() invocations, but the result is invariably the same. http://localhost:7000/api works as it should, http://localhost:7000/api/ returns the text “undefined” with a 404 not found header.

Leave function won't fire on hashchange

First, let me say, great library. Exactly what I think this world needs :)

Im having a little issue. when I navigate away from a certain page, i have leave event, and it just won't fire. Is there something specific I need to do?

recursion when used with webshims

When used alongwith webshims we get the error "too much recursion", specifically on firefox 3.6.23 .
Here is jsfiddle http://jsfiddle.net/zfTBS/1/ .

I have tried with latest versions of all libs but no luck. jquery(1.6.4), webshims (1.8.2) and SugarSkull latest.

I am not sure whether webshims/SugarSkull so might add same issue for webshims too.

Same thing works well on Chrome.

Thanks for this wonderful library. We love it.

Better Examples

Need to write some better examples of how SugarSkull works.

Simple Tokens

@hij1nx I'm wondering if you'd be open to the idea of simple tokens. Here's what I mean:

Say you want to match the group name and project name from this URL: /project/mobile-team/iphone-app

With Regex

'/project\\/([a-z0-9-]+)\\/([a-z0-9-]+)': viewProject

With Simple Params

'/project/:group/:project': viewProject

Your readme mentions that simple tokens like this can't handle every situation, and that's true. But they handle probably 95% of the use cases for routing, and they're very easy to use. I'm sure that's why Express, Sammy, and CouchDB use them. Actually the only thing you gain from regex routes is the ability to distinguish from /articles/4 and /articles/four for example. But the increase in power comes at the cost of being more difficult to use.

I have an idea for a simple syntax that would allow both simple params AND regex distinction where you need it. Just include the regex filter after the simple param. So for example using the same URL /project/mobile-team/iphone-app you could:

Match any param

'/project/:group/:project': viewProject

Match only params of a certain type (with regex)

'/project/:group/:project(\\d+)': viewProject 
//would match /project/mobile-team/43 but not /project/mobile-team/iphone-app

The (\\d+) isn't a capture, but rather a definition of what type the simple param must be in order to match. What do you think? This lets your routes be simple and concise, and only use regular expressions in the rare cases where you need them. This would make sugarskull.js easier to use for developers (like me) who aren't experts with regular expressions, without losing any of the flexibility that sugarskull.js currently has.

If you're interested but don't have the time would you be open to a pull request for this?

Thanks!

'/:page': ':page'

Router({
  '/:page': ':page'
}).use({ 
  resource: {
    pizza: function() { ... },
    taco: function() { ... },
    muffins: function() { ... }
  }
})

this would be handy as it would save me from doing something redundant like this:

var pages = {
  pizza: function() { ... },
  taco: function() { ... },
  muffins: function() { ... }
}

Router({
  '/:page': {
    on: function(page) {
      pages[page]()
    }
  }
}).use({ 
  resource: pages
})

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.