Home

@wq/router

wq version: 1.1 1.2/1.3
Docs > wq.app: Modules

@wq/router

@wq/router

@wq/router is a wq.app module that captures URL changes and "responds" with locally-rendered HTML pages. @wq/router is primarily used with @wq/app, a higher-level module that automatically registers the appropriate routes via the wq configuration object. If you have a route that does not match @wq/app's conventions, you can use the direct route registration APIs described at the bottom.

As of wq.app 1.2, @wq/router is based on Redux-First Router and leverages similar concepts.

Installation

wq.app for PyPI

@wq/app for npm

python3 -m venv venv      # create virtual env (if needed)
. venv/bin/activate       # activate virtual env
python3 -m pip install wq # install wq framework (wq.app, wq.db, etc.)
# pip install wq.app      # install wq.app only
npm install @wq/app       # install all @wq/app deps including @wq/router
# npm install @wq/router  # install only @wq/router and deps

API

When using @wq/app, the router is made available as app.router. Otherwise, @wq/router is typically imported as router, though any local variable name can be used.

wq.app for PyPI

@wq/app for npm

define(['wq/router', ...], function(router, ...) {
    router.init(someConfig);
    router.register('custom-path/:category/:id', 'custom_route', someContextFn);
    router.jqmInit();
});
import router from '@wq/router';

router.init(someConfig);
router.register('custom-path/:category/:id', 'custom_route', someContextFn);
router.jqmInit();

The router module provides the following methods and properties.

Initialization

router.init(config)

router.init(config) initializes the router with a base url and other configuration options. Note that @wq/app automatically calls router.init() during startup, passing the "router" property of the app config.

There are six available configuration options:

name purpose
store New in wq.app 1.2. The @wq/store instance to register the router with. Defaults to the main store.
base_url The root or "index" url of the website, minus any trailing slash. This is often an empty string if the website is running at the root of the domain. For apps running in PhoneGap/Cordova, this will be the path to the folder containing index.html.
tmpl404 The name of the template to use for router.notFound() The default is "404".
getTemplateName New in wq.app 1.2. Function to map route names to template names. The default is to assume they are the same (though @wq/app overrides this in some cases).
injectOnce Whether to render and inject templates once (true) or every time they are requested (false). The default is false.
debug When debug is active, each call to router.go() will be logged with template and context information.
// Basic usage (assumes application is at /)
router.init();

// Custom usage (application is at /app/; custom 404 template name)
router.init({
    'base_url': "/app",
    'tmpl404': "notfound"
});

router.jqmInit()

The version of jQuery Mobile included with wq.app does not initialize until router.jqmInit() is called. This helps ensure all routes are registered before jQuery Mobile starts rendering content. By default, router.jqmInit() is automatically called by @wq/app during initialization, after all of the routes defined in the wq configuration object have been registered. This can be turned off to give more time to register custom routes.

Route Information

The context() and run() plugin hooks (described below) both use a routeInfo argument that is automatically updated for every page navigation. The same info is also supplied to the default template context and can be accessed as {{router_info}}. router_info/routeInfo has a number of properties that are useful in template rendering and navigation:

name purpose
name New in wq.app 1.2. The name of the current route.
path The path of the current page being rendered (relative to the base_url).
path_enc URL-encoded version of the path for use in e.g. other URLs.
params Any URL query parameters will be available as properties (e.g. "/path?filter=1" will be available as router_info.params.filter).
slugs New in wq.app 1.2. Any named URL fragments, e.g. /path/<slug> will be available as router_info.slugs.slug.
base_url The root url of the application, as passed to router.init().
full_path The root url and the path (concatenated for convenience).
full_path_enc URL-encoded version of the full path for use in e.g. other URLs.
prev_path The path of the previous page.
context (run() plugins only) The last context object rendered by router.go().

When used with @wq/app, router_info will contain the following additional properties:

name restricted to purpose
page - The name of the current page as listed in the wq configuration object
page_config - The full wq configuration for the current page.
mode - The current rendering mode (usually one of list, detail, or edit)
variant edit / list modes New in wq.app 1.2. Edit modes have a default variant (e.g. item_edit) and a new variant (e.g. item_edit:new). Lists rendered with parent-based filtering (e.g. /categories/:parent_id/items) will include parent_page as the variant (e.g. item_list:category). See URL structure for more info.
item_id detail/edit modes The unique identifier of the currently rendering item
item detail/edit modes, run() callback The full current item retrieved from the model. If the model data is not already stored locally, it will be loaded automatically via a JSON AJAX request.
parent_id list mode, parent variant The id of the parent record being used to filter the list
parent_page list mode, parent variant The name of the parent page as listed in the wq configuration object
parent_url list mode, parent variant The relative path to the parent record (e.g. parent_page + 's/' + parent_id)

For server rendering, wq.db includes a router_info context processor that mimics the client side template variable.

Plugin Hooks

@wq/router provides support for the following @wq/app plugin hooks.

context(ctx, routeInfo)

