millermedeiros / crossroads.js

JavaScript Routes
http://millermedeiros.github.com/crossroads.js/
1.44k stars 156 forks source link

Signal when routing / bypassing on router and any piped routers? #125

Closed asos-tomp closed 9 years ago

asos-tomp commented 9 years ago

I had a requirement to know when a router has successfully routed (or has bypassed) both itself and for any piped routers it has attached.

This allows for a global 404 handler to be attached, or otherwise. It should function for any depth of router hierarchy.

I patched crossroads as per below, providing a "routedOrSubRouted" signal - it actually fires after each parse() with a true/false if a route was found - so is more like a "route or bypass" event. I needed to set ignoreState = true on all routers. Otherwise when parsing a previously found route, the routed signal doesn't fire, and this code assumes no route was found.

If there's less messy way to achieve this (or similar functionality a consideration for a pull request), please let me know!

p.s. I'm using underscore, but should be easy to refactor this to use vanilla JS. Also using require, to explain the define().

define(["crossroads", "signals", "underscore"], function (crossroads, signals, _) {

    crossroads.__proto__.create = _.wrap(crossroads.__proto__.create, function (create) {
        var created = create();
        created.routedOrSubRouted = new signals.Signal();
        return created;
    });

    crossroads.__proto__.parse = _.wrap(crossroads.__proto__.parse, function (parse) {
        this._foundRoute = false;

        this.routed.addOnce(function () {
            this._foundRoute = true;
        }, this);

        parse.apply(this, [].slice.call(arguments, 1));

        this.routedOrSubRouted.dispatch.apply(this.routedOrSubRouted, [this.foundRoute()]);
    });

    crossroads.__proto__.foundRoute = function () {
        return this._foundRoute || (_.find(this._piped, function (subRouter) {
            return subRouter.foundRoute() == true;
        }) !== undefined);
    };
});
millermedeiros commented 9 years ago

it all depends on how you are chaining/piping all the routers and how you trigger the top parse call.. I would probably increase a counter on each routed dispatch similar to this:

var bypassed = new Signal();
var routed = new Signal();
var _nRouted = 0;

var create = crossroads.create;
crossroads.create = function() {
  var out = create();
  out.routed.add(registerRouted);
  return out;
};

function registerRouted() {
  _nRouted += 1;
}

var parse = crossroads.parse;
crossroads.parse = function() {
  _nRouted = 0;
  parse.apply(crossroads, arguments);
  // since signals and crossroads are synchronous this should be reliable
  if (_nRouted ===  0) {
    bypassed.dispatch();
  } else {
    routed.dispatch();
  }
};

PS: I wrote this code directly on the github comment box and did not test it, it's just to give an idea of a different implementation.

millermedeiros commented 9 years ago

you could also probably just add the routed signal during the parse call on all the _piped routes..

millermedeiros commented 9 years ago
var parse = crossroads.__proto__.parse;
crossroads.__proto__.parse = function() {
  var nRouted = 0;
  var registerRouted = function() {
    nRouted += 1;
  };

  this._piped.concat(this).forEach(function(router) {
    router.routed.addOnce(registerRouted);
  });

  parse.apply(this, arguments);

  this._piped.concat(this).forEach(function(router) {
    router.routed.remove(registerRouted);
  });

  var signal = !nRouted ? bypassed : routed;
  signal.dispatch();
};