Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OPTIONALLY better handling of routing when stop is called from a trigger (stop() generates current behaviour; stop(true) generates the new behaviour) #681

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,113 @@ function localeCheck(context, redirect, stop) {

> **Note**: When using the stop function, you should always pass the second **redirect** argument, even if you won't use it.

#### New Behaviour of Stop, and the Invocation Context of Trigger Functions

This segment outlines new behavior added to `stop`.

From the forgoing discussion, we learnt that that triggers are invoked with three arguments:
1. `context`: information about the route (specifically, the output of `FlowRouter.current()`)
2. `redirect`: a function that may be used for redirection to other routes
3. `stop`: a function that aborts routing when invoked

`stop` now takes a single argument called `goBackOnStop` which defaults to `false` (its original behavior in FlowRouter). When set to `true`:
- After using the `stop` function in `triggersExit` function, a duplicate history item (in the "next" slot) will be created. (Sorry...) But `triggersExit` triggers will fire again when attempting to route away once more. The process is as follows:
1. `stop` is called
2. a "re-entrant" exit (more below) is made from the "new route" which was prevented from completing
3. a "re-entrant" entry (more below) is made into the "old route" from which stop was called
- After using the `stop` function in `triggersEnter` function, a history item (in the "next" slot) will be created for the route on which "enter" was "stopped".
1. `stop` is called
2. a "re-entrant" exit (more below) is made from the "new route" which was prevented from completing
3. a "re-entrant" entry (more below) is made into the "old route" that was exited before stop was called if it exists (i.e.: the failed entry is not into the first history item)

However, to achieve that, the router will "exit" the routes whose routing attempts didn't successfully complete after being aborted with `stop`. It will then enter the original source route. This information is made available to all trigger functions in their invocation contexts.

Additional information is available to entry/exit triggers in the form of their invocation context (i.e.: `this`).

In particular, for entry triggers `this` takes the form:
```javascript
{
type: "enter",
route: /* new route */,
newRoute: /* new route */,
oldRoute: /* previous route */,
router: /* essentially FlowRouter */,
stopped: /* whether stop has been called */,
isReentrant_followingStoppedExit: /* a boolean that indicates whether this
entry is a result of route entry/exit
after an exit is stopped using the stop
function (third argument of a trigger)

what happens is:
(1) stop is called
(2) a "re-entrant" exit is made from the
"new route" which was prevented from
completing
(3) a "re-entrant" entry is made into
the "old route" from which stop was
called
*/,
isReentrant_followingStoppedEnter:/* a boolean that indicates whether this
entry is a result of route entry/exit
after an enter is stopped using the stop
function (third argument of a trigger)

what happens is:
(1) stop is called
(2) a "re-entrant" exit is made from the
"new route" which was prevented from
completing
(3) a "re-entrant" entry is made into
the "old route" that was exited
before stop was called if it exists
(i.e.: the failed entry is not into
the first history item)
*/
}
```

In particular, and for exit triggers `this` takes the form:
```javascript
{
type: "exit",
route: /* current, soon to be former, route */,
oldRoute: /* current, soon to be former, route */,
router: /* essentially FlowRouter */,
stopped: /* whether stop has been called */
isReentrant_followingStoppedExit: /* a boolean that indicates whether this
entry is a result of route entry/exit
after an exit is stopped using the stop
function (third argument of a trigger)

what happens is:
(1) stop is called
(2) a "re-entrant" exit is made from the
"new route" which was prevented from
completing
(3) a "re-entrant" entry is made into
the "old route" from which stop was
called
*/,
isReentrant_followingStoppedEnter:/* a boolean that indicates whether this
entry is a result of route entry/exit
after an enter is stopped using the stop
function (third argument of a trigger)

what happens is:
(1) stop is called
(2) a "re-entrant" exit is made from the
"new route" which was prevented from
completing
(3) a "re-entrant" entry is made into
the "old route" that was exited
before stop was called if it exists
(i.e.: the failed entry is not into
the first history item)
*/
}
```


## Not Found Routes

You can configure Not Found routes like this:
Expand Down
34 changes: 32 additions & 2 deletions client/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,22 @@ Router.prototype.route = function(pathDef, options, group) {
};

var triggers = self._triggersEnter.concat(route._triggersEnter);
Triggers.runTriggers(
var triggersEnterInvocationContext = {
type: "enter",
route: route,
newRoute: route,
oldRoute: oldRoute,
router: self,
stopped: false,
isReentrant_followingStoppedExit: !!self.__is_reentrant_following_stop_exit__,
isReentrant_followingStoppedEnter: !!self.__is_reentrant_following_stop_enter__
};
delete self.__is_reentrant_following_stop_exit__;
delete self.__is_reentrant_following_stop_enter__;

// triggers for entry aren't run when "going back to an old route"
// after stopping entry to another route
Triggers.runTriggers.call(triggersEnterInvocationContext,
triggers,
self._current,
self._redirectFn,
Expand All @@ -113,7 +128,22 @@ Router.prototype.route = function(pathDef, options, group) {
// calls when you exit from the page js route
route._exitHandle = function(context, next) {
var triggers = self._triggersExit.concat(route._triggersExit);
Triggers.runTriggers(
var triggersExitInvocationContext = {
type: "exit",
route: route,
oldRoute: route,
router: self,
stopped: false,
isReentrant_followingStoppedExit: !!self.__is_reentrant_following_stop_exit__,
isReentrant_followingStoppedEnter: !!self.__is_reentrant_following_stop_enter__
};

if (!self._current.path && !!self.__is_reentrant_following_stop_exit__) {
// patch up the path in a re-entrant exit
self._current.path = context.path;
}

Triggers.runTriggers.call(triggersExitInvocationContext,
triggers,
self._current,
self._redirectFn,
Expand Down
50 changes: 47 additions & 3 deletions client/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Triggers.createRouteBoundTriggers = function(triggers, names, negate) {
matched = (negate)? matched * -1 : matched;

if(matched === 1) {
originalTrigger(context, next);
originalTrigger.apply(this, arguments);
}
};
return modifiedTrigger;
Expand All @@ -74,11 +74,53 @@ Triggers.runTriggers = function(triggers, context, redirectFn, after) {
var inCurrentLoop = true;
var alreadyRedirected = false;

var triggerInvocationContext = this;
var _goBackOnStop = false;

for(var lc=0; lc<triggers.length; lc++) {
var trigger = triggers[lc];
trigger(context, doRedirect, doStop);
trigger.call(triggerInvocationContext, context, doRedirect, doStop);

if(abort) {
// Sorry to say, quick hack like this to enable firing off exit triggers
// again following a stop comes at a price of a repeat entry in one's
// browser history (as the next item). Benign, perhaps, but annoying.
//
// This seems to be an outcome of the way FlowRouter's trigger system is
// designed.

if (triggerInvocationContext.stopped && _goBackOnStop) {
var router = triggerInvocationContext.router;

if (triggerInvocationContext.type === "exit") {
Meteor.defer(function() {
// FlowRouter will rebuild this eventually
router._current = {
// path: context.path, // need to leave this out
params: context.params,
queryParams: context.queryParams,
route: triggerInvocationContext.route,
oldRoute: triggerInvocationContext.route,
};

router.__is_reentrant_following_stop_exit__ = true;
router._page.replace(context.path);
Meteor.defer(function() {
if (router._page.len > 0) {
router._page.back();
}
});
});
}

if (triggerInvocationContext.type === "enter") {
Meteor.defer(function() {
router.__is_reentrant_following_stop_enter__ = true;
router._page.back();
});
}
}

return;
}
}
Expand Down Expand Up @@ -106,7 +148,9 @@ Triggers.runTriggers = function(triggers, context, redirectFn, after) {
redirectFn(url, params, queryParams);
}

function doStop() {
function doStop(goBackOnStop = false) {
_goBackOnStop = goBackOnStop;
abort = true;
triggerInvocationContext.stopped = abort;
}
};
1 change: 1 addition & 0 deletions package.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Npm.depends({

Package.onUse(function(api) {
configure(api);
api.use('ecmascript');
api.export('FlowRouter');
});

Expand Down