The context() plugin hook can be used to generate template contexts for rendering by @wq/template. When @wq/router renders a page, it generates a default context with basic route info, then adds any context supplied by @wq/app, then finally runs each registered context() plugin to determine the final context. Note that context plugins are executed in the order they are registered. context() plugins do not need to be named.

The ctx argument passed to context() includes the default context as well as any additional attributes from previous context plugins. The routeInfo argument provides path information about the page (see above) and is also available as ctx.router_info. context() should return an object with any additional properties to be added to the existing context, or a Promise that resolves to such an object.

wq.app for PyPI
@wq/app for npm
// myapp/date.js
define({
    "context": function(ctx, routeInfo) {
        if (routeInfo.name !== "custom_route") {
             return;
        }
        var date = new Date();
        return {
            "year": date.getFullYear()
        };
    }
});

// myapp/main.js
define(["wq/app", "./date", "./config"], function(app, date, config) {

app.use(date);
app.init(config).then(...);

});
// src/date.js
export default {
    context(ctx, routeInfo) {
        if (routeInfo.name !== "custom_route") {
             return;
        }
        var date = new Date();
        return {
            "year": date.getFullYear()
        };
    }
};

// src/index.js
import app from '@wq/app';
import date from './date';
import config from './config';

app.use(date);
app.init(config).then(...);

run($page, routeInfo)

The run() context hook is called right after rendering and displaying a page (the pageshow event in jQuery Mobile). $page is the equivalent of $.mobile.activePage and should be used to scope jQuery lookups to avoid conflicts with any previously rendered pages (i.e., use $page.find('button#custom') rather than $('button#custom') in your callback). routeInfo is a route information object (see above).

wq.app for PyPI
@wq/app for npm
// myapp/button.js
define({
    "run": function($page, routeInfo) {
        if (routeInfo.name !== "custom_route") {
             return;
        }
        $page.find('button#custom').on('click', function() {
            $page.find('div#custom-result').html('clicked');
        });
    }
});

// myapp/main.js
define(["wq/app", "./button", "./config"], function(app, button, config) {

app.use(button);
app.init(config).then(...);

});
// src/button.js
export default {
    run($page, routeInfo) {
        if (routeInfo.name !== "custom_route") {
             return;
        }
        $page.find('button#custom').on('click', function() {
            $page.find('div#custom-result').html('clicked');
        });
    }
};

// src/index.js
import app from '@wq/app';
import button from './button';
import config from './config';

app.use(button);
app.init(config).then(...);

thunks

The thunks plugin hook can be used to define arbitrary asynchronous functions to be executed in response to Redux actions. Thunks in wq.app are based on the Redux-First Router model, meaning they are defined internally as pathless routes (rather than Redux-Thunk-style action creators).

A thunk plugin should define a thunks object mapping each Redux action type to a thunk definition. While it is not required, the plugin should generally also have a name and actions defined as well.

wq.app for PyPI
@wq/app for npm
// myapp/search.js
define({
    "name": "search",
    "actions": {
        "runSearch": function(query) {
            return {
                "type": "RUN_SEARCH",
                "payload": {
                    "query": query
                }
            }
        }
    },
    "thunks": {
        "RUN_SEARCH": function(dispatch, getState, bag) {
            var action = bag.action;
            return someSearch(action.payload.query).then(function(result) {
                dispatch({
                    "type": "SEARCH_COMPLETE",
                    "payload": result
                });
            });
        }
    },
    "reducer": function(state, action) { ... }
});

// myapp/main.js
define(["wq/app", "./search", "./config"], function(app, search, config) {

app.use(search);
app.init(config).then(...);

search.runSearch("example");  // @wq/store bound action

});

render(state)

render() plugins provide the option to respond to state changes outside of the default page rendering pipeline. For example, the @wq/app spinner plugin hides and shows the jQuery Mobile spinner based on the plugin's state. render() functions are called every time the Redux state is updated, and passed the root state object. A render() plugin would typically have a name, corresponding actions, and a reducer (as defined in @wq/store).

// src/timer.js
export default {
    name: "timer",
    actions: {
        startTimer() {
            return {
                "type": "START_TIMER"
            };
        },
        stopTimer() {
            return {
                "type": "STOP_TIMER"
            };
        }
    },
    reducer(timerState={}, action) {
        switch (action.type) {
            case "START_TIMER":
                return {"active": true};
            case "STOP_TIMER":
                return {"active": false};
            default:
                return timerState;
        }
    }
    render(state) {
        if (state.timer.active) {
            someShowMethod();
        } else {
            someHideMethod();
        }
    }
};

// src/index.js
import app from '@wq/app';
import timer from './timer';
import config from './config';

app.use(timer);
app.init(config).then(...);

timer.start(); // Equivalent to app.store.dispatch(timer.actions.start())
timer.stop();
// src/search.js
export default {
    name: "search",
    actions: {
        runSearch(query) {
            return {
                "type": "RUN_SEARCH",
                "payload": {
                    "query": query
                }
            }
        }
    },
    thunks: {
        async RUN_SEARCH(dispatch, getState, bag) {
            const { action } = bag;
            const result = await someSearch(action.payload.query);
            dispatch({
                "type": "SEARCH_COMPLETE",
                "payload": result
            });
        }
    },
    reducer(state, action) { ... }
};

