Compare commits

...

44 Commits
2.1.0 ... 2.2.1

Author SHA1 Message Date
Tj Holowaychuk
4e1aefa5b5 Release 2.2.1 2011-04-04 12:23:37 -07:00
Tj Holowaychuk
89383ecc57 misc refactoring 2011-04-03 14:27:40 -07:00
Tj Holowaychuk
08046f7692 Added better partial() object collection support
only respecting .length is fine in some cases, but
if the object has a length and has holes, it will likely
produce an unexpected result, or an undefined local in the
render call, which may some times be ideal, but most likely not.

this change allows arbitrary objects to be passed to "collection: ",
however unfortunately there is no way to arbitrarily assume the object
is a collection without passing it as the option
2011-04-03 14:27:05 -07:00
Tj Holowaychuk
885fb1fa92 docs 2011-04-01 17:55:26 -07:00
Tj Holowaychuk
6a58c71528 Merge branch 'feature/view-layout-control' 2011-04-01 17:47:11 -07:00
Tj Holowaychuk
371d66ba2a Added layout(path) helper to change the layout within a view. Closes #610 2011-04-01 17:47:03 -07:00
Tj Holowaychuk
25bddf3fb5 added layout control example 2011-04-01 17:39:18 -07:00
Tj Holowaychuk
f6e9fb13f8 Removed "request" and "response" locals
sorry, changed my mind. easy enough to expose these if you want to,
but they are to large by default
2011-04-01 17:25:05 -07:00
Tj Holowaychuk
f0df8434e7 markdown escaping 2011-03-31 08:24:43 -07:00
Tj Holowaychuk
e4d3f239e5 Updated connect submodule 2011-03-30 22:01:16 -07:00
Tj Holowaychuk
bcc22dfa6f Updated connect submodule 2011-03-30 21:59:17 -07:00
Tj Holowaychuk
f614709a01 errorHandler title 2011-03-30 21:59:15 -07:00
Tj Holowaychuk
11250d22c9 misc refactoring 2011-03-30 21:44:21 -07:00
Tj Holowaychuk
4b4de29725 Performance improved with better view caching
the entire View object is now cached in-memory, along with the lookup
logic as well. This increases rendering (with jade at least) by about 260 rps
2011-03-30 21:40:05 -07:00
Tj Holowaychuk
99b49d2718 updated docs 2011-03-30 12:11:22 -07:00
Tj Holowaychuk
6230ec55be more docs 2011-03-30 12:02:14 -07:00
Tj Holowaychuk
0733d3c585 connect 1.2.0 2011-03-30 11:58:26 -07:00
Tj Holowaychuk
6f8370ff0e Updated connect submodule 2011-03-30 11:58:09 -07:00
Tj Holowaychuk
bc244ed07e docs for app.match.VERB() 2011-03-30 11:56:14 -07:00
Tj Holowaychuk
41266aa8e4 docs for app.lookup.VERB() 2011-03-30 11:49:21 -07:00
Tj Holowaychuk
45faee3e5b Release 2.2.0 2011-03-30 11:40:47 -07:00
Tj Holowaychuk
74ff735e10 Updated haml submodule 2011-03-30 11:00:47 -07:00
Tj Holowaychuk
97879f2b16 Updated formidable submodule 2011-03-30 11:00:43 -07:00
Tj Holowaychuk
1a338251ad Updated connect-form submodule 2011-03-30 11:00:34 -07:00
Tj Holowaychuk
354dc768c1 Updated jade submodule 2011-03-30 11:00:25 -07:00
Tj Holowaychuk
8a0796cd94 Updated ejs submodule 2011-03-30 11:00:21 -07:00
Tj Holowaychuk
cbc3b26584 changed express(1) --help 2011-03-30 10:58:01 -07:00
Tj Holowaychuk
d628583db8 removed colors from express(1) 2011-03-30 10:56:11 -07:00
Tj Holowaychuk
058777be1e connect >= 1.1.6 2011-03-29 18:11:00 -07:00
Tj Holowaychuk
260d03a0c4 Merge branch 'feature/route-lookup' 2011-03-29 18:04:49 -07:00
Tj Holowaychuk
6dcf6f41cc Added app.VERB() -> [Function...], app.lookup.VERB(), and app.match.VERB(). Closes #606 2011-03-29 18:04:43 -07:00
Tj Holowaychuk
799f790886 Updated connect submodule 2011-03-29 17:40:14 -07:00
Tj Holowaychuk
cb3c4b0ea9 Updated connect submodule 2011-03-29 17:38:39 -07:00
Tj Holowaychuk
798d255ba6 Release 2.1.1 2011-03-29 10:40:26 -07:00
Tj Holowaychuk
28ba9e8ac5 Fixed res.partial(); next(err) when no callback is given [reported by aheckmann] 2011-03-29 09:56:58 -07:00
Tj Holowaychuk
7888cb0506 docs 2011-03-29 09:51:38 -07:00
Tj Holowaychuk
5e284a20cc Updated connect submodule 2011-03-29 09:49:45 -07:00
Tj Holowaychuk
770357e727 Updated expresso submodule 2011-03-29 09:49:18 -07:00
Aaron Heckmann
673ba22555 res.send(undefined) returns a 204
closes #600

Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-03-29 08:52:36 -07:00
Aaron Heckmann
fb38d9cfb7 add test for res.send(undefined)
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-03-29 08:52:34 -07:00
Aaron Heckmann
dda89a57ec ignore .swo .swp
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-03-29 08:52:34 -07:00
Tj Holowaychuk
62df63d3a0 doc typo 2011-03-29 08:39:35 -07:00
Tj Holowaychuk
e71696cf34 expose err.view when failing to locate a view
allows for:

   err.view.path

