aliirz / backbone.routemanager-1

Better route management for Backbone.js projects.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

backbone.routemanager

Created by Tim Branyen @tbranyen

Provides missing features to the Backbone.Router.

Depends on Underscore, Backbone and jQuery. You can swap out the jQuery dependency completely with a custom configuration.

Tutorials and Screencasts

Download & Include

Development is fully commented source, Production is minified and stripped of all comments except for license/credits.

Include in your application after jQuery, Underscore, and Backbone have been included.

<script src="/js/jquery.js"></script>
<script src="/js/underscore.js"></script>
<script src="/js/backbone.js"></script>

<script src="/js/backbone.routemanager.js"></script>

Usage

Defining a RouteManager

A route manager is your App Router, you will be able to define any number of Nested Routers.

Assuming you have an app namespace

// A basic route manager, works just like a Backbone.Router (cause it is one)
var AppRouter = Backbone.RouteManager.extend({
  routes: {
    "": "index"
  },

  index: function() {
    window.alert("Navigated successfully");
  }
});

// Create a new instance of the app router
app.router = new AppRouter();

// Trigger the index route
app.router.navigate("", true);

Alternative method of defining a RouteManager

If you don't wish to extend the Backbone.RouteManager you can simply make a new instance of the constructor and assign that to save yourself a step.

// A basic route manager, works just like a Backbone.Router (cause it is one)
app.router = new Backbone.RouteManager({
  routes: {
    "": "index"
  },

  index: function() {
    window.alert("Navigated successfully");
  }
});

// Trigger the index route
app.router.navigate("", true);

Named params object

All route callbacks get access to a special object on the Router called params which maps directly to the variables and splats defined in the route.

// A basic route manager, works just like a Backbone.Router (cause it is one)
var AppRouter = new Backbone.RouteManager({
  routes: {
    ":id/*path": "index"
  },

  index: function(id, path) {
    // You can use the arbitrarily named args you define... or you can use the
    // params object on the router.
    window.alert(id == this.params.id);
    window.alert(path == this.params.path);
  }
});

// Create a new instance of the app router
app.router = new AppRouter();

// Trigger the index route
app.router.navigate("5/hi", true);

This is useful for a number of reasons including a special significance for before/after filters that are defined later on.

Nested Routers

So far we haven't seen anything special that we couldn't do already with a normal Backbone.Router. One of the major benefits of RouteManager is that you can define nested Routers. These are defined in the same way as normal routes, except you pass a Backbone.Router class instead.

Nested Routers are just normal Backbone.Router's.

For example:

var SubRouter = Backbone.Router.extend({
  routes: {
    "": "index",
    "test": "test"
  },

  index: function() {
    window.alert("SubRouter navigated successfully");
  },

  test: function() {
    window.alert("sub/test triggered correctly");
  }
});

var AppRouter = Backbone.RouteManager.extend({
  routes: {
    // Define a root level route
    "": "index",

    // Define a sub route
    "sub/": SubRouter
  },

  index: function() {
    window.alert("MasterRouter navigated successfully");
  }
});

// Create a new instance of the app router
app.router = new AppRouter();

// Trigger the test route under sub
app.router.navigate("sub/test", true);

Before/After filters

The real meat of RouteManager, besides the nested Routers, is the ability to define functions to be run before and after a route has fired. This has huge benefits for keeping your Router DRY and flexible.

To define a before and after filter, simply create respectively named objects on your Router along with a key/val set matching routes and callbacks.

The this context inside the filter functions is different from the route callback. this is a special object that can put the function in async/defer mode (which is discussed later). It has a reference to the router that can be accessed with this.router if you wish to store properties or access it.

Backbone.Router.extend({
  before: {
    "": ["auth", "layout"]
  },

  after: {
    "": ["render"]
  }

  routes: {
    "": "index"
  },

  // Before callbacks
  auth: function() {}
  layout: function() {}

  // Route callbacks
  index: function() {}

  // After callbacks
  render: function() {}
});

Params => arguments mapping

Your filter functions may need to be callable from outside of the RouteManager filter environment. Therefore no automatic mapping is possible, but there is a very simple boolean logic assignment that you can make that ensures your functions are correctly callable.

Backbone.Router.extend({
  before: {
    "user/:id": ["getUser"]
  },

  routes: {
    "": "index"
  },

  // Before callbacks
  getUser: function(id) {
    id = id || this.params.id;

    // id maps to this.params.id or whatever value is passed in
    console.log(id);
  }

  // Route callbacks
  index: function() {}
});

Because of this check you can now do something like this:

