Skip to content

A tool for facilitating the maintenance of FlowRouter routes cleanly.

Notifications You must be signed in to change notification settings

convexset/meteor-flow-router-tree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FlowRouterTree

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.

Table of Contents

Install

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").)

Usage By Example

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: ""
}

Options

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)

Customizing Trigger Order

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).

Additional Properties and Methods (Post Creation)

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

Possibly Useful Helpers

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.

Debug

  • FlowRouterTree.showDebugOutputOnServer(): show debug output on the server
  • FlowRouterTree.hideDebugOutput(): stop showing debug output on the server

About

A tool for facilitating the maintenance of FlowRouter routes cleanly.

Resources

Stars

Watchers

Forks

Packages

No packages published