etc
2011-03-28 14:44:12 -07:00
Tj Holowaychuk
b5d8d58670 repo 2011-03-27 14:23:43 -07:00
31 changed files with 535 additions and 103 deletions

2
.gitignore vendored
View File

@@ -6,5 +6,7 @@ lib-cov
*.dat
*.out
*.pid
*.swp
*.swo
benchmarks/graphs
testing.js

View File

@@ -1,4 +1,33 @@
2.2.1 / 2011-04-04
==================
* Added `layout(path)` helper to change the layout within a view. Closes #610
* Fixed `partial()` collection object support.
Previously only anything with `.length` would work.
When `.length` is present one must still be aware of holes,
however now `{ collection: {foo: 'bar'}}` is valid, exposes
`keyInCollection` and `keysInCollection`.
* Performance improved with better view caching
* Removed `request` and `response` locals
* Changed; errorHandler page title is now `Express` instead of `Connect`
2.2.0 / 2011-03-30
==================
* Added `app.lookup.VERB()`, ex `app.lookup.put('/user/:id')`. Closes #606
* Added `app.match.VERB()`, ex `app.match.put('/user/12')`. Closes #606
* Added `app.VERB(path)` as alias of `app.lookup.VERB()`.
* Dependency `connect >= 1.2.0`
2.1.1 / 2011-03-29
==================
* Added; expose `err.view` object when failing to locate a view
* Fixed `res.partial()` call `next(err)` when no callback is given [reported by aheckmann]
* Fixed; `res.send(undefined)` responds with 204 [aheckmann]
2.1.0 / 2011-03-24
==================

View File

