diff --git a/client/route.js b/client/route.js index f9eadbe..a8b53f7 100644 --- a/client/route.js +++ b/client/route.js @@ -42,15 +42,6 @@ Route.prototype.getAllSubscriptions = function() { return this._subsMap; }; - -Route.prototype._processTriggersEnter = function(context) { - _.each(this._triggersEnter, function(fn) { - if (typeof fn === 'function') { - fn(context); - } - }); -}; - Route.prototype._processMiddlewares = function(context, after) { var currentIndex = 0; var self = this; @@ -76,8 +67,6 @@ Route.prototype._processMiddlewares = function(context, after) { Route.prototype.callAction = function(current) { var self = this; - self._processTriggersEnter(current); - self._processMiddlewares(current.context, function() { self._action(current.params, current.queryParams); }); diff --git a/client/router.js b/client/router.js index 60e5934..950082a 100644 --- a/client/router.js +++ b/client/router.js @@ -21,11 +21,8 @@ Router = function () { // when doing subscriptions this.safeToRun = false; - var self = this; - this.triggers = { - enter: self._getRegisterTriggersFn(self._triggersEnter), - exit: self._getRegisterTriggersFn(self._triggersExit) - }; + this._redirectFn = _.bind(this._page.redirect, this._page); + this._initTriggersAPI(); }; Router.prototype.route = function(path, options, group) { @@ -38,7 +35,8 @@ Router.prototype.route = function(path, options, group) { var self = this; var route = new Route(this, path, options, group); - route._handler = function (context, next) { + // calls when the page route being activates + route._actionHandle = function (context, next) { var oldRoute = self._current.route; self._current = { @@ -53,7 +51,31 @@ Router.prototype.route = function(path, options, group) { // to backward compatibility self._current.params.query = self._current.queryParams; - self._invalidateTracker(); + // we need to invalidate if all the triggers have been completed + // if not that means, we've been redirected to another path + // then we don't need to invalidate + var afterAllTriggersRan = function() { + self._invalidateTracker(); + }; + + var triggers = self._triggersEnter.concat(route._triggersEnter); + Triggers.runTriggers( + triggers, + self._current, + self._redirectFn, + afterAllTriggersRan + ); + }; + + // calls when you exit from the page js route + route._exitHandle = function(context, next) { + var triggers = self._triggersExit.concat(route._triggersExit); + Triggers.runTriggers( + triggers, + self._current, + self._redirectFn, + next + ); }; this._routes.push(route); @@ -317,8 +339,6 @@ Router.prototype._buildTracker = function() { Tracker.nonreactive(function() { var currentContext = self._current; - self._processTriggersEnter(currentContext); - var isRouteChange = currentContext.oldRoute !== currentContext.route; var isFirstRoute = !currentContext.oldRoute; // first route is not a route change @@ -353,98 +373,11 @@ Router.prototype._buildTracker = function() { Router.prototype._invalidateTracker = function() { this.safeToRun = true; this._tracker.invalidate(); -}; - -Router.prototype._getRegisterTriggersFn = function(triggers) { - var fn = function(triggerFns, options) { - options = options || {}; - - if (options.only && options.except) { - var message = "triggers does not support 'only' and 'except' at the same time."; - throw new Error(message); - } - - _.each(triggerFns, function(fn) { - if (typeof fn !== 'function') { - return; - } - - if (options.only) { - fn._only = {}; - _.each(options.only, function(name) { - fn._only[name] = 1; - }); - } - - if (options.except) { - fn._except = {}; - _.each(options.except, function(name) { - fn._except[name] = 1; - }); - } - - triggers.push(fn); - }); - }; - - return fn; -}; - -Router.prototype._shouldCallTrigger = function(current, fn) { - var name = current.route.name; - var shouldCall; - - if (typeof fn !== 'function') { - return false; - } - - if (fn._only) { - shouldCall = !!fn._only[name]; - } else if (fn._except) { - shouldCall = !fn._except[name]; - } else { - shouldCall = true; - } - - return shouldCall; -}; - -Router.prototype._processTriggersEnter = function(current) { - var self = this; - - _.each(this._triggersEnter, function(fn) { - if (self._shouldCallTrigger(current, fn)) { - fn(current); - } - }); -}; - -Router.prototype._processTriggersExit = function(ctx, next) { - var self = this; - - _.each(self._triggersExit, function(fn) { - if (self._shouldCallTrigger(self._current, fn)) { - fn(self._current); - } - }); - - next(); -}; - -Router.prototype._registerRouteTriggersExit = function(route) { - var self = this; - - if (route._triggersExit.length > 0) { - // add route's exit triggers - self._page.exit(route.path, function(ctx, next) { - _.each(route._triggersExit, function(fn) { - if (typeof fn === 'function') { - fn(self._current); - } - }); - - next(); - }); + // we need to trigger the above invalidations immediately + // otherwise, we need to face some issues with route context swapping + // if this is a running autorun we don't need to flush it + if(!Tracker.currentComputation) { + Tracker.flush(); } }; @@ -460,16 +393,29 @@ Router.prototype._updateCallbacks = function () { }); _.each(self._routes, function(route) { - self._page(route.path, route._handler); - self._registerRouteTriggersExit(route); + self._page(route.path, route._actionHandle); + self._page.exit(route.path, route._exitHandle); }); - self._page.exit("*", self._processTriggersExit.bind(self)); - self._page("*", function(context) { self._notfoundRoute(context); }); }; +Router.prototype._initTriggersAPI = function() { + var self = this; + this.triggers = { + enter: function(triggers, filter) { + triggers = Triggers.applyFilters(triggers, filter); + _.extend(self._triggersEnter, triggers); + }, + + exit: function(triggers, filter) { + triggers = Triggers.applyFilters(triggers, filter); + _.extend(self._triggersExit, triggers); + } + }; +}; + Router.prototype._page = page; -Router.prototype._qs = qs; +Router.prototype._qs = qs; \ No newline at end of file diff --git a/client/triggers.js b/client/triggers.js new file mode 100644 index 0000000..2fc0f2f --- /dev/null +++ b/client/triggers.js @@ -0,0 +1,108 @@ +// a set of utility functions for triggers + +Triggers = {}; + +// Apply filters for a set of triggers +// @triggers - a set of triggers +// @filter - filter with array fileds with `only` and `except` +// support only either `only` or `except`, but not both +Triggers.applyFilters = function(triggers, filter) { + if(!(triggers instanceof Array)) { + triggers = [triggers]; + } + + if(!filter) { + return triggers; + } + + if(filter.only && filter.except) { + throw new Error("Triggers don't support only and except filters at once"); + } + + if(filter.only && !(filter.only instanceof Array)) { + throw new Error("only filters needs to be an array"); + } + + if(filter.except && !(filter.except instanceof Array)) { + throw new Error("except filters needs to be an array"); + } + + if(filter.only) { + return Triggers.createRouteBoundTriggers(triggers, filter.only); + } + + if(filter.except) { + return Triggers.createRouteBoundTriggers(triggers, filter.except, true); + } + + throw new Error("Provided a filter but not supported"); +}; + +// create triggers by bounding them to a set of route names +// @triggers - a set of triggers +// @names - list of route names to be bound (trigger runs only for these names) +// @negate - negate the result (triggers won't run for above names) +Triggers.createRouteBoundTriggers = function(triggers, names, negate) { + var namesMap = {}; + _.each(names, function(name) { + namesMap[name] = true; + }); + + var filteredTriggers = _.map(triggers, function(originalTrigger) { + var modifiedTrigger = function(context, next) { + var routeName = context.route.name; + var matched = (namesMap[routeName])? 1: -1; + matched = (negate)? matched * -1 : matched; + + if(matched === 1) { + originalTrigger(context, next); + } + }; + return modifiedTrigger; + }); + + return filteredTriggers; +}; + +// run triggers and abort if redirected +// @triggers - a set of triggers +// @context - context we need to pass (it must have the route) +// @redirectFn - function which used to redirect +// @after - called after if only all the triggers runs +Triggers.runTriggers = function(triggers, context, redirectFn, after) { + var abort = false; + var inCurrentLoop = true; + var alreadyRedirected = false; + + for(var lc=0; lc