A tool for facilitating the creation of a clean set of FlowRouter routes. The idea is that routes might be viewed as a forest of trees, and child nodes (routes) should inherit configuration aspects of parent nodes (routes) by default unless the settings are explicitly changed. This cleans up one's routes.js
substantially.
This is available as convexset:flow-router-tree
on Atmosphere. (Install with meteor add convexset:flow-router-tree
.)
If you get an error message like:
WARNING: npm peer requirements not installed:
- package-utils@^0.2.1 not installed.
Read more about installing npm peer dependencies:
http://guide.meteor.com/using-packages.html#peer-npm-dependencies
It is because, by design, the package does not include instances of these from npm
to avoid repetition. (In this case, meteor npm install --save package-utils
will deal with the problem.)
See this or this for more information.
Now, if you see a message like
WARNING: npm peer requirements not installed:
underscore@1.5.2 installed, underscore@^1.8.3 needed
it is because you or something you are using is using Meteor's cruddy old underscore
package. Install a new version from npm
. (And, of course, you may use the npm
version in a given scope via require("underscore")
.)
See the example in ./demo/
.
Start by creating a root node. Here is one with all the options in. (Scary perhaps, but it's nice.)
var root = FlowRouterTree.createNode({
name: 'rootNode',
description: "Main",
path: '',
params: {
layout: "MainLayout",
header: "Header",
content: "Main",
footer: "Footer"
},
actionFactory: FlowRouterTree.configureParameterizedAction(
function blazeLayoutRenderThreeComponent(params, queryParams, actionParams) {
BlazeLayout.render(actionParams.layout, {
header: actionParams.header,
content: actionParams.content,
footer: actionParams.footer,
routeParams: {
params: params,
queryParams: queryParams
}
});
},
['layout', 'header', 'content', 'footer']
),
triggersEnter: {
enterTrigger: function(context, redirect) {
console.log('Entry Trigger');
console.log('Context:', context);
}
},
triggersExit: {
exitTrigger: function(context, redirect) {
console.log('Default Exit Trigger');
}
},
providesParentRoutePrefix: true,
makeRoute: true,
});
Here are the default options:
{
parent: null,
params: {},
actionFactory: null,
triggersEnter: {},
triggersExit: {},
providesParentRoutePrefix: false,
makeRoute: true,
description: ""
}
Option | Description |
---|---|
parent |
parent node |
path |
Path component. The full route is generated by combining path components, such as /component_1/component_2/.../component_n . But with caveats, see providesParentRoutePrefix . |
providesParentRoutePrefix |
Indicates whether this path component is included in those of this node's children. It is always included in the route for this node. e.g.: if component_2 is left out, we obtain /component_1/component_3/.../component_n . |
actionFactory |
A parameterized version of FlowRouter actions. This means passing a parameterized action function (with an additional third argument containing an objects with parameters) through FlowRouterTree.configureParameterizedAction like so: FlowRouterTree.configureParameterizedAction(actionParameterized, ['nameOfParam1', 'nameOfParam2', ...]) |
params |
Parameters that will be passed into the parameter factory. The parameters passed are those specified in the call to FlowRouterTree.configureParameterizedAction (in the array of argument names). If not present, they will be inherited from the nearest ancestor node where it is specified. |
triggersEnter |
A dictionary (object) of FlowRouter entry triggers. Inherits from parents by key (name). Override using null . |
triggersExit |
A dictionary (object) of FlowRouter entry triggers. Inherits from parents by key (name). Override using null . |
makeRoute |
Whether node provides an actual route (if false, it is just a placeholder) |
description |
A text description of the node |
accessChecks |
See convexset:access-check and use the same format as the accessChecks key. (Leaving unset implies inheritance from parent; Defining accessChecks overwrites checks on the parent, if applicable; Set to null to not inherit checks from parent) |
The following compare functions for sorting triggers can be set:
FlowRouterTree.triggersEnterSortFunction
FlowRouterTree.triggersExitSortFunction
They compare objects of schema
{
triggerName: /* the given "trigger name" */,
triggerFunction: /* the actual trigger function */,
sourceName: /* name of the source node */,
sourceLevelsUp: /* number of levels up in the route tree where this trigger
comes from. 0 means this the current route; 1 means
its immediate parent */,
sourceNode: /* the node object itself, please don't mishandle */,
}
The default behaviour is that:
- "enter" triggers are ordered by seniority (the most senior ancestor "enter" triggers run first)
- "exit" triggers are ordered by youth (the most junior descendant "exit" triggers run first)
One semi-sketchy pattern would be to include a priority note in the trigger name such as:
// ...
triggersEnter: {
someTrigger: someTrigger,
otherTrigger: someTrigger,
"importantTrigger|priority=3": importantTrigger,
"moreImportantTrigger|priority=1": moreImportantTrigger
},
// ...
and making changes to the default behaviour like so:
const originalTriggersEnterSortFunction = FlowRouterTree.triggersEnterSortFunction;
const originalTriggersExitSortFunction = FlowRouterTree.triggersExitSortFunction;
function parsePriority(t) {
if (t.triggerName.split('|').length < 2) { return Infinity; }
const prefix = 'priority=';
const s = t.triggerName.split('|')[1] || '';
const p = parseFloat(s.substr(s.indexOf(prefix) + prefix.length));
return Number.isNaN(p) ? Infinity : p;
}
FlowRouterTree.triggersEnterSortFunction = function(t1, t2) {
const t1P = parsePriority(t1);
const t2P = parsePriority(t2);
if (t1P === t2P) {
return originalTriggersEnterSortFunction(t1, t2);
} else {
return t1P < t2P ? -1 : 1;
}
};
FlowRouterTree.triggersExitSortFunction = function(t1, t2) {
const t1P = parsePriority(t1);
const t2P = parsePriority(t2);
if (t1P === t2P) {
return originalTriggersExitSortFunction(t1, t2);
} else {
return t1P < t2P ? -1 : 1;
}
};
The accessChecks
key: Using convexset:access-check
Use the same syntax as access checks for Meteor Methods and Publications in convexset:access-check apply (via the accessChecks
key).
The where
sub-key is ignored, however and set to refer to the client.
Generally speaking, client-side failure callbacks should result in routing to a page which the current user is more likely to be authorized to be on. For example, access controls on a restricted route/template might boot an unauthorized user to the "main user dashboard" (MUD?) and access controls on the MUD might boot an unauthorized user to the login page (where probably no access controls apply except perhaps geographical ones by IP address, in which case...)
Access checks are implemented as "entry triggers" and will be the first in the list.
The failure callback will be called and the used should rely on the invocation context (i.e.: this
) to identify the "flow-router-tree
" context and react accordingly.
Checks and failure callbacks are invoked with the following context (i.e.: "this
"):
{
contextType: "flow-router-tree",
context: context,
redirect: redirect,
stop: stop
}
where context
, redirect
and stop
are as outlined in FlowRouter triggers (see this for use of stop
).
Note: Due to the nature of how FlowRouter
is designed, it is recommended that stop
(see below) not be used until PR #681 is merged as the current (2.12.1) implementation does not handle the "current path state" properly on stop()
.
The parameters passed into the AccessCheck
argument map, if any, will be the same as outlined in the Options above, based on the parameters outlined in the actionFactory
(with inheritance from parent nodes as necessary).
Property / Method | Description |
---|---|
route |
the route of this node |
routePrefix |
the route prefix of this node |
roots() |
returns the names of root nodes (nodes with no parents) |
nodes |
a dictionary (object) of node names pointing to nodes |
tree |
a dictionary of nodes names each pointing to the name of the relevant parent node |
The FlowRouterTree.SampleParameterizedActions
dictionary:
blazeLayoutRenderThreeComponent
with parameters['layout', 'header', 'content', 'footer']
(actual source featured above)blazeLayoutRenderOneComponent
with parameters['layout', 'content']
The FlowRouterTree.SampleTriggerFactories
dictionary:
redirectAfterLoginFactory(loginRouteName, sessionVariableNameForRedirectPath)
: Provides route-level authentication, redirecting visitors to the login screen if not authenticated and sends the user back once logged-in. See this for more information.
FlowRouterTree.showDebugOutputOnServer()
: show debug output on the serverFlowRouterTree.hideDebugOutput()
: stop showing debug output on the server