Skip to content

Commit

Permalink
improve readability by using a single object literal for all prototyp…
Browse files Browse the repository at this point in the history
…e methods

move module.exports to the end of the file

use noop function for initialize

make assignments in var statement

fix typo

add test for BaseRouters option initialization

reduce complexity of statements

fix comment typo

separated concerns of getController

remove outdated comment

add method to manually override the isAMDEnvironment flag

add test for getAction

add test for getRedirect

remove unnecessary if

refactoring buildRoutes

add test for buildRoutes

remove helper function from prototype

extract string parsing to separate function

add test for route

remove empty lines

use reduce instead forEach

use path.join
  • Loading branch information
Mathias Schreck authored and lo1tuma committed Jan 10, 2014
1 parent cf634b5 commit c8fc149
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 168 deletions.
326 changes: 158 additions & 168 deletions shared/base/router.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
var _ = require('underscore'),
Backbone = require('backbone'),
isServer = (typeof window === 'undefined');
Backbone = require('backbone'),
isServer = (typeof window === 'undefined'),
isAMDEnvironment = !isServer && (typeof define !== 'undefined'),
path = require('path');

if (!isServer) {
Backbone.$ = window.$ || require('jquery');
}

function noop() {}

module.exports = BaseRouter;
function stringRouteDefinitionToObject(element) {
var parts = element.split('#');
return {
controller: parts[0],
action: parts[1]
};
}

function parseRouteDefinitions(definitions) {
return definitions.reduce(function(route, element) {
if (_.isString(element)) {
element = stringRouteDefinitionToObject(element);
}
return _.extend(route, element);
}, {});
}

