Comparar commits

...

22 Commits

Autor SHA1 Mensagem Data
Tj Holowaychuk bf596dc023 Release 2.0.0 2011-03-17 18:06:30 -07:00
Tj Holowaychuk 220d88d654 Fixed up index view path alternative
previously was doing ../index, which was not intended
now doing ../VIEW/index
2011-03-17 15:36:44 -07:00
Tj Holowaychuk a254e64bdb Changed; res.locals() without object returns the locals 2011-03-17 14:50:35 -07:00
Tj Holowaychuk 1555b92fb8 Release 2.0.0rc3 2011-03-17 13:01:59 -07:00
Tj Holowaychuk d5b7a40b39 Fixed partials example 2011-03-17 13:00:43 -07:00
Tj Holowaychuk bd1ab7ab96 pass the function 2011-03-17 12:48:06 -07:00
Tj Holowaychuk 2ff991bfcf refactored res.render() 2011-03-17 12:45:11 -07:00
Tj Holowaychuk 0b1378a539 Added res.locals(obj) 2011-03-17 12:13:59 -07:00
Tj Holowaychuk 723c908bd7 Added res.partial() callback support 2011-03-17 12:10:32 -07:00
Tj Holowaychuk 4874404701 typo 2011-03-17 11:33:35 -07:00
Tj Holowaychuk 4c1374840a Release 2.0.0rc2 2011-03-17 11:01:20 -07:00
Tj Holowaychuk 5da01633fd Fixed SlowBuffer support. Closes #584 2011-03-17 10:37:34 -07:00
Tj Holowaychuk cdbd8af527 migration docs for partials 2011-03-17 09:31:32 -07:00
Tj Holowaychuk a6fdc1bfd2 Fixed .filename view engine option [reported by drudge] 2011-03-16 16:58:02 -07:00
Tj Holowaychuk 20b8facb05 docs for partial changes 2011-03-16 15:53:27 -07:00
Tj Holowaychuk 909914f7af Changed; partial() "locals" are now optional
this means that:

   partial("user", { name: "tj" })

with the intent of receiving "user" instead of "name" in this
case is invalid, "name" here is a local, however passing a non-plain
object such as a User object is fine:

    partial("user", new User("tj"));
