Skip to content
jayfresh edited this page Nov 5, 2011 · 11 revisions

Routing

The external view to routing was described in Calipso Router, explaining how the core library passes requests through to the module route function using the Event Based Routing approach to trigger dependent modules.

Here we will describe how the router attached to a module (module.router) actually responds when it is invoked, and how modules register the routes that they will respond to. This will be described in more detail, with examples in the initialisation and routing section describing how modules work, but here we will talk about the functions provided in the lib/Router.js library that allow the modules to function.

lib/Router.js

This file exports a single function that creates a router for a specified module, this is invoked as each module is loaded, and then attached to the module in calipso.modules (e.g. calipso.modules.content.router).

This object exposes the following properties and functions:

moduleName: 'contentVersions',
modulePath: 'modules/core/contentVersions',
addRoute: [Function],
routes: [],
route: [Function]

The local storage of moduleName and modulePath are there to simplify debugging and logging, allowing the router to access these details without worrying about its parent object.

The next three are the critical ones and will be described in turn.

addRoute

This function is called to add a route to the router. This function takes the following parameters:

addRoute: function(path, fn, options, next);

path:  a string in the form 'GET /url' where the first piece is the HTTP method to respond to.
       OR
       a regex function (it matches only on GET requests).
fn:    the function in the module to call if the route matches.
options: optional additional configuration options, specifically:
    template - the name of the template to use to render the function output
    block - the name of the block in the response to put the rendered output into
    admin - is the route an administrative route (user must have isAdmin = true)

Example 1: Specific URL - /template

To create a route that responds to the specific URL of http://server/template.

module.router.addRoute('GET /template', templatePage, {
  template: 'templateShow',
  block: 'content.template'
}, this.parallel());

Note: The use of this.parallel() as the next function is due to the fact that most routes are defined within the module initialisation function, and are controlled by step.

Lets walk through each of these parameters in sequence.

'GET /template'

This is the description of the route that will be matched based on the incoming request. The matching approach is lifted exactly from Express, so if you are familiar with how url matching in Express works you will be familiar with how it works in Calipso.

In this example it is a string, not a regex function. The format of this string takes the following format:

'<HTTP METHOD> <URL>'

Where:

<HTTP METHOD> = GET, PUT, POST, DEL
<URL> = relative url, can include selectors prefixed by a : - e.g. /content/:id that will be captured as moduleParams

For example:

'GET /template'
'GET /content/:id'
'POST /content'
'GET /content/show/:id/versions/diff/:a/:b'

The final of these will result in the actual url being matched, with the params being pulled out into the moduleParams hashmap:

'/content/show/1234/versions/diff/1/4' => MATCH
req.moduleParams === {id:'1234',a:'1',b:'4'};

You can also specify optional parameters, e.g. 'GET /content/:id.:format?'.

Note on Module Params

Parameters matched in a pattern (e.g. /content/:id) will mean that id will only be available within module functions that matched them, e.g. inside a route with a regex of /.*/, the req.moduleParams.id === undefined even if you are viewing /content/1234.

However, if the match all regex route is defined as last, then you may be able to use the res.params object (which is progressively loaded by all the modules that match), to get visibility of the full set of matched routes and their parameters.

Note on Query String

If you pass any parameters as query string parameters (e.g. /template?id=1) these will not be matched as part of the url, but will be extracted by the router and added to the moduleParams hashmap. If they duplicate those matched in the URL they will over-ride them (e.g. these happen last).

Note on Form Submission

Often forms are submitted as part of a POST, these are processed outside of the router (in the core Calipso library, as forms need to be processed by formidable as early in the request processing chain as possible. Once it is processed, the contents of any form are available on the request object - req.form.

templatePage :: function reference

The second parameter to the routing function is always the function that is called when the route is matched. This function must take the following parameters:

function allPages(req, res, template|html, block, next)

These parameters are all required, and are described as:

  • req: the incoming request
  • res: the outgoing response
  • template|html:
    • template: the name of the template file (relative to local ./templates folder) OR
    • html: raw html to be rendered
  • block: the name of the block to render the content to
  • next: callback

The typical flow for a function is shown in the following snippet from the template module, responding to the following route:

// Add a route to every page, notice the 'end:false' to ensure block further routing
module.router.addRoute(/.*/, allPages, {
  template: 'templateAll',
  block: 'side.template.all'
}, this.parallel());

Is this function:

/**
 * Small block that will appear on every page
 */
function allPages(req, res, template, block, next) {

  // All available parameters
  // NOTE: This only works here because this template is last:true (see exports).
  var params = res.params;

  // Get some data (e.g. this could be a call off to Mongo based on the params
  var item = {
      variable: "Hello World",
      params: params
  };

  // Now render the output
  calipso.theme.renderItem(req, res, template, block, {
    item: item
  },next);

};

Using the following template (in EJS):

<div id="template-module">
  <h2><%= t('All Pages Template') %></h2>
  <p>Variable: <%= item.variable %></p>
  <p>Params:</p>
    <pre>
      <%= JSON.stringify(item.params) %>
    </pre>
 </div>

What this will do, is that after the module router has matched the route, it will call the function, which will then use the calipso.theme.renderItem to render the template and put the result into the specified block, in this instance, the output will be available via:

res.renderedBlocks['side.template.all']

And, specifically, can be used within a theme backing file (as retrieving a block is asynchronous), for more information please see the later section on how the themes and theme helper functions work. However, an example is:

// All side objects
options.getBlock(/^side.*/,this.parallel());

// Explicit
options.getBlock('side.template.all',this.parallel());

The example of where this is used is in themes/core/cleanslate/templates/default/sidePanel.js and the corresponding sidePanel.html.

options {}

The third parameter is an optional hashmap of options that are used in addition to the URL pattern / regex to match the route or to pass parameters through to the routing function. Current valid options are (with their default value first):

Filter Options
  • admin: false - indicate if only a logged in administrator can access this route.
Cache Options
  • cache: false - indicate if this function is cacheable.
  • cacheTtl: 600 - time to live for this output in the cache.
Pass Through Parameters
  • template: the name of the template to pass through for rendering (this also causes the template to be compiled and cached).
  • block: the name of the block to place the rendered output into.

For example, for the content module, 'GET tag/:tag.:format?' route, the options are:

{template:'list',cache:true,block:'content.tag.list'}

Callback

The final option is the callback used for the router to callback when it has completed the configuration of the route (it is asynchronous as the templates are loaded in this process and this is done asynchronously).

In most examples this is the parallel() option of step (calipso.lib.step), as this is used to control the initialisation flow.