// src/index.js
import app from '@wq/app';
import search from './search';
import config from './config';

app.use(search);
app.init(config).then(...);

search.runSearch("example");  // @wq/store bound action

Direct Route Configuration

router.register()

router.register() is used to register a new URL route with the application. When the user navigates to a URL added via router.register(), @wq/router will generate a context object, and render it into the page (via @wq/template).

Note: router.register() is called automatically by @wq/app for all pages defined in the wq configuration object. The default routes should cover all common CRUD use cases. In particular, routes like items/, items/:slug, items/:slug/edit, items/new, and categories/:parent_id/items generally would not need to be registered manually, as long as item is a defined page and has a foreign key to category. See URL Structure for more info.

router.register() takes up to three arguments, specified below.

name purpose
path A string containing a partial regular expression indicating the URL route / path to watch for. The path will be automatically prepended with the baseurl and appended with a regular expression that matches URL parameters. As of wq.app 1.2, URL fragments can be specified using the Path-to-RegExp syntax. For backwards compatibility with 1.1, the string <slug> can also be used.
name The name of the route, which serves two purposes. First, it defines the type of the Redux action that is dispatched when the user navigates to the route. Second, it defines which Mustache template to use when rendering the page (see getTemplateName() above). If the path does not contain any fragments or /, it can automatically be used as the name. Otherwise, the name argument is required.
contextFn If specified, will be used to generate the template context when rendering the route. The function should accept an existing context object and return a new object with additional keys (or a Promise that resolves to such an object). Note that all routes (whether registered manually or via @wq/app) can use context() plugins as described above.

Changed in wq.app 1.2: With the exeption of path, the arguments to router.register() have completely changed, though they serve a similar function. With wq.app 1.1 and earlier, a callback function was required, and needed to explicitly call router.go() with the generated context. Starting in 1.2, the callback function is optional, and only needs to return a new context object.

wq.app for PyPI
@wq/app for npm
router.register('custom-path/:category/:id', 'custom_route', function(ctx) {
    var url = ctx.router_info.path,
        category = ctx.router_info.slugs.category,
        id = ctx.router_info.slugs.id;

    return someQuery(category, id).then(function(info) {
        if (info) {
            return {
                'category': category,
                'id': id,
                'info': info
            }
        } else {
            return router.notFound(info);
        }
    }
});
router.register('custom-path/:category/:id', 'custom_route', async ctx => {
    const url = ctx.router_info.path,
          {category, id} = ctx.router_info.slugs;

    const info = await someQuery(category, id);
    if (info) {
        return {
            category,
            id,
            info
        };
    } else {
        return router.notFound(info);
    }
});

router.registerFirst()

New in wq.app 1.2. Like router.register(), but ensures that the route is evaluated before all other routes. This can help in cases where there is a potential ambiguity between matched routes.

router.registerLast()

New in wq.app 1.2. Like router.register(), but ensures that the route is evaluated after all other routes. This can help in cases where there is a potential ambiguity between matched routes.

router.onShow(pathOrName, fn)

New in wq.app 1.2. Shortcut for router.addRoute(pathOrName, 's', fn).

router.addRoute(pathOrName, eventCode, fn, obj)

Note: In nearly all cases, it is preferable to use a run() plugin hook (see above) rather than calling router.addRoute() or router.onShow() directly.

router.addRoute() is used to register custom behaviors in response to page events other than initial navigation. The most common use case is to customize a page after it rendered via the pageshow event (see the router.onShow() shortcut above). router.addRoute() was originally inspired by jQuery Mobile Router. As of wq.app 1.2, jQuery Mobile Router is no longer used in or included with wq.app.

router.addRoute() takes up to four arguments, specified in order below.

name purpose
pathOrName A string containing the URL route to watch for. This can be either the registered path, or (as of wq.app 1.2), the name of the route.
events The jQuery Mobile Router-style event code to which this route applies. As of wq.app 1.2, this should be one of s (pageshow), c (pagecreate), i (pageinit), or l (pageload).
fn A callback function (or the name of a callback function if obj is specified). When a URL matches, the callback function will be called. Changed in wq.app 1.2: The function will not be passed any arguments.
obj (Optional) An object that contains a callback function (used with a string fn).

router.go()

As of wq.app 1.2, router.go() is called automatically whenever the Redux state changes. So, it is not necessary to call it directly.

router.notFound()

router.notFound() is a context function that signals that the page was not found. See the example for router.register() above.

router.rawHTML(html)

New in wq.app 1.2. router.rawHTML() is a context function that signals there is raw HTML that can be injected directly. This can be used with content rendered by the server.

router.refresh()

New in wq.app 1.2. Re-renders the current route using the last generate context.

router.reload()

New in wq.app 1.2. Regenerates the context for the current route and renders it.

router.push(url)

New in wq.app 1.2. Navigates to the specified URL (assuming it matches a registered route).