@@ -11,7 +11,7 @@ var fs = require('fs')
* Framework version.
*/
var version = '2.1.0';
var version = '2.2.0';
/**
* Add session support.
@@ -37,14 +37,14 @@ var templateEngine = 'jade';
var usage = ''
+ '\n'
+ ' \x1b[1mUsage\x1b[0m: express [options] [PATH]\n'
+ ' Usage: express [options] [path]\n'
+ '\n'
+ ' \x1b[1mOptions\x1b[0m:\n'
+ ' -s, --sessions Add session support\n'
+ ' -t, --template ENGINE Add template ENGINE support (jade|ejs). Defaults to jade\n'
+ ' -c, --css ENGINE Add stylesheet ENGINE support (less|sass|stylus). Defaults to plain css\n'
+ ' -v, --version Output framework version\n'
+ ' -h, --help Output help information\n'
+ ' Options:\n'
+ ' -s, --sessions add session support\n'
+ ' -t, --template <engine> add template <engine> support (jade|ejs). default=jade\n'
+ ' -c, --css <engine> add stylesheet <engine> support (less|sass|stylus). default=plain css\n'
+ ' -v, --version output framework version\n'
+ ' -h, --help output help information\n'
;
/**

View File

@@ -232,6 +232,8 @@
<li><a href="#app.error()">error()</a></li>
<li><a href="#app.helpers()">helpers()</a></li>
<li><a href="#app.dynamichelpers()">dynamicHelpers()</a></li>
<li><a href="#app.lookup">lookup</a></li>
<li><a href="#app.match">match</a></li>
<li><a href="#app.mounted()">mounted()</a></li>
<li><a href="#app.register()">register()</a></li>
<li><a href="#app.listen()">listen()</a></li>
@@ -1222,7 +1224,7 @@ res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOn
<pre><code>res.cookie('rememberme', 'yes', { maxAge: 900000 });
</code></pre>
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieDecoder</em> middleware, which provides the <em>req.cookies</em> object:</p>
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieParser</em> middleware, which provides the <em>req.cookies</em> object:</p>
<pre><code>app.use(express.cookieParser());
@@ -1506,21 +1508,30 @@ should call <em>next(err)</em> if it does not wish to deal with the exception:</
<p>Registers static view helpers.</p>
<p> app.helpers({</p>
<pre><code> name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
<pre><code>app.helpers({
name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
});
</code></pre>
<p> });</p>
<p>Our view could now utilize the <em>firstName</em> and <em>lastName</em> variables,
as well as the <em>name()</em> function exposed.</p>
<pre><code>&lt;%= name(firstName, lastName) %&gt;
</code></pre>
<p>Express also provides a few locals by default:</p>
<pre><code>- `settings` the app's settings object
- `filename` the view's filename
- `request` the request object
- `response` the response object
- `app` the application itself
</code></pre>
<p>This method is aliased as <em>app.locals()</em>.</p>
<h3 id="app.dynamichelpers()">app.dynamicHelpers(obj)</h3>
<p>Registers dynamic view helpers. Dynamic view helpers
@@ -1540,6 +1551,110 @@ becomes the local variable it is associated with.</p>
<pre><code>&lt;%= session.name %&gt;
</code></pre>
<h3 id="app.lookup">app.lookup</h3>
<p> The <em>app.lookup</em> http methods returns an array of callback functions
associated with the given <em>path</em>.</p>
<p> Suppose we define the following routes:</p>
<pre><code> app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
</code></pre>
<p> We can utilize this lookup functionality to check which routes
have been defined, which can be extremely useful for higher level
frameworks built on Express.</p>
<pre><code> app.lookup.get('/user/:id');
// =&gt; [Function]
app.lookup.get('/user/:id/:op?');
// =&gt; [Function]
app.lookup.put('/user/:id');
// =&gt; [Function]
app.lookup.all('/user/:id');
// =&gt; [Function, Function]
app.lookup.all('/hey');
// =&gt; []
</code></pre>
<p> To alias <em>app.lookup.VERB()</em>, we can simply invoke <em>app.VERB()</em>
without a callback, as a shortcut, for example the following are
equivalent:</p>
<pre><code> app.lookup.get('/user');
app.get('/user');
</code></pre>
<p> Each function returned has the following properties:</p>
<pre><code> var fn = app.get('/user/:id/:op?')[0];
fn.regexp
// =&gt; /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
fn.keys
// =&gt; ['id', 'op']
fn.path
// =&gt; '/user/:id/:op?'
fn.method
// =&gt; 'GET'
</code></pre>
<h3 id="app.match">app.match</h3>
<p> The <em>app.match</em> http methods return an array of callback functions
which match the given <em>url</em>, which may include a query string etc. This
is useful when you want reflect on which routes have the opportunity to
respond.</p>
<p> Suppose we define the following routes:</p>
<pre><code> app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
</code></pre>
<p> Our match against <strong>GET</strong> will return two functions, since the <em>:op</em>
in our second route is optional.</p>
<pre><code> app.match.get('/user/1');
// =&gt; [Function, Function]
</code></pre>
<p> This second call returns only the callback for <em>/user/:id/:op?</em>.</p>
<pre><code> app.match.get('/user/23/edit');
// =&gt; [Function]
</code></pre>
<p> We can also use <em>all()</em> to disregard the http method:</p>
<pre><code> app.match.all('/user/20');
// =&gt; [Function, Function, Function]
</code></pre>
<p> Each function matched has the following properties:</p>
<pre><code> var fn = app.match.get('/user/23/edit')[0];
fn.keys
// =&gt; ['id', 'op']
fn.params
// =&gt; { id: '23', op: 'edit' }
fn.method
// =&gt; 'GET'
</code></pre>
<h3 id="app.mounted()">app.mounted(fn)</h3>
<p>Assign a callback <em>fn</em> which is called when this <em>Server</em> is passed to <em>Server#use()</em>.</p>

View File

@@ -165,7 +165,7 @@ For example we can __POST__ some json, and echo the json back using the _bodyPar
app.listen(3000);
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id(\\d+)'_ which will _not_ match unless the placeholder value contains only digits.
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id(\\\\d+)'_ which will _not_ match unless the placeholder value contains only digits.
### Passing Route Control
@@ -878,7 +878,7 @@ The _maxAge_ property may be used to set _expires_ relative to _Date.now()_ in m
res.cookie('rememberme', 'yes', { maxAge: 900000 });
To parse incoming _Cookie_ headers, use the _cookieDecoder_ middleware, which provides the _req.cookies_ object:
To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which provides the _req.cookies_ object:
app.use(express.cookieParser());
@@ -1145,9 +1145,7 @@ Express also provides a few locals by default:
- `settings` the app's settings object
- `filename` the view's filename
- `request` the request object
- `response` the response object
- `app` the application itself
- `layout(path)` specify the layout from within a view
This method is aliased as _app.locals()_.
@@ -1168,6 +1166,101 @@ All views would now have _session_ available so that session data can be accesse
<%= session.name %>
### app.lookup
The _app.lookup_ http methods returns an array of callback functions
associated with the given _path_.
Suppose we define the following routes:
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
We can utilize this lookup functionality to check which routes
have been defined, which can be extremely useful for higher level
frameworks built on Express.
app.lookup.get('/user/:id');
// => [Function]
app.lookup.get('/user/:id/:op?');
// => [Function]
app.lookup.put('/user/:id');
// => [Function]
app.lookup.all('/user/:id');
// => [Function, Function]
app.lookup.all('/hey');
// => []
To alias _app.lookup.VERB()_, we can simply invoke _app.VERB()_
without a callback, as a shortcut, for example the following are
equivalent:
app.lookup.get('/user');
app.get('/user');
Each function returned has the following properties:
var fn = app.get('/user/:id/:op?')[0];
fn.regexp
// => /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
fn.keys
// => ['id', 'op']
fn.path
// => '/user/:id/:op?'
fn.method
// => 'GET'
### app.match
The _app.match_ http methods return an array of callback functions
which match the given _url_, which may include a query string etc. This
is useful when you want reflect on which routes have the opportunity to
respond.
Suppose we define the following routes:
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
Our match against __GET__ will return two functions, since the _:op_
in our second route is optional.
app.match.get('/user/1');
// => [Function, Function]
This second call returns only the callback for _/user/:id/:op?_.
app.match.get('/user/23/edit');
// => [Function]
We can also use _all()_ to disregard the http method:
app.match.all('/user/20');
// => [Function, Function, Function]
Each function matched has the following properties:
var fn = app.match.get('/user/23/edit')[0];
fn.keys
// => ['id', 'op']
fn.params
// => { id: '23', op: 'edit' }
fn.method
// => 'GET'
### app.mounted(fn)
Assign a callback _fn_ which is called when this _Server_ is passed to _Server#use()_.

View File

@@ -237,7 +237,7 @@ app.listen(3000);
<ul>
<li><a href="http://github.com/visionmedia/express-resource">express-resource</a> provides resourceful routing</li>
<li><a href="http://github.com/visionmedia/express-messages">express-messages</a> flash message notification rendering</li>
<li><a href="http://github.com/visionmedia/express-configure">express-configure</a> async configuration support (load settings from redis etc)</li>
<li><a href="http://github.com/visionmedia/express-configuration">express-configure</a> async configuration support (load settings from redis etc)</li>
<li><a href="http://github.com/visionmedia/express-namespace">express-namespace</a> namespaced routing support</li>
</ul>

View File

@@ -0,0 +1,35 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
app.set('views', __dirname + '/views');
// set default layout, usually "layout"
app.set('view options', { layout: 'layouts/default' });
// Set our default template engine to "jade"
// which prevents the need for extensions
// (although you can still mix and match)
app.set('view engine', 'ejs');
app.get('/', function(req, res){
res.render('pages/default');
});
app.get('/alternate', function(req, res){
// note that we do not explicitly
// state the layout here, the view does,
// although we could do it here as well.
res.render('pages/alternate');
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>Alternate Layout</h1>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>Default Layout</h1>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,2 @@
<% layout('layouts/alternate') %>
<h1>Page</h1>

View File

@@ -0,0 +1 @@
<h1>Page</h1>

View File

@@ -27,7 +27,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.1.0';
exports.version = '2.2.1';
/**
* Shortcut for `new Server(...)`.
@@ -69,3 +69,8 @@ require('./response');
*/
require('./request');
// Error handler title
exports.errorHandler.title = 'Express';