2011-03-16 15:37:05 -07:00
Tj Holowaychuk 3f31ebc676 fixed express-contrib example reference 2011-03-15 15:23:28 -07:00
Tj Holowaychuk f3c068a90c Merge branch 'integration' 2011-03-15 10:52:58 -07:00
Tj Holowaychuk 90d7e193d1 Refactored Server#use()
eventually we should just emit some events from connect
2011-03-15 10:52:53 -07:00
Tj Holowaychuk c9f5bb6f17 added .app test for the mounted server as well 2011-03-15 10:39:59 -07:00
Ben Weaver 9865a4c4f2 Clean up patch to Server#use(), add test case for restoring res#app property. 2011-03-15 10:37:18 -07:00
Ben Weaver f12baf32d4 Restore original res.app when out() is called. 2011-03-15 10:37:18 -07:00
22 arquivos alterados com 470 adições e 193 exclusões
+22
Ver Arquivo
@@ -1,4 +1,26 @@
2.0.0 / 2011-03-17
==================
* Fixed up index view path alternative.
* Changed; `res.locals()` without object returns the locals
2.0.0rc3 / 2011-03-17
==================
* Added `res.locals(obj)` to compliment `res.local(key, val)`
* Added `res.partial()` callback support
* Fixed recursive error reporting issue in `res.render()`
2.0.0rc2 / 2011-03-17
==================
* Changed; `partial()` "locals" are now optional
* Fixed `SlowBuffer` support. Closes #584 [reported by tyrda01]
* Fixed .filename view engine option [reported by drudge]
* Fixed blog example
* Fixed `{req,res}.app` reference when mounting [Ben Weaver]
2.0.0rc / 2011-03-14
==================
+1 -1
Ver Arquivo
@@ -11,7 +11,7 @@ var fs = require('fs')
* Framework version.
*/
var version = '2.0.0rc';
var version = '2.0.0';
/**
* Add session support.
+26 -5
Ver Arquivo
@@ -523,15 +523,15 @@ A good example of this is specifying custom _ejs_ opening and closing tags:
### View Partials
The Express view system has built-in support for partials and collections, which are "mini" views representing a document fragment. For example rather than iterating
in a view to display comments, we would use a partial with collection support:
in a view to display comments, we could use partial collection:
partial('comment', { collection: comments });
If no other options are desired, we can omit the object and simply pass our array, which is equivalent to above:
If no other options or local variables are desired, we can omit the object and simply pass our array, which is equivalent to above:
partial('comment', comments);
When using the partial collection support a few "magic" variables are provided
When using the partial collection support a few "magic" locals are provided
for free:
* _firstInCollection_ true if this is the first object
@@ -938,7 +938,7 @@ of _movie.director_ we could use _this.director_.
partial('movie', { collection: movies, as: this });
// In view: this.director
Another alternative is to "explode" the properties of the collection item into
Another alternative is to "expand" the properties of the collection item into
pseudo globals (local variables) by using _as: global_, which again is syntactic sugar:
partial('movie', { collection: movies, as: global });
@@ -960,9 +960,21 @@ This same logic applies to a single partial object usage:
When a non-collection (does _not_ have _.length_) is passed as the second argument, it is assumed to be the _object_, after which the object's local variable name is derived from the view name:
partial('movie', movie);
var movie = new Movie('Nightmare Before Christmas', 'Tim Burton')
partial('movie', movie)
// => In view: movie.director
The exception of this, is when a "plain" object, aka "{}" or "new Object" is passed, which is considered an object with local variable. For example some may expect a "movie" local with the following, however since it is a plain object "director" and "title" are simply locals:
var movie = { title: 'Nightmare Before Christmas', director: 'Tim Burton' };
partial('movie', movie)
For cases like this where passing a plain object is desired, simply assign it to a key, or use the `object` key which will use the filename-derived variable name. The examples below are equivalent:
partial('movie', { locals: { movie: movie }})
partial('movie', { movie: movie })
partial('movie', { object: movie })
This exact API can be utilized from within a route, to respond with a fragment via Ajax or WebSockets, for example we can render a collection of users directly from a route:
app.get('/users', function(req, res){
@@ -995,6 +1007,15 @@ Get or set the given local variable _name_. The locals built up for a response a
res.render('movie', { displayReviews: true });
});
### res.local(obj)
Assign several locals with the given _obj_. The following are equivalent:
res.local('foo', bar);
res.local('bar', baz);
res.locals({ foo: bar, bar, baz });
### app.set(name[, val])
Apply an application level setting _name_ to _val_, or
+23
Ver Arquivo
@@ -121,6 +121,29 @@ However now we have the alternative _maxAge_ property which may be used to set _
// render a single comment
res.partial('comment', comment);
### partial() locals
Both _res.partial()_ and the _partial()_ functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass _user_ to a partial, along with _date_ like so:
partial('user', { object: user, locals: { date: new Date }})
or perhaps if you preferred not to use the inferred name _user_ you may used a local for this as well:
partial('user', { locals: { user: user, date: new Date }})
With recent changes to Express 2.x the object passed is now both, so the following is valid for the _object_ option and locals:
partial('user', { object: user, date: new Date })
Or the following which is equivalent, however the local var name is explicitly set to _user_ instead of deduced from the filename.
partial('user', { user: user, date: new Date })
When a "basic" object aka _{}_ or _new Object_ is passed, it is considered options, otherwise it is considered the _object_. The following are equivalent:
partial('user', user);
partial('user', { object: user });
### Template Engine Compliance
To comply with Express previously engines needed the following signature:
+3 -3
Ver Arquivo
@@ -7,7 +7,7 @@ require.paths.unshift(__dirname + '/../../support');
*/
var express = require('../../lib/express')
, messages = require('express-contrib/messages');
, messages = require('express-messages');
var app = module.exports = express.createServer();
@@ -22,8 +22,8 @@ app.mounted(function(other){
console.log('ive been mounted!');
});
// Flash message helper provided by express-contrib
// $ npm install express-contrib
// Flash message helper provided by express-messages
// $ npm install express-messages
app.dynamicHelpers({
messages: messages
+2 -2
Ver Arquivo
@@ -10,10 +10,10 @@ h1 Blog
| It looks like you have no posts!
p
| Click
a(href=base + '/post/add') here
a(href=base + '/post/add') here
| to create a post. Login
| as
em "admin"
em "admin"
| and
em "express"
| .
+26 -8
Ver Arquivo
@@ -15,10 +15,11 @@ var pub = __dirname + '/public';
// Auto-compile sass to css with "compiler"
// and then serve with connect's staticProvider
var app = express.createServer(
express.compiler({ src: pub, enable: ['sass'] })
, express.static(pub)
);
var app = express.createServer();
app.use(express.compiler({ src: pub, enable: ['sass'] }));
app.use(app.router);
app.use(express.static(pub));
app.use(express.errorHandler({ dump: true, stack: true }));
// Optional since express defaults to CWD/views
@@ -29,17 +30,30 @@ app.set('views', __dirname + '/views');
// (although you can still mix and match)
app.set('view engine', 'jade');
function User(name, email) {
this.name = name;
this.email = email;
}
// Dummy users
var users = [
{ name: 'tj', email: 'tj@vision-media.ca' }
, { name: 'ciaran', email: 'ciaranj@gmail.com' }
, { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
new User('tj', 'tj@vision-media.ca')
, new User('ciaran', 'ciaranj@gmail.com')
, new User('aaron', 'aaron.heckmann+github@gmail.com')
];
app.get('/', function(req, res){
res.render('users', { users: users });
});
app.get('/users/callback', function(req, res){
// a callback is also accepted
res.partial('users/user', users, function(err, html){
if (err) throw err;
res.send(html);
});
});
app.get('/users', function(req, res){
// we can use res.partial() as if
// we were in a view, utilizing the same api
@@ -48,7 +62,11 @@ app.get('/users', function(req, res){
});
app.get('/users/list', function(req, res){
res.partial('users/list', { object: users });
// use "object" to utilize the name deduced from
// the view filename. The examples below are equivalent
//res.partial('users/list', { object: users });
res.partial('users/list', { list: users });
});
app.get('/user/:id', function(req, res){
+1 -6
Ver Arquivo
@@ -28,13 +28,8 @@ var ninja = {
};
app.get('/', function(req, res){
res.render('ninjas', { ninja: ninja });
res.render('ninja', { 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');
@@ -1,8 +1,12 @@
h1= ninja.name
// file, partial name, and partial object all match ('summary')
// the partial filename prefix '_' is completely optional
#summary!= partial('summary', ninja.summary)
// the partial filename prefix '_' is completely optional.
// In this case we need to specify ninja.summary as the object
// option, since it is a "plain" object Express cannot otherwise
// tell if it is intended to be locals, or THE summary object
#summary!= partial('summary', { object: ninja.summary })
// file, partial name = '_weapon', resolves to 'weapon' object within partial
#weapons
+1 -1
Ver Arquivo
@@ -27,7 +27,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.0.0rc';
exports.version = '2.0.0';
/**
* Shortcut for `new Server(...)`.
+27 -16
Ver Arquivo
@@ -51,7 +51,7 @@ Server.prototype.init = function(middleware){
this.dynamicViewHelpers = {};
this.errorHandlers = [];
// default "home" to /
// default "home" to /
this.set('home', '/');
// set "env" to NODE_ENV, defaulting to "development"
@@ -133,25 +133,36 @@ Server.prototype.registerErrorHandlers = function(){
*/
Server.prototype.use = function(route, middleware){
var app, home;
var app, home, handle;
if ('string' != typeof route) {
middleware = route, route = '/';
}
// express app
if (middleware.handle && middleware.set) app = middleware;
// restore .app property on req and res
if (app) {
app.route = route;
middleware = function(req, res, next) {
var orig = req.app;
app.handle(req, res, function(err){
req.app = res.app = orig;
next(err);
});
};
}
connect.HTTPServer.prototype.use.call(this, route, middleware);
// mounted an app
if (middleware.handle) {
app = middleware;
// express app
if (app.set) {
home = app.set('home');
if ('/' == home) home = '';
app.set('home', app.route + home);
app.parent = this;
}
// mounted hook
// mounted an app, invoke the hook
// and adjust some settings
if (app) {
home = app.set('home');
if ('/' == home) home = '';
app.set('home', app.route + home);
app.parent = this;
if (app.__mounted) app.__mounted.call(app, this);
}
@@ -205,7 +216,7 @@ Server.prototype.register = function(){
* @api public
*/
Server.prototype.helpers =
Server.prototype.helpers =
Server.prototype.locals = function(obj){
utils.merge(this.viewHelpers, obj);
return this;
@@ -230,7 +241,7 @@ Server.prototype.dynamicHelpers = function(obj){
*
* Param mapping is used to provide pre-conditions to routes
* which us normalized placeholders. For example ":user_id" may
* attempt to load the user from the database, where as ":num" may
* attempt to load the user from the database, where as ":num" may
* pass the value through `parseInt(num, 10)`.
*
* When the callback function accepts only a single argument, the
@@ -239,7 +250,7 @@ Server.prototype.dynamicHelpers = function(obj){
* app.param('page', function(n){ return parseInt(n, 10); });
*
* After which "/users/:page" would automatically provide us with
* an integer for `req.params.page`. If desired we could use the callback
* an integer for `req.params.page`. If desired we could use the callback
* signature shown below, and immediately `next(new Error('invalid page'))`
* when `parseInt` fails.
*
+24 -5
Ver Arquivo
@@ -68,7 +68,7 @@ res.send = function(body, headers, status){
}
break;
case 'object':
if (body instanceof Buffer) {
if (Buffer.isBuffer(body)) {
if (!this.header('Content-Type')) {
this.contentType('.bin');
}
@@ -87,7 +87,7 @@ res.send = function(body, headers, status){
// populate Content-Length
if (!this.header('Content-Length')) {
this.header('Content-Length', body instanceof Buffer
this.header('Content-Length', Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body));
}
@@ -378,8 +378,27 @@ res.redirect = function(url, status){
*/
res.local = function(name, val){
this.locals = this.locals || {};
this._locals = this._locals || {};
return undefined === val
? this.locals[name]
: this.locals[name] = val;
? this._locals[name]
: this._locals[name] = val;
};
/**
* Assign several locals with the given `obj`,
* or return the locals.
*
* @param {Object} obj
* @return {Object|Undefined}
* @api public
*/
res.locals = function(obj){
if (obj) {
for (var key in obj) {
this.local(key, obj[key]);
}
} else {
return this._locals;
}
};
+100 -51
Ver Arquivo
@@ -47,30 +47,47 @@ exports.register = View.register;
* @api private
*/
function renderPartial(res, view, options, locals, parent){
function renderPartial(res, view, options, parentLocals, parent){
var collection, object, locals;
// Inherit parent view extension when not present
if (parent && !~view.indexOf('.')) {
view += parent.extension;
}
// Allow collection to be passed as second param
if (options) {
if ('length' in options) {
options = { collection: options };
} else if (!options.collection && !options.locals && !options.object) {
options = { object: options };
// collection
if (options.collection) {
collection = options.collection;
delete options.collection;
} else if ('length' in options) {
collection = options;
options = {};
}
// locals
if (options.locals) {
locals = options.locals;
delete options.locals;
}
// object
if ('Object' != options.constructor.name) {
object = options;
options = {};
} else if (undefined != options.object) {
object = options.object;
delete options.object;
}
} else {
options = {};
}
// Inherit locals from parent
union(options, locals);
union(options, parentLocals);
// Merge locals
if (options.locals) {
merge(options, options.locals);
}
if (locals) merge(options, locals);
// Partials dont need layouts
options.renderPartial = true;
@@ -81,31 +98,29 @@ function renderPartial(res, view, options, locals, parent){
// Render partial
function render(){
if (options.object) {
if (object) {
if ('string' == typeof name) {
options[name] = options.object;
options[name] = object;
} else if (name === global) {
merge(options, options.object);
merge(options, object);
} else {
options.scope = options.object;
options.scope = object;
}
}
return res.render(view, options, null, parent);
return res.render(view, options, null, parent, true);
}
// Collection support
var collection = options.collection;
if (collection) {
var len = collection.length
, buf = '';
delete options.collection;
options.collectionLength = len;
for (var i = 0; i < len; ++i) {
var val = collection[i];
options.firstInCollection = i === 0;
options.indexInCollection = i;
options.lastInCollection = i === len - 1;
options.object = val;
object = val;
buf += render();
}
return buf;
@@ -115,7 +130,9 @@ function renderPartial(res, view, options, locals, parent){
};
/**
* Render `view` partial with the given `options`.
* Render `view` partial with the given `options`. Optionally a
* callback `fn(err, str)` may be passed instead of writing to
* the socket.
*
* Options:
*
@@ -130,16 +147,23 @@ function renderPartial(res, view, options, locals, parent){
* For example _video.html_ will have a object _video_ available to it.
*
* @param {String} view
* @param {Object|Array} options, collection, or object
* @param {Object|Array|Function} options, collection, callback, or object
* @param {Function} fn
* @return {String}
* @api public
*/
res.partial = function(view, options){
res.partial = function(view, options, fn){
var app = this.app
, options = options || {}
, parent = {};
// accept callback as second argument
if ('function' == typeof options) {
fn = options;
options = {};
}
// root "views" option
parent.dirname = app.set('views') || process.cwd() + '/views';
@@ -148,8 +172,23 @@ res.partial = function(view, options){
parent.extension = '.' + app.set('view engine');
}
var str = renderPartial(this, view, options, null, parent);
this.send(str);
// render the partial
try {
var str = renderPartial(this, view, options, null, parent);
} catch (err) {
if (fn) {
fn(err);
} else {
throw err;
}
}
// callback or transfer
if (fn) {
fn(null, str);
} else {
this.send(str);
}
};
/**
@@ -169,12 +208,12 @@ res.partial = function(view, options){
* @api public
*/
res.render = function(view, opts, fn, parent){
res.render = function(view, opts, fn, parent, sub){
// support callback function as second arg
if (typeof opts === 'function') {
if ('function' == typeof opts) {
fn = opts, opts = null;
}
var options = {}
, self = this
, app = this.app
@@ -186,8 +225,8 @@ res.render = function(view, opts, fn, parent){
// merge "view options"
if (viewOptions) merge(options, viewOptions);
// merge res.locals
if (this.locals) merge(options, this.locals);
// merge res._locals
if (this._locals) merge(options, this._locals);
// merge render() options
if (opts) merge(options, opts);
@@ -233,7 +272,8 @@ res.render = function(view, opts, fn, parent){
// Try index ex: ./views/user/index.jade
if (!view.exists) view = new View(orig.indexPath, options);
// Try ../name ../user from within ./user
// Try ../<name>/index ex: ../user/index.jade
// when calling partial('user') within the same dir
if (!view.exists && !options.isLayout) view = new View(orig.upIndexPath, options);
// Try layout relative to the "views" dir
@@ -270,13 +310,8 @@ res.render = function(view, opts, fn, parent){
return renderPartial(self, path, opts, options, view);
};
function error(err) {
if (fn) {
fn(err);
} else {
self.req.next(err);
}
}
// Provide filename to engine
options.filename = view.path;
// Attempt render
try {
@@ -285,22 +320,36 @@ res.render = function(view, opts, fn, parent){
? cache[view.path] || (cache[view.path] = engine.compile(view.contents, options))
: engine.compile(view.contents, options)
, str = template.call(options.scope, options);
} catch (err) {
return error(err);
}
// Layout support
if (layout) {
options.isLayout = true;
options.layout = false;
options.body = str;
self.render(layout, options, fn, view);
} else if (partial) {
return str;
} else if (fn) {
fn(null, str);
} else {
this.send(str);
// layout expected
if (layout) {
options.isLayout = true;
options.layout = false;
options.body = str;
this.render(layout, options, fn, view, true);
// partial return
} else if (partial) {
return str;
// render complete, and
// callback given
} else if (fn) {
fn(null, str);
// respond
} else {
this.send(str);
}
} catch (err) {
// callback given
if (fn) {
fn(err);
// unwind to root call
} else if (sub) {
throw err;
// root template, next(err)
} else {
this.req.next(err);
}
}
};
+3 -2
Ver Arquivo
@@ -47,6 +47,7 @@ var View = exports = module.exports = function View(view, options) {
this.basename = basename(view);
this.engine = this.resolveEngine();
this.extension = '.' + this.engine;
this.name = this.basename.replace(this.extension, '');
this.path = this.resolvePath();
this.dirname = dirname(this.path);
};
@@ -160,14 +161,14 @@ View.prototype.__defineGetter__('indexPath', function(){
});
/**
* Return ../index path alternative.
* Return ../<name>/index path alternative.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('upIndexPath', function(){
return this.dirname + '/index' + this.extension;
return this.dirname + '/../' + this.name + '/index' + this.extension;
});
/**
+2 -2
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.0.0rc",
"version": "2.0.0",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,7 +10,7 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": ">= 1.0.1 < 2.0.0",
"connect": ">= 1.1.0 < 2.0.0",
"mime": ">= 0.0.1",
"qs": ">= 0.0.6"
},
+26 -1
Ver Arquivo
@@ -406,6 +406,31 @@ module.exports = {
{ url: '/regular' },
{ body: 'hey' });
},
'test .app property after returning control to parent': function() {
var app = express.createServer()
, blog = express.createServer();
// Mounted servers did not restore `req.app` and `res.app` when
// passing control back to parent via `out()` in `#handle()`.
blog.get('/', function(req, res, next){
req.app.should.equal(blog);
res.app.should.equal(blog);
next();
});
app.use(blog);
app.use(function(req, res, next) {
res.send((res.app === app) ? 'restored' : 'not-restored');
});
assert.response(app,
{ url: '/' },
{ body: 'restored' }
);
},
'test route middleware': function(){
var app = express.createServer();
@@ -534,4 +559,4 @@ module.exports = {
{ url: '/user/12', method: 'OPTIONS' },
{ headers: { Allow: 'GET,PUT' }});
}
};
};
+1
Ver Arquivo
@@ -0,0 +1 @@
li #{title} #{item}
+2
Ver Arquivo
@@ -0,0 +1,2 @@
p Hits #{hits}
p Misses #{misses}
+174 -88
Ver Arquivo
@@ -105,68 +105,68 @@ module.exports = {
'test #render()': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res){
res.render('index.jade', { layout: false });
});
app.get('/jade', function(req, res){
res.render('index', { layout: false });
});
app.get('/haml', function(req, res){
res.render('hello.haml', { layout: false });
});
app.get('/callback/layout/no-options', function(req, res){
res.render('hello.jade', function(err, str){
assert.ok(!err);
res.send(str.replace(':(', ':)'));
});
});
app.get('/callback/layout', function(req, res){
res.render('hello.jade', {}, function(err, str){
assert.ok(!err);
res.send(str.replace(':(', ':)'));
});
});
app.get('/callback', function(req, res){
res.render('hello.haml', { layout: false }, function(err, str){
assert.ok(!err);
res.send(str.replace('Hello World', ':)'));
});
});
app.get('/invalid', function(req, res){
res.render('invalid.jade', { layout: false });
});
app.get('/invalid-async', function(req, res){
process.nextTick(function(){
res.render('invalid.jade', { layout: false });
});
});
app.get('/error', function(req, res){
res.render('invalid.jade', { layout: false }, function(err){
res.send(err.arguments[0]);
});
});
app.get('/absolute', function(req, res){
res.render(__dirname + '/fixtures/index.jade', { layout: false });
});
app.get('/ferret', function(req, res){
res.render('ferret', { layout: false, ferret: { name: 'Tobi' }});
});
app.get('/status', function(req, res){
res.render('hello.jade', { status: 500 });
});
assert.response(app,
{ url: '/status' },
{ status: 500 });
@@ -214,14 +214,14 @@ module.exports = {
'test #render() layout': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res){
res.render('index.jade');
});
app.get('/jade', function(req, res){
res.render('index');
});
assert.response(app,
{ url: '/' },
{ body: '<html><body><p>Welcome</p></body></html>' });
@@ -230,7 +230,7 @@ module.exports = {
'test #render() specific layout': function(beforeExit){
var app = create()
, called;
app.get('/', function(req, res){
res.render('index.jade', { layout: 'cool-layout.jade' }, function(err, html){
called = true;
@@ -249,7 +249,7 @@ module.exports = {
app.get('/nope', function(req, res){
res.render('index.jade', { layout: 'nope.jade' });
});
assert.response(app,
{ url: '/' },
{ body: '<cool><p>Welcome</p></cool>' });
@@ -268,7 +268,7 @@ module.exports = {
assert.ok(~res.body.indexOf('Error: failed to locate view'));
assert.ok(~res.body.indexOf('nope'));
});
beforeExit(function(){
assert.ok(called, 'Layout callback never called');
});
@@ -303,17 +303,17 @@ module.exports = {
'test #render() view helpers': function(beforeExit){
var app = create()
, calls = 0;
app.locals({
lastName: 'holowaychuk'
});
app.helpers({
greetings: function(sess, lastName){
return 'Hello ' + sess.name + ' ' + lastName;
}
});
var ret = app.dynamicHelpers({
session: function(req, res){
++calls;
@@ -323,19 +323,19 @@ module.exports = {
return req.session;
}
});
assert.equal(app, ret, 'Server#helpers() is not chainable');
app.get('/', function(req, res){
req.session = { name: 'tj' };
res.render('dynamic-helpers.jade', { layout: false });
});
app.get('/ejs', function(req, res){
req.session = { name: 'tj' };
res.render('dynamic-helpers.ejs', { layout: false });
});
app.get('/precedence', function(req, res){
req.session = { name: 'tj' };
res.render('dynamic-helpers.jade', {
@@ -352,7 +352,7 @@ module.exports = {
assert.response(app,
{ url: '/precedence' },
{ body: '<html><body><p>Hello tj foobar</p></body></html>' });
beforeExit(function(){
assert.equal(3, calls);
});
@@ -360,9 +360,9 @@ 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'] });
@@ -371,7 +371,7 @@ module.exports = {
assert.response(app,
{ url: '/' },
{ body: '<html><body><ul><li>one</li><li>two</li></ul></body></html>' });
// Auto-assigned local w/ collection array
var movies = [
{ title: 'Nightmare Before Christmas', director: 'Tim Burton' },
@@ -380,7 +380,7 @@ module.exports = {
app.get('/movies', function(req, res){
res.render('movies.jade', { movies: movies });
});
var html = [
'<html>',
'<body>',
@@ -397,11 +397,11 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/movies' },
{ body: html });
// as: str collection option
app.get('/user', function(req, res){
res.partial('user', {
@@ -413,7 +413,7 @@ module.exports = {
assert.response(app,
{ url: '/user' },
{ body: '<p>tj</p>' });
// as: with object collection
app.get('/user/object', function(req, res){
res.partial('user.jade', {
@@ -425,7 +425,7 @@ module.exports = {
assert.response(app,
{ url: '/user' },
{ body: '<p>tj</p>' });
// as: this collection option
app.get('/person', function(req, res){
res.partial('person.jade', {
@@ -438,7 +438,7 @@ module.exports = {
assert.response(app,
{ url: '/person' },
{ body: '<p>name: tj</p>' });
// as: global collection option
app.get('/videos', function(req, res){
res.partial('video.jade', {
@@ -446,7 +446,7 @@ module.exports = {
collection: movies
});
});
assert.response(app,
{ url: '/videos' },
{ body: '<p>Tim Burton</p><p>James Cameron</p>' });
@@ -473,7 +473,7 @@ module.exports = {
assert.response(app,
{ url: '/movie' },
{ body: '<li><div class="title">Nightmare Before Christmas</div><div class="director">Tim Burton</div></li>' });
app.get('/video-global', function(req, res){
res.partial('video.jade', {
object: movies[0],
@@ -485,7 +485,7 @@ module.exports = {
assert.response(app,
{ url: '/video-global' },
{ body: '<p>Tim Burton</p>' });
app.get('/person-this', function(req, res){
res.partial('person.jade', {
object: { name: 'tj' },
@@ -498,7 +498,7 @@ module.exports = {
assert.response(app,
{ url: '/person-this' },
{ body: '<p>User: tj</p>' });
// No options
app.get('/nothing', function(req, res){
res.partial('hello.ejs');
@@ -507,29 +507,103 @@ module.exports = {
assert.response(app,
{ url: '/nothing' },
{ body: 'Hello' });
// Path segments + "as"
app.get('/role/as', function(req, res){
res.partial('user/role.ejs', { as: 'role', collection: ['admin', 'member'] });
});
assert.response(app,
{ url: '/role/as' },
{ body: '<li>Role: admin</li><li>Role: member</li>' });
// Deduce name from last segment
app.get('/role', function(req, res){
res.partial('user/role.ejs', ['admin', 'member']);
});
assert.response(app,
{ url: '/role' },
{ body: '<li>Role: admin</li><li>Role: member</li>' });
// Non-basic object support
function Movie(title, director){
this.title = title;
this.director = director;
}
app.get('/movie/object', function(req, res){
res.partial('movie', new Movie('The TJ', 'tj'));
});
assert.response(app,
{ url: '/movie/object' },
{ body: '<li><div class="title">The TJ</div><div class="director">tj</div></li>' });
// Locals
app.get('/stats', function(req, res){
res.partial('stats', {
hits: 12
, misses: 1
});
});
assert.response(app,
{ url: '/stats' },
{ body: '<p>Hits 12</p><p>Misses 1</p>' });
// Locals
app.get('/stats/locals', function(req, res){
res.partial('stats', {
locals: {
hits: 12
, misses: 1
}
});
});
assert.response(app,
{ url: '/stats/locals' },
{ body: '<p>Hits 12</p><p>Misses 1</p>' });
// Collection + locals
app.get('/items', function(req, res){
res.partial('item-title', {
collection: ['foo', 'bar']
, title: 'test'
, as: 'item'
});
});
assert.response(app,
{ url: '/items' },
{ body: '<li>test foo</li><li>test bar</li>' });
app.get('/stats/callback', function(req, res){
res.partial('stats', { hits: 12, misses: 1 }, function(err, html){
res.send('got: ' + html);
});
});
assert.response(app,
{ url: '/stats/callback' },
{ body: 'got: <p>Hits 12</p><p>Misses 1</p>' });
app.get('/stats/callback/2', function(req, res){
res.locals({ hits: 12, misses: 1 });
res.partial('stats', function(err, html){
res.send('got: ' + html);
});
});
assert.response(app,
{ url: '/stats/callback/2' },
{ body: 'got: <p>Hits 12</p><p>Misses 1</p>' });
},
'test #partial() with several calls': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('list.jade', { layout: false });
});
@@ -541,7 +615,7 @@ module.exports = {
'test #partial() with several calls using locals': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('list2.jade', { layout: false });
});
@@ -553,9 +627,9 @@ module.exports = {
'test #partial() locals': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res, next){
res.partial('pet-count', {
locals: {
@@ -565,7 +639,7 @@ module.exports = {
}
});
});
assert.response(app,
{ url: '/' },
{ body: 'We have 5 cool pets\n' });
@@ -573,59 +647,71 @@ module.exports = {
'test #partial() locals precedence': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('greetings.jade', {
name: 'TJ'
, locals: { otherName: 'Overridden' }
});
});
assert.response(app,
{ url: '/' },
{ body: '<html><body><h1>TJ</h1><p>Welcome Overridden</p></body></html>' });
},
'test #partial() index': function(){
var app = create();
app.set('view engine', 'jade');
function Ferret(name){ this.name = name; };
app.get('/ferret', function(req, res){
res.partial('ferret', { name: 'Tobi' });
res.partial('ferret', new Ferret('Tobi'));
});
app.get('/ferret/basic', function(req, res){
res.partial('ferret', { ferret: { name: 'Tobi' }});
});
assert.response(app,
{ url: '/ferret/basic' },
{ body: '<li class="ferret">Tobi</li>' });
assert.response(app,
{ url: '/ferret' },
{ body: '<li class="ferret">Tobi</li>' });
},
'test #partial() relative index': function(){
var app = create();
app.set('view engine', 'jade');
function Ferret(name) { this.name = name; }
app.get('/ferret', function(req, res){
var tobi = { name: 'Tobi' }
, loki = { name: 'Loki' };
var tobi = new Ferret('Tobi')
, loki = new Ferret('Loki');
res.partial('ferret/list', { object: [tobi, loki] });
});
assert.response(app,
{ url: '/ferret' },
{ body: '<ul id="ferrets"><li class="ferret">Tobi</li><li class="ferret">Loki</li></ul>' });
},
'test #partial() object': function(){
var app = create();
app.get('/', function(req, res, next){
res.partial('movie.jade', {
title: 'Foobar'
, director: 'Tim Burton'
});
function Movie(title, director) {
this.title = title;
this.director = director;
}
res.partial('movie.jade', new Movie('Foobar', 'Tim Burton'));
});
assert.response(app,
{ url: '/' },
{ body: '<li><div class="title">Foobar</div><div class="director">Tim Burton</div></li>' });
@@ -633,13 +719,13 @@ module.exports = {
'test #partial() locals with collection': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('pet-land.jade', {
pets: ['Ewald']
});
});
assert.response(app,
{ url: '/' },
{ body: '<html><body><div><li>Ewald is the coolest of Animal land</li></div></body></html>' });
@@ -647,14 +733,14 @@ module.exports = {
'test #partial() inheriting initial locals': function(){
var app = create();
app.get('/pets', function(req, res, next){
res.render('pets.jade', {
site: 'My Cool Pets'
, pets: ['Tobi', 'Jane', 'Bandit']
});
});
var html = [
'<html>',
'<body>',
@@ -668,7 +754,7 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/pets' },
{ body: html });
@@ -676,7 +762,7 @@ module.exports = {
'test #partial() with array-like collection': function(){
var app = create();
var movies = {
0: { title: 'Nightmare Before Christmas', director: 'Tim Burton' },
1: { title: 'Avatar', director: 'James Cameron' },
@@ -702,7 +788,7 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/movies' },
{ body: html });
@@ -711,7 +797,7 @@ module.exports = {
'test "partials" setting': function(){
var app = create();
app.set('partials', __dirname + '/fixtures/sub-templates');
app.get('/', function(req, res){
res.render('items.jade', {
layout: false,
@@ -808,12 +894,12 @@ module.exports = {
, open: '<?'
, title: 'Original'
});
function setTitle(req, res, next) {
res.local('title', 'Wahoo');
next();
}
app.get('/video', setTitle, function(req, res, next){
res.local('close', '?>');
res.render('video.ejs', { layout: false, title: 'keyboard cat' });
@@ -823,20 +909,20 @@ module.exports = {
res.local('close', '?>');
res.render('video.ejs', { layout: false });
});
app.get('/video/3', function(req, res, next){
res.local('close', '?>');
res.render('video.ejs', { layout: false });
});
assert.response(app,
{ url: '/video' },
{ body: '<h1>keyboard cat</h1>' });
assert.response(app,
{ url: '/video/2' },
{ body: '<h1>Wahoo</h1>' });
assert.response(app,
{ url: '/video/3' },
{ body: '<h1>Original</h1>' });
@@ -861,11 +947,11 @@ module.exports = {
}
next();
});
app.get('/pets', function(req, res, next){
res.render('pets.jade');
});
var html = [
'<html>',
'<body>',
@@ -879,7 +965,7 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/pets' },
{ body: html });
@@ -887,12 +973,12 @@ module.exports = {
'test .charset with res.render()': function(){
var app = create();
app.get('/', function(req, res){
res.charset = 'ISO-8859-1';
res.render('hello.jade');
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
@@ -900,11 +986,11 @@ module.exports = {
'test charset res.render() option': function(){
var app = create();
app.get('/', function(req, res){
res.render('hello.jade', { charset: 'ISO-8859-1' });
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
@@ -913,11 +999,11 @@ module.exports = {
'test charset option': function(){
var app = create();
app.set('view options', { charset: 'ISO-8859-1' });
app.get('/', function(req, res){
res.render('hello.jade');
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
@@ -926,11 +1012,11 @@ module.exports = {
'test charset override': function(){
var app = create();
app.set('view options', { charset: 'ISO-8859-1' });
app.get('/', function(req, res){
res.render('hello.jade', { charset: 'utf8' });
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=utf8' }});