/**
* Base router class shared betwen ClientRouter and ServerRouter.
* Base router class shared between ClientRouter and ServerRouter.
*/
function BaseRouter(options) {
this.route = this.route.bind(this);
Expand All @@ -20,189 +37,162 @@ function BaseRouter(options) {
this.initialize(options);
}

/**
* Config
* - errorHandler: function to correctly handle error
* - paths
* - entryPath (required)
* - routes (optional)
* - controllerDir (optional)
*/
BaseRouter.prototype.options = null;

/**
* Internally stored route definitions.
*/
BaseRouter.prototype._routes = null;

BaseRouter.prototype.reverseRoutes = false;

BaseRouter.prototype.initialize = function(options) {};

BaseRouter.prototype._initOptions = function(options) {
var paths;

this.options = options || {};
paths = this.options.paths = this.options.paths || {};
paths.entryPath = paths.entryPath || options.entryPath;
paths.routes = paths.routes || paths.entryPath + 'app/routes';
paths.controllerDir = paths.controllerDir || paths.entryPath + 'app/controllers';
};

BaseRouter.prototype.getController = function(controllerName) {
var controllerDir = this.options.paths.controllerDir,
controller,
controllerPath;

// preload all controllers on the server or in non-AMD env
// for requirejs return path that will be lazy loaded
controllerPath = controllerDir + "/" + controllerName + "_controller";

if (!isServer && typeof define !== 'undefined') {
controller = controllerPath;
} else {
controller = require(controllerPath);
}
_.extend(BaseRouter.prototype, Backbone.Events, {
/**
* Config
* - errorHandler: function to correctly handle error
* - paths
* - entryPath (required)
* - routes (optional)
* - controllerDir (optional)
*/
options: null,

/**
* Internally stored route definitions.
*/
_routes: null,

reverseRoutes: false,

initialize: noop,

_initOptions: function(options) {
var entryPath;

options = options || {};
options.paths = options.paths || {};

entryPath = options.paths.entryPath || options.entryPath;
options.paths = _.defaults(options.paths, {
entryPath: entryPath,
routes: path.join(entryPath, 'app/routes'),
controllerDir: path.join(entryPath, 'app/controllers')
});

this.options = options;
},

getControllerPath: function(controllerName) {
var controllerDir = this.options.paths.controllerDir;
return path.join(controllerDir, controllerName + '_controller');
},

loadController: function(controllerName) {
var controllerPath = this.getControllerPath(controllerName);
return require(controllerPath);
},

getAction: function(route) {
var controller, action;

if (route.controller) {
if (isAMDEnvironment) {
action = this.getControllerPath(route.controller);
} else {
controller = this.loadController(route.controller);
action = controller[route.action];
}
}

return controller;
};
return action;
},

/**
* Given an object with 'controller' and 'action' properties,
* return the corresponding action function.
*/
BaseRouter.prototype.getAction = function(route) {
var controller, action;
if (route.controller) {
controller = this.getController(route.controller);
if (typeof controller == 'object') {
action = controller[route.action];
}
// In AMD environment controller is path string,
// which will be loaded when controller is needed.
else if (typeof controller == 'string') {
action = controller;
}
}
return action;
};
getRedirect: function(route, params) {
var redirect = route.redirect;

BaseRouter.prototype.getRedirect = function(route, params) {
var redirect = route.redirect;
if (redirect != null) {
/**
* Support function and string.
*/
if (typeof redirect === 'function') {
redirect = redirect(params);
}
}
return redirect;
};

/**
* Build route definitions based on the routes file.
*/
BaseRouter.prototype.buildRoutes = function() {
var routeBuilder = require(this.options.paths.routes),
return redirect;
},

getRouteBuilder: function() {
return require(this.options.paths.routes);
},

buildRoutes: function() {
var routeBuilder = this.getRouteBuilder(),
routes = [];

function captureRoutes() {
routes.push(_.toArray(arguments));
}
function captureRoutes() {
routes.push(_.toArray(arguments));
}

try {
routeBuilder(captureRoutes);
if (this.reverseRoutes) {
routes = routes.reverse();
}
routes.forEach(function(route) {
this.route.apply(this, route);
}, this);
} catch (e) {
throw new Error("Error building routes: " + e.stack);
}
return this.routes();
};

/**
* Returns a copy of current route definitions.
*/
BaseRouter.prototype.routes = function() {
return this._routes.slice().map(function(route) {
return route.slice();
});
};
routes.forEach(this.addRouteDefinition, this);

/**
* Method passed to routes file to build up routes definition.
* Adds a single route definition.
*/
BaseRouter.prototype.route = function(pattern) {
var definitions = _.toArray(arguments).slice(1),
route = this.parseDefinitions(definitions),
action = this.getAction(route),
handler,
routeObj;

if (!(pattern instanceof RegExp) && pattern.slice(0, 1) !== '/') {
pattern = "/" + pattern;
}

handler = this.getHandler(action, pattern, route);
routeObj = [pattern, route, handler];
this._routes.push(routeObj);
this.trigger('route:add', routeObj);
return routeObj;
};

BaseRouter.prototype.parseDefinitions = function(definitions) {
var route = {};
return this.routes();
},

definitions.forEach(function(element) {
var parts;

/**
* Handle i.e. 'users#show'.
*/
if (_.isString(element)) {
parts = element.split('#');
_.extend(route, {
controller: parts[0],
action: parts[1]
});
} else {
/**
* Handle objects ,i.e. {controller: 'users', action: 'show'}.
*/
_.extend(route, element);
addRouteDefinition: function(route) {
try {
this.route.apply(this, route);
} catch (error) {
error.message = 'Error building routes (' + error.message + ')';
throw error;
}
},

/**
* Returns a copy of current route definitions.
*/
routes: function() {
return this._routes.slice().map(function(route) {
return route.slice();
});
},

/**
* Method passed to routes file to build up routes definition.
* Adds a single route definition.
*/
route: function(pattern) {
var action, definitions, handler, route, routeObj;

definitions = _.toArray(arguments).slice(1);
route = parseRouteDefinitions(definitions);
action = this.getAction(route);

if (!(pattern instanceof RegExp) && pattern.slice(0, 1) !== '/') {
pattern = "/" + pattern;
}
});
return route;
};

/**
* Support omitting view path; default it to ":controller/:action".
*/
BaseRouter.prototype.defaultHandlerParams = function(viewPath, locals, route) {
if (typeof viewPath !== 'string') {
locals = viewPath;
viewPath = route.controller + '/' + route.action;
}
return [viewPath, locals];
};
handler = this.getHandler(action, pattern, route);
routeObj = [pattern, route, handler];
this._routes.push(routeObj);
this.trigger('route:add', routeObj);
return routeObj;
},

/**
* Support omitting view path; default it to ":controller/:action".
*/
defaultHandlerParams: function(viewPath, locals, route) {
if (typeof viewPath !== 'string') {
locals = viewPath;
viewPath = route.controller + '/' + route.action;
}
return [viewPath, locals];
},

/**
* Methods to be extended by subclasses.
* -------------------------------------
*/
/**
* Methods to be extended by subclasses.
* -------------------------------------
*/

/**
* This is the method that renders the request.
*/
BaseRouter.prototype.getHandler = noop;
/**
* This is the method that renders the request.
*/
getHandler: noop
});

/**
* Mix in Backbone.Events.
*/
_.extend(BaseRouter.prototype, Backbone.Events);
module.exports = BaseRouter;
module.exports.setAMDEnvironment = function(flag) {
isAMDEnvironment = flag;
};

0 comments on commit c8fc149

Please sign in to comment.