$routing Class
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.
Item Index
Methods
Methods
goto
-
route
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
StringRoute to go to
Returns:
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
FunctionThe 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
StringThe 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