mirror of
https://github.com/expressjs/express.git
synced 2026-02-27 03:07:33 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
658e064220 | ||
|
|
66b472e567 | ||
|
|
3d242a607e | ||
|
|
bc68e5e7a3 | ||
|
|
aa6858189c | ||
|
|
06fda62c9e | ||
|
|
5688ea650d | ||
|
|
f5c4e9d612 | ||
|
|
b9eda2a59d | ||
|
|
8c168b0971 | ||
|
|
9c20a50ee2 | ||
|
|
eac666574e | ||
|
|
e4c840f6b8 | ||
|
|
3afbcd0acf | ||
|
|
8f054dbcaf | ||
|
|
ccc39e5aa2 | ||
|
|
53d4da2a9c | ||
|
|
d9e7153fc9 | ||
|
|
dc02b0d5ae | ||
|
|
e0bc5711b8 | ||
|
|
957cf45fa1 | ||
|
|
f4487343df | ||
|
|
ca1bdb31e3 | ||
|
|
236a412459 | ||
|
|
759a57bdb6 | ||
|
|
1abb674a07 | ||
|
|
961146a287 | ||
|
|
573f940985 | ||
|
|
882916bb2e | ||
|
|
47d1c62732 | ||
|
|
d39293c025 | ||
|
|
2942dafdfd | ||
|
|
4e1aefa5b5 | ||
|
|
89383ecc57 | ||
|
|
08046f7692 | ||
|
|
885fb1fa92 | ||
|
|
6a58c71528 | ||
|
|
371d66ba2a | ||
|
|
25bddf3fb5 | ||
|
|
f6e9fb13f8 | ||
|
|
f0df8434e7 | ||
|
|
e4d3f239e5 | ||
|
|
bcc22dfa6f | ||
|
|
f614709a01 | ||
|
|
11250d22c9 | ||
|
|
4b4de29725 | ||
|
|
99b49d2718 | ||
|
|
6230ec55be | ||
|
|
0733d3c585 | ||
|
|
6f8370ff0e | ||
|
|
bc244ed07e | ||
|
|
41266aa8e4 |
37
History.md
37
History.md
@@ -1,11 +1,46 @@
|
||||
|
||||
2.3.0 / 2011-04-25
|
||||
==================
|
||||
|
||||
* Added options support to `res.clearCookie()`
|
||||
* Added `res.helpers()` as alias of `res.locals()`
|
||||
* Added; json defaults to UTF-8 with `res.send()`. Closes #632. [Daniel * Dependency `connect >= 1.4.0`
|
||||
* Changed; auto set Content-Type in res.attachement [Aaron Heckmann]
|
||||
* Renamed "cache views" to "view cache". Closes #628
|
||||
* Fixed caching of views when using several apps. Closes #637
|
||||
* Fixed gotcha invoking `app.param()` callbacks once per route middleware.
|
||||
Closes #638
|
||||
* Fixed partial lookup precedence. Closes #631
|
||||
Shaw]
|
||||
|
||||
|
||||
2.2.2 / 2011-04-12
|
||||
==================
|
||||
|
||||
* Added second callback support for `res.download()` connection errors
|
||||
* Fixed `filename` option passing to template engine
|
||||
|
||||
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.1.6`
|
||||
* Dependency `connect >= 1.2.0`
|
||||
|
||||
2.1.1 / 2011-03-29
|
||||
==================
|
||||
|
||||
@@ -54,6 +54,7 @@ The following are the major contributors of Express (in no specific order).
|
||||
|
||||
## More Information
|
||||
|
||||
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
|
||||
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
|
||||
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
|
||||
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
|
||||
|
||||
@@ -11,7 +11,7 @@ var fs = require('fs')
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
var version = '2.2.0';
|
||||
var version = '2.3.0';
|
||||
|
||||
/**
|
||||
* Add session support.
|
||||
|
||||
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>
|
||||
|
||||
115
docs/guide.md
115
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
|
||||
|
||||
@@ -816,7 +816,7 @@ Options may also be passed to the internal _fs.createReadStream()_ call, for exa
|
||||
// handle
|
||||
});
|
||||
|
||||
### res.download(file[, filename[, callback]])
|
||||
### res.download(file[, filename[, callback[, callback2]]])
|
||||
|
||||
Transfer the given _file_ as an attachment with optional alternative _filename_.
|
||||
|
||||
@@ -828,12 +828,20 @@ This is equivalent to:
|
||||
res.attachment(file);
|
||||
res.sendfile(file);
|
||||
|
||||
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_:
|
||||
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_. Within this callback you may still respond, as the header has not been sent.
|
||||
|
||||
res.download(path, 'expenses.doc', function(err){
|
||||
// handle
|
||||
});
|
||||
|
||||
An optional second callback, _callback2_ may be given to allow you to act on connection related errors, however you should not attempt to respond.
|
||||
|
||||
res.download(path, function(err){
|
||||
// error or finished
|
||||
}, function(err){
|
||||
// connection related error
|
||||
});
|
||||
|
||||
### res.send(body|status[, headers|status[, status]])
|
||||
|
||||
The _res.send()_ method is a high level response utility allowing you to pass
|
||||
@@ -886,7 +894,7 @@ To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which pro
|
||||
// use req.cookies.rememberme
|
||||
});
|
||||
|
||||
### res.clearCookie(name)
|
||||
### res.clearCookie(name[, options])
|
||||
|
||||
Clear cookie _name_ by setting "expires" far in the past.
|
||||
|
||||
@@ -1145,9 +1153,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 +1174,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>
|
||||
@@ -14,7 +14,7 @@ exports.boot = function(app){
|
||||
// App settings and middleware
|
||||
|
||||
function bootApplication(app) {
|
||||
app.use(express.logger({ format: ':method :url :status' }));
|
||||
app.use(express.logger(':method :url :status'));
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieParser());
|
||||
@@ -45,6 +45,7 @@ function bootApplication(app) {
|
||||
},
|
||||
|
||||
hasMessages: function(req){
|
||||
if (!req.session) return false;
|
||||
return Object.keys(req.session.flash || {}).length;
|
||||
},
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ var exports = module.exports = connect.middleware;
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
exports.version = '2.2.0';
|
||||
exports.version = '2.3.0';
|
||||
|
||||
/**
|
||||
* Shortcut for `new Server(...)`.
|
||||
@@ -69,3 +69,8 @@ require('./response');
|
||||
*/
|
||||
|
||||
require('./request');
|
||||
|
||||
// Error handler title
|
||||
|
||||
exports.errorHandler.title = 'Express';
|
||||
|
||||
|
||||
36
lib/http.js
36
lib/http.js
@@ -44,12 +44,13 @@ Server.prototype.__proto__ = connect.HTTPServer.prototype;
|
||||
|
||||
Server.prototype.init = function(middleware){
|
||||
var self = this;
|
||||
this.cache = {};
|
||||
this.match = {};
|
||||
this.lookup = {};
|
||||
this.settings = {};
|
||||
this.redirects = {};
|
||||
this.isCallbacks = {};
|
||||
this.viewHelpers = {};
|
||||
this._locals = {};
|
||||
this.dynamicViewHelpers = {};
|
||||
this.errorHandlers = [];
|
||||
|
||||
@@ -91,12 +92,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');
|
||||
@@ -104,7 +99,7 @@ Server.prototype.init = function(middleware){
|
||||
|
||||
// default production configuration
|
||||
this.configure('production', function(){
|
||||
this.enable('cache views');
|
||||
this.enable('view cache');
|
||||
});
|
||||
|
||||
// register error handlers on "listening"
|
||||
@@ -247,7 +242,7 @@ Server.prototype.register = function(){
|
||||
|
||||
Server.prototype.helpers =
|
||||
Server.prototype.locals = function(obj){
|
||||
utils.merge(this.viewHelpers, obj);
|
||||
utils.merge(this._locals, obj);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -456,7 +451,7 @@ Server.prototype.configure = function(env, fn){
|
||||
// Generate routing methods
|
||||
|
||||
function generateMethod(method){
|
||||
Server.prototype[method] = function(path, fn){
|
||||
Server.prototype[method] = function(path){
|
||||
var self = this;
|
||||
|
||||
// Lookup
|
||||
@@ -467,27 +462,10 @@ function generateMethod(method){
|
||||
}
|
||||
|
||||
// Ensure router is mounted
|
||||
if (!this.__usedRouter) {
|
||||
this.use(this.router);
|
||||
}
|
||||
|
||||
// Route specific middleware support
|
||||
if (arguments.length > 2) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
fn = args.pop();
|
||||
(function stack(middleware){
|
||||
middleware.forEach(function(fn){
|
||||
if (Array.isArray(fn)) {
|
||||
stack(fn);
|
||||
} else {
|
||||
self[method](path, fn);
|
||||
}
|
||||
});
|
||||
})(args);
|
||||
}
|
||||
if (!this.__usedRouter) this.use(this.router);
|
||||
|
||||
// Generate the route
|
||||
this.routes[method](path, fn);
|
||||
this.routes[method].apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
return arguments.callee;
|
||||
|
||||
@@ -75,10 +75,12 @@ res.send = function(body, headers, status){
|
||||
}
|
||||
} else {
|
||||
if (!this.header('Content-Type')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.contentType('.json');
|
||||
}
|
||||
body = JSON.stringify(body);
|
||||
if (this.req.query.callback && this.app.set('jsonp callback')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.header('Content-Type', 'text/javascript');
|
||||
body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
|
||||
}
|
||||
@@ -130,6 +132,7 @@ res.send = function(body, headers, status){
|
||||
*/
|
||||
|
||||
res.sendfile = function(path, options, fn){
|
||||
var next = this.req.next;
|
||||
options = options || {};
|
||||
|
||||
// support function as second arg
|
||||
@@ -140,7 +143,7 @@ res.sendfile = function(path, options, fn){
|
||||
|
||||
options.path = path;
|
||||
options.callback = fn;
|
||||
send(this.req, this, this.req.next, options);
|
||||
send(this.req, this, next, options);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -175,6 +178,7 @@ res.contentType = function(type){
|
||||
*/
|
||||
|
||||
res.attachment = function(filename){
|
||||
if (filename) this.contentType(filename);
|
||||
this.header('Content-Disposition', filename
|
||||
? 'attachment; filename="' + path.basename(filename) + '"'
|
||||
: 'attachment');
|
||||
@@ -183,32 +187,41 @@ res.attachment = function(filename){
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path`, with optional
|
||||
* `filename` as an attachment and optional callback `fn(err)`.
|
||||
* `filename` as an attachment and optional callback `fn(err)`,
|
||||
* and optional `fn2(err)` which is invoked when an error has
|
||||
* occurred after headers have been sent.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String|Function} filename or fn
|
||||
* @param {Function} fn
|
||||
* @return {Type}
|
||||
* @param {Function} fn2
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.download = function(path, filename, fn){
|
||||
res.download = function(path, filename, fn, fn2){
|
||||
var self = this;
|
||||
|
||||
// support callback as second arg
|
||||
if ('function' == typeof filename) {
|
||||
fn2 = fn;
|
||||
fn = filename;
|
||||
filename = null;
|
||||
}
|
||||
|
||||
// transfer the file
|
||||
this.attachment(filename || path).sendfile(path, function(err){
|
||||
if (err) self.removeHeader('Content-Disposition');
|
||||
if (fn) return fn(err);
|
||||
var sentHeader = self._header;
|
||||
if (err) {
|
||||
self.req.next('ENOENT' == err.code
|
||||
? null
|
||||
: err);
|
||||
if (!sentHeader) self.removeHeader('Content-Disposition');
|
||||
if (sentHeader) {
|
||||
fn2 && fn2(err);
|
||||
} else if (fn) {
|
||||
fn(err);
|
||||
} else {
|
||||
self.req.next(err);
|
||||
}
|
||||
} else if (fn) {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -235,11 +248,15 @@ res.header = function(name, val){
|
||||
* Clear cookie `name`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.clearCookie = function(name){
|
||||
this.cookie(name, '', { expires: new Date(1) });
|
||||
res.clearCookie = function(name, options){
|
||||
var opts = { expires: new Date(1) };
|
||||
this.cookie(name, '', options
|
||||
? utils.merge(options, opts)
|
||||
: opts);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -394,7 +411,8 @@ res.local = function(name, val){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.locals = function(obj){
|
||||
res.locals =
|
||||
res.helpers = function(obj){
|
||||
if (obj) {
|
||||
for (var key in obj) {
|
||||
this.local(key, obj[key]);
|
||||
|
||||
139
lib/view.js
139
lib/view.js
@@ -21,14 +21,6 @@ var path = require('path')
|
||||
, http = require('http')
|
||||
, res = http.ServerResponse.prototype;
|
||||
|
||||
/**
|
||||
* Memory cache.
|
||||
*
|
||||
* @type Object
|
||||
*/
|
||||
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Expose constructors.
|
||||
*/
|
||||
@@ -113,16 +105,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();
|
||||
@@ -238,10 +253,11 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
var options = {}
|
||||
, self = this
|
||||
, app = this.app
|
||||
, helpers = app.viewHelpers
|
||||
, helpers = app._locals
|
||||
, dynamicHelpers = app.dynamicViewHelpers
|
||||
, viewOptions = app.set('view options')
|
||||
, cacheTemplates = app.set('cache views');
|
||||
, cacheViews = app.enabled('view cache')
|
||||
, root = app.set('views') || process.cwd() + '/views';
|
||||
|
||||
// merge "view options"
|
||||
if (viewOptions) merge(options, viewOptions);
|
||||
@@ -258,10 +274,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
|
||||
@@ -284,33 +297,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);
|
||||
var err = new Error('failed to locate view "' + orig.view + '"');
|
||||
err.view = orig;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Dynamic helper support
|
||||
if (false !== options.dynamicHelpers) {
|
||||
// cache
|
||||
@@ -336,15 +322,54 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
return renderPartial(self, path, opts, options, view);
|
||||
};
|
||||
|
||||
// Provide filename to engine
|
||||
options.filename = view.path;
|
||||
// cached view
|
||||
if (app.cache[view]) {
|
||||
view = app.cache[view];
|
||||
options.filename = view.path;
|
||||
// resolve view
|
||||
} else {
|
||||
var orig = view = new View(view, options);
|
||||
|
||||
// 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);
|
||||
// Try _ prefix ex: ./views/_<name>.jade
|
||||
if (partial) {
|
||||
view = new View(orig.prefixPath, options);
|
||||
if (!view.exists) view = orig;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
options.filename = view.path;
|
||||
var engine = view.templateEngine;
|
||||
view.fn = engine.compile(view.contents, options)
|
||||
if (cacheViews) app.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.2.0",
|
||||
"version": "2.3.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.1.6 < 2.0.0",
|
||||
"connect": ">= 1.4.0 < 2.0.0",
|
||||
"mime": ">= 0.0.1",
|
||||
"qs": ">= 0.0.6"
|
||||
},
|
||||
|
||||
Submodule support/connect updated: b8e5d9c28e...af32d828a3
Submodule support/jade updated: 9ec358e0f1...bdeb8b9fb4
@@ -435,8 +435,9 @@ module.exports = {
|
||||
);
|
||||
},
|
||||
|
||||
'test route middleware': function(){
|
||||
var app = express.createServer();
|
||||
'test route middleware': function(beforeExit){
|
||||
var app = express.createServer()
|
||||
, calls = 0;
|
||||
|
||||
function allow(role) {
|
||||
return function(req, res, next) {
|
||||
@@ -460,6 +461,11 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
app.param('user', function(req, res, next, user){
|
||||
++calls;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
@@ -471,8 +477,12 @@ module.exports = {
|
||||
app.get('/tobi', [allow('member')], [[restrictAge(18)]], function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
app.get('/user/:user', [allow('member')], [[restrictAge(18)]], function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
['xxx', 'booze', 'tobi'].forEach(function(thing){
|
||||
['xxx', 'booze', 'tobi', 'user/tj'].forEach(function(thing){
|
||||
assert.response(app,
|
||||
{ url: '/' + thing },
|
||||
{ body: 'Unauthorized', status: 401 });
|
||||
@@ -483,6 +493,10 @@ module.exports = {
|
||||
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
|
||||
{ body: 'OK', status: 200 });
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
calls.should.equal(3);
|
||||
});
|
||||
},
|
||||
|
||||
'test named capture groups': function(){
|
||||
|
||||
1
test/fixtures/_foobar.jade
vendored
Normal file
1
test/fixtures/_foobar.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p two
|
||||
1
test/fixtures/foobar.jade
vendored
Normal file
1
test/fixtures/foobar.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p one
|
||||
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}
|
||||
@@ -61,7 +61,7 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/bool' },
|
||||
{ body: 'true'
|
||||
, headers: { 'Content-Type': 'application/json' }});
|
||||
, headers: { 'Content-Type': 'application/json; charset=utf-8' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/html' },
|
||||
@@ -76,7 +76,7 @@ module.exports = {
|
||||
{ body: '{"foo":"bar"}'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -85,7 +85,7 @@ module.exports = {
|
||||
{ body: 'test({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript'
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -93,7 +93,7 @@ module.exports = {
|
||||
{ url: '/jsonp?callback=baz' },
|
||||
{ body: 'baz({"foo":"bar"});'
|
||||
, status: 201, headers: {
|
||||
'Content-Type': 'text/javascript'
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -102,7 +102,7 @@ module.exports = {
|
||||
{ body: 'invalid({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript'
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -111,7 +111,7 @@ module.exports = {
|
||||
{ body: '{"foo":"bar"}'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -207,12 +207,14 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/javascripts/jquery.js' },
|
||||
{ body: 'whatever'
|
||||
, headers: { 'Content-Disposition': 'attachment; filename="jquery.js"' }});
|
||||
, headers: { 'Content-Type': 'application/javascript'
|
||||
, 'Content-Disposition': 'attachment; filename="jquery.js"' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/style.css' },
|
||||
{ body: 'some stylezzz'
|
||||
, headers: { 'Content-Disposition': 'attachment' }});
|
||||
, headers: { 'Content-Type': 'text/html; charset=utf-8'
|
||||
, 'Content-Disposition': 'attachment' }});
|
||||
},
|
||||
|
||||
'test #redirect()': function(){
|
||||
@@ -568,7 +570,7 @@ module.exports = {
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.clearCookie('rememberme');
|
||||
res.clearCookie('rememberme', { path: '/foo' });
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
@@ -576,7 +578,7 @@ module.exports = {
|
||||
{ url: '/' },
|
||||
function(res){
|
||||
res.headers['set-cookie']
|
||||
.should.eql(['rememberme=; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
|
||||
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -631,6 +657,14 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/error' },
|
||||
{ status: 500 });
|
||||
|
||||
app.get('/underscore', function(req, res, next){
|
||||
res.partial('foobar');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/underscore' },
|
||||
{ body: '<p>two</p>' });
|
||||
},
|
||||
|
||||
'test #partial() with several calls': function(){
|
||||
|
||||
Reference in New Issue
Block a user