base-routes
Version:
Plugin for adding routes support to your `base` application. Requires templates support to work.
315 lines (274 loc) • 8.21 kB
JavaScript
'use strict';
var debug = require('debug')('base:routes');
var rethrow = require('template-error');
var router = require('en-route');
var utils = require('./utils');
module.exports = function(options) {
return function baseRoutes(app) {
if (!utils.isValid(app)) return;
/**
* The `Router` and `Route` classes are on the `app` instance,
* in case they need to be accessed directly.
*
* ```js
* var router = new app.Router();
* var route = new app.Route();
* ```
* @api public
*/
this.Router = router.Router;
this.Route = router.Route;
/**
* Lazily initalize `router`, to allow options and
* custom methods to be defined after instantiation.
*/
this.define('lazyRouter', function(methods) {
if (this.router == null) {
this.router = new this.Router({methods: utils.methods});
}
if (typeof methods !== 'undefined') {
this.router.method(methods);
}
});
/**
* Handle middleware `method` for the given `file`.
*
* ```js
* app.handle('methodName', file, next);
* ```
* @name .handle
* @param {String} `methodName` Name of the router method to handle.
* @param {Object} `file` View object
* @param {Function} `next` Callback function
* @return {undefined}
* @api public
*/
this.define('handle', function(method, file, next) {
debug('handling "%s" middleware for "%s"', method, file.basename);
this.lazyRouter();
if (typeof next !== 'function') {
next = function(err, file) {
app.handleError(method, file, function() {
throw err;
});
};
}
file.options = file.options || {};
if (!file.options.handled) {
file.options.handled = [];
}
// set router method on file.options
file.options.method = method;
file.options.handled.push(method);
this.emit(method, file);
// create callback
var cb = this.handleError(method, file, next);
// if not an instance of `Templates`, or if we're inside a collection
// or the collection is not specified on file.options just handle the route and return
if (!this.isTemplates || this.isCollection || !file.options.collection) {
this.router.handle(file, cb);
return;
}
// handle the app routes first, then handle the collection routes
var collection = this[file.options.collection];
this.router.handle(file, function(err) {
if (err) {
cb(err);
return;
}
collection.handle(method, file, cb);
});
});
/**
* Run the given middleware handler only if the file has not
* already been handled by `method`.
*
* ```js
* app.handleOnce(method, file, callback);
* // example
* app.handleOnce('onLoad', file, callback);
* ```
* @name .handleOnce
* @param {Object} `method` The name of the handler method to call.
* @param {Object} `file`
* @return {undefined}
* @api public
*/
this.define('handleOnce', function(method, file, next) {
if (!file.options.handled) {
file.options.handled = [];
}
if (typeof next !== 'function') {
next = file.next;
}
if (file.options.handled.indexOf(method) === -1) {
this.handle(method, file, next);
return;
}
next(null, file);
});
/**
* Handle middleware errors.
*/
this.define('handleError', function(method, file, next) {
var app = this;
return function(err) {
next = next || file.next;
if (typeof next !== 'function') {
throw new TypeError('expected a callback function');
}
if (err) {
if (err._handled === true) {
next();
return;
}
err._handled = true;
err.source = err.stack.split('\n')[1].trim();
err.reason = app._name + '#handle("' + method + '"): ' + file.path;
err.file = file;
if (app.hasListeners('error')) {
app.emit('error', err);
}
if (typeof next !== 'function') {
throw err;
}
if (err instanceof ReferenceError) {
try {
rethrow(file.content, file.data);
} catch (e) {
next(e);
return;
}
}
next(err);
return;
}
next(null, file);
};
});
/**
* Create a new Route for the given path. Each route
* contains a separate middleware stack. See the [en-route][]
* API documentation for details on adding handlers and
* middleware to routes.
*
* ```js
* app.create('posts');
* app.route(/blog/)
* .all(function(file, next) {
* // do something with file
* next();
* });
*
* app.post('whatever', {path: 'blog/foo.bar', content: 'bar baz'});
* ```
* @name .route
* @param {String} `path`
* @return {Object} Returns the instance for chaining.
* @api public
*/
this.define('route', function(/*path*/) {
this.lazyRouter();
return this.router.route.apply(this.router, arguments);
});
/**
* Add callback triggers to route parameters, where
* `name` is the name of the parameter and `fn` is the
* callback function.
*
* ```js
* app.param('title', function(view, next, title) {
* //=> title === 'foo.js'
* next();
* });
*
* app.onLoad('/blog/:title', function(view, next) {
* //=> view.path === '/blog/foo.js'
* next();
* });
* ```
* @name .param
* @param {String} `name`
* @param {Function} `fn`
* @return {Object} Returns the instance for chaining.
* @api public
*/
this.define('param', function(/*name, fn*/) {
this.lazyRouter();
this.router.param.apply(this.router, arguments);
return this;
});
/**
* Special route method that works just like the
* `router.METHOD()` methods, except that it matches
* all verbs.
*
* ```js
* app.all(/\.hbs$/, function(view, next) {
* // do stuff to view
* next();
* });
* ```
* @name .all
* @param {String} `path`
* @param {Function} `callback`
* @return {Object} `this` for chaining
* @api public
*/
this.define('all', function(path/*, callback*/) {
this.lazyRouter();
var route = this.route(path);
route.all.apply(route, [].slice.call(arguments, 1));
return this;
});
/**
* Add a router handler method to the instance. Interchangeable
* with the [handlers]() method.
*
* ```js
* app.handler('onFoo');
* // or
* app.handler(['onFoo', 'onBar']);
* ```
* @name .handler
* @param {String} `method` Name of the handler method to define.
* @return {Object} Returns the instance for chaining
* @api public
*/
this.define('handler', function(method) {
this.handlers(method);
return this;
});
/**
* Add one or more router handler methods to the instance.
*
* ```js
* app.handlers(['onFoo', 'onBar', 'onBaz']);
* // or
* app.handlers('onFoo');
* ```
* @name .handlers
* @param {Array|String} `methods` One or more method names to define.
* @return {Object} Returns the instance for chaining
* @api public
*/
this.define('handlers', function(methods) {
this.lazyRouter(methods);
mixinHandlers(methods);
return this;
});
function mixinHandlers(methods) {
utils.arrayify(methods).forEach(function(method) {
app.define(method, function(path) {
var route = this.route(path);
var args = [].slice.call(arguments, 1);
route[method].apply(route, args);
return this;
});
});
}
// Mix router handler methods onto the intance
mixinHandlers(utils.methods);
return baseRoutes;
};
};