UNPKG

cloak.router

Version:
226 lines (186 loc) 5.19 kB
var History; var config = require('cloak.core/config'); var Class = require('cloak.core/utils/class'); var events = require('cloak.core/utils/events'); var EventEmitter = require('cloak.core/utils/eventemitter'); // Create the config module var conf = config.module('router', { html4: true, historyjs: true }); // // Parses for variable spots in pathnames // var pathVariable = /:([^\/]+)/g; // // Router class // var Router = module.exports = Class.extend(EventEmitter, { // // Initialize // init: function() { var self = this; // Setup EventEmitter2 EventEmitter.call(this, config.module('core').get('ee')); this.running = false; this.routes = Object.keys(this.routes).map(function(route) { return self._prepareRoute(route, self.routes[route]); }); if (typeof this.initialize === 'function') { this.initialize(); } }, // // Start the router ... // // @return void // start: function() { var self = this; if (conf.get('historyjs')) { History = History || require('html5-history'); if (conf.get('html4')) { require('html5-history/html4'); History.initHtml4(); } } if (! this.running) { this.emit('start'); this.running = true; if (History) { History.Adapter.bind(window, 'statechange', this._handleStateChange.bind(this)); } else { window.addEventListener('popstate', this._handleStateChange.bind(this)); } // Bind an event to all absolute local anchors in the document to use pushState // instead of normal navigation events.select(document).on('click', 'a[href^="/"]', function(evt) { // Allow anything other than left-click to pass through normally if ((evt.which == null && evt.which < 2) || (evt.which != null && evt.button < 2)) { // Allow any click which also involved a modifier key to pass through normally if (! evt.altKey && ! evt.ctrlKey && ! evt.shiftKey && ! evt.metaKey) { evt.preventDefault(); self.go(evt.target.getAttribute('href')); return false; } } }); this.emit('started'); this._handleStateChange(); } }, // // Includes a sub-router into this router // // @param {SubRouter} the router class to include // @return this // use: function(SubRouter) { var subRouter = new SubRouter(); // Do this so the sub-router cannot run by itself subRouter.running = true; // Steal the sub-router's routes this.routes.push.apply(this.routes, subRouter.routes); return this; }, // // Redirect the app // // @param {to} either a number to traverse the history, or a path to redirect to // @param {data} optional; when redirecting, this data will be made available to the recieving route function // @return void // go: function(to, data) { if (typeof to === 'number') { History ? History.go(to) : history.go(to); } else { History ? History.pushState(data || null, null, to) : history.pushState(data || null, null, to); } }, // -------------------------------------------------------- // // Parse the routes object into regex->function associations // // @param {path} the route path string // @param {funcName} the function name // @return object // _prepareRoute: function(path, funcName) { if (typeof this[funcName] !== 'function') { throw new Error('Router: cannot bind URI "' + path + '" to missing method "' + funcName + '"'); } var result = { path: path, func: this[funcName].bind(this), params: [ ] }; result.regex = path.replace(pathVariable, function(match, $1) { result.params.push($1); return '([^/]+)'; }); pathVariable.lastIndex = 0; result.regex += (result.regex.slice(-1) === '/') ? '?' : '/?'; result.regex = new RegExp('^' + result.regex + '$'); return result; }, // // Runs when the route changes // // @return void // _handleStateChange: function() { var state = History ? History.getState() : getState(); var route = this._findRoute(state.hash); if (! route) { this.emit('notfound', state); return; } this.emit('redirect', route); route.func(route.params, state); }, // // Find the correct route object for the given path string // // @param {path} the path to route to // @return object // _findRoute: function(path) { path = path.split('?')[0]; path = path.replace('#', ''); for (var i = 0, c = this.routes.length; i < c; i++) { var route = this.routes[i]; var match = route.regex.exec(path); if (match) { return this._prepareMatch(path, route, match); } } }, // // When a matching route is found, prepare it for use, parsing out params // // @param {path} the pathname redirected to // @param {route} the route object // @param {match} the matched param values // @return object // _prepareMatch: function(path, route, match) { var params = { }; route.params.forEach(function(param, index) { params[param] = match[index + 1] || null; }); return { path: path, func: route.func, params: params }; } }); // ------------------------------------------------------------- function getState() { return { hash: location.pathname }; }