API Docs for: 0.1.4
Show:

$routing Class

Module: Builtins

Routing for Mimeo

This builtin handles routing by managing the browsers history and matching routes with injectables (usually components.)

The general workflow would be to inject $routing into a .run() injectable on your root module along with the injectables you want to match to the routes, and define routes there:

 mimeo.module('example', [])
     .run([
         '$routing',
         'usersComponent',
         'loginComponent',
         ($routing) => {
             $routing.set('/users', usersComponent);
             $routing.set('/login', loginComponent);
         }
     );

Generating output

How output is generated is up to the matched injectable. Once an injectable is matched to a route, it is invoked with three parameters:

  • context
  • renderer
  • targetDOMNode

Context is an object that contains information about the matched route. See the set method for more details. Renderer is a helper to produce output and can be configured. targetDOMNode is the DOM node that was associated with the route.

Since the injectable has access to the DOM node, it can simply update the nodes content to produce output. The renderer is not strictly necessary. However, when using a rendering library like React, manually calling ReactDOM.render(exampleComponent, targetDOMNode) is annoying and also makes it impossible to switch to e.g. ReactDOMServer.renderToStaticMarkup(exampleComponent) to produce output in NodeJS.

Using a renderer has the advantage of being able to change the rendering method depending on the environment the app is in. Using setMakeRenderer to define a default renderer allows the matched injectable to simply call renderer(exampleComponent) and not deal with the specifics of generating output. An example for React:

 mimeo.module('example', [])
     // target is not used since the custom renderer will take care of
     // mounting the react node
     .component(['usersComponent', () => ($context, $render) => {
         let Users = React.createClass({}); // example component

         return $render(<Users />);
     })
     .run(['$routing', 'usersComponent', ($routing, usersComponent) => {
         $routing.setMakeRenderer(function(targetDOMNode) {
             return function(reactNode) {
                 return ReactDOM.render(reactNode, targetDOMNode);
             };
         });

         $routing.set('/users', usersComponent);
     });

Initiate routing

There are three ways to change the current route:

  • goto
  • a-tag with a href and a 'data-internal' attribute
  • a-tag with a href, a 'data-internal' and 'data-no-history' attribute

.goto() is mainly used for server-side rendering. If you set a a renderer that supports server-side output, you won't have to change your components to generate the output. .goto() will return a promise that is full-filled with the return value from the component. You can have your server-side entry-point attach to that promise and then do with the output what you need (e.g. send an email, save to a static .html file, etc.)

The other two are simply a-tags in your html. $routing attaches an event handler to the document that listens to clicks on a-tags with a 'data-internal' attribute. The value from the 'href' attribute is used as the route to handle. The 'data-no-history' attribute controls whether a new browser-history entry is created. If the attribute is present, no history is created.

Methods

goto

(
  • route
)
Promise

Matches route and executes all associated injectables

The return values from the matched injectables are turned into a promise using $q.when(), and then aggregated with $q.all() and then returned by goto(). This allows handling asynchronous requests on the server.

Parameters:

  • route String

    Route to go to

Returns:

Promise:

Promise that is resolved with the return values from all matched routes

Example:

 mimeo.module('example', []).
     .component('Blog', ['$http', ($http) => () => {
         return $http.get('/example-api/blogs')
             .then((response) => {
                 return response.data;
             })
             .then((blogPosts) => {
                 return //turn blog posts into html
             });
     })
     .run(['$routing', 'Blog', ($routing, Blog) => {
         $routing.set('/blogs', Blog);
     }])
     .run(['$routing', ($routing) => {
         $routing.goto('/blogs').then((blogHtml) => {
             // save to cdn
         });
     });

onRouting

(
  • handler
)

Add event handlers to be executed whenever a new route is handled, via $routing.goto(), the window.onpopstate event or a click on a controlled link.

Parameters:

  • handler Function

    The callback to be executed when a new url is handled. It receives four parameters:

     - url {string} The url handled (regardless if handlers are found)
     - parts {object} Parsed url, same as $context.url that's passed
         to a route handler
     - handlerExecuted {Boolean} Whether a handler was found and
         executed
     - defaultRouteExecuted {Boolean} Whether the url handled was the
         default route

set

(
  • route
  • target
  • injectable
  • [name]
)

Sets a handler for a route. There can be multiple handlers for any route.

The route matching is handled by (the route-recognizer package, read the docs regarding the route syntax here)[https://github.com/tildeio/route-recognizer#usage]. You can capture parts of the url with :name and *name:

 $routing.set('/users/:id')
 //=> matches /users/1 to { id: 1 }

 $routing.set('/about/*path')
 //=> matches /about/location/city to { path: 'location/city' }

Captured segments of the url will be available in $context.params.

Setting a route matches an injectable with a url:

 $routing.set('/example-url', exampleInjectable);

The injectable that will receive three parameters:

  • $context - information about the current route and access to url parameters
  • $renderer - the renderer $routing is configured to use. Default just set the html content of the target DOM node
  • $target - DOM node that the content should end up in. Useful if you don't want to use $renderer for a specific route

Set routes in a .run() block on your root module:

 mimeo.bootstrap('example', [])
     .component(['users', () => ($context, $renderer) => {
         $renderer('<ul><li>John</li><li>Alice</li</ul>');
     }])
     .component(['loginForm', () => ($context, $renderer) => {
         $renderer('<form></form>');
     }])
     .run([
         '$routing',
         'users',
         'loginForm',
         ($routing, users, loginForm) => {
             $routing.set('/users', users);
             $routing.set('/login', loginForm);
         }
     ]);

The .run() block needs to have all component-injectables you want to set as route handlers injected. .set() requires the actual injectables to be passed in, not the injectables name.

$context contains information about the current route, it has three attributes:

  • $context.params will contain any matched segments from the url.
  • $context.query will contain decoded query parameters as a key-value hash. Repeating keys will create an array: /example?a=1&b=2&c=3 //=> { a: [1, 2, 3] }
  • $context.url represents the parsed url as a key-value store.

$context.url example for http://localhost:3000/?example-key=value:

 $context.url = {
     anchor: '',
     authority: 'localhost:3000',
     directory: '/',
     file: '',
     host: 'localhost',
     password: '',
     path: '/',
     port: '3000',
     protocol: 'http',
     query: 'example-key=value',
     relative: '/?example-key=value',
     source: 'http://localhost:3000/?example-key=value',
     user: '',
     userInfo: ''
 }

Parameters:

  • route String
  • target String
  • injectable Function
  • [name] String optional

setDefaultRoute

(
  • newDefaultRoute
)

Set a default route to redirect to when the current route isn't matched to anything

Parameters:

  • newDefaultRoute String

    The default path to route to if the current path wasn't matched by any defined route

setMakeRenderer

(
  • newMakeRenderer
)

Set a custom factory for render functions

Render factories receive the DOM target node for the route and produce an executable that can be used to render content (that executable is called renderer).

A new renderer is created every time a route is matched by passing the routes target DOM node to the makeRenderer function.

Renderer functions are passed to the injectable that is matched with the route. setMakeRenderer sets the factory that creates the render functions.

The default makeRenderer factory produces renderer functions that simply set innerHTML on the target DOM node:

 function(targetAsDOMNode) {
     return function(toRender) {
         targetAsDOMNode.innerHTML = toRender;
     };
 }

The injectable for any given route can use the render method like this:

 mimeo.module('example', [])
     .component(['component', () => ($context, $renderer) => {
         $renderer('<h1>Headline content</h1>');
     }]);

When using a rendering library, it's often beneficial to set a custom renderer factory to simplify rendering in the component. E.g. with React, custom components are mounted on DOM nodes via

 ReactDOM.render(<Component/>, DOMNode);

A custom setMakeRenderer for React would create a function that accepts a React component and mounts it to the routes target DOM node:

 $routing.setMakeRenderer(function(targetDOMNode) {
     return function(component) {
         ReactDOM.render(component, targetDOMNode);
     }
 });

Parameters:

  • newMakeRenderer Function
    • Set the renderer factory. Gets the routes target DOM node passed in