View File

@@ -44,6 +44,8 @@ Server.prototype.__proto__ = connect.HTTPServer.prototype;
Server.prototype.init = function(middleware){
var self = this;
this.match = {};
this.lookup = {};
this.settings = {};
this.redirects = {};
this.isCallbacks = {};
@@ -89,12 +91,6 @@ Server.prototype.init = function(middleware){
, app: this
});
// default dynamic locals
this.dynamicHelpers({
request: function(req, res){ return req; }
, response: function(req, res){ return res; }
});
// default development configuration
this.configure('development', function(){
this.enable('hints');
@@ -108,6 +104,21 @@ Server.prototype.init = function(middleware){
// register error handlers on "listening"
// so that they disregard definition position.
this.on('listening', this.registerErrorHandlers.bind(this));
// route lookup methods
methods.forEach(function(method){
self.match[method] = function(url){
return self.router.match(url, 'all' == method
? null
: method);
};
self.lookup[method] = function(path){
return self.router.lookup(path, 'all' == method
? null
: method);
};
});
};
/**
@@ -442,6 +453,13 @@ function generateMethod(method){
Server.prototype[method] = function(path, fn){
var self = this;
// Lookup
if (1 == arguments.length) {
return this.router.lookup(path, 'all' == method
? null
: method);
}
// Ensure router is mounted
if (!this.__usedRouter) {
this.use(this.router);

View File

@@ -51,7 +51,7 @@ res.send = function(body, headers, status){
status = status || this.statusCode;
// allow 0 args as 204
if (!arguments.length) body = status = 204;
if (!arguments.length || undefined === body) body = status = 204;
// determine content type
switch (typeof body) {

View File

@@ -113,16 +113,39 @@ function renderPartial(res, view, options, parentLocals, parent){
// Collection support
if (collection) {
var len = collection.length
, buf = '';
, buf = ''
, keys
, key
, val;
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;
object = val;
buf += render();
if ('number' == typeof len || Array.isArray(collection)) {
for (var i = 0; i < len; ++i) {
val = collection[i];
options.firstInCollection = i == 0;
options.indexInCollection = i;
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
} else {
keys = Object.keys(collection);
len = keys.length;
options.collectionLength = len;
options.collectionKeys = keys;
for (var i = 0; i < len; ++i) {
key = keys[i];
val = collection[key];
options.keyInCollection = key;
options.firstInCollection = i == 0;
options.indexInCollection = i;
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
}
return buf;
} else {
return render();
@@ -179,8 +202,9 @@ res.partial = function(view, options, fn){
if (fn) {
fn(err);
} else {
throw err;
this.req.next(err);
}
return;
}
// callback or transfer
@@ -220,7 +244,8 @@ res.render = function(view, opts, fn, parent, sub){
// callback given
if (fn) {
fn(err);
// unwind to root call
// unwind to root call to prevent
// several next(err) calls
} else if (sub) {
throw err;
// root template, next(err)
@@ -239,7 +264,8 @@ res._render = function(view, opts, fn, parent, sub){
, helpers = app.viewHelpers
, dynamicHelpers = app.dynamicViewHelpers
, viewOptions = app.set('view options')
, cacheTemplates = app.set('cache views');
, cacheViews = app.set('cache views')
, root = app.set('views') || process.cwd() + '/views';
// merge "view options"
if (viewOptions) merge(options, viewOptions);
@@ -256,10 +282,7 @@ res._render = function(view, opts, fn, parent, sub){
// status support
if (options.status) this.statusCode = options.status;
// Defaults
var self = this
, root = app.set('views') || process.cwd() + '/views'
, partial = options.renderPartial
var partial = options.renderPartial
, layout = options.layout;
// Layout support
@@ -282,31 +305,6 @@ res._render = function(view, opts, fn, parent, sub){
// charset option
if (options.charset) this.charset = options.charset;
// Populate view
var orig = view = new View(view, options);
// Try _ prefix ex: ./views/_<name>.jade
if (!view.exists) view = new View(orig.prefixPath, options);
// Try index ex: ./views/user/index.jade
if (!view.exists) view = new View(orig.indexPath, options);
// 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 root ex: <root>/user.jade
if (!view.exists) view = new View(orig.rootPath, options);
// Try root _ prefix ex: <root>/_user.jade
if (!view.exists && partial) view = new View(view.prefixPath, options);
// Does not exist
if (!view.exists) {
if (app.enabled('hints')) hintAtViewPaths(orig, options);
throw new Error('failed to locate view "' + orig.view + '"');
}
// Dynamic helper support
if (false !== options.dynamicHelpers) {
// cache
@@ -335,12 +333,49 @@ res._render = function(view, opts, fn, parent, sub){
// Provide filename to engine
options.filename = view.path;
// Attempt render
var engine = view.templateEngine
, template = cacheTemplates
? cache[view.path] || (cache[view.path] = engine.compile(view.contents, options))
: engine.compile(view.contents, options)
, str = template.call(options.scope, options);
// cached view
if (cache[view]) {
view = cache[view];
// resolve view
} else {
var orig = view = new View(view, options);
// Try _ prefix ex: ./views/_<name>.jade
if (!view.exists) view = new View(orig.prefixPath, options);
// Try index ex: ./views/user/index.jade
if (!view.exists) view = new View(orig.indexPath, options);
// 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 root ex: <root>/user.jade
if (!view.exists) view = new View(orig.rootPath, options);
// Try root _ prefix ex: <root>/_user.jade
if (!view.exists && partial) view = new View(view.prefixPath, options);
// Does not exist
if (!view.exists) {
if (app.enabled('hints')) hintAtViewPaths(orig, options);
var err = new Error('failed to locate view "' + orig.view + '"');
err.view = orig;
throw err;
}
var engine = view.templateEngine;
view.fn = engine.compile(view.contents, options)
if (cacheViews) cache[orig.view] = view;
}
// layout helper
options.layout = function(path){
layout = path;
};
// render
var str = view.fn.call(options.scope, options);
// layout expected
if (layout) {

View File

@@ -17,17 +17,11 @@ var utils = require('../utils')
, stat = fs.statSync;
/**
* Memory cache.
* Require cache.
*/
var cache = {};
/**
* Existance cache.
*/
var exists = {};
/**
* Initialize a new `View` with the given `view` path and `options`.
*
@@ -38,7 +32,6 @@ var exists = {};
var View = exports = module.exports = function View(view, options) {
options = options || {};
// TODO: more caching
this.view = view;
this.root = options.root;
this.relative = false !== options.relative;
@@ -60,16 +53,11 @@ var View = exports = module.exports = function View(view, options) {
*/
View.prototype.__defineGetter__('exists', function(){
var path = this.path;
if (null != exists[path]) {
return exists[path];
} else {
try {
stat(path);
return exists[path] = true;
} catch (err) {
return exists[path] = false;
}
try {
stat(this.path);
return true;
} catch (err) {
return false;
}
});

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.1.0",
"version": "2.2.1",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,11 +10,12 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": ">= 1.1.1 < 2.0.0",
"connect": ">= 1.2.0 < 2.0.0",
"mime": ">= 0.0.1",
"qs": ">= 0.0.6"
},
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
"repository": "git://github.com/visionmedia/express",
"main": "index",
"bin": { "express": "./bin/express" },
"engines": { "node": ">= 0.4.1 < 0.5.0" }

View File

@@ -561,5 +561,47 @@ module.exports = {
assert.response(app,
{ url: '/user/12', method: 'OPTIONS' },
{ headers: { Allow: 'GET,PUT' }});
},
'test app.lookup': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/edit', function(){});
app.get('/user').should.have.length(1);
app.get('/user/:id').should.have.length(1);
app.get('/user/:id/:op?').should.have.length(1);
app.put('/user/:id').should.have.length(1);
app.get('/user/:id/edit').should.have.length(1);
app.get('/').should.have.be.empty;
app.all('/user/:id').should.have.length(2);
app.lookup.get('/user').should.have.length(1);
app.lookup.get('/user/:id').should.have.length(1);
app.lookup.get('/user/:id/:op?').should.have.length(1);
app.lookup.put('/user/:id').should.have.length(1);
app.lookup.get('/user/:id/edit').should.have.length(1);
app.lookup.get('/').should.have.be.empty;
app.lookup.all('/user/:id').should.have.length(2);
},
'test app.match': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/edit', function(){});
app.match.get('/user').should.have.length(1);
app.match.get('/user/12').should.have.length(2);
app.match.get('/user/12/:op?').should.have.length(1);
app.match.put('/user/100').should.have.length(1);
app.match.get('/user/5/edit').should.have.length(2);
app.match.get('/').should.have.be.empty;
app.match.all('/user/123').should.have.length(3);
}
};

