diff --git a/lib/application.js b/lib/application.js index 401a95d496..897a683c90 100644 --- a/lib/application.js +++ b/lib/application.js @@ -14,6 +14,7 @@ var connect = require('connect') , methods = Router.methods.concat('del', 'all') , middleware = require('./middleware') , debug = require('debug')('express:application') + , locals = require('./utils').locals , View = require('./view') , url = require('url') , utils = connect.utils @@ -64,25 +65,6 @@ app.defaultConfiguration = function(){ this.use(connect.query()); this.use(middleware.init(this)); - // app locals - this.locals = function(obj){ - for (var key in obj) self.locals[key] = obj[key]; - return self; - }; - - // response locals - this.locals.use = function(fn){ - if (3 == fn.length) { - self.viewCallbacks.push(fn); - } else { - self.viewCallbacks.push(function(req, res, done){ - fn(req, res); - done(); - }); - } - return this; - }; - // inherit view callbacks this.on('mount', function(parent){ this.request.__proto__ = parent.request; @@ -101,6 +83,9 @@ app.defaultConfiguration = function(){ return this._router.middleware; }); + // setup locals + this.locals = locals(this); + // default locals this.locals.settings = this.settings; diff --git a/lib/middleware.js b/lib/middleware.js index dfd085b6fd..3e757ec036 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,10 +1,16 @@ /*! * Express - middleware - * Copyright(c) 2010-2011 TJ Holowaychuk + * Copyright(c) 2010-2012 TJ Holowaychuk * MIT Licensed */ +/** + * Module dependencies. + */ + +var utils = require('./utils'); + /** * Initialization middleware, exposing the * request and response to eachother, as well @@ -26,10 +32,7 @@ exports.init = function(app){ req.__proto__ = app.request; res.__proto__ = app.response; - res.locals = function(obj){ - for (var key in obj) res.locals[key] = obj[key]; - return res; - }; + res.locals = utils.locals(res); next(); } diff --git a/lib/response.js b/lib/response.js index cc142bec33..d716b13039 100644 --- a/lib/response.js +++ b/lib/response.js @@ -627,7 +627,7 @@ res.render = function(view, options, fn){ } // invoke view callbacks - var callbacks = app.viewCallbacks + var callbacks = app.viewCallbacks.concat(self.viewCallbacks) , pending = callbacks.length , len = pending , done; diff --git a/lib/utils.js b/lib/utils.js index 7c1a2b2e29..fa8caccdc8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -11,6 +11,39 @@ var mime = require('mime'); +/** + * Make `locals()` bound to the given `obj`. + * + * This is used for `app.locals` and `res.locals`. + * + * @param {Object} obj + * @return {Function} + * @api private + */ + +exports.locals = function(obj){ + obj.viewCallbacks = obj.viewCallbacks || []; + + function locals(obj){ + for (var key in obj) locals[key] = obj[key]; + return obj; + }; + + locals.use = function(fn){ + if (3 == fn.length) { + obj.viewCallbacks.push(fn); + } else { + obj.viewCallbacks.push(function(req, res, done){ + fn(req, res); + done(); + }); + } + return obj; + }; + + return locals; +}; + /** * Check if `path` looks absolute. * diff --git a/test/res.locals.use.js b/test/res.locals.use.js new file mode 100644 index 0000000000..f305feecba --- /dev/null +++ b/test/res.locals.use.js @@ -0,0 +1,104 @@ + +var express = require('../') + , request = require('./support/http'); + +describe('res', function(){ + describe('.locals.use(fn)', function(){ + it('should run in parallel on res.render()', function(done){ + var app = express(); + var calls = []; + app.set('views', __dirname + '/fixtures'); + + app.locals.first = 'tobi'; + + app.use(function(req, res, next){ + res.locals.use(function(req, res, done){ + process.nextTick(function(){ + calls.push('one'); + res.locals.last = 'holowaychuk'; + done(); + }); + }); + next(); + }); + + app.use(function(req, res, next){ + res.locals.use(function(req, res, done){ + process.nextTick(function(){ + calls.push('two'); + res.locals.species = 'ferret'; + done(); + }); + }); + next(); + }); + + app.use(function(req, res){ + calls.push('render'); + res.render('pet.jade'); + }); + + request(app) + .get('/') + .end(function(res){ + calls.should.eql(['render', 'one', 'two']); + res.body.should.equal('

tobi holowaychuk is a ferret

'); + done(); + }) + }) + + describe('with arity < 3', function(){ + it('should done() for you', function(done){ + var app = express(); + + app.set('views', __dirname + '/fixtures'); + app.locals.first = 'tobi'; + + app.use(function(req, res, next){ + app.locals.use(function(req, res){ + res.locals.last = 'holowaychuk'; + res.locals.species = 'ferret'; + }); + next(); + }); + + app.use(function(req, res){ + res.render('pet.jade'); + }); + + request(app) + .get('/') + .end(function(res){ + res.body.should.equal('

tobi holowaychuk is a ferret

'); + done(); + }) + }) + }) + + it('should not override res.render() locals', function(done){ + var app = express(); + + app.set('views', __dirname + '/fixtures'); + app.locals.first = 'tobi'; + + app.use(function(req, res, next){ + app.locals.use(function(req, res){ + res.locals.last = 'holowaychuk'; + res.locals.species = 'ferret'; + }); + next(); + }); + + app.use(function(req, res){ + res.render('pet.jade', { last: 'ibot' }); + }); + + request(app) + .get('/') + .end(function(res){ + res.body.should.equal('

tobi ibot is a ferret

'); + done(); + }) + }) + }) +})