WakaRoute
WakaRoute is a client-side router plugin for wakaPAC. It sends a MSG_ROUTE_BEFORE message before each navigation — allowing any component to cancel it by returning false — followed by MSG_ROUTE_CHANGE on commit, with route match status in msg.wparam and extracted URL parameters pre-computed in msg.detail.params. Only components that declare a data-pac-route attribute receive either message.
Getting Started
Include the script after wakaPAC, register the plugin, then navigate:
<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/wakapac.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/plugins/wakaroute.min.js"></script>
<script>
wakaPAC.use(wakaRoute); // must be called before any wakaPAC() calls
wakaRoute.navigate('/users/42');
</script>
Declare which route a component responds to with the data-pac-route attribute. Handle MSG_ROUTE_CHANGE in msgProc — the router pre-computes msg.detail.params from the component's own pattern, so no manual matching is needed:
<div id="user-view" data-pac-id="user-view" data-pac-route="/users/{id}"></div>
wakaPAC('#user-view', {
msgProc(msg) {
if (msg.message !== wakaPAC.MSG_ROUTE_CHANGE) {
return;
}
if (msg.wparam) {
this.userId = msg.detail.params.id;
}
}
});
Only components that declare a data-pac-route attribute receive MSG_ROUTE_CHANGE. Components without a route attribute are not notified.
Usage
Route Patterns
Patterns are declared either in HTML via data-pac-route or passed directly to matchPattern(). WakaRoute uses the same URL pattern conventions as Canvas. The supported syntax is:
| Syntax | Description | Example |
|---|---|---|
{name} |
Named segment — matches one path segment (no slashes). Captured into params.name. |
/users/{id} |
{name:*} |
Alias for {name} — explicit single-segment form. |
/tags/{tag:*} |
{name:**} |
Named multi-segment wildcard — matches everything including slashes. Captured into params.name. May appear multiple times in a single pattern. |
/files/{rest:**} |
* |
Bare single-segment wildcard — matches one segment but does not capture. | /files/*/info |
** |
Bare multi-segment wildcard — matches any number of segments but does not capture. May appear multiple times in a single pattern. | /shop/**/item/** |
Patterns are anchored — they must match the full path. A pattern /about does not match /about/team.
Extracting Parameters
Call matchPattern() inside msgProc to test the current path against a pattern and extract named parameters. It returns a plain object on match, or null if the pattern does not match:
const params = wakaRoute.matchPattern('/users/{id}/posts/{postId}', msg.detail.path);
// params → { id: '42', postId: '7' } or null
URL-encoded segments are automatically decoded:
wakaRoute.matchPattern('/search/{term}', '/search/hello%20world');
// → { term: 'hello world' }
Query Parameters
MSG_ROUTE_CHANGE detail always includes a query object parsed from the URL search string. Flag parameters (no value) are stored as boolean true. PHP-style bracket keys (key[]=) collect into arrays:
// URL: /users/42?tab=profile&debug
msg.detail.path // → '/users/42'
msg.detail.query // → { tab: 'profile', debug: true }
// URL: /search?tag[]=news&tag[]=tech
msg.detail.query // → { tag: ['news', 'tech'] }
Showing and Hiding Views
A common pattern is to show a container only when its route matches and hide it otherwise. Use msg.wparam to drive visibility — it is 1 when the component's own pattern matched and 0 when it did not:
<div id="user-view" data-pac-id="user-view" data-pac-route="/users/{id}" hidden></div>
<div id="users-list" data-pac-id="users-list" data-pac-route="/users" hidden></div>
wakaPAC('[data-pac-route]', {
msgProc(msg) {
if (msg.message !== wakaPAC.MSG_ROUTE_CHANGE) {
return;
}
if (msg.wparam) {
wakaPAC.showContainer(this.pacId);
} else {
wakaPAC.hideContainer(this.pacId);
}
}
});
Programmatic Navigation
Call navigate() to push a new entry onto the browser history and broadcast MSG_ROUTE_CHANGE. Pass { replace: true } to replace the current history entry instead:
wakaRoute.navigate('/users/42');
wakaRoute.navigate('/users/42', { replace: true }); // no new history entry
Browser back and forward navigation is handled automatically — pressing the back button broadcasts MSG_ROUTE_CHANGE with the restored path.
Reading the Current Route
Call currentRoute() at any time to read the current path and query without waiting for a message. The path is seeded from location.pathname when the plugin is registered:
const { path, query } = wakaRoute.currentRoute();
// → { path: '/users/42', query: { tab: 'profile' } }
The returned object is a copy — mutations do not affect internal state.
API
wakaPAC.use(wakaRoute)
Register the plugin. Must be called before any components are created.
| Parameter | Type | Description |
|---|---|---|
wakaRoute | object | The WakaRoute plugin object. |
Returns void | ||
wakaRoute.navigate(path, opts?)
Navigate to path, update browser history, and send MSG_ROUTE_CHANGE to all components with a data-pac-route attribute. Before committing, broadcasts MSG_ROUTE_BEFORE — if any component returns false from msgProc, the navigation is cancelled and history is unchanged. Throws if called before wakaPAC.use(wakaRoute).
| Parameter | Type | Description |
|---|---|---|
path | string | The path to navigate to, e.g. '/users/42'. |
opts | object | Optional navigation options. |
opts.replace | boolean | When true, replaces the current history entry instead of pushing a new one. The back button will skip the replaced entry. Default: false. |
Returns boolean | false if navigation was blocked by a component, true otherwise. | |
wakaRoute.matchPattern(pattern, path)
Test path against pattern. Returns a plain object of extracted named parameters on match, or null if the pattern does not match.
| Parameter | Type | Description |
|---|---|---|
pattern | string | A route pattern string, e.g. '/users/{id}/posts/{postId}'. Patterns are compiled once and cached. |
path | string | The path to test against the pattern, e.g. msg.detail.path. |
Returns object|null | Plain object of extracted named parameters on match, e.g. { id: '42', postId: '7' }. null if the pattern does not match. | |
wakaRoute.currentRoute()
Returns a copy of the current route state. Safe to call before the first navigation. The path is seeded from location.pathname when the plugin is registered. Mutations to the returned object do not affect internal state.
| Parameter | Type | Description |
|---|---|---|
Returns { path, query } | Current path as a string and parsed query string as a plain object. Empty object when there is no query string. | |
wakaRoute.getRouteTable()
Returns a snapshot of all components that declared a data-pac-route attribute, keyed by pacId. Useful for debugging.
| Parameter | Type | Description |
|---|---|---|
Returns object | Snapshot of registered route components keyed by pacId. Mutations do not affect internal state. | |
wakaRoute.destroy()
Removes the popstate listener and clears all router state. Useful for test teardown.
| Parameter | Type | Description |
|---|---|---|
Returns void | ||
Messages
WakaRoute delivers two message types to msgProc. Only components that declare a data-pac-route attribute receive them.
Before Navigate (MSG_ROUTE_BEFORE)
Fired before each navigation commits — programmatic or browser back/forward — to all components that declare a data-pac-route attribute. Return false from msgProc to cancel the navigation. If cancelled, history is not updated and MSG_ROUTE_CHANGE is not sent. To redirect instead of blocking, call wakaRoute.navigate() inside msgProc.
wakaPAC('#protected-view', {
msgProc(msg) {
if (msg.message === wakaPAC.MSG_ROUTE_BEFORE && msg.wparam) {
if (!isLoggedIn()) {
wakaRoute.navigate('/login');
return false; // cancel original navigation
}
}
}
});
Message Parameters
| Parameter | Type | Description |
|---|---|---|
wParam |
number | 1 if the component's own data-pac-route pattern matched the target path, 0 if it did not. |
lParam |
number | Always 0. |
Detail Properties
| Property | Type | Description |
|---|---|---|
detail.path |
string | The target path, e.g. '/users/42'. |
detail.query |
object | Parsed query string of the current URL (before the navigation commits). |
detail.params |
object|null | Extracted URL parameters from the component's own data-pac-route pattern matched against the target path. null if the pattern did not match. |
Route Change (MSG_ROUTE_CHANGE)
Fired on every navigation — programmatic or browser back/forward — to all components that declare a data-pac-route attribute. The router pre-computes msg.detail.params from the component's own pattern, so no manual matching is needed inside msgProc.
Message Parameters
| Parameter | Type | Description |
|---|---|---|
wParam |
number | 1 if the component's own data-pac-route pattern matched the current path, 0 if it did not. Use this for cheap match/no-match checks before inspecting detail.params. |
lParam |
number | Always 0. |
Detail Properties
| Property | Type | Description |
|---|---|---|
detail.path |
string | The current location.pathname, e.g. '/users/42'. |
detail.query |
object | Parsed query string as a plain object, e.g. ?tab=profile&debug becomes { tab: 'profile', debug: true }. Flag parameters (no =value) are stored as boolean true. PHP-style bracket keys collect into arrays: ?tag[]=a&tag[]=b becomes { tag: ['a', 'b'] }. Empty object when there is no query string. |
detail.params |
object|null | Extracted URL parameters from the component's own data-pac-route pattern. null if the pattern did not match the current path. |
Notes
WakaRoute uses the History API (pushState / replaceState). The server must serve the same
HTML file for all routes, and a local HTTP server is required during development — npx serve . works
well for this. The router does not manage scroll position on navigation. If your application requires scroll
restoration, handle it in msgProc after matching the route.
Best Practices
- Register before creating components — call
wakaPAC.use(wakaRoute)before anywakaPAC()calls sodata-pac-routeattributes are picked up during component creation. - Use
data-pac-routeas the source of truth — declare the pattern on the container element. The router reads it automatically and delivers pre-computedparamswith each message, keepingmsgProcfree of pattern strings. - Keep guards in the component — handle
MSG_ROUTE_BEFOREin the component that owns the decision. Returnfalseto block, or callwakaRoute.navigate()before returningfalseto redirect. - Name wildcard segments descriptively — prefer
/files/{rest:**}over a bare**when you need the captured value. Reserve bare wildcards for patterns where the intermediate segments are irrelevant. - Use
matchPattern()for ad-hoc matching — when a component needs to test paths beyond its own registered pattern,matchPattern()is available. Patterns are compiled once and cached, so repeated calls are safe and efficient.