1
test/fixtures/error.jade vendored Normal file
View File

@@ -0,0 +1 @@
= user.name

2
test/fixtures/layout-switch.jade vendored Normal file
View File

@@ -0,0 +1,2 @@
- layout('layouts/alternate')
h1 My Page

1
test/fixtures/layouts/alternate.jade vendored Normal file
View File

@@ -0,0 +1 @@
#alternate!= body

1
test/fixtures/object-item.jade vendored Normal file
View File

@@ -0,0 +1 @@
li #{keyInCollection}: #{item}

View File

@@ -50,6 +50,10 @@ module.exports = {
res.send();
});
app.get('/undefined', function(req, res, next){
res.send(undefined);
});
app.get('/bool', function(req, res, next){
res.send(true);
});
@@ -148,6 +152,13 @@ module.exports = {
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
assert.response(app,
{ url: '/undefined' },
{ status: 204 }, function(res){
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
},
'test #contentType()': function(){

View File

@@ -287,6 +287,19 @@ module.exports = {
{ body: '<cool><p>Welcome</p></cool>' });
},
'test #render() view layout control': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res){
res.render('layout-switch');
});
assert.response(app,
{ url: '/' },
{ body: '<div id="alternate"><h1>My Page</h1></div>' });
},
'test #render() "view engine" with periods in dirname': function(){
var app = create();
app.set('view engine', 'jade');
@@ -358,6 +371,19 @@ module.exports = {
});
},
'test #partial() collection object': function(){
var app = create();
app.get('/', function(req, res){
var items = { 2: 'foo', bar: 'bar' };
res.partial('object-item.jade', { as: 'item', collection: items });
});
assert.response(app,
{ url: '/' },
{ body: '<li>2: foo</li><li>bar: bar</li>' });
},
'test #partial()': function(){
var app = create();
@@ -619,6 +645,18 @@ module.exports = {
assert.response(app,
{ url: '/root/underscore' },
{ body: '<p>Testing</p>' });
// error in template
app.get('/error', function(req, res){
process.nextTick(function(){
res.partial('error');
});
});
assert.response(app,
{ url: '/error' },
{ status: 500 });
},
'test #partial() with several calls': function(){