Routes
Associate request endpoints to specified application logic
Routes create independence between your markup and JavaScript. It also allows us to easily integrate HTML5 History. Wee routing serves as the blueprint for your project's JavaScript.
Lifecycle Methods
Lifecycle methods hook into the router and execute at key points in the route evaluation process.
The following methods can be registered in a route handler and/or directly on route records. Lifecycle methods execute in the order that they are listed.
Parameters
Variable | Type | Default | Description | Required |
---|---|---|---|---|
to | object | - | Current route match | - |
from | object | - | Previous route match | - |
next | function | - | Callback that continues route evaluation (before methods only) | - |
Before
The before method is available only to route records and will execute after the route is matched and before any other registered methods are executed. This method receives a third parameter. This parameter is a function that must be executed before the other methods registered to the route will evaluate.
Fetching Data
Asynchronous actions can be taken from within before
, preventing further evaluation of the matched route record until next
is executed.
import $router from 'wee-routes';
import admin from './admin';
import $fetch from 'wee-fetch';
$router.map([
{
path: '/admin',
handler: admin,
before(to, from, next) {
$fetch('/data.json').then(() => next());
}
}
]).run();
Guards
Because the next
function must be executed for evaluation of the route record to continue, before methods can act as a guard. You can halt evaluation of the route record by passing false
to the next
function.
import $router from 'wee-routes';
import admin from './admin';
$router.map([
{
path: '/admin',
handler: admin,
before(to, from, next) {
if (notAuthorized) {
// Stop evaluation and go no further
next(false);
} else {
// Continue
next();
}
}
}
]).run();
Before Init
The beforeInit method is the same as the before method except that it is available only to route handlers. It will execute after the route is matched and before any other registered methods are executed except a before method registered on the matched record. This method fires only if the previously matched route record is different from the currently matched record.
Before Update
The beforeUpdate method is the same as the before method except that it is available only to route handlers. It will execute after the route is matched and before any other registered methods are executed except a before method registered on the matched record. This method fires if the user navigates to the same page as they were previously.
Init
The init method fires only if the previously matched route record is different from the currently matched record. In simpler terms, the init method fires the first time that the user visits a section of a website. This makes it easier to distinguish initialization of a page from logic that updates a page.
import $router from 'wee-routes';
$router.map([
{ path: '/', init(to, from) {} }
]).run();
Update
The update method fires if the user navigates to the same page as they were previously. A common example of this would be when a filter of some kind is updated by the user that changes the query string value of the URL. The same route record will match, however the page will need to be adjusted to match the constraints of the updated filter.
import $router from 'wee-routes';
$router.map([
{ path: '/', init(to, from) {}, update(to, from) {} }
]).run();
Unload
The unload method evaluates when navigating away from a route. This is relevant when PJAX has been enabled and navigation on the site is using History. In this case, we may need to clean up after ourselves to prevent a memory leak. The unload method makes it easy to do this.
This method takes either function or a namespace. If a namespace is provided, Wee will destroy anything generated or bound by wee-events
, wee-screen
, and wee-store
registered with the same namespace.
import $router from 'wee-routes';
import admin from './admin';
import home from './home';
$router.map([
{ path: '/admin', handler: adminHandler, unload(to, from) {} }, // Providing function
{ path: '/', handler: home, unload: 'home' } // Providing namespace
]).run();
After
The after method will execute after all other methods have completed. Unlike the before method, this method is not passed a next
function.
import $router from 'wee-routes';
$router.map([
{ path: '/admin', after(to, from) {} }
]).run();
Matches
A route match represents the state of the current active or previous route. This object is passed as the to
and from
parameters that are passed into lifecycle methods as well as to other configuration methods such as scrollBehavior
and transition
.
Properties
Variable | Type | Default | Description | Required |
---|---|---|---|---|
name | string | - | Name of route record | - |
meta | object | { } | Meta data passed from route record | - |
path | string | - | URL path | - |
hash | string | - | URL hash | - |
query | object | { } | Query string parsed from URL | - |
search | string | - | Raw URL query string | - |
segments | array | [ ] | URL path segments | - |
params | object | { } | Parameters extracted from URL path based on route record path | - |
fullPath | string | - | URL path, query, and hash | - |
matched | object | - | All nested path segments of the current route record | - |
transition | object | null | Transition specified on route record | - |
Records
Route records are objects that map a URL to parts of your application.
When a route record is matched, the callbacks defined on that record are executed in a specific order. At a minimum, route records must consist of a path
and either a handler
or init
property.
Properties
Variable | Type | Default | Description | Required |
---|---|---|---|---|
path | string | - | The URL to be matched | ✔ |
handler | RouteHandler, array | - | Route handler(s) to be evaluated when route is matched | |
children | array | - | Nested routes | |
name | string | - | The name of the route object. Records can be referenced by name instead of path | |
meta | object | - | Custom properties to be passed to matched route record functions | |
before | function | - | Executes at the beginning of matched route evaluation and before any registered route handler before callbacks | |
init | function | - | Evaluates the first time that the route is matched | |
update | function | - | Evaluates in place of init when immediately preceding route match is same as current route match | |
after | function | - | Executes at the end of matched route evaluation and after any registered route handler after callbacks | |
unload | function | - | Evaluates when navigating away from current route record | |
transition | object | - | Transition specific to route (overrides global transition) |
Path
The path is the url that you wish to match. Paths can take an assortment of syntaxes. The full list of possible options are described in path-to-regexp, the package that Wee Routes uses for evaluating routes. Below is a description of some of these options.
Wildcards and Parameters
You may use *
to indicate a wildcard segment. If you wish to capture that segment and pass it to your function, you use parameters which are names preceded by a colon, i.e :id
. Route parameters will be accessible in the route object which is passed to the various callbacks registered on a matched route record.
import $router from 'wee-routes';
import { blog } from './blog';
$router.map([
{ path: '/blog/:id', handler: blog },
{ path: '*', init(to, from) {} }
]).run();
// and in `./scripts/blog`exportfunctionblog(route) {
// route.params.id will now be the matched segment.
}
Optional Parameters
You can make parameters in a path optional by adding a ?
to the end of the parameter. For example we could change the path example above:
{ path: '/blog/:id?', handler: blog }
This route record would now match for either /blog
or /blog/1
.
Name
The routes name allows you to reference the route by name, regardless of any changes to the path.
import $router from 'wee-routes';
import adminHandler from './admin';
$router.map([
{ name: 'admin', path: '/admin', handler: adminHandler }
]).run();
Meta
Sometimes you may want to pass specific properties into the registered functions for a route record. This can be accomplished through the meta property.
import $router from 'wee-routes';
import adminHandler from './admin';
$router.map([
{ path: '/admin', handler: adminHandler, meta: { isAdmin: true } }
]).run();
// and in './scripts/admin'exportdefault
new RouteHandler {
init(to) {
console.log(to.meta.isAdmin); // true
}
}
Handler
The handler property can take a Route Handler or an array of Route Handlers.
import $router from 'core/router';
import blogHandler from './blog';
import commonHandler from './common';
import socialHandler from '../components/social';
import commentsHandler from '../components/comments';
const common = [commonHandler];
$router.map([
{ path: '/', handler: common },
{
path: '/blog/:id',
handler: [blogHandler, socialHandler, commentsHandler, ...common],
},
]).run();
Children
You may nest routes under the children
key. It will accept an array of routes, structured exactly as the original route record is structured and supports all of the same options.
Note
You must omit the parent segment from the child paths
When a child route is matched, this causes both the child route and it's parent route to be evaluated (parent -> children).
import $router from 'wee-routes';
import adminHandler from './admin';
import postHandler from './post';
$router.map([
{
path: '/admin',
handler: adminHandler,
children: [
{ path: 'post', handler: postHandler }
]
}
]).run();
Route Handlers
Route Handlers are special objects that are used throughout an application to define router functionality. They can greatly clean up your route mapping by housing the various functions in their own file/module.
import $router from 'wee-routes';
import about from './about';
$router.map([
{
path: '/about',
before() {},
init() {},
update() {},
after() {}
}
]).run();
// becomes
$router.map([
{ path: '/about', handler: about }
]).run();
In the about
module, you would export a RouteHandler
instance with some or all of the following methods:
import { RouteHandler } from 'wee-routes';
export default new RouteHandler({
beforeInit(to, from, next) {
// ...
},
init(to, from) {
// ...
},
beforeUpdate(to, from, next) {
// ...
},
update(to, from) {
// ...
},
unload: 'about',
after(to, from) {
}
});
There are other, larger benefits to using Route Handlers besides cleaning up your main route file. Since Route Handlers will live inside page-specific modules, your Route Handler functions will have access to all page-specific variables and data. This works the other direction as well. If you need to dynamically retrieve any data with an AJAX request in a beforeInit
hook for a particular page, for example, you can easily pass that data to your page-specific code.
Lastly, Route Handlers can be shared between multiple route records. This can be advantageous because you may, for example, have a common set of logic that you want triggered on every page. However, you may need that common module updated independently, rather than initialized, when the route record is being processed for the first time because the route handler was already initialized on a different route. This is easily achievable with Route Handlers.
import $router from 'wee-routes';
import common from './common';
import home from './home';
import about from './about';
$router.map([
{ path: '/', handler: [common, home] },
{ path: '/about', handler: [common, about] }
]);
$router.run('/'); // home.init and common.init will fire
$router.run('/about'); // about.init and common.update will fire
Handler Methods
Route Handlers have a specific set of possible methods/properties. Many of these are the same as route records, however there are some important additions. beforeUpdate
and beforeInit
are added to give more control over possible data fetching or module bootstrapping.
Option | Description | Required |
---|---|---|
beforeInit | Executes before init | - |
beforeUpdate | Executes before update | - |
init | Executes on first evaluation of handler | - |
update | Executes after first evaluation of handler | - |
unload | Executes when leaving route record | - |
after | Executes after all other methods | - |
Router
The router is a method that can take an options object. This configuration object will affect how routing behaves.It also has all of the route API methods chained to it. This allows us to initialize routes in one of two ways:
import $router from 'wee-routes';
// With custom configuration
$router({
scrollBehavior() {},
transition: {},
}).map().run();
// Using router defaults
$router.map().run();
Options
Variable | Type | Default | Description | Required |
---|---|---|---|---|
scrollBehavior | function | - | Defines scroll position of new page after navigation | - |
transition | object | - | Transition during navigation | - |
Scroll Behavior
When navigating a user with History, we may want to scroll to the top of the page, or preserve the scrolling position of history entries just like the browser does during a full page reload (or some other custom behavior). This is controlled with the scrollBehavior
function. The default scroll functionality would look like this if passed in manually to the router:
import $router from 'wee-routes';
$router({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 };
}
}
});
Parameters
Variable | Type | Default | Description | Required |
---|---|---|---|---|
to | object | - | Current route match | - |
from | object | - | Previous route match | - |
savedPosition | object | - | Saved x and y values | - |
The savedPosition parameter exists if the navigation was triggered with the popstate event and therefore has the saved scroll state from when the page was previously visited.
Transitions
Transitions allow for a smooth experience when navigating with the router. The most basic implementation looks something like the following:
import $router from 'wee-routes';
$router({
transition: {
target: 'body',
class: '-is-loading',
timeout: 200,
},
});
Here we are telling the router to apply a given modifier class to a single container element on the page that we want to transition out and back in again.
Note
Make sure transition class target is is not being replaced by PJAX
, or the transition will fail.
The expectation with this implementation is that the designated class is applying a CSS transition which means that the target element must stay on the DOM throughout the navigation process. The timeout property is recommended to ensure that transitions work consistently. The value of the timeout property would typically be the same length of time as the transition applied on the target or longer.
Note
The need for a timeout will be corrected in a future version of Wee.
The other implementation uses custom callbacks and looks like the following:
import $router from 'wee-routes';
$router({
transition: {
leave(to, from, next) {
// Custom logic for exiting previous pageif (! transitionSuccessful) {
next(newError('something went wrong'));
}
next();
},
enter(to, from) {
// Custom logic for entering newly navigated page
}
}
})
Parameters
Variable | Type | Default | Description | Required |
---|---|---|---|---|
to | object | - | Current route match | - |
from | object | - | Previous route match | - |
next | function | - | Callback that continues transition evaluation (leave only) | - |
This implemenation allows you to do anything you can dream up. Passing an error instance will cause the navigation to stop processing and return to the previous page, again same as a before
hook.
Transitions can be configured globally or per route. Transitions on a route record will take precedence:
import $router from 'wee-routes';
$router({
transition: {
target: 'body',
class: '-is-loading',
timeout: 200,
},
}).map([
{
path: '/',
transition: {
leave() {},
enter() {},
},
},
]);
$router.onError
Register global error handler
This method registers global router error handlers. These handlers will be executed if anything goes wrong with navigation and/or route processing (including PJAX navigations). An error that is thrown within the router will differ in the properties they contain. However, these are the properties you can count on to determine the action you should take in your error callback:
Error Properties
Variable | Type | Default | Description | Required |
---|---|---|---|---|
errorType | string | - | Name of error constructor | - |
message | string | - | Description of what went wrong | - |
import $router from 'wee-routes';
$router.map([
{ path: '/', init(to, from) {} },
]).onError((error) => {
// Handle error
}).run();
All custom errors in Wee extend Error
. Babel cannot currently extend built-in types so we cannot use instanceof
inspections of our custom errors to determine the type, hence errorType
is added to all custom errors in Wee to fill this need.
$router.push
Navigate with History API
This method allows you to navigate to a url and add the navigation to the browser's history. It also hooks into PJAX if enabled on router, allowing you to override partial targets or pause partial replacement during the request. This method returns a promise.
Parameters
Variable | Type | Default | Description | Required |
---|---|---|---|---|
path | string | - | Destination URL | ✔ |
modifyPjax | boolean, object | false | Modify/pause PJAX during request. Passing true will pause PJAX altogether for request. | - |
Modify PJAX
Variable | Type | Default | Description | Required |
---|---|---|---|---|
partials | array | - | Targets used for partial replacement | - |
$router.push('/about');
$router.push('/about', true);
$router.push('/about', { partials: ['.some-other-target'] });
$router.replace
Navigate with History API, replacing browser history entry
This method allows you to navigate to a url and replace the most recent browser history entry. It also hooks into PJAX if enabled on router, allowing you to override partial targets or pause partial replacement during the request. This method returns a promise.
Parameters
Variable | Type | Default | Description | Required |
---|---|---|---|---|
path | string | - | Destination URL | ✔ |
modifyPjax | boolean, object | false | Modify/pause PJAX during request. Passing true will pause PJAX altogether for request. | - |
Modify PJAX
Variable | Type | Default | Description | Required |
---|---|---|---|---|
partials | array | - | Targets used for partial replacement | - |
$router.replace('/about');
$router.replace('/about', true);
$router.replace('/about', { partials: ['.some-other-target'] });
$router.afterEach
Register global after method
The function passed to afterEach
will be executed at the very end of the matched route record's evaluation. Like beforeEach
, you can register as many as you like, and they will execute in the order they were registered.
import $router from 'wee-routes';
import common from './common';
import about from './about';
$router.map([
{ path: '/', handler: common },
{ path: '/about', handler: about },
])
.afterEach((to, from) => {})
.run();
$router.beforeEach
Register global before method
The function passed to beforeEach
will be executed before any route record specific before methods and can act as a guard in the same way that before does for an individual route record. This function is passed the same parameters as all other before methods. You can register as many beforeEach
methods as you like, and they will execute in the order they are registered.
import $router from 'wee-routes';
import common from './common';
import about from './about';
$router.map([
{ path: '/', handler: common },
{ path: '/about', handler: about },
]).beforeEach((to, from, next) => {
// ...
next();
}).run();
$router.run
Evaluate routes against current URL
import $router from 'wee-routes';
import common from './common';
import about from './about';
$router.map([
{ path: '/', handler: common },
{ name: 'about', path: '/about', handler: about },
]).run().catch((error) => {
// Run returns a promise// Do something with error
});
$router.notFound
Add 404 style wildcard route
This method takes a route record object, excluding the path
or name
properties of a normal route record. This route record will be evaluated if no other routes match. notFound
is simply a convenience method. You can achieve the same functionality by registering a wildcard route record at the end of your map array with path: '*'
and name: 'notFound'
. This method, however, is a little more expressive.
import $router from 'wee-routes';
import common from './common';
import about from './about';
import unknown from './404';
$router.map([
{ path: '/', handler: common },
{ path: '/about', handler: about }
])
.notFound({ handler: unknown })
.run();
$router.map
Define routes for application
Parameters
Variable | Type | Default | Description | Required |
---|---|---|---|---|
routes | array | - | Array of route records | ✔ |
import $router from 'wee-routes';
import common from './common';
import about from './about';
$router.map([
{ path: '/', handler: common },
{ path: '/about', handler: about },
]).run();
$router.pjax
Enable and configure global PJAX behavior
This function enables a specific implementation of history navigation in which html partials are returned and replaced on the DOM rather than a full page load. This works very well to make websites that use static templates feel more like a single page application.
Variable | Type | Default | Description | Required |
---|---|---|---|---|
options | object | - | Configuration options | - |
Options Object
Variable | Type | Default | Description | Required |
---|---|---|---|---|
bind | object, array | { click: 'a' } | Target for binding events that will trigger navigation | - |
partials | array | ['title', 'main'] | DOM targets to be replaced | - |
replace | function | - | Hook for manipulating returned HTML | - |
request | $fetch | - | Custom fetch instance if request configuration is required | - |
context | string, HTMLElement | document | Context for elements defined in bind option | - |
onError | function | - | Execute callback if pjax triggered navigation fails | - |
import $router from 'wee-routes';
import about from './about';
import common from './common';
$router.pjax({
bind: {
click: 'a',
},
partials: ['title', 'main'],
}).map([
{ path: '/', handler: common },
{ path: '/about', handler: about },
]).run();
URL Validity
When pjax
is executed, it binds events to designated targets on the DOM that will trigger an AJAX request for the destination page's HTML and then navigate to the destination URL. During this binding phase, the URL of each DOM target will be evaluated to ensure the URL is valid. The following criteria determine a valid URL.
- URL exists (either
href
if DOM target is<a>
ordata-url
if another DOM element) - URL does not open new window (checks for
_blank
attribute) - URL is absolute URL, not relative (
https://something.com/about
vs/about
) - Target is not a download link
- Target does not have
data-static
attribute - URL is not external link
- URL is current page, only with hash added