mirror of
https://github.com/expressjs/express.git
synced 2026-02-27 03:07:33 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e1aefa5b5 | ||
|
|
89383ecc57 | ||
|
|
08046f7692 | ||
|
|
885fb1fa92 | ||
|
|
6a58c71528 | ||
|
|
371d66ba2a | ||
|
|
25bddf3fb5 | ||
|
|
f6e9fb13f8 | ||
|
|
f0df8434e7 | ||
|
|
e4d3f239e5 | ||
|
|
bcc22dfa6f | ||
|
|
f614709a01 | ||
|
|
11250d22c9 | ||
|
|
4b4de29725 | ||
|
|
99b49d2718 | ||
|
|
6230ec55be | ||
|
|
0733d3c585 | ||
|
|
6f8370ff0e | ||
|
|
bc244ed07e | ||
|
|
41266aa8e4 | ||
|
|
45faee3e5b | ||
|
|
74ff735e10 | ||
|
|
97879f2b16 | ||
|
|
1a338251ad | ||
|
|
354dc768c1 | ||
|
|
8a0796cd94 | ||
|
|
cbc3b26584 | ||
|
|
d628583db8 | ||
|
|
058777be1e | ||
|
|
260d03a0c4 | ||
|
|
6dcf6f41cc | ||
|
|
799f790886 | ||
|
|
cb3c4b0ea9 | ||
|
|
798d255ba6 | ||
|
|
28ba9e8ac5 | ||
|
|
7888cb0506 | ||
|
|
5e284a20cc | ||
|
|
770357e727 | ||
|
|
673ba22555 | ||
|
|
fb38d9cfb7 | ||
|
|
dda89a57ec | ||
|
|
62df63d3a0 | ||
|
|
e71696cf34 | ||
|
|
b5d8d58670 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,5 +6,7 @@ lib-cov
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
benchmarks/graphs
|
||||
testing.js
|
||||
|
||||
29
History.md
29
History.md
@@ -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
|
||||
==================
|
||||
|
||||
|
||||
16
bin/express
16
bin/express
@@ -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'
|
||||
;
|
||||
|
||||
/**
|
||||
|
||||
131
docs/guide.html
131
docs/guide.html
@@ -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><%= name(firstName, lastName) %>
|
||||
</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><%= session.name %>
|
||||
</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');
|
||||
// => [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');
|
||||
// => []
|
||||
</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
|
||||
// => /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
|
||||
|
||||
fn.keys
|
||||
// => ['id', 'op']
|
||||
|
||||
fn.path
|
||||
// => '/user/:id/:op?'
|
||||
|
||||
fn.method
|
||||
// => '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');
|
||||
// => [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');
|
||||
// => [Function]
|
||||
</code></pre>
|
||||
|
||||
<p> We can also use <em>all()</em> to disregard the http method:</p>
|
||||
|
||||
<pre><code> app.match.all('/user/20');
|
||||
// => [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
|
||||
// => ['id', 'op']
|
||||
|
||||
fn.params
|
||||
// => { id: '23', op: 'edit' }
|
||||
|
||||
fn.method
|
||||
// => '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>
|
||||
|
||||
103
docs/guide.md
103
docs/guide.md
@@ -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()_.
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
35
examples/layout-control/app.js
Normal file
35
examples/layout-control/app.js
Normal 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');
|
||||
6
examples/layout-control/views/layouts/alternate.ejs
Normal file
6
examples/layout-control/views/layouts/alternate.ejs
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<h1>Alternate Layout</h1>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
6
examples/layout-control/views/layouts/default.ejs
Normal file
6
examples/layout-control/views/layouts/default.ejs
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<h1>Default Layout</h1>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
2
examples/layout-control/views/pages/alternate.ejs
Normal file
2
examples/layout-control/views/pages/alternate.ejs
Normal file
@@ -0,0 +1,2 @@
|
||||
<% layout('layouts/alternate') %>
|
||||
<h1>Page</h1>
|
||||
1
examples/layout-control/views/pages/default.ejs
Normal file
1
examples/layout-control/views/pages/default.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Page</h1>
|
||||
@@ -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';
|
||||
|
||||
|
||||
30
lib/http.js
30
lib/http.js
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
127
lib/view.js
127
lib/view.js
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Submodule support/connect updated: 2b3cea7744...5a4de3e19c
Submodule support/connect-form updated: ccefcd28db...e861cc85d6
Submodule support/ejs updated: 673e6f23cb...499c4815a5
Submodule support/expresso updated: 2c8759f147...855e0dea07
Submodule support/formidable updated: 63347d825b...5d98e9c75c
Submodule support/haml updated: 4122210f38...55bb6fdc79
Submodule support/jade updated: 1e23782b6f...9ec358e0f1
@@ -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
1
test/fixtures/error.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
= user.name
|
||||
2
test/fixtures/layout-switch.jade
vendored
Normal file
2
test/fixtures/layout-switch.jade
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
- layout('layouts/alternate')
|
||||
h1 My Page
|
||||
1
test/fixtures/layouts/alternate.jade
vendored
Normal file
1
test/fixtures/layouts/alternate.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#alternate!= body
|
||||
1
test/fixtures/object-item.jade
vendored
Normal file
1
test/fixtures/object-item.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
li #{keyInCollection}: #{item}
|
||||
@@ -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(){
|
||||
|
||||
@@ -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(){
|
||||
|
||||
Reference in New Issue
Block a user