Added res.partial(), using the same interface as partial() within a view. Closes #539

This commit is contained in:
Tj Holowaychuk 2011-03-02 14:27:07 -08:00
parent 509312773d
commit dd9406cd52
3 changed files with 83 additions and 47 deletions

View File

@ -31,5 +31,10 @@ app.get('/', function(req, res){
res.render('ninjas', { ninja: ninja });
});
app.get('/li', function(req, res){
res.partial('li', { object: 'Testing', as: 'value' });
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@ -18,7 +18,8 @@ var path = require('path')
, Partial = require('./view/partial')
, union = require('./utils').union
, merge = utils.merge
, http = require('http');
, http = require('http')
, res = http.ServerResponse.prototype;
/**
* Memory cache.
@ -42,28 +43,12 @@ exports.Partial = Partial;
exports.register = View.register;
/**
* Render `view` partial with the given `options`.
* Partial render helper.
*
* Options:
* - `object` Single object with name derived from the view (unless `as` is present)
*
* - `as` Variable name for each `collection` value, defaults to the view name.
* * as: 'something' will add the `something` local variable
* * as: this will use the collection value as the template context
* * as: global will merge the collection value's properties with `locals`
*
* - `collection` Array of objects, the name is derived from the view name itself.
* For example _video.html_ will have a object _video_ available to it.
*
* @param {String} view
* @param {Object|Array} options, collection, or object
* @return {String}
* @api public
* @api private
*/
http.ServerResponse.prototype.partial = function(view, options, locals, parent){
var self = this;
function renderPartial(res, view, options, locals, parent){
// Inherit parent view extension when not present
if (parent && !~view.indexOf('.')) {
view += parent.extension;
@ -106,7 +91,7 @@ http.ServerResponse.prototype.partial = function(view, options, locals, parent){
options.scope = options.object;
}
}
return self.render(view, options, null, parent);
return res.render(view, options, null, parent);
}
// Collection support
@ -130,6 +115,44 @@ http.ServerResponse.prototype.partial = function(view, options, locals, parent){
}
};
/**
* Render `view` partial with the given `options`.
*
* Options:
*
* - `object` Single object with name derived from the view (unless `as` is present)
*
* - `as` Variable name for each `collection` value, defaults to the view name.
* * as: 'something' will add the `something` local variable
* * as: this will use the collection value as the template context
* * as: global will merge the collection value's properties with `locals`
*
* - `collection` Array of objects, the name is derived from the view name itself.
* For example _video.html_ will have a object _video_ available to it.
*
* @param {String} view
* @param {Object|Array} options, collection, or object
* @return {String}
* @api public
*/
res.partial = function(view, options){
var app = this.app
, options = options || {}
, parent = {};
// root "views" option
parent.dirname = app.set('views') || process.cwd() + '/views';
// utilize "view engine" option
if (app.set('view engine')) {
parent.extension = '.' + app.set('view engine');
}
var str = renderPartial(this, view, options, null, parent);
this.send(str, options.headers, options.status);
};
/**
* Render `view` with the given `options` and optional callback `fn`.
* When a callback function is given a response will _not_ be made
@ -151,7 +174,7 @@ http.ServerResponse.prototype.partial = function(view, options, locals, parent){
* @api public
*/
http.ServerResponse.prototype.render = function(view, opts, fn, parent){
res.render = function(view, opts, fn, parent){
// support callback function as second arg
if (typeof opts === 'function') {
fn = opts, opts = null;
@ -239,7 +262,7 @@ http.ServerResponse.prototype.render = function(view, opts, fn, parent){
// Always expose partial() as a local
options.partial = function(path, opts){
return self.partial(path, opts, options, view);
return renderPartial(self, path, opts, options, view);
};
function error(err) {

View File

@ -433,6 +433,8 @@ module.exports = {
'test #partial()': function(){
var app = create();
app.set('view engine', 'jade');
// Auto-assigned local w/ collection option
app.get('/', function(req, res){
res.render('items.jade', { items: ['one', 'two'] });
@ -474,10 +476,10 @@ module.exports = {
// as: str collection option
app.get('/user', function(req, res){
res.send(res.partial('user.jade', {
res.partial('user', {
as: 'person',
collection: [{ name: 'tj' }]
}));
});
});
assert.response(app,
@ -486,10 +488,10 @@ module.exports = {
// as: with object collection
app.get('/user/object', function(req, res){
res.send(res.partial('user.jade', {
res.partial('user.jade', {
as: 'person',
collection: { 0: { name: 'tj' }, length: 1 }
}));
});
});
assert.response(app,
@ -498,11 +500,11 @@ module.exports = {
// as: this collection option
app.get('/person', function(req, res){
res.send(res.partial('person.jade', {
res.partial('person.jade', {
as: this,
collection: [{ name: 'tj' }],
locals: { label: 'name:' }
}));
});
});
assert.response(app,
@ -511,10 +513,10 @@ module.exports = {
// as: global collection option
app.get('/videos', function(req, res){
res.send(res.partial('video.jade', {
res.partial('video.jade', {
as: global,
collection: movies
}));
});
});
assert.response(app,
@ -523,10 +525,10 @@ module.exports = {
// Magic variables
app.get('/magic', function(req, res){
res.send(res.partial('magic.jade', {
res.partial('magic.jade', {
as: 'word',
collection: ['one', 'two', 'three']
}));
});
});
assert.response(app,
@ -535,9 +537,9 @@ module.exports = {
// Non-collection support
app.get('/movie', function(req, res){
res.send(res.partial('movie.jade', {
res.partial('movie.jade', {
object: movies[0]
}));
});
});
assert.response(app,
@ -545,10 +547,10 @@ module.exports = {
{ body: '<li><div class="title">Nightmare Before Christmas</div><div class="director">Tim Burton</div></li>' });
app.get('/video-global', function(req, res){
res.send(res.partial('video.jade', {
res.partial('video.jade', {
object: movies[0],
as: global
}));
});
});
// Non-collection as: global
@ -557,21 +559,25 @@ module.exports = {
{ body: '<p>Tim Burton</p>' });
app.get('/person-this', function(req, res){
res.send(res.partial('person.jade', {
res.partial('person.jade', {
object: { name: 'tj' },
locals: { label: 'User:' },
headers: { 'Content-Type': 'text/html; utf-8' },
status: 500,
as: this
}));
});
});
// Non-collection as: this
assert.response(app,
{ url: '/person-this' },
{ body: '<p>User: tj</p>' });
{ body: '<p>User: tj</p>'
, status: 500
, headers: { 'Content-Type': 'text/html; utf-8' }});
// No options
app.get('/nothing', function(req, res){
res.send(res.partial('hello.ejs'));
res.partial('hello.ejs');
});
assert.response(app,
@ -580,7 +586,7 @@ module.exports = {
// Path segments + "as"
app.get('/role/as', function(req, res){
res.send(res.partial('user/role.ejs', { as: 'role', collection: ['admin', 'member'] }));
res.partial('user/role.ejs', { as: 'role', collection: ['admin', 'member'] });
});
assert.response(app,
@ -589,7 +595,7 @@ module.exports = {
// Deduce name from last segment
app.get('/role', function(req, res){
res.send(res.partial('user/role.ejs', ['admin', 'member']));
res.partial('user/role.ejs', ['admin', 'member']);
});
assert.response(app,
@ -624,14 +630,16 @@ module.exports = {
'test #partial() locals': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res, next){
res.send(res.partial('pet-count.jade', {
res.partial('pet-count', {
locals: {
pets: {
length: 5
}
}
}));
});
});
assert.response(app,
@ -658,10 +666,10 @@ module.exports = {
var app = create();
app.get('/', function(req, res, next){
res.send(res.partial('movie.jade', {
res.partial('movie.jade', {
title: 'Foobar'
, director: 'Tim Burton'
}));
});
});
assert.response(app,