// Since you are calling this function on its own, the id will be mapped to
// whatever value you'd like, without needing `this.params`.
router.getUser(5);

There are three different types of functions that can be put inside the filters array: Synchronous, Asynchronous, and Promise/Deferreds. These can be mixed and matched together to create a logical and performant flow to your route.

Filters run sequentially and will always block the next function until they are complete. This means if you have ["a","b","c"] as your array of callbacks, a will have to complete before b is executed. The only exception to this is when a Promise/Deferred is encountered and added to the list to resolve.

If you wish to stop the chain at any point (subsequently stopping the route callback from triggering as well), you can do this in a number of ways depending on the type of filter. Each way is discussed in the respective section.

Synchronous filters

These are the most basic filter and require nothing special to use. Just create normal functions that should be executed before a route.

Backbone.Router.extend({
  before: {
    "": ["sync"]
  },

  sync: function() {
    console.log("this runs before index");
  },

  routes: {
    "": "index"
  }
});

To signify an error in your synchronous function to cause remaining functions to not be called, simply return false in your function.

  sync: function() {
    console.log("not going to run any more filter functions");

    return false;
  },

Asynchronous filters

When your code is affected by a non-blocking call (think XHR, setTimeout, etc), the above synchronous definition cannot work, as there would be no way to tell RouteManager when the function is done.

To put the function into asynchronous mode simply assign a done callback with var done = this.async() and call done() when you are finished in the function.

Backbone.Router.extend({
  before: {
    "": ["sync", "async"]
  },

  ...,

  async: function() {
    var done = this.async();

    window.setTimeout(function() {
      console.log("this runs after sync and before index");

      // Progress to the next 
      done();
    }, 1000);
  },

  routes: {
    "": "index"
  }
});

To signify an error in your asynchronous function to cause remaining functions to not be called, simply call done with false.

  async: function() {
    var done = this.async();

    window.setTimeout(function() {
      console.log("this runs after sync and never calls index");

      // Do not progress to the next
      done(false);
    }, 1000);
  },

Deferred/Promise filters

There may be times that you will work with asynchronous code that can be run in parallel. The deferred filter mode was designed for this use case.
When RouteManager encounters a deferred filter it puts it into a bucket and continues on to the next function, it will continue grouping all deferreds until it hits a non-deferred in which it will wait till they resolve and then execute.

To put a filter function into the deferred mode, simply assign this.defer to a variable and call resolve or reject when its finished.

Backbone.Router.extend({
  before: {
    "": ["sync", "async", "defer"]
  },

  ...,

  defer: function() {
    var deferred = this.defer();

    window.setTimeout(function() {
      console.log("this runs after sync, async, and before index");

      // Progress to the next 
      deferred.resolve();
    }, 1000);
  },

  routes: {
    "": "index"
  }
});

It may be useful to leverage an existing deferred instead of creating a new one. This is particularly useful when calling fetch on models and collections. Simply pass the deferred to defer and it will be used internally.

defer: function() {
  this.defer(collection.fetch());
}

To signify an error in your deferred function to cause remaining functions to not be called, simply call reject on the deferred.

  defer: function() {
    var deferred = this.defer();

    window.setTimeout(function() {
      console.log("this runs after sync, async, and stops index");

      // Do not progress to the next 
      deferred.reject();
    }, 1000);
  },

Configuration

Overriding RouteManager options has been designed to work just like Backbone.sync. You can override at a global level using RouteManager.configure or you can specify when instantiating a RouteManager instance.

Global level

Lets say you wanted to use underscore.deferred for the Promise lib instead of jQuery.

// Override all RouteManagers to use underscore.deferred
Backbone.RouteManager.configure({
  deferred: function() {
    return new _.Deferred();
  },

  when: function(promises) {
    return _.when.apply(null, promises);
  }
});

Instance level

In this specific router, use underscore.deferred for the Promise lib instead of jQuery.

app.router = new Backbone.RouteManager({
  routes: {
    "": "index"
  },

  options: {
    deferred: function() {
      return new _.Deferred();
    },

    when: function(promises) {
      return _.when.apply(null, promises);
    }
  }
});

Defaults

  • Deferred: Uses jQuery deferreds for internal operation, this may be overridden to use a different Promises/A compliant deferred.
deferred: function() {
  return $.Deferred();
}
  • When: This function will trigger callbacks based on the success/failure of one or more deferred objects.
when: function(promises) {
  return $.when.apply(null, promises);
}

Release History

About

Better route management for Backbone.js projects.

License:MIT License


Languages

Language:JavaScript 91.5%Language:CSS 7.3%Language:HTML 1.2%