UNPKG

ampersand-router

Version:

Clientside router with fallbacks for browsers that don't support pushState. Mostly lifted from Backbone.js.

127 lines (111 loc) 4.65 kB
/*$AMPERSAND_VERSION*/ var classExtend = require('ampersand-class-extend'); var Events = require('ampersand-events'); var extend = require('lodash/assign'); var isRegExp = require('lodash/isRegExp'); var isFunction = require('lodash/isFunction'); var result = require('lodash/result'); var ampHistory = require('./ampersand-history'); // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. var Router = module.exports = function (options) { options || (options = {}); this.history = options.history || ampHistory; if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. extend(Router.prototype, Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function () {}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function (query, num) { // ... // }); // route: function (route, name, callback) { if (!isRegExp(route)) route = this._routeToRegExp(route); if (isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; this.history.route(route, function (fragment) { var args = router._extractParameters(route, fragment); if (router.execute(callback, args, name) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); router.history.trigger('route', router, name, args); } }); return this; }, // Execute a route handler with the provided parameters. This is an // excellent place to do pre-route setup or post-route cleanup. execute: function (callback, args, name) { if (callback) callback.apply(this, args); }, // Simple proxy to `ampHistory` to save a fragment into the history. navigate: function (fragment, options) { this.history.navigate(fragment, options); return this; }, // Reload the current route as if it was navigated to from somewhere // else reload: function () { this.history.loadUrl(this.history.fragment); return this; }, // Helper for doing `internal` redirects without adding to history // and thereby breaking backbutton functionality. redirectTo: function (newUrl) { this.navigate(newUrl, {replace: true}); }, // Bind all defined routes to `history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes: function () { if (!this.routes) return; this.routes = result(this, 'routes'); var route, routes = Object.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp: function (route) { route = route .replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function (match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. _extractParameters: function (route, fragment) { var encodedParams = route.exec(fragment).slice(1); var searchParm = encodedParams.pop(); function decodeOrNull(p) { return p ? decodeURIComponent(p) : null; } var params = encodedParams.map(decodeOrNull); searchParm && params.push(searchParm); return params; } }); Router.extend = classExtend;