Compare commits

...

193 Commits

Author SHA1 Message Date
Tj Holowaychuk
2572a78648 Release 2.3.1 2011-04-26 15:26:04 -07:00
Tj Holowaychuk
470774cfba Fixed template caching collision issue. Closes #644
the parent view is resolved first so we always have the absolute path
available, so this should prevent collisions.
2011-04-26 15:24:31 -07:00
Tj Holowaychuk
351f6abe4c Merge branch 'refactor/router' 2011-04-26 14:36:51 -07:00
Tj Holowaychuk
53a16e1795 Added app.match() as app.match.all() 2011-04-26 13:59:59 -07:00
Tj Holowaychuk
ff77c8b205 Added app.lookup() as app.lookup.all() 2011-04-26 13:59:37 -07:00
Tj Holowaychuk
b33f38b109 Added app.remove() for app.remove.all() 2011-04-26 13:58:43 -07:00
Tj Holowaychuk
b9596d7ce8 Added app.remove.VERB() 2011-04-26 13:57:05 -07:00
Tj Holowaychuk
251175c025 test for previous commit 2011-04-26 13:30:47 -07:00
Tj Holowaychuk
fda1bc4630 moved fn.params to route.params 2011-04-26 13:30:21 -07:00
Tj Holowaychuk
83c2c176a9 misc 2011-04-26 13:28:20 -07:00
Tj Holowaychuk
9be5992f22 misc refactoring 2011-04-26 13:26:49 -07:00
Tj Holowaychuk
d8d23c0bf8 misc 2011-04-26 13:21:19 -07:00
Tj Holowaychuk
b2689fc40e docs 2011-04-26 13:16:19 -07:00
Tj Holowaychuk
a4cfde350f moved more tests 2011-04-26 13:14:21 -07:00
Tj Holowaychuk
7374027457 added router.test.js 2011-04-26 13:13:07 -07:00
Tj Holowaychuk
63328c2177 added .index to match() retvals 2011-04-26 13:01:53 -07:00
Tj Holowaychuk
c4e2ce23e5 more Route tests 2011-04-26 12:59:25 -07:00
Tj Holowaychuk
dacad53b2e Added instancoef Route test 2011-04-26 12:57:14 -07:00
Tj Holowaychuk
4ffd5280a7 expose Route 2011-04-26 12:55:31 -07:00
Tj Holowaychuk
74310fb464 misc refactoring 2011-04-26 12:53:57 -07:00
Tj Holowaychuk
8b2268cf38 Started Route implementation 2011-04-26 12:41:05 -07:00
Tj Holowaychuk
fb655f4981 added lib/router/methods.js 2011-04-26 11:49:51 -07:00
Tj Holowaychuk
7208c33d72 refactored view.js 2011-04-26 11:40:44 -07:00
Tj Holowaychuk
4efb25d048 refactored https.js 2011-04-26 11:39:09 -07:00
Tj Holowaychuk
a3678cd7f6 refactored http.js 2011-04-26 11:34:41 -07:00
Tj Holowaychuk
393d38f1ab stubbed Route 2011-04-26 11:31:31 -07:00
Tj Holowaychuk
805b9ac3a9 docs for "view cache" setting 2011-04-25 12:06:20 -07:00
Tj Holowaychuk
379b9812be refactored options() helper 2011-04-25 10:26:08 -07:00
Tj Holowaychuk
a9992b5647 docs 2011-04-25 10:19:13 -07:00
Tj Holowaychuk
b6c0a9b1b5 moved router to router/index 2011-04-25 10:17:13 -07:00
Tj Holowaychuk
1d2dd2a375 Merge branch 'feature/router' 2011-04-25 10:13:46 -07:00
Tj Holowaychuk
63db694aa2 utilizing local router 2011-04-25 10:13:30 -07:00
Tj Holowaychuk
b6aca36ad9 htmlEscape -> escape 2011-04-25 10:11:38 -07:00
Tj Holowaychuk
8420ae93fd removed old path utils 2011-04-25 10:10:43 -07:00
Tj Holowaychuk
6722716fa7 Moved router over from connect 2011-04-25 10:08:54 -07:00
Tj Holowaychuk
658e064220 Release 2.3.0 2011-04-25 09:49:54 -07:00
Tj Holowaychuk
66b472e567 connect >= 1.4.0 2011-04-25 09:32:57 -07:00
Tj Holowaychuk
3d242a607e Fixed caching of views when using several apps. Closes #637
simple fix :)
2011-04-25 09:25:23 -07:00
Tj Holowaychuk
bc68e5e7a3 misc 2011-04-25 09:20:31 -07:00
Tj Holowaychuk
aa6858189c misc refactor 2011-04-23 18:54:49 -07:00
Tj Holowaychuk
06fda62c9e Merge branch 'refactor/route-middleware' 2011-04-22 16:49:04 -07:00
Tj Holowaychuk
5688ea650d Fixed gotcha invoking app.param() callbacks once per route middleware. Closes #638 2011-04-22 16:48:54 -07:00
Tj Holowaychuk
f5c4e9d612 Updated connect submodule 2011-04-22 16:34:42 -07:00
Tj Holowaychuk
b9eda2a59d Updated connect submodule 2011-04-22 16:11:13 -07:00
Tj Holowaychuk
8c168b0971 connect >= 1.3.1 < 2.0.0 2011-04-22 16:11:07 -07:00
Tj Holowaychuk
9c20a50ee2 Added failing test for gotcha 2011-04-22 14:44:08 -07:00
Tj Holowaychuk
eac666574e viewHelpers -> _locals 2011-04-21 08:26:30 -07:00
Tj Holowaychuk
e4c840f6b8 Added res.helpers() as alias of res.locals()
to match app.locals() / app.helpers()
2011-04-20 15:26:22 -07:00
Daniel Shaw
3afbcd0acf JSON and JSONP default to UTF-8 in the same way as HTML. Closes #632.
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-04-20 09:00:02 -07:00
Daniel Shaw
8f054dbcaf JSON and JSONP default to UTF-8 in the same way as HTML. Introduces app.set('charset') to set charset default at the application level. Closes #632.
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-04-20 09:00:02 -07:00
Tj Holowaychuk
ccc39e5aa2 Fixed partial lookup precedence. Closes #631 2011-04-19 10:23:16 -07:00
Tj Holowaychuk
53d4da2a9c Added failing partial precedence test 2011-04-19 10:19:45 -07:00
Tj Holowaychuk
d9e7153fc9 Renamed "cache views" to "view cache". Closes #628 2011-04-17 16:42:03 -07:00
Tj Holowaychuk
dc02b0d5ae Added options support to res.clearCookie() 2011-04-17 16:37:16 -07:00
Aaron Heckmann
e0bc5711b8 auto set Content-Type in res.attachement
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-04-14 13:48:22 -07:00
Tj Holowaychuk
957cf45fa1 Release 2.2.2 2011-04-12 02:44:47 -07:00
Tj Holowaychuk
f4487343df Fixed filename option passing to template engine 2011-04-12 02:43:03 -07:00
Tj Holowaychuk
ca1bdb31e3 Updated jade submodule 2011-04-12 02:41:30 -07:00
Tj Holowaychuk
236a412459 docs 2011-04-11 09:46:28 -07:00
Tj Holowaychuk
759a57bdb6 Added second callback support for res.download() connection errors
since you can no longer respond, it will be helpful to have separate callbacks
2011-04-11 09:44:19 -07:00
Tj Holowaychuk
1abb674a07 temp fix for nodes res.removeHeader() after sent "bug" 2011-04-11 09:14:11 -07:00
Tj Holowaychuk
961146a287 link to express-expose 2011-04-08 17:51:41 -07:00
Tj Holowaychuk
573f940985 fixed example hasMessages
sessions are not always available
2011-04-08 12:44:03 -07:00
Tj Holowaychuk
882916bb2e Updated jade submodule 2011-04-08 12:36:41 -07:00
Tj Holowaychuk
47d1c62732 Updated connect submodule 2011-04-08 12:36:35 -07:00
Tj Holowaychuk
d39293c025 refactor 2011-04-08 12:36:26 -07:00
Tj Holowaychuk
2942dafdfd >= connect 1.2.3 2011-04-05 11:25:16 -07:00
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
Tj Holowaychuk
14e6a667f5 Release 2.1.0 2011-03-24 13:47:38 -07:00
Tj Holowaychuk
0c324783ae Merge branch 'feature/root-partial-lookup' 2011-03-24 13:34:02 -07:00
Tj Holowaychuk
5d6ce251ca Added <root>/_<name> partial lookup support 2011-03-24 13:33:52 -07:00
Tj Holowaychuk
92c04cee1d Added; partial lookup relative to view root. Closes #447
for example when nested deep, if you have a
partial located at ./views/messages.jade

partial("messages");

will still work
2011-03-24 13:26:32 -07:00
Tj Holowaychuk
7fdf587a7b added test for root partial lookup 2011-03-24 13:21:23 -07:00
Tj Holowaychuk
1e46218b09 doc typo 2011-03-24 11:46:40 -07:00
Tj Holowaychuk
c56fcd8fb9 better --help output 2011-03-22 13:00:36 -07:00
Tj Holowaychuk
319fbf7f64 Added "request", "response", and "app" locals 2011-03-22 12:16:58 -07:00
Tj Holowaychuk
bf06d9077c docs for "filename" local 2011-03-22 12:14:08 -07:00
Tj Holowaychuk
9d2bd29ee1 Added 'settings' local variable, containing the app's settings 2011-03-22 12:06:19 -07:00
Tj Holowaychuk
d11fa1f74e added .settings test 2011-03-22 12:02:57 -07:00
Tj Holowaychuk
c824da0dab test indentation 2011-03-22 11:59:29 -07:00
Tj Holowaychuk
9362c83a33 removed dead test 2011-03-22 11:59:05 -07:00
Tj Holowaychuk
0c38098f02 tweak req.flash() failure message 2011-03-21 16:13:40 -07:00
Pau Ramon
be7068f569 Better error output when using flash without session middleware. 2011-03-21 16:12:55 -07:00
Tj Holowaychuk
b122bf22e3 link typo 2011-03-21 12:06:36 -07:00
Tj Holowaychuk
b7232f38f3 Added res.send(bool) support
application/json
2011-03-21 10:34:04 -07:00
Tj Holowaychuk
c1b72ac1b7 Fixed stylus example for latest version 2011-03-21 08:45:43 -07:00
Roman Shtylman
b9e0d15878 check that this.params is valid before calling hasOwnProperty 2011-03-21 08:13:05 -07:00
Tj Holowaychuk
cf26cf7afc wrap try/catch around render() 2011-03-18 11:44:48 -07:00
Tj Holowaychuk
a75e60ae47 fixed docs due to markdown-js not supporting html 2011-03-18 09:18:29 -07:00
Tj Holowaychuk
187dc5dd03 connect 1.1.1 2011-03-18 08:49:59 -07:00
Tj Holowaychuk
9c9e2afade refactored res.redirect() 2011-03-18 08:49:46 -07:00
Tj Holowaychuk
fae1ba98c1 doc typo 2011-03-17 19:05:59 -07:00
Tj Holowaychuk
c3e632620a doc typo 2011-03-17 19:05:28 -07:00
Tj Holowaychuk
dd7158ac46 regenerated docs 2011-03-17 18:56:44 -07:00
Tj Holowaychuk
eefe51c7a7 removed manpages from make 2011-03-17 18:55:13 -07:00
Tj Holowaychuk
bf596dc023 Release 2.0.0 2011-03-17 18:06:30 -07:00
Tj Holowaychuk
220d88d654 Fixed up index view path alternative
previously was doing ../index, which was not intended
now doing ../VIEW/index
2011-03-17 15:36:44 -07:00
Tj Holowaychuk
a254e64bdb Changed; res.locals() without object returns the locals 2011-03-17 14:50:35 -07:00
Tj Holowaychuk
1555b92fb8 Release 2.0.0rc3 2011-03-17 13:01:59 -07:00
Tj Holowaychuk
d5b7a40b39 Fixed partials example 2011-03-17 13:00:43 -07:00
Tj Holowaychuk
bd1ab7ab96 pass the function 2011-03-17 12:48:06 -07:00
Tj Holowaychuk
2ff991bfcf refactored res.render() 2011-03-17 12:45:11 -07:00
Tj Holowaychuk
0b1378a539 Added res.locals(obj) 2011-03-17 12:13:59 -07:00
Tj Holowaychuk
723c908bd7 Added res.partial() callback support 2011-03-17 12:10:32 -07:00
Tj Holowaychuk
4874404701 typo 2011-03-17 11:33:35 -07:00
Tj Holowaychuk
4c1374840a Release 2.0.0rc2 2011-03-17 11:01:20 -07:00
Tj Holowaychuk
5da01633fd Fixed SlowBuffer support. Closes #584 2011-03-17 10:37:34 -07:00
Tj Holowaychuk
cdbd8af527 migration docs for partials 2011-03-17 09:31:32 -07:00
Tj Holowaychuk
a6fdc1bfd2 Fixed .filename view engine option [reported by drudge] 2011-03-16 16:58:02 -07:00
Tj Holowaychuk
20b8facb05 docs for partial changes 2011-03-16 15:53:27 -07:00
Tj Holowaychuk
909914f7af Changed; partial() "locals" are now optional
this means that:

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

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

    partial("user", new User("tj"));
2011-03-16 15:37:05 -07:00
Tj Holowaychuk
3f31ebc676 fixed express-contrib example reference 2011-03-15 15:23:28 -07:00
Tj Holowaychuk
f3c068a90c Merge branch 'integration' 2011-03-15 10:52:58 -07:00
Tj Holowaychuk
90d7e193d1 Refactored Server#use()
eventually we should just emit some events from connect
2011-03-15 10:52:53 -07:00
Tj Holowaychuk
c9f5bb6f17 added .app test for the mounted server as well 2011-03-15 10:39:59 -07:00
Ben Weaver
9865a4c4f2 Clean up patch to Server#use(), add test case for restoring res#app property. 2011-03-15 10:37:18 -07:00
Ben Weaver
f12baf32d4 Restore original res.app when out() is called. 2011-03-15 10:37:18 -07:00
Tj Holowaychuk
80f4d08e8b Release 2.0.0rc 2011-03-14 15:01:37 -07:00
Tj Holowaychuk
07c9cae923 Fixed; expose HTTPSServer constructor 2011-03-14 14:36:22 -07:00
Tj Holowaychuk
d867cc9271 Fixed express(1) default test charset. Cloeses #579 [reported by secoif] 2011-03-13 10:15:53 -07:00
Tj Holowaychuk
3a1fe1e295 Fixed; default charset to utf-8 instead of utf8 for lame IE [reported by NickP] 2011-03-11 16:34:41 -08:00
Tj Holowaychuk
3cacf050df Docs for staticProvider() changes 2011-03-11 14:11:45 -08:00
Tj Holowaychuk
1536d73d1b Release 2.0.0beta3 2011-03-09 15:45:55 -08:00
Tj Holowaychuk
55143a9d44 Updated connect submodule 2011-03-09 15:39:10 -08:00
Tj Holowaychuk
c92e193916 Fixed res.redirect(). RFC states absolute 2011-03-09 15:18:22 -08:00
Tj Holowaychuk
d12452fc49 docs for charset 2011-03-09 10:59:47 -08:00
Tj Holowaychuk
3d8400a40c docs for res.contentType() literal 2011-03-09 10:54:35 -08:00
Tj Holowaychuk
1fe0aea0b0 Added test for res.contentType() literal 2011-03-09 10:53:50 -08:00
Tj Holowaychuk
5df2544883 fixed a test 2011-03-09 10:46:59 -08:00
Tj Holowaychuk
acbf224277 default res.send() string charset to utf8 2011-03-09 10:42:14 -08:00
Tj Holowaychuk
d0d17a0d35 charset tests using ISO-8859-1 2011-03-09 10:33:28 -08:00
Tj Holowaychuk
f7b53d33bc more tests 2011-03-09 10:33:06 -08:00
Tj Holowaychuk
92be06874b more tests 2011-03-09 10:31:45 -08:00
Tj Holowaychuk
f327455d9d Added charset option for render() 2011-03-09 10:31:10 -08:00
Tj Holowaychuk
54415bf2af moved a test 2011-03-09 10:29:03 -08:00
Tj Holowaychuk
bac62dfcd9 added charset test with res.render() 2011-03-09 10:26:37 -08:00
Tj Holowaychuk
820b43c1f3 Added .charset + res.send() test 2011-03-09 10:23:05 -08:00
Tj Holowaychuk
a5b69290d5 fixed a test 2011-03-09 10:20:44 -08:00
Tj Holowaychuk
5c7a9c86f6 Updated connect submodule 2011-03-09 10:20:03 -08:00
Tj Holowaychuk
7f83f916f6 Updated jade submodule 2011-03-09 09:46:47 -08:00
Masahiro Hayashi
c15a949cc3 Updated express command
- Fixed a genereated test's bug
- Added a forgotten semicolon
2011-03-08 13:42:06 -08:00
Tj Holowaychuk
ce47f96570 dont change NODE_ENV in tests 2011-03-08 13:39:30 -08:00
Tj Holowaychuk
57b035cd94 hinting at fully resolved paths 2011-03-08 12:58:16 -08:00
Tj Holowaychuk
f1f126171c Added view resolution hints when in development 2011-03-08 12:51:40 -08:00
Tj Holowaychuk
71e1bcd855 fixed a test 2011-03-08 12:02:35 -08:00
Tj Holowaychuk
b5579b6307 Added layout lookup support relative to the page view
for example if you render ./views/forum/thread and
./views/forum/thread/layout.jade exists, it will be used,
falling back on ./views/layout.jade.
2011-03-08 12:01:45 -08:00
Tj Holowaychuk
110b0fe14a misc refactor 2011-03-08 11:47:03 -08:00
Tj Holowaychuk
d7488bbb62 Removed Partial constructor (not used) 2011-03-08 11:42:03 -08:00
Tj Holowaychuk
2007407e7b docs 2011-03-08 09:56:24 -08:00
Tj Holowaychuk
d152e7e780 Added res.render() status support back
useful for error templates that display the status _and_
set the status code
2011-03-08 09:56:05 -08:00
73 changed files with 2498 additions and 3673 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,121 @@
2.3.1 / 2011-04-26
==================
* Added `app.match()` as `app.match.all()`
* Added `app.lookup()` as `app.lookup.all()`
* Added `app.remove()` for `app.remove.all()`
* Added `app.remove.VERB()`
* Fixed template caching collision issue. Closes #644
* Moved router over from connect and started refactor
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.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
==================
* Added `<root>/_?<name>` partial lookup support. Closes #447
* Added `request`, `response`, and `app` local variables
* Added `settings` local variable, containing the app's settings
* Added `req.flash()` exception if `req.session` is not available
* Added `res.send(bool)` support (json response)
* Fixed stylus example for latest version
* Fixed; wrap try/catch around `res.render()`
2.0.0 / 2011-03-17
==================
* Fixed up index view path alternative.
* Changed; `res.locals()` without object returns the locals
2.0.0rc3 / 2011-03-17
==================
* Added `res.locals(obj)` to compliment `res.local(key, val)`
* Added `res.partial()` callback support
* Fixed recursive error reporting issue in `res.render()`
2.0.0rc2 / 2011-03-17
==================
* Changed; `partial()` "locals" are now optional
* Fixed `SlowBuffer` support. Closes #584 [reported by tyrda01]
* Fixed .filename view engine option [reported by drudge]
* Fixed blog example
* Fixed `{req,res}.app` reference when mounting [Ben Weaver]
2.0.0rc / 2011-03-14
==================
* Fixed; expose `HTTPSServer` constructor
* Fixed express(1) default test charset. Closes #579 [reported by secoif]
* Fixed; default charset to utf-8 instead of utf8 for lame IE [reported by NickP]
2.0.0beta3 / 2011-03-09
==================
* Added support for `res.contentType()` literal
The original `res.contentType('.json')`,
`res.contentType('application/json')`, and `res.contentType('json')`
will work now.
* Added `res.render()` status option support back
* Added charset option for `res.render()`
* Added `.charset` support (via connect 1.0.4)
* Added view resolution hints when in development and a lookup fails
* Added layout lookup support relative to the page view.
For example while rendering `./views/user/index.jade` if you create
`./views/user/layout.jade` it will be used in favour of the root layout.
* Fixed `res.redirect()`. RFC states absolute url [reported by unlink]
* Fixed; default `res.send()` string charset to utf8
* Removed `Partial` constructor (not currently used)
2.0.0beta2 / 2011-03-07
==================

View File

@@ -1,13 +1,5 @@
DOCS = docs/index.md \
docs/screencasts.md \
docs/executable.md \
docs/contrib.md \
docs/guide.md \
docs/migrate.md \
docs/applications.md
MANPAGES =$(DOCS:.md=.1)
DOCS = $(shell find docs/*.md)
HTMLDOCS =$(DOCS:.md=.html)
test:
@@ -24,19 +16,14 @@ test:
test-cov:
@TESTFLAGS=--cov $(MAKE) test
docs: $(MANPAGES) $(HTMLDOCS)
docs: $(HTMLDOCS)
@ echo "... generating TOC"
@./support/toc.js docs/guide.html
%.1: %.md
@echo "... $< -> $@"
@ronn -r --pipe $< > $@
%.html: %.md
@echo "... $< -> $@"
@ronn -5 --pipe --fragment $< \
@markdown $< \
| cat docs/layout/head.html - docs/layout/foot.html \
| sed 's/NAME/Express/g' \
> $@
site:

View File

@@ -54,7 +54,8 @@ The following are the major contributors of Express (in no specific order).
## More Information
* [express-configure](http://github.com/visionmedia/express-configure) async configuration support
* [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
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates

View File

@@ -11,7 +11,7 @@ var fs = require('fs')
* Framework version.
*/
var version = '2.0.0beta2';
var version = '2.3.0';
/**
* Add session support.
@@ -36,14 +36,15 @@ var templateEngine = 'jade';
*/
var usage = ''
+ '\x1b[1mUsage\x1b[0m: 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'
+ ' Usage: express [options] [path]\n'
+ '\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'
;
/**
@@ -220,7 +221,7 @@ var app = [
, ''
, 'if (!module.parent) {'
, ' app.listen(3000);'
, ' console.log("Express server listening on port %d", app.address().port)'
, ' console.log("Express server listening on port %d", app.address().port);'
, '}'
, ''
].join('\n');

View File

@@ -1,70 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "APPLICATIONS" "" "March 2011" "" ""
.
.SH "NAME"
\fBapplications\fR
.
.P
Learnboost \fIhttp://learnboost\.com\fR is a free online gradebook application, aimed to crush the competition with innovative, realtime, enjoyable features\.
.
.P
\fIhttp://learnboost\.com\fR
.
.P
Storify \fIhttp://storify\.com\fR lets you turn what people post on social media websites into compelling stories\.
.
.P
\fIhttp://storify\.com\fR
.
.P
Pakistan Survey \fIhttp://pakistansurvey\.org/\fR by Development Seed \fIhttp://developmentseed\.org\fR, provides in\-depth agency\-specific analysis from regional experts with data from 1,000 interviews across 120 villages in all seven tribal agencies and mapping of 142 reported drone strikes in FATA through July 2010\.
.
.P
\fIhttp://pakistansurvey\.org\fR
.
.P
Markup\.IO \fIhttp://markup\.io\fR allows you to draw directly on \fIany\fR website, then share with others to share your thoughts\.
.
.P
\fIhttp://markup\.io\fR
.
.P
Scrabb\.ly \fIhttp://scrabb\.ly\fR is a massively multiplayer scrabble game initially created for the Node Knockout \fIhttp://nodeknockout\.com/\fR competition\.
.
.P
\fIhttp://scrabb\.ly\fR
.
.P
ClickDummy \fIhttp://clickdummy\.net/\fR is a rapid mockup prototyping application for designers and dummies\.
.
.P
\fIhttp://clickdummy\.net\fR
.
.P
Node Knockout \fIhttp://nodeknockout\.com\fR organized the first ever node\-specific competition with hundreds of contestants\.
.
.P
\fIhttp://nodeknockout\.com\fR
.
.P
Widescript \fIhttp://widescript\.com\fR is an innovative app that helps you focus and interact with your texts \- on your desktop, your couch or on the go\.
.
.P
\fIhttp://widescript\.com\fR
.
.P
e\-resistable \fIhttp://www\.e\-resistible\.co\.uk/\fR is an online order takeaway system providing an intuitive way to fill your belly from your computer!
.
.P
\fIhttp://www\.e\-resistible\.co\.uk\fR
.
.P
Top Twitter Trends \fIhttp://toptwittertrends\.com\fR utilizes MongoDB, Socket\.IO, jQuery and many other exciting libraries to bring you trending tweets in realtime\.
.
.P
\fIhttp://toptwittertrends\.com\fR
.
.P
The applications shown above are not listed in any specific order\. To have an application added or removed please contact TJ Holowaychuk \fIhttp://github\.com/visionmedia\fR\.

View File

@@ -190,11 +190,6 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>applications</code>
</p>
<br />
@@ -228,7 +223,7 @@
<p><a href="http://nodeknockout.com"><img src="images/apps/nodeko.png" alt="Node Knockout Competition Express" /></a></p>
<p><a href="http://widescript.com">Widescript</a> is an innovative app that helps you focus and interact with your texts - on your desktop, your couch or on the go.</p>
<p><a href="http://widescript.com">Widescript</a> is an innovative app that helps you focus and interact with your texts &ndash; on your desktop, your couch or on the go.</p>
<p><a href="http://widescript.com"><img src="images/apps/widescript.png" alt="Widescript" /></a></p>
@@ -244,9 +239,7 @@
<p>The applications shown above are not listed in any specific order. To have an application added or removed please contact <a href="http://github.com/visionmedia">TJ Holowaychuk</a>.</p>
</div>
</div>
</div>
</body>
</html>
</html>

View File

@@ -1,82 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "CONTRIB" "" "March 2011" "" ""
.
.SH "NAME"
\fBcontrib\fR
.
.SS "Development Dependencies"
Express development dependencies are stored within the \fI\./support\fR directory\. To update them execute:
.
.IP "" 4
.
.nf
$ git submodule update \-\-init
.
.fi
.
.IP "" 0
.
.SS "Running Tests"
Express uses the Expresso \fIhttp://github\.com/visionmedia/expresso\fR TDD framework to write and run elegant test suites extremely fast\. To run all test suites simply execute:
.
.IP "" 4
.
.nf
$ make test
.
.fi
.
.IP "" 0
.
.P
To target specific suites we may specify the files via:
.
.IP "" 4
.
.nf
$ make test TESTS=test/view\.test\.js
.
.fi
.
.IP "" 0
.
.P
To check test coverage run:
.
.IP "" 4
.
.nf
$ make test\-cov
.
.fi
.
.IP "" 0
.
.SS "Contributions"
To accept a contribution, you should follow these guidelines:
.
.IP "\(bu" 4
All tests \fImust\fR pass
.
.IP "\(bu" 4
Your alterations or additions \fImust\fR include tests
.
.IP "\(bu" 4
Your commit(s) should be \fIfocused\fR, do not commit once for several changes
.
.IP "\(bu" 4
Do \fInot\fR alter release information such as the \fIversion\fR, or \fIHistory\.md\fR
.
.IP "\(bu" 4
Indents are \fI2\fR spaces\.
.
.IP "" 0
.
.SS "Documentation"
To contribute documentation edit the markdown files in \fI\./docs\fR, however do \fInot\fR run \fImake docs\fR, as they will be re\-built and published with each release\.

View File

@@ -190,12 +190,7 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>contrib</code>
</p>
<h3 id="Development-Dependencies">Development Dependencies</h3>
<h3>Development Dependencies</h3>
<p>Express development dependencies are stored within the <em>./support</em> directory. To
update them execute:</p>
@@ -203,7 +198,7 @@ update them execute:</p>
<pre><code>$ git submodule update --init
</code></pre>
<h3 id="Running-Tests">Running Tests</h3>
<h3>Running Tests</h3>
<p>Express uses the <a href="http://github.com/visionmedia/expresso">Expresso</a> TDD
framework to write and run elegant test suites extremely fast. To run all test suites
@@ -222,7 +217,7 @@ simply execute:</p>
<pre><code>$ make test-cov
</code></pre>
<h3 id="Contributions">Contributions</h3>
<h3>Contributions</h3>
<p>To accept a contribution, you should follow these guidelines:</p>
@@ -235,13 +230,11 @@ simply execute:</p>
</ul>
<h3 id="Documentation">Documentation</h3>
<h3>Documentation</h3>
<p>To contribute documentation edit the markdown files in <em>./docs</em>, however
do <em>not</em> run <em>make docs</em>, as they will be re-built and published with each release.</p>
</div>
</div>
</div>
</body>
</html>
</html>

View File

@@ -1,31 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "EXECUTABLE" "" "March 2011" "" ""
.
.SH "NAME"
\fBexecutable\fR
.
.SH "Synopsis"
.
.nf
express [options] [PATH]
.
.fi
.
.SH "Description"
The \fIexpress\fR executable generates apps at the given \fBPATH\fR or the current working directory\. Although Express is not bound to a specific application structure, this executable creates a maintainable base app\.
.
.SH "Options"
.
.nf
\-s, \-\-sessions Add session support
\-t, \-\-template ENGINE Add template ENGINE support (jade|ejs)\. Defaults to jade
\-c, \-\-css ENGINE Add stylesheet ENGINE support (less|sass|stylus)\. Defaults to plain css
\-v, \-\-version Output framework version
\-h, \-\-help Output help information
.
.fi

View File

@@ -190,23 +190,18 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>executable</code>
</p>
<h2 id="Synopsis">Synopsis</h2>
<h2>Synopsis</h2>
<pre><code>express [options] [PATH]
</code></pre>
<h2 id="Description">Description</h2>
<h2>Description</h2>
<p>The <em>express</em> executable generates apps at the given <strong>PATH</strong> or the
current working directory. Although Express is not bound to a specific
application structure, this executable creates a maintainable base app.</p>
<h2 id="Options">Options</h2>
<h2>Options</h2>
<pre><code> -s, --sessions Add session support
-t, --template ENGINE Add template ENGINE support (jade|ejs). Defaults to jade
@@ -214,9 +209,7 @@ application structure, this executable creates a maintainable base app.</p>
-v, --version Output framework version
-h, --help Output help information
</code></pre>
</div>
</div>
</div>
</body>
</html>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -179,60 +179,64 @@
</a>
<div id="wrapper">
<div id="container"><ul id="toc">
<li><a href="#Installation">Installation</a></li>
<li><a href="#Creating-A-Server">Creating A Server</a></li>
<li><a href="#Creating-An-HTTPS-Server">Creating An HTTPS Server</a></li>
<li><a href="#Configuration">Configuration</a></li>
<li><a href="#Settings">Settings</a></li>
<li><a href="#Routing">Routing</a></li>
<li><a href="#Passing-Route-Control">Passing Route Control</a></li>
<li><a href="#Middleware">Middleware</a></li>
<li><a href="#Route-Middleware">Route Middleware</a></li>
<li><a href="#HTTP-Methods">HTTP Methods</a></li>
<li><a href="#Error-Handling">Error Handling</a></li>
<li><a href="#Route-Param-Pre-conditions">Route Param Pre-conditions</a></li>
<li><a href="#View-Rendering">View Rendering</a></li>
<li><a href="#View-Partials">View Partials</a></li>
<li><a href="#View-Lookup">View Lookup</a></li>
<li><a href="#Template-Engines">Template Engines</a></li>
<li><a href="#Session-Support">Session Support</a></li>
<li><a href="#Migration-Guide">Migration Guide</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#creating-a server">Creating A Server</a></li>
<li><a href="#creating-an https server">Creating An HTTPS Server</a></li>
<li><a href="#configuration">Configuration</a></li>
<li><a href="#settings">Settings</a></li>
<li><a href="#routing">Routing</a></li>
<li><a href="#passing-route control">Passing Route Control</a></li>
<li><a href="#middleware">Middleware</a></li>
<li><a href="#route-middleware">Route Middleware</a></li>
<li><a href="#http-methods">HTTP Methods</a></li>
<li><a href="#error-handling">Error Handling</a></li>
<li><a href="#route-param pre-conditions">Route Param Pre-conditions</a></li>
<li><a href="#view-rendering">View Rendering</a></li>
<li><a href="#view-partials">View Partials</a></li>
<li><a href="#view-lookup">View Lookup</a></li>
<li><a href="#template-engines">Template Engines</a></li>
<li><a href="#session-support">Session Support</a></li>
<li><a href="#migration-guide">Migration Guide</a></li>
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Request</a><ul class="section" id="section-Request">
<li><a href="#req-header-key-defaultValue-">header()</a></li>
<li><a href="#req-accepts-type-">accepts()</a></li>
<li><a href="#req-is-type-">is()</a></li>
<li><a href="#req-param-name-default-">param()</a></li>
<li><a href="#req-flash-type-msg-">flash()</a></li>
<li><a href="#req-isXMLHttpRequest">isXMLHttpRequest</a></li>
<li><a href="#req.header()">header()</a></li>
<li><a href="#req.accepts()">accepts()</a></li>
<li><a href="#req.is()">is()</a></li>
<li><a href="#req.param()">param()</a></li>
<li><a href="#req.flash()">flash()</a></li>
<li><a href="#req.isxmlhttprequest">isXMLHttpRequest</a></li>
</ul></li>
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Response</a><ul class="section" id="section-Response">
<li><a href="#res-header-key-val-">header()</a></li>
<li><a href="#res-contentType-type-">contentType()</a></li>
<li><a href="#res-attachment-filename-">attachment()</a></li>
<li><a href="#res-sendfile-path-options-callback-">sendfile()</a></li>
<li><a href="#res-download-file-filename-callback-">download()</a></li>
<li><a href="#res-send-body-status-headers-status-status-">send()</a></li>
<li><a href="#res-redirect-url-status-">redirect()</a></li>
<li><a href="#res-cookie-name-val-options-">cookie()</a></li>
<li><a href="#res-clearCookie-name-">clearCookie()</a></li>
<li><a href="#res-render-view-options-fn-">render()</a></li>
<li><a href="#res-partial-view-options-">partial()</a></li>
<li><a href="#res-local-name-val-">local()</a></li>
<li><a href="#res.header()">header()</a></li>
<li><a href="#res.charset">charset</a></li>
<li><a href="#res.contenttype()">contentType()</a></li>
<li><a href="#res.attachment()">attachment()</a></li>
<li><a href="#res.sendfile()">sendfile()</a></li>
<li><a href="#res.download()">download()</a></li>
<li><a href="#res.send()">send()</a></li>
<li><a href="#res.redirect()">redirect()</a></li>
<li><a href="#res.cookie()">cookie()</a></li>
<li><a href="#res.clearcookie()">clearCookie()</a></li>
<li><a href="#res.render()">render()</a></li>
<li><a href="#res.partial()">partial()</a></li>
<li><a href="#res.local()">local()</a></li>
<li><a href="#res.locals()">locals()</a></li>
</ul></li>
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Server</a><ul class="section" id="section-Server">
<li><a href="#app-set-name-val-">set()</a></li>
<li><a href="#app-enable-name-">enable()</a></li>
<li><a href="#app-enabled-name-">enabled()</a></li>
<li><a href="#app-disable-name-">disable()</a></li>
<li><a href="#app-disabled-name-">disabled()</a></li>
<li><a href="#app-configure-env-function-function-">configure()</a></li>
<li><a href="#app-redirect-name-val-">redirect()</a></li>
<li><a href="#app-error-function-">error()</a></li>
<li><a href="#app-helpers-obj-">helpers()</a></li>
<li><a href="#app-dynamicHelpers-obj-">dynamicHelpers()</a></li>
<li><a href="#app-mounted-fn-">mounted()</a></li>
<li><a href="#app-register-ext-exports-">register()</a></li>
<li><a href="#app-listen-port-host-">listen()</a></li>
<li><a href="#app.set()">set()</a></li>
<li><a href="#app.enable()">enable()</a></li>
<li><a href="#app.enabled()">enabled()</a></li>
<li><a href="#app.disable()">disable()</a></li>
<li><a href="#app.disabled()">disabled()</a></li>
<li><a href="#app.configure()">configure()</a></li>
<li><a href="#app.redirect()">redirect()</a></li>
<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>
</ul></li>
</ul>
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
@@ -246,17 +250,12 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>guide</code>
</p>
<h3 id="Installation">Installation</h3>
<h3 id="installation">Installation</h3>
<pre><code>$ npm install express
</code></pre>
<h3 id="Creating-A-Server">Creating A Server</h3>
<h3 id="creating-a server">Creating A Server</h3>
<p> To create an instance of the <em>express.HTTPServer</em>, simply invoke the <em>createServer()</em> method. With our instance <em>app</em> we can then define routes based on the HTTP verbs, in this example <em>app.get()</em>.</p>
@@ -269,15 +268,15 @@ app.get('/', function(req, res){
app.listen(3000);
</code></pre>
<h3 id="Creating-An-HTTPS-Server">Creating An HTTPS Server</h3>
<h3 id="creating-an https server">Creating An HTTPS Server</h3>
<p> To initialize a <em>express.HTTPSServer</em> we do the same as above, however we
pass an options object, accepting <em>key</em>, <em>cert</em> and the others mentioned in node's <a href="http://nodejs.org/docs/v0.3.7/api/https.html#https.createServer">https documentation</a>.</p>
pass an options object, accepting <em>key</em>, <em>cert</em> and the others mentioned in node&rsquo;s <a href="http://nodejs.org/docs/v0.3.7/api/https.html#https.createServer">https documentation</a>.</p>
<pre><code> var app = require('express').createServer({ key: ... });
</code></pre>
<h3 id="Configuration">Configuration</h3>
<h3 id="configuration">Configuration</h3>
<p>Express supports arbitrary environments, such as <em>production</em> and <em>development</em>. Developers
can use the <em>configure()</em> method to setup needs required by the current environment. When
@@ -302,7 +301,7 @@ app.configure('development', function(){
app.configure('production', function(){
var oneYear = 31557600000;
app.use(express.static({ root: __dirname + '/public', maxAge: oneYear }));
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
app.use(express.errorHandler());
});
</code></pre>
@@ -332,7 +331,7 @@ app.configure('production', function(){
<p>This is <em>very</em> important, as many caching mechanisms are <em>only enabled</em> when in production.</p>
<h3 id="Settings">Settings</h3>
<h3 id="settings">Settings</h3>
<p>Express supports the following settings out of the box:</p>
@@ -344,10 +343,10 @@ app.configure('production', function(){
</ul>
<h3 id="Routing">Routing</h3>
<h3 id="routing">Routing</h3>
<p>Express utilizes the HTTP verbs to provide a meaningful, expressive routing API.
For example we may want to render a user's account for the path <em>/user/12</em>, this
For example we may want to render a user&rsquo;s account for the path <em>/user/12</em>, this
can be done by defining the route below. The values associated to the named placeholders
are available as <code>req.params</code>.</p>
@@ -363,7 +362,7 @@ when <em>/user/:id</em> is compiled, a simplified version of the regexp may look
</code></pre>
<p>Regular expression literals may also be passed for complex uses. Since capture
groups with literal <em>RegExp</em>'s are anonymous we can access them directly <code>req.params</code>. So our first capture group would be <em>req.params[0]</em> and the second would follow as <em>req.params[1]</em>.</p>
groups with literal <em>RegExp</em>&rsquo;s are anonymous we can access them directly <code>req.params</code>. So our first capture group would be <em>req.params[0]</em> and the second would follow as <em>req.params[1]</em>.</p>
<pre><code>app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function(req, res){
res.send(req.params);
@@ -432,9 +431,9 @@ app.post('/', function(req, res){
app.listen(3000);
</code></pre>
<p>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 <em>'/user/:id(\d+)'</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
<p>Typically we may use a &ldquo;dumb&rdquo; placeholder such as &ldquo;/user/:id&rdquo; which has no restrictions, however say for example we are limiting a user id to digits, we may use <em>&lsquo;/user/:id(\d+)&rsquo;</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
<h3 id="Passing-Route-Control">Passing Route Control</h3>
<h3 id="passing-route control">Passing Route Control</h3>
<p>We may pass control to the next <em>matching</em> route, by calling the <em>third</em> argument,
the <em>next()</em> function. When a match cannot be made, control is passed back to Connect,
@@ -489,7 +488,7 @@ app.get('*', function(req, res){
app.listen(3000);
</code></pre>
<h3 id="Middleware">Middleware</h3>
<h3 id="middleware">Middleware</h3>
<p>Middleware via <a href="http://github.com/senchalabs/connect">Connect</a> can be
passed to <em>express.createServer()</em> as you would with a regular Connect server. For example:</p>
@@ -507,7 +506,7 @@ var app = express.createServer(
<pre><code>app.use(express.logger({ format: ':method :uri' }));
</code></pre>
<p>Typically with connect middleware you would <em>require('connect')</em> like so:</p>
<p>Typically with connect middleware you would <em>require(&lsquo;connect&rsquo;)</em> like so:</p>
<pre><code>var connect = require('connect');
app.use(connect.logger());
@@ -520,7 +519,7 @@ app.use(connect.bodyParser());
app.use(express.bodyParser());
</code></pre>
<h3 id="Route-Middleware">Route Middleware</h3>
<h3 id="route-middleware">Route Middleware</h3>
<p>Routes may utilize route-specific middleware by passing one or more additional callbacks (or arrays) to the method. This feature is extremely useful for restricting access, loading data used by the route etc.</p>
@@ -580,7 +579,7 @@ app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
});
</code></pre>
<p>Commonly used "stacks" of middleware can be passed as an array (<em>applied recursively</em>), which can be mixed and matched to any degree.</p>
<p>Commonly used &ldquo;stacks&rdquo; of middleware can be passed as an array (<em>applied recursively</em>), which can be mixed and matched to any degree.</p>
<pre><code>var a = [middleware1, middleware2]
, b = [middleware3, middleware4]
@@ -596,11 +595,11 @@ app.get('/', all, function(){});
<p>For this example in full, view the <a href="http://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js">route middleware example</a> in the repository.</p>
<h3 id="HTTP-Methods">HTTP Methods</h3>
<h3 id="http-methods">HTTP Methods</h3>
<p>We have seen <em>app.get()</em> a few times, however Express also exposes other familiar HTTP verbs in the same manor, such as <em>app.post()</em>, <em>app.del()</em>, etc.</p>
<p> A common example for <em>POST</em> usage, is when "submitting" a form. Below we simply set our form method to "post" in our html, and control will be given to the route we have defined below it.</p>
<p> A common example for <em>POST</em> usage, is when &ldquo;submitting&rdquo; a form. Below we simply set our form method to &ldquo;post&rdquo; in our html, and control will be given to the route we have defined below it.</p>
<pre><code> &lt;form method="post" action="/"&gt;
&lt;input type="text" name="user[name]" /&gt;
@@ -609,7 +608,7 @@ app.get('/', all, function(){});
&lt;/form&gt;
</code></pre>
<p>By default Express does not know what to do with this request body, so we should add the <em>bodyParser</em> middleware, which will parse <em>application/x-www-form-urlencoded</em> and <em>application/json</em> request bodies and place the variables in <em>req.body</em>. We can do this by "using" the middleware as shown below:</p>
<p>By default Express does not know what to do with this request body, so we should add the <em>bodyParser</em> middleware, which will parse <em>application/x-www-form-urlencoded</em> and <em>application/json</em> request bodies and place the variables in <em>req.body</em>. We can do this by &ldquo;using&rdquo; the middleware as shown below:</p>
<pre><code>app.use(express.bodyParser());
</code></pre>
@@ -622,7 +621,7 @@ app.get('/', all, function(){});
});
</code></pre>
<p>When using methods such as <em>PUT</em> with a form, we can utilize a hidden input named <em>_method</em>, which can be used to alter the HTTP method. To do so we first need the <em>methodOverride</em> middleware, which should be placed below <em>bodyParser</em> so that it can utilize it's <em>req.body</em> containing the form values.</p>
<p>When using methods such as <em>PUT</em> with a form, we can utilize a hidden input named <em>_method</em>, which can be used to alter the HTTP method. To do so we first need the <em>methodOverride</em> middleware, which should be placed below <em>bodyParser</em> so that it can utilize it&rsquo;s <em>req.body</em> containing the form values.</p>
<pre><code>app.use(express.bodyParser());
app.use(express.methodOverride());
@@ -643,7 +642,7 @@ app.put('/', function(){
});
</code></pre>
<h3 id="Error-Handling">Error Handling</h3>
<h3 id="error-handling">Error Handling</h3>
<p>Express provides the <em>app.error()</em> method which receives exceptions thrown within a route,
or passed to <em>next(err)</em>. Below is an example which serves different pages based on our
@@ -655,7 +654,7 @@ ad-hoc <em>NotFound</em> exception:</p>
Error.captureStackTrace(this, arguments.callee);
}
NotFound.protoype.__proto__ = Error.prototype;
NotFound.prototype.__proto__ = Error.prototype;
app.get('/404', function(req, res){
throw new NotFound;
@@ -685,7 +684,7 @@ handle exceptions in different ways based on the environment.</p>
</code></pre>
<p>Here we assume all errors as 500 for the simplicity of
this demo, however you can choose whatever you like. For example when node performs filesystem syscalls, you may receive an error object with the <em>error.code</em> of <em>ENOENT</em>, meaning "no such file or directory", we can utilize this in our error handling and display a page specific to this if desired.</p>
this demo, however you can choose whatever you like. For example when node performs filesystem syscalls, you may receive an error object with the <em>error.code</em> of <em>ENOENT</em>, meaning &ldquo;no such file or directory&rdquo;, we can utilize this in our error handling and display a page specific to this if desired.</p>
<pre><code>app.error(function(err, req, res){
res.render('500.jade', {
@@ -696,7 +695,7 @@ this demo, however you can choose whatever you like. For example when node perfo
<p>Our apps could also utilize the Connect <em>errorHandler</em> middleware
to report on exceptions. For example if we wish to output exceptions
in "development" mode to <em>stderr</em> we can use:</p>
in &ldquo;development&rdquo; mode to <em>stderr</em> we can use:</p>
<pre><code>app.use(express.errorHandler({ dumpExceptions: true }));
</code></pre>
@@ -710,7 +709,7 @@ that are passed or thrown, so we can set <em>showStack</em> to true:</p>
<p>The <em>errorHandler</em> middleware also responds with <em>json</em> if <em>Accept: application/json</em>
is present, which is useful for developing apps that rely heavily on client-side JavaScript.</p>
<h3 id="Route-Param-Pre-conditions">Route Param Pre-conditions</h3>
<h3 id="route-param pre-conditions">Route Param Pre-conditions</h3>
<p>Route param pre-conditions can drastically improve the readability of your application, through implicit loading of data, and validation of request urls. For example if you are constantly fetching common data for several routes, such as loading a user for <em>/user/:id</em>, we might typically do something like below:</p>
@@ -751,12 +750,12 @@ is present, which is useful for developing apps that rely heavily on client-side
<pre><code>app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
</code></pre>
<h3 id="View-Rendering">View Rendering</h3>
<h3 id="view-rendering">View Rendering</h3>
<p>View filenames take the form "&lt;name&gt;.&lt;engine&gt;", where &lt;engine&gt; is the name
<p>View filenames take the form &ldquo;&lt;name&gt;.&lt;engine&gt;&rdquo;, where &lt;engine&gt; is the name
of the module that will be required. For example the view <em>layout.ejs</em> will
tell the view system to <em>require('ejs')</em>, the module being loaded must export the method <em>exports.compile(str, options)</em>, and return a <em>Function</em> to comply with Express. To alter this behaviour
<em>app.register()</em> can be used to map engines to file extensions, so that for example "foo.html" can be rendered by ejs.</p>
tell the view system to <em>require(&lsquo;ejs&rsquo;)</em>, the module being loaded must export the method <em>exports.compile(str, options)</em>, and return a <em>Function</em> to comply with Express. To alter this behaviour
<em>app.register()</em> can be used to map engines to file extensions, so that for example &ldquo;foo.html&rdquo; can be rendered by ejs.</p>
<p>Below is an example using <a href="http://github.com/visionmedia/jade">Jade</a> to render <em>index.html</em>,
and since we do not use <em>layout: false</em> the rendered contents of <em>index.jade</em> will be passed as
@@ -824,20 +823,20 @@ mix and match template engines:</p>
});
</code></pre>
<h3 id="View-Partials">View Partials</h3>
<h3 id="view-partials">View Partials</h3>
<p>The Express view system has built-in support for partials and collections, which are "mini" views representing a document fragment. For example rather than iterating
in a view to display comments, we would use a partial with collection support:</p>
<p>The Express view system has built-in support for partials and collections, which are &ldquo;mini&rdquo; views representing a document fragment. For example rather than iterating
in a view to display comments, we could use partial collection:</p>
<pre><code>partial('comment', { collection: comments });
</code></pre>
<p>If no other options are desired, we can omit the object and simply pass our array, which is equivalent to above:</p>
<p>If no other options or local variables are desired, we can omit the object and simply pass our array, which is equivalent to above:</p>
<pre><code>partial('comment', comments);
</code></pre>
<p>When using the partial collection support a few "magic" variables are provided
<p>When using the partial collection support a few &ldquo;magic&rdquo; locals are provided
for free:</p>
<ul>
@@ -848,25 +847,26 @@ for free:</p>
</ul>
<p>Local variables passed (or generated) take precedence, however locals passed to the parent view are available in the child view as well. So for example if we were to render a blog post with <em>partial('blog/post', post)</em> it would generate the <em>post</em> local, but the view calling this function had the local <em>user</em>, it would be available to the <em>blog/post</em> view as well.</p>
<p>Local variables passed (or generated) take precedence, however locals passed to the parent view are available in the child view as well. So for example if we were to render a blog post with <em>partial(&lsquo;blog/post&rsquo;, post)</em> it would generate the <em>post</em> local, but the view calling this function had the local <em>user</em>, it would be available to the <em>blog/post</em> view as well.</p>
<p>For documentation on altering the object name view <a href="http://expressjs.com/guide.html#res-partial-view-options-">res.partial()</a>.</p>
<p><strong>NOTE:</strong> be careful about when you use partial collections, as rendering an array with a length of 100 means we have to render 100 views. For simple collections you may inline the iteration instead of using partial collection support to decrease overhead.</p>
<h3 id="View-Lookup">View Lookup</h3>
<h3 id="view-lookup">View Lookup</h3>
<p>View lookup is performed relative to the parent view, for example if we had a page view named <em>views/user/list.jade</em>, and within that view we did <em>partial('edit')</em> it would attempt to load <em>views/user/edit.jade</em>, whereas <em>partial('../messages')</em> would load <em>views/messages.jade</em>.</p>
<p>View lookup is performed relative to the parent view, for example if we had a page view named <em>views/user/list.jade</em>, and within that view we did <em>partial(&lsquo;edit&rsquo;)</em> it would attempt to load <em>views/user/edit.jade</em>, whereas <em>partial(&lsquo;../messages&rsquo;)</em> would load <em>views/messages.jade</em>.</p>
<p>The view system also allows for index templates, allowing you to have a directory of the same name. For example within a route we may have <em>res.render('users')</em> either <em>views/users.jade</em>, or <em>views/users/index.jade</em>.</p>
<p>The view system also allows for index templates, allowing you to have a directory of the same name. For example within a route we may have <em>res.render(&lsquo;users&rsquo;)</em> either <em>views/users.jade</em>, or <em>views/users/index.jade</em>.</p>
<p>When utilizing index views as shown above, we may reference <em>views/users/index.jade</em> from a view in the same directory by <em>partial('users')</em>, and the view system will try <em>../users/index</em>, preventing us from needing to call <em>partial('index')</em>.</p>
<p>When utilizing index views as shown above, we may reference <em>views/users/index.jade</em> from a view in the same directory by <em>partial(&lsquo;users&rsquo;)</em>, and the view system will try <em>../users/index</em>, preventing us from needing to call <em>partial(&lsquo;index&rsquo;)</em>.</p>
<h3 id="Template-Engines">Template Engines</h3>
<h3 id="template-engines">Template Engines</h3>
<p>Below are a few template engines commonly used with Express:</p>
<ul>
<li><a href="http://github.com/visionmedia/haml.js">Haml</a> haml implementation</li>
<li><a href="http://jade-lang.com">Jade</a> haml.js successor</li>
<li><a href="http://github.com/visionmedia/ejs">EJS</a> Embedded JavaScript</li>
<li><a href="http://github.com/mauricemach/coffeekup">CoffeeKup</a> CoffeeScript based templating</li>
@@ -874,9 +874,9 @@ for free:</p>
</ul>
<h3 id="Session-Support">Session Support</h3>
<h3 id="session-support">Session Support</h3>
<p>Sessions support can be added by using Connect's <em>session</em> middleware. To do so we also need the <em>cookieParser</em> middleware place above it, which will parse and populate cookie data to <em>req.cookies</em>.</p>
<p>Sessions support can be added by using Connect&rsquo;s <em>session</em> middleware. To do so we also need the <em>cookieParser</em> middleware place above it, which will parse and populate cookie data to <em>req.cookies</em>.</p>
<pre><code>app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat" }));
@@ -917,11 +917,11 @@ app.get('/add-to-cart', function(req, res){
<p>The <em>req.session</em> object also has methods such as <em>Session#touch()</em>, <em>Session#destroy()</em>, <em>Session#regenerate()</em> among others to maintain and manipulate sessions. For more information view the <a href="http://senchalabs.github.com/connect/middleware-session.html">Connect Session</a> documentation.</p>
<h3 id="Migration-Guide">Migration Guide</h3>
<h3 id="migration-guide">Migration Guide</h3>
<p> Express 1.x developers may reference the <a href="migrate.html">Migration Guide</a> to get up to speed on how to upgrade your application to work with Express 2.x, Connect 1.x, and Node 0.4.x.</p>
<h3 id="req-header-key-defaultValue-">req.header(key[, defaultValue])</h3>
<h3 id="req.header()">req.header(key[, defaultValue])</h3>
<p>Get the case-insensitive request header <em>key</em>, with optional <em>defaultValue</em>:</p>
@@ -941,14 +941,14 @@ req.header('Referrer');
// =&gt; "http://google.com"
</code></pre>
<h3 id="req-accepts-type-">req.accepts(type)</h3>
<h3 id="req.accepts()">req.accepts(type)</h3>
<p>Check if the <em>Accept</em> header is present, and includes the given <em>type</em>.</p>
<p>When the <em>Accept</em> header is not present <em>true</em> is returned. Otherwise
the given <em>type</em> is matched by an exact match, and then subtypes. You
may pass the subtype such as "html" which is then converted internally
to "text/html" using the mime lookup table.</p>
may pass the subtype such as &ldquo;html&rdquo; which is then converted internally
to &ldquo;text/html&rdquo; using the mime lookup table.</p>
<pre><code>// Accept: text/html
req.accepts('html');
@@ -966,7 +966,7 @@ req.accepts('png');
// =&gt; false
</code></pre>
<h3 id="req-is-type-">req.is(type)</h3>
<h3 id="req.is()">req.is(type)</h3>
<p>Check if the incoming request contains the <em>Content-Type</em>
header field, and it contains the give mime <em>type</em>.</p>
@@ -987,7 +987,7 @@ header field, and it contains the give mime <em>type</em>.</p>
<p>Ad-hoc callbacks can also be registered with Express, to perform
assertions again the request, for example if we need an expressive
way to check if our incoming request is an image, we can register <em>"an image"</em>
way to check if our incoming request is an image, we can register <em>&ldquo;an image&rdquo;</em>
callback:</p>
<pre><code> app.is('an image', function(req){
@@ -996,7 +996,7 @@ callback:</p>
</code></pre>
<p>Now within our route callbacks, we can use to to assert content types
such as <em>"image/jpeg"</em>, <em>"image/png"</em>, etc.</p>
such as <em>&ldquo;image/jpeg&rdquo;</em>, <em>&ldquo;image/png&rdquo;</em>, etc.</p>
<pre><code> app.post('/image/upload', function(req, res, next){
if (req.is('an image')) {
@@ -1010,17 +1010,17 @@ such as <em>"image/jpeg"</em>, <em>"image/png"</em>, etc.</p>
<p>Keep in mind this method is <em>not</em> limited to checking <em>Content-Type</em>, you
can perform any request assertion you wish.</p>
<p>Wildcard matches can also be made, simplifying our example above for <em>"an image"</em>, by asserting the <em>subtype</em> only:</p>
<p>Wildcard matches can also be made, simplifying our example above for <em>&ldquo;an image&rdquo;</em>, by asserting the <em>subtype</em> only:</p>
<pre><code>req.is('image/*');
</code></pre>
<p>We may also assert the <em>type</em> as shown below, which would return true for <em>"application/json"</em>, and <em>"text/json"</em>.</p>
<p>We may also assert the <em>type</em> as shown below, which would return true for <em>&ldquo;application/json&rdquo;</em>, and <em>&ldquo;text/json&rdquo;</em>.</p>
<pre><code>req.is('*/json');
</code></pre>
<h3 id="req-param-name-default-">req.param(name[, default])</h3>
<h3 id="req.param()">req.param(name[, default])</h3>
<p>Return the value of param <em>name</em> when present or <em>default</em>.</p>
@@ -1035,7 +1035,7 @@ can perform any request assertion you wish.</p>
should be an object. This can be done by using
the _express.bodyParser middleware.</p>
<h3 id="req-flash-type-msg-">req.flash(type[, msg])</h3>
<h3 id="req.flash()">req.flash(type[, msg])</h3>
<p>Queue flash <em>msg</em> of the given <em>type</em>.</p>
@@ -1059,7 +1059,7 @@ req.flash();
<pre><code>req.flash('info', 'email delivery to _%s_ from _%s_ failed.', toUser, fromUser);
</code></pre>
<h3 id="req-isXMLHttpRequest">req.isXMLHttpRequest</h3>
<h3 id="req.isxmlhttprequest">req.isXMLHttpRequest</h3>
<p>Also aliased as <em>req.xhr</em>, this getter checks the <em>X-Requested-With</em> header
to see if it was issued by an <em>XMLHttpRequest</em>:</p>
@@ -1068,7 +1068,7 @@ to see if it was issued by an <em>XMLHttpRequest</em>:</p>
req.isXMLHttpRequest
</code></pre>
<h3 id="res-header-key-val-">res.header(key[, val])</h3>
<h3 id="res.header()">res.header(key[, val])</h3>
<p>Get or set the response header <em>key</em>.</p>
@@ -1082,7 +1082,28 @@ res.header('Content-Length');
// =&gt; 123
</code></pre>
<h3 id="res-contentType-type-">res.contentType(type)</h3>
<h3 id="res.charset">res.charset</h3>
<p>Sets the charset for subsequent <code>Content-Type</code> header fields. For example <code>res.send()</code> and <code>res.render()</code> default to &ldquo;utf8&rdquo;, so we may explicitly set the charset before rendering a template:</p>
<pre><code>res.charset = 'ISO-8859-1';
res.render('users');
</code></pre>
<p>or before responding with <code>res.send()</code>:</p>
<pre><code>res.charset = 'ISO-8859-1';
res.send(str);
</code></pre>
<p>or with node&rsquo;s <code>res.end()</code>:</p>
<pre><code>res.charset = 'ISO-8859-1';
res.header('Content-Type', 'text/plain');
res.end(str);
</code></pre>
<h3 id="res.contenttype()">res.contentType(type)</h3>
<p>Sets the <em>Content-Type</em> response header to the given <em>type</em>.</p>
@@ -1091,14 +1112,24 @@ res.header('Content-Length');
// Content-Type is now "image/png"
</code></pre>
<h3 id="res-attachment-filename-">res.attachment([filename])</h3>
<p>A literal <em>Content-Type</em> works as well:</p>
<p>Sets the <em>Content-Disposition</em> response header to "attachment", with optional <em>filename</em>.</p>
<pre><code> res.contentType('application/json');
</code></pre>
<p>Or simply the extension without leading <code>.</code>:</p>
<pre><code> res.contentType('json');
</code></pre>
<h3 id="res.attachment()">res.attachment([filename])</h3>
<p>Sets the <em>Content-Disposition</em> response header to &ldquo;attachment&rdquo;, with optional <em>filename</em>.</p>
<pre><code> res.attachment('path/to/my/image.png');
</code></pre>
<h3 id="res-sendfile-path-options-callback-">res.sendfile(path[, options[, callback]])</h3>
<h3 id="res.sendfile()">res.sendfile(path[, options[, callback]])</h3>
<p>Used by <code>res.download()</code> to transfer an arbitrary file.</p>
@@ -1124,7 +1155,7 @@ an error occurs, or when the transfer is complete. By default failures call <cod
});
</code></pre>
<h3 id="res-download-file-filename-callback-">res.download(file[, filename[, callback]])</h3>
<h3 id="res.download()">res.download(file[, filename[, callback]])</h3>
<p>Transfer the given <em>file</em> as an attachment with optional alternative <em>filename</em>.</p>
@@ -1145,7 +1176,7 @@ res.sendfile(file);
});
</code></pre>
<h3 id="res-send-body-status-headers-status-status-">res.send(body|status[, headers|status[, status]])</h3>
<h3 id="res.send()">res.send(body|status[, headers|status[, status]])</h3>
<p>The <em>res.send()</em> method is a high level response utility allowing you to pass
objects to respond with json, strings for html, Buffer instances, or numbers representing the status code. The following are all valid uses:</p>
@@ -1163,9 +1194,9 @@ objects to respond with json, strings for html, Buffer instances, or numbers rep
assigned through <code>res.send()</code> or previously with <code>res.header()</code> or <code>res.contentType()</code>
it will not be set again.</p>
<p>Note that this method <em>end()</em>s the response, so you will want to use node's <em>res.write()</em> for multiple writes or streaming.</p>
<p>Note that this method <em>end()</em>s the response, so you will want to use node&rsquo;s <em>res.write()</em> for multiple writes or streaming.</p>
<h3 id="res-redirect-url-status-">res.redirect(url[, status])</h3>
<h3 id="res.redirect()">res.redirect(url[, status])</h3>
<p>Redirect to the given <em>url</em> with a default response <em>status</em> of 302.</p>
@@ -1176,11 +1207,11 @@ res.redirect('home');
res.redirect('back');
</code></pre>
<p>Express supports "redirect mapping", which by default provides <em>home</em>, and <em>back</em>.
<p>Express supports &ldquo;redirect mapping&rdquo;, which by default provides <em>home</em>, and <em>back</em>.
The <em>back</em> map checks the <em>Referrer</em> and <em>Referer</em> headers, while <em>home</em> utilizes
the "home" setting and defaults to "/".</p>
the &ldquo;home&rdquo; setting and defaults to &ldquo;/&rdquo;.</p>
<h3 id="res-cookie-name-val-options-">res.cookie(name, val[, options])</h3>
<h3 id="res.cookie()">res.cookie(name, val[, options])</h3>
<p>Sets the given cookie <em>name</em> to <em>val</em>, with options <em>httpOnly</em>, <em>secure</em>, <em>expires</em> etc.</p>
@@ -1193,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());
@@ -1202,26 +1233,26 @@ app.get('/', function(req, res){
});
</code></pre>
<h3 id="res-clearCookie-name-">res.clearCookie(name)</h3>
<h3 id="res.clearcookie()">res.clearCookie(name)</h3>
<p>Clear cookie <em>name</em> by setting "expires" far in the past.</p>
<p>Clear cookie <em>name</em> by setting &ldquo;expires&rdquo; far in the past.</p>
<pre><code>res.clearCookie('rememberme');
</code></pre>
<h3 id="res-render-view-options-fn-">res.render(view[, options[, fn]])</h3>
<h3 id="res.render()">res.render(view[, options[, fn]])</h3>
<p>Render <em>view</em> with the given <em>options</em> and optional callback <em>fn</em>.
When a callback function is given a response will <em>not</em> be made
automatically, however otherwise a response of <em>200</em> and <em>text/html</em> is given.</p>
<p>The <em>options</em> passed are the local variables as well, for example if we want to expose "user" to the view, and prevent a local we do so within the same object:</p>
<p>The <em>options</em> passed are the local variables as well, for example if we want to expose &ldquo;user&rdquo; to the view, and prevent a local we do so within the same object:</p>
<pre><code>var user = { name: 'tj' };
res.render('index', { layout: false, user: user });
</code></pre>
<h3 id="res-partial-view-options-">res.partial(view[, options])</h3>
<h3 id="res.partial()">res.partial(view[, options])</h3>
<p>Render <em>view</em> partial with the given <em>options</em>. This method is always available
to the view as a local variable.</p>
@@ -1231,9 +1262,9 @@ to the view as a local variable.</p>
<li><p><em>as</em> Variable name for each <em>collection</em> or <em>object</em> value, defaults to the view name.</p>
<ul>
<li>as: 'something' will add the <em>something</em> local variable</li>
<li>as: &lsquo;something&rsquo; will add the <em>something</em> local variable</li>
<li>as: this will use the collection value as the template context</li>
<li>as: global will merge the collection value's properties with <em>locals</em></li>
<li>as: global will merge the collection value&rsquo;s properties with <em>locals</em></li>
</ul>
</li>
<li><p><em>collection</em> Array of objects, the name is derived from the view name itself.
@@ -1252,7 +1283,7 @@ partial('movie', movies);
// In view: movie.director
</code></pre>
<p>To change the local from <em>movie</em> to <em>video</em> we can use the "as" option:</p>
<p>To change the local from <em>movie</em> to <em>video</em> we can use the &ldquo;as&rdquo; option:</p>
<pre><code>partial('movie', { collection: movies, as: 'video' });
// In view: video.director
@@ -1265,7 +1296,7 @@ of <em>movie.director</em> we could use <em>this.director</em>.</p>
// In view: this.director
</code></pre>
<p>Another alternative is to "explode" the properties of the collection item into
<p>Another alternative is to &ldquo;expand&rdquo; the properties of the collection item into
pseudo globals (local variables) by using <em>as: global</em>, which again is syntactic sugar:</p>
<pre><code>partial('movie', { collection: movies, as: global });
@@ -1287,12 +1318,26 @@ partial('movie', { object: movie });
// In view: movie.director
</code></pre>
<p>When a non-collection (does <em>not</em> have <em>.length</em>) is passed as the second argument, it is assumed to be the <em>object</em>, after which the object's local variable name is derived from the view name:</p>
<p>When a non-collection (does <em>not</em> have <em>.length</em>) is passed as the second argument, it is assumed to be the <em>object</em>, after which the object&rsquo;s local variable name is derived from the view name:</p>
<pre><code>partial('movie', movie);
<pre><code>var movie = new Movie('Nightmare Before Christmas', 'Tim Burton')
partial('movie', movie)
// =&gt; In view: movie.director
</code></pre>
<p>The exception of this, is when a &ldquo;plain&rdquo; object, aka &ldquo;{}&rdquo; or &ldquo;new Object&rdquo; is passed, which is considered an object with local variable. For example some may expect a &ldquo;movie&rdquo; local with the following, however since it is a plain object &ldquo;director&rdquo; and &ldquo;title&rdquo; are simply locals:</p>
<pre><code>var movie = { title: 'Nightmare Before Christmas', director: 'Tim Burton' };
partial('movie', movie)
</code></pre>
<p>For cases like this where passing a plain object is desired, simply assign it to a key, or use the <code>object</code> key which will use the filename-derived variable name. The examples below are equivalent:</p>
<pre><code> partial('movie', { locals: { movie: movie }})
partial('movie', { movie: movie })
partial('movie', { object: movie })
</code></pre>
<p>This exact API can be utilized from within a route, to respond with a fragment via Ajax or WebSockets, for example we can render a collection of users directly from a route:</p>
<pre><code>app.get('/users', function(req, res){
@@ -1309,7 +1354,7 @@ partial('movie', { object: movie });
});
</code></pre>
<h3 id="res-local-name-val-">res.local(name[, val])</h3>
<h3 id="res.local()">res.local(name[, val])</h3>
<p>Get or set the given local variable <em>name</em>. The locals built up for a response are applied to those given to the view rendering methods such as <code>res.render()</code>.</p>
@@ -1327,7 +1372,17 @@ partial('movie', { object: movie });
});
</code></pre>
<h3 id="app-set-name-val-">app.set(name[, val])</h3>
<h3 id="res.locals()">res.locals(obj)</h3>
<p> Assign several locals with the given <em>obj</em>. The following are equivalent:</p>
<pre><code> res.local('foo', bar);
res.local('bar', baz);
res.locals({ foo: bar, bar, baz });
</code></pre>
<h3 id="app.set()">app.set(name[, val])</h3>
<p>Apply an application level setting <em>name</em> to <em>val</em>, or
get the value of <em>name</em> when <em>val</em> is not present:</p>
@@ -1343,7 +1398,7 @@ app.set('views');
// =&gt; ...path...
</code></pre>
<h3 id="app-enable-name-">app.enable(name)</h3>
<h3 id="app.enable()">app.enable(name)</h3>
<p>Enable the given setting <em>name</em>:</p>
@@ -1355,7 +1410,7 @@ app.enabled('some arbitrary setting');
// =&gt; true
</code></pre>
<h3 id="app-enabled-name-">app.enabled(name)</h3>
<h3 id="app.enabled()">app.enabled(name)</h3>
<p>Check if setting <em>name</em> is enabled:</p>
@@ -1367,7 +1422,7 @@ app.enabled('view cache');
// =&gt; true
</code></pre>
<h3 id="app-disable-name-">app.disable(name)</h3>
<h3 id="app.disable()">app.disable(name)</h3>
<p>Disable the given setting <em>name</em>:</p>
@@ -1379,7 +1434,7 @@ app.disabled('some setting');
// =&gt; false
</code></pre>
<h3 id="app-disabled-name-">app.disabled(name)</h3>
<h3 id="app.disabled()">app.disabled(name)</h3>
<p>Check if setting <em>name</em> is disabled:</p>
@@ -1393,7 +1448,7 @@ app.disabled('view cache');
// =&gt; true
</code></pre>
<h3 id="app-configure-env-function-function-">app.configure(env|function[, function])</h3>
<h3 id="app.configure()">app.configure(env|function[, function])</h3>
<p>Define a callback function for the given <em>env</em> (or all environments) with callback <em>function</em>:</p>
@@ -1406,7 +1461,7 @@ app.configure('development', function(){
});
</code></pre>
<h3 id="app-redirect-name-val-">app.redirect(name, val)</h3>
<h3 id="app.redirect()">app.redirect(name, val)</h3>
<p>For use with <em>res.redirect()</em> we can map redirects at the application level as shown below:</p>
@@ -1415,7 +1470,7 @@ app.configure('development', function(){
<p>Now in a route we may call:</p>
<p> res.redirect('google');</p>
<p> res.redirect(&lsquo;google&rsquo;);</p>
<p>We may also map dynamic redirects:</p>
@@ -1438,7 +1493,7 @@ redirect <em>Location</em> would be <em>/post/12/comments</em>.</p>
<pre><code>res.redirect('/posts');
</code></pre>
<h3 id="app-error-function-">app.error(function)</h3>
<h3 id="app.error()">app.error(function)</h3>
<p>Adds an error handler <em>function</em> which will receive the exception as the first parameter as shown below.
Note that we may set several error handlers by making several calls to this method, however the handler
@@ -1449,26 +1504,35 @@ should call <em>next(err)</em> if it does not wish to deal with the exception:</
});
</code></pre>
<h3 id="app-helpers-obj-">app.helpers(obj)</h3>
<h3 id="app.helpers()">app.helpers(obj)</h3>
<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) %>
<pre><code>&lt;%= name(firstName, lastName) %&gt;
</code></pre>
<h3 id="app-dynamicHelpers-obj-">app.dynamicHelpers(obj)</h3>
<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
are simply functions which accept <em>req</em>, <em>res</em>, and are
@@ -1487,7 +1551,111 @@ becomes the local variable it is associated with.</p>
<pre><code>&lt;%= session.name %&gt;
</code></pre>
<h3 id="app-mounted-fn-">app.mounted(fn)</h3>
<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>
@@ -1502,10 +1670,10 @@ blog.mounted(function(parent){
app.use(blog);
</code></pre>
<h3 id="app-register-ext-exports-">app.register(ext, exports)</h3>
<h3 id="app.register()">app.register(ext, exports)</h3>
<p>Register the given template engine <em>exports</em>
as <em>ext</em>. For example we may wish to map ".html"
as <em>ext</em>. For example we may wish to map &ldquo;.html&rdquo;
files to jade:</p>
<pre><code> app.register('.html', require('jade'));
@@ -1513,8 +1681,8 @@ files to jade:</p>
<p>This is also useful for libraries that may not
match extensions correctly. For example my haml.js
library is installed from npm as "hamljs" so instead
of layout.hamljs, we can register the engine as ".haml":</p>
library is installed from npm as &ldquo;hamljs&rdquo; so instead
of layout.hamljs, we can register the engine as &ldquo;.haml&rdquo;:</p>
<pre><code> app.register('.haml', require('haml-js'));
</code></pre>
@@ -1523,7 +1691,7 @@ of layout.hamljs, we can register the engine as ".haml":</p>
specification, we can also wrap their api this way. Below
we map <em>.md</em> to render markdown files, rendering the html once
since it will not change on subsequent calls, and support local substitution
in the form of "{name}".</p>
in the form of &ldquo;{name}&rdquo;.</p>
<pre><code> app.register('.md', {
compile: function(str, options){
@@ -1537,7 +1705,7 @@ in the form of "{name}".</p>
});
</code></pre>
<h3 id="app-listen-port-host-">app.listen([port[, host]])</h3>
<h3 id="app.listen()">app.listen([port[, host]])</h3>
<p>Bind the app server to the given <em>port</em>, which defaults to 3000. When <em>host</em> is omitted all
connections will be accepted via <em>INADDR_ANY</em>.</p>
@@ -1563,9 +1731,7 @@ Content-Length: 11
Hello World
</code></pre>
</div>
</div>
</div>
</body>
</html>
</html>

View File

@@ -47,7 +47,7 @@ otherwise the first call to _app.get()_, _app.post()_, etc will mount the routes
app.configure('production', function(){
var oneYear = 31557600000;
app.use(express.static({ root: __dirname + '/public', maxAge: oneYear }));
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
app.use(express.errorHandler());
});
@@ -82,6 +82,7 @@ Express supports the following settings out of the box:
* _views_ Root views directory defaulting to **CWD/views**
* _view engine_ Default view engine name for views rendered without extensions
* _view options_ An object specifying global view options
* _view cache_ Enable view caching (enabled in production)
### Routing
@@ -165,7 +166,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
@@ -372,7 +373,7 @@ ad-hoc _NotFound_ exception:
Error.captureStackTrace(this, arguments.callee);
}
NotFound.protoype.__proto__ = Error.prototype;
NotFound.prototype.__proto__ = Error.prototype;
app.get('/404', function(req, res){
throw new NotFound;
@@ -523,15 +524,15 @@ A good example of this is specifying custom _ejs_ opening and closing tags:
### View Partials
The Express view system has built-in support for partials and collections, which are "mini" views representing a document fragment. For example rather than iterating
in a view to display comments, we would use a partial with collection support:
in a view to display comments, we could use partial collection:
partial('comment', { collection: comments });
If no other options are desired, we can omit the object and simply pass our array, which is equivalent to above:
If no other options or local variables are desired, we can omit the object and simply pass our array, which is equivalent to above:
partial('comment', comments);
When using the partial collection support a few "magic" variables are provided
When using the partial collection support a few "magic" locals are provided
for free:
* _firstInCollection_ true if this is the first object
@@ -753,6 +754,24 @@ Get or set the response header _key_.
res.header('Content-Length');
// => 123
### res.charset
Sets the charset for subsequent `Content-Type` header fields. For example `res.send()` and `res.render()` default to "utf8", so we may explicitly set the charset before rendering a template:
res.charset = 'ISO-8859-1';
res.render('users');
or before responding with `res.send()`:
res.charset = 'ISO-8859-1';
res.send(str);
or with node's `res.end()`:
res.charset = 'ISO-8859-1';
res.header('Content-Type', 'text/plain');
res.end(str);
### res.contentType(type)
Sets the _Content-Type_ response header to the given _type_.
@@ -761,6 +780,14 @@ Sets the _Content-Type_ response header to the given _type_.
res.contentType(filename);
// Content-Type is now "image/png"
A literal _Content-Type_ works as well:
res.contentType('application/json');
Or simply the extension without leading `.`:
res.contentType('json');
### res.attachment([filename])
Sets the _Content-Disposition_ response header to "attachment", with optional _filename_.
@@ -790,7 +817,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_.
@@ -802,12 +829,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
@@ -852,7 +887,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());
@@ -860,7 +895,7 @@ To parse incoming _Cookie_ headers, use the _cookieDecoder_ middleware, which pr
// use req.cookies.rememberme
});
### res.clearCookie(name)
### res.clearCookie(name[, options])
Clear cookie _name_ by setting "expires" far in the past.
@@ -912,7 +947,7 @@ of _movie.director_ we could use _this.director_.
partial('movie', { collection: movies, as: this });
// In view: this.director
Another alternative is to "explode" the properties of the collection item into
Another alternative is to "expand" the properties of the collection item into
pseudo globals (local variables) by using _as: global_, which again is syntactic sugar:
partial('movie', { collection: movies, as: global });
@@ -934,9 +969,21 @@ This same logic applies to a single partial object usage:
When a non-collection (does _not_ have _.length_) is passed as the second argument, it is assumed to be the _object_, after which the object's local variable name is derived from the view name:
partial('movie', movie);
var movie = new Movie('Nightmare Before Christmas', 'Tim Burton')
partial('movie', movie)
// => In view: movie.director
The exception of this, is when a "plain" object, aka "{}" or "new Object" is passed, which is considered an object with local variable. For example some may expect a "movie" local with the following, however since it is a plain object "director" and "title" are simply locals:
var movie = { title: 'Nightmare Before Christmas', director: 'Tim Burton' };
partial('movie', movie)
For cases like this where passing a plain object is desired, simply assign it to a key, or use the `object` key which will use the filename-derived variable name. The examples below are equivalent:
partial('movie', { locals: { movie: movie }})
partial('movie', { movie: movie })
partial('movie', { object: movie })
This exact API can be utilized from within a route, to respond with a fragment via Ajax or WebSockets, for example we can render a collection of users directly from a route:
app.get('/users', function(req, res){
@@ -969,6 +1016,15 @@ Get or set the given local variable _name_. The locals built up for a response a
res.render('movie', { displayReviews: true });
});
### res.locals(obj)
Assign several locals with the given _obj_. The following are equivalent:
res.local('foo', bar);
res.local('bar', baz);
res.locals({ foo: bar, bar, baz });
### app.set(name[, val])
Apply an application level setting _name_ to _val_, or
@@ -1083,17 +1139,25 @@ should call _next(err)_ if it does not wish to deal with the exception:
Registers static view helpers.
app.helpers({
name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
});
app.helpers({
name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
});
Our view could now utilize the _firstName_ and _lastName_ variables,
as well as the _name()_ function exposed.
<%= name(firstName, lastName) %>
Express also provides a few locals by default:
- `settings` the app's settings object
- `filename` the view's filename
- `layout(path)` specify the layout from within a view
This method is aliased as _app.locals()_.
### app.dynamicHelpers(obj)
Registers dynamic view helpers. Dynamic view helpers
@@ -1111,6 +1175,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

@@ -1,126 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "INDEX" "" "March 2011" "" ""
.
.SH "NAME"
\fBindex\fR
.
.IP "" 4
.
.nf
var app = express\.createServer();
app\.get(\'/\', function(req, res){
res\.send(\'Hello World\');
});
app\.listen(3000);
.
.fi
.
.IP "" 0
.
.SH "Features"
.
.IP "\(bu" 4
Robust routing
.
.IP "\(bu" 4
Redirection helpers
.
.IP "\(bu" 4
Dynamic view helpers
.
.IP "\(bu" 4
Application level view options
.
.IP "\(bu" 4
Content negotiation
.
.IP "\(bu" 4
Application mounting
.
.IP "\(bu" 4
Focus on high performance
.
.IP "\(bu" 4
View rendering and partials support
.
.IP "\(bu" 4
Environment based configuration
.
.IP "\(bu" 4
Session based flash notifications
.
.IP "\(bu" 4
Built on Connect \fIhttp://github\.com/senchalabs/connect\fR
.
.IP "\(bu" 4
Executable \fIexecutable\.html\fR for generating applications quickly
.
.IP "\(bu" 4
High test coverage
.
.IP "" 0
.
.SH "Contributors"
The following are the major contributors of Express (in no specific order)\.
.
.IP "\(bu" 4
TJ Holowaychuk (visionmedia \fIhttp://github\.com/visionmedia\fR)
.
.IP "\(bu" 4
Ciaran Jessup (ciaranj \fIhttp://github\.com/ciaranj\fR)
.
.IP "\(bu" 4
Aaron Heckmann (aheckmann \fIhttp://github\.com/aheckmann\fR)
.
.IP "\(bu" 4
Guillermo Rauch (guille \fIhttp://github\.com/guille\fR)
.
.IP "" 0
.
.SH "Third\-Party Modules"
The following modules compliment or extend Express directly:
.
.IP "\(bu" 4
express\-resource \fIhttp://github\.com/visionmedia/express\-resource\fR provides resourceful routing
.
.IP "\(bu" 4
express\-messages \fIhttp://github\.com/visionmedia/express\-messages\fR flash message notification rendering
.
.IP "\(bu" 4
express\-configure \fIhttp://github\.com/visionmedia/express\-configure\fR async configuration support (load settings from redis etc)
.
.IP "\(bu" 4
express\-namespace \fIhttp://github\.com/visionmedia/express\-namespace\fR namespaced routing support
.
.IP "" 0
.
.SH "More Information"
.
.IP "\(bu" 4
Google Group \fIhttp://groups\.google\.com/group/express\-js\fR for discussion
.
.IP "\(bu" 4
Follow tjholowaychuk \fIhttp://twitter\.com/tjholowaychuk\fR on twitter for updates
.
.IP "\(bu" 4
View the Connect \fIhttp://senchalabs\.github\.com/connect\fR documentation
.
.IP "\(bu" 4
View the Connect Wiki \fIhttp://wiki\.github\.com/senchalabs/connect/\fR for contrib middleware
.
.IP "\(bu" 4
View the examples \fIhttp://github\.com/visionmedia/express/tree/master/examples/\fR
.
.IP "\(bu" 4
View the source \fIhttp://github\.com/visionmedia/express\fR
.
.IP "\(bu" 4
View the contrib guide \fIcontrib\.html\fR
.
.IP "" 0

View File

@@ -190,11 +190,6 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>index</code>
</p>
<pre><code>var app = express.createServer();
app.get('/', function(req, res){
@@ -204,7 +199,7 @@ app.get('/', function(req, res){
app.listen(3000);
</code></pre>
<h2 id="Features">Features</h2>
<h2>Features</h2>
<ul>
<li>Robust routing</li>
@@ -223,7 +218,7 @@ app.listen(3000);
</ul>
<h2 id="Contributors">Contributors</h2>
<h2>Contributors</h2>
<p>The following are the major contributors of Express (in no specific order).</p>
@@ -235,19 +230,19 @@ app.listen(3000);
</ul>
<h2 id="Third-Party-Modules">Third-Party Modules</h2>
<h2>Third-Party Modules</h2>
<p>The following modules compliment or extend Express directly:</p>
<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>
<h2 id="More-Information">More Information</h2>
<h2>More Information</h2>
<ul>
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
@@ -259,9 +254,7 @@ app.listen(3000);
<li>View the <a href="contrib.html">contrib guide</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
</html>

View File

@@ -38,7 +38,7 @@ The following modules compliment or extend Express directly:
* [express-resource](http://github.com/visionmedia/express-resource) provides resourceful routing
* [express-messages](http://github.com/visionmedia/express-messages) flash message notification rendering
* [express-configure](http://github.com/visionmedia/express-configure) async configuration support (load settings from redis etc)
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support (load settings from redis etc)
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced routing support
## More Information

View File

@@ -1,272 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "MIGRATE" "" "March 2011" "" ""
.
.SH "NAME"
\fBmigrate\fR
.
.SS "Express 1\.x to 2\.x Migration"
.
.SS "HTTPS"
Creating an HTTPS server is simply, simply pass the TLS options to \fIexpress\.createServer()\fR:
.
.IP "" 4
.
.nf
var app = express\.createServer({
key: \.\.\.
, cert: \.\.\.
});
app\.listen(443);
.
.fi
.
.IP "" 0
.
.SS "req\.header() Referrer"
Previously if anyone was doing something similar to:
.
.IP "" 4
.
.nf
req\.headers\.referrer || req\.headers\.referer
req\.header(\'Referrer\') || req\.header(\'Referer\')
.
.fi
.
.IP "" 0
.
.P
With the new special\-case we may now simply use \fIReferrer\fR which will return either if defined:
.
.IP "" 4
.
.nf
req\.header(\'Referrer\')
.
.fi
.
.IP "" 0
.
.SS "res\.local(name, val)"
Previously all local variables had to be passed to \fIres\.render()\fR, or either \fIapp\.helpers()\fR or \fIapp\.dynamicHelpers()\fR, now we may do this at the request\-level progressively\. The \fIres\.local()\fR method accepts a \fIname\fR and \fIval\fR, however the locals passed to \fIres\.render()\fR will take precedence\.
.
.P
For example we may utilize this feature to create locals in middleware:
.
.IP "" 4
.
.nf
function loadUser(req, res, next) {
User\.get(req\.params\.id, function(err, user){
res\.local(\'user\', user);
next();
});
}
app\.get(\'/user/:id\', loadUser, function(req, res){
res\.render(\'user\');
});
.
.fi
.
.IP "" 0
.
.SS "req\.param(name[, defaultValue])"
Previously only \fIname\fR was accepted, so some of you may have been doing the following:
.
.IP "" 4
.
.nf
var id = req\.param(\'id\') || req\.user\.id;
.
.fi
.
.IP "" 0
.
.P
The new \fIdefaultValue\fR argument can handle this nicely:
.
.IP "" 4
.
.nf
var id = req\.param(\'id\', req\.user\.id);
.
.fi
.
.IP "" 0
.
.SS "app\.helpers() / app\.locals()"
\fIapp\.locals()\fR is now an alias of \fIapp\.helpers()\fR, as helpers makes more sense for functions\.
.
.SS "req\.accepts(type)"
\fIreq\.accepts()\fR now accepts extensions:
.
.IP "" 4
.
.nf
// Accept: text/html
req\.accepts(\'html\');
req\.accepts(\'\.html\');
// => true
// Accept: text/*; application/json
req\.accepts(\'html\');
req\.accepts(\'text/*\');
req\.accepts(\'text/plain\');
req\.accepts(\'application/json\');
// => true
req\.accepts(\'image/png\');
req\.accepts(\'png\');
// => false
.
.fi
.
.IP "" 0
.
.SS "res\.cookie()"
Previously only directly values could be passed, so for example:
.
.IP "" 4
.
.nf
res\.cookie(\'rememberme\', \'yes\', { expires: new Date(Date\.now() + 900000) });
.
.fi
.
.IP "" 0
.
.P
However now we have the alternative \fImaxAge\fR property which may be used to set \fIexpires\fR relative to \fIDate\.now()\fR in milliseconds, so our example above can now become:
.
.IP "" 4
.
.nf
res\.cookie(\'rememberme\', \'yes\', { maxAge: 900000 });
.
.fi
.
.IP "" 0
.
.SS "res\.download() / res\.sendfile()"
Both of these methods now utilize Connect\'s static file server behind the scenes (actually the previous Express code was ported to Connect 1\.0)\. With this change comes a change to the callback as well\. Previously the \fIpath\fR and \fIstream\fR were passed, however now only an \fIerror\fR is passed, when no error has occurred the callback will be invoked indicating that the file transfer is complete\. The callback remains optional:
.
.IP "" 4
.
.nf
res\.download(\'/path/to/file\');
res\.download(\'/path/to/file\', function(err){
if (err) {
console\.error(err);
} else {
console\.log(\'transferred\');
}
});
.
.fi
.
.IP "" 0
.
.P
The \fIstream threshold\fR setting was removed\.
.
.SS "res\.render()"
Previously locals were passed as a separate key:
.
.IP "" 4
.
.nf
res\.render(\'user\', { layout: false, locals: { user: user }});
.
.fi
.
.IP "" 0
.
.P
In Express 2\.0 both the locals and the options are one in the same, meaning you cannot have a local variable named \fIlayout\fR as it is reserved for express, however this cleans up the API:
.
.IP "" 4
.
.nf
res\.render(\'user\', { layout: false, user: user });
.
.fi
.
.IP "" 0
.
.SS "res\.partial()"
Express 2\.0 adds the \fIres\.partial()\fR method, helpful for rendering partial fragments over WebSockets or Ajax requests etc\. The API is identical to the \fIpartial()\fR calls within views\.
.
.IP "" 4
.
.nf
// render a collection of comments
res\.partial(\'comment\', [comment1, comment2]);
// render a single comment
res\.partial(\'comment\', comment);
.
.fi
.
.IP "" 0
.
.SS "Template Engine Compliance"
To comply with Express previously engines needed the following signature:
.
.IP "" 4
.
.nf
engine\.render(str, options, function(err){});
.
.fi
.
.IP "" 0
.
.P
Now they must export a \fIcompile()\fR function, returning a function which when called with local variables will render the template\. This allows Express to cache the compiled function in memory during production\.
.
.IP "" 4
.
.nf
var fn = engine\.compile(str, options);
fn(locals);
.
.fi
.
.IP "" 0
.
.SS "View Partial Lookup"
Previously partials were loaded relative to the now removed \fIview partials\fR directory setting, or by default \fIviews/partials\fR, now they are relative to the view calling them, read more on view lookup \fIguide\.html#View\-Lookup\fR\.
.
.SS "Mime Types"
Express and Connect now utilize the \fImime\fR module in npm, so to add more use:
.
.IP "" 4
.
.nf
require(\'mime\')\.define({ \'foo/bar\': [\'foo\', \'bar\'] });
.
.fi
.
.IP "" 0

View File

@@ -190,14 +190,9 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>migrate</code>
</p>
<h3 id="Express-1-x-to-2-x-Migration">Express 1.x to 2.x Migration</h3>
<h3>Express 1.x to 2.x Migration</h3>
<h3 id="HTTPS">HTTPS</h3>
<h3>HTTPS</h3>
<p> Creating an HTTPS server is simply, simply pass the TLS options to <em>express.createServer()</em>:</p>
@@ -209,7 +204,7 @@
app.listen(443);
</code></pre>
<h3 id="req-header-Referrer">req.header() Referrer</h3>
<h3>req.header() Referrer</h3>
<p> Previously if anyone was doing something similar to:</p>
@@ -222,7 +217,7 @@
<pre><code> req.header('Referrer')
</code></pre>
<h3 id="res-local-name-val-">res.local(name, val)</h3>
<h3>res.local(name, val)</h3>
<p> Previously all local variables had to be passed to <em>res.render()</em>, or either <em>app.helpers()</em> or <em>app.dynamicHelpers()</em>, now we may do this at the request-level progressively. The <em>res.local()</em> method accepts a <em>name</em> and <em>val</em>, however the locals passed to <em>res.render()</em> will take precedence.</p>
@@ -240,7 +235,7 @@
});
</code></pre>
<h3 id="req-param-name-defaultValue-">req.param(name[, defaultValue])</h3>
<h3>req.param(name[, defaultValue])</h3>
<p> Previously only <em>name</em> was accepted, so some of you may have been doing the following:</p>
@@ -252,11 +247,11 @@
<pre><code> var id = req.param('id', req.user.id);
</code></pre>
<h3 id="app-helpers-app-locals-">app.helpers() / app.locals()</h3>
<h3>app.helpers() / app.locals()</h3>
<p> <em>app.locals()</em> is now an alias of <em>app.helpers()</em>, as helpers makes more sense for functions.</p>
<h3 id="req-accepts-type-">req.accepts(type)</h3>
<h3>req.accepts(type)</h3>
<p> <em>req.accepts()</em> now accepts extensions:</p>
@@ -277,7 +272,7 @@
// =&gt; false
</code></pre>
<h3 id="res-cookie-">res.cookie()</h3>
<h3>res.cookie()</h3>
<p> Previously only directly values could be passed, so for example:</p>
@@ -289,9 +284,9 @@
<pre><code>res.cookie('rememberme', 'yes', { maxAge: 900000 });
</code></pre>
<h3 id="res-download-res-sendfile-">res.download() / res.sendfile()</h3>
<h3>res.download() / res.sendfile()</h3>
<p> Both of these methods now utilize Connect's static file server behind the scenes (actually the previous Express code was ported to Connect 1.0). With this change comes a change to the callback as well. Previously the <em>path</em> and <em>stream</em> were passed, however now only an <em>error</em> is passed, when no error has occurred the callback will be invoked indicating that the file transfer is complete. The callback remains optional:</p>
<p> Both of these methods now utilize Connect&rsquo;s static file server behind the scenes (actually the previous Express code was ported to Connect 1.0). With this change comes a change to the callback as well. Previously the <em>path</em> and <em>stream</em> were passed, however now only an <em>error</em> is passed, when no error has occurred the callback will be invoked indicating that the file transfer is complete. The callback remains optional:</p>
<pre><code> res.download('/path/to/file');
@@ -306,7 +301,7 @@
<p> The <em>stream threshold</em> setting was removed.</p>
<h3 id="res-render-">res.render()</h3>
<h3>res.render()</h3>
<p> Previously locals were passed as a separate key:</p>
@@ -318,7 +313,7 @@
<pre><code> res.render('user', { layout: false, user: user });
</code></pre>
<h3 id="res-partial-">res.partial()</h3>
<h3>res.partial()</h3>
<p> Express 2.0 adds the <em>res.partial()</em> method, helpful for rendering partial fragments over WebSockets or Ajax requests etc. The API is identical to the <em>partial()</em> calls within views.</p>
@@ -329,7 +324,35 @@
res.partial('comment', comment);
</code></pre>
<h3 id="Template-Engine-Compliance">Template Engine Compliance</h3>
<h3>partial() locals</h3>
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
<pre><code> partial('user', { object: user, locals: { date: new Date }})
</code></pre>
<p>or perhaps if you preferred not to use the inferred name <em>user</em> you may used a local for this as well:</p>
<pre><code> partial('user', { locals: { user: user, date: new Date }})
</code></pre>
<p> With recent changes to Express 2.x the object passed is now both, so the following is valid for the <em>object</em> option and locals:</p>
<pre><code> partial('user', { object: user, date: new Date })
</code></pre>
<p> Or the following which is equivalent, however the local var name is explicitly set to <em>user</em> instead of deduced from the filename.</p>
<pre><code> partial('user', { user: user, date: new Date })
</code></pre>
<p> When a &ldquo;basic&rdquo; object aka <em>{}</em> or <em>new Object</em> is passed, it is considered options, otherwise it is considered the <em>object</em>. The following are equivalent:</p>
<pre><code> partial('user', user);
partial('user', { object: user });
</code></pre>
<h3>Template Engine Compliance</h3>
<p> To comply with Express previously engines needed the following signature:</p>
@@ -342,19 +365,29 @@
fn(locals);
</code></pre>
<h3 id="View-Partial-Lookup">View Partial Lookup</h3>
<h3>View Partial Lookup</h3>
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#View-Lookup">view lookup</a>.</p>
<h3 id="Mime-Types">Mime Types</h3>
<h3>Mime Types</h3>
<p> Express and Connect now utilize the <em>mime</em> module in npm, so to add more use:</p>
<pre><code> require('mime').define({ 'foo/bar': ['foo', 'bar'] });
</code></pre>
</div>
<h3>static() middleware</h3>
<p> Previously named <code>staticProvider()</code>, the now <code>static()</code> middleware takes a single directory path, followed by options.</p>
<pre><code> app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
</code></pre>
<p>Previously when using options the <code>root</code> option would be used for this:</p>
<pre><code> app.use(express.staticProvider({ root: __dirname + '/public', maxAge: oneYear }));
</code></pre>
</div>
</div>
</body>
</html>
</html>

View File

@@ -121,6 +121,29 @@ However now we have the alternative _maxAge_ property which may be used to set _
// render a single comment
res.partial('comment', comment);
### partial() locals
Both _res.partial()_ and the _partial()_ functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass _user_ to a partial, along with _date_ like so:
partial('user', { object: user, locals: { date: new Date }})
or perhaps if you preferred not to use the inferred name _user_ you may used a local for this as well:
partial('user', { locals: { user: user, date: new Date }})
With recent changes to Express 2.x the object passed is now both, so the following is valid for the _object_ option and locals:
partial('user', { object: user, date: new Date })
Or the following which is equivalent, however the local var name is explicitly set to _user_ instead of deduced from the filename.
partial('user', { user: user, date: new Date })
When a "basic" object aka _{}_ or _new Object_ is passed, it is considered options, otherwise it is considered the _object_. The following are equivalent:
partial('user', user);
partial('user', { object: user });
### Template Engine Compliance
To comply with Express previously engines needed the following signature:
@@ -140,4 +163,15 @@ However now we have the alternative _maxAge_ property which may be used to set _
Express and Connect now utilize the _mime_ module in npm, so to add more use:
require('mime').define({ 'foo/bar': ['foo', 'bar'] });
require('mime').define({ 'foo/bar': ['foo', 'bar'] });
### static() middleware
Previously named `staticProvider()`, the now `static()` middleware takes a single directory path, followed by options.
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
Previously when using options the `root` option would be used for this:
app.use(express.staticProvider({ root: __dirname + '/public', maxAge: oneYear }));

View File

@@ -1,28 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "SCREENCASTS" "" "March 2011" "" ""
.
.SH "NAME"
\fBscreencasts\fR
.
.SS "Introduction"
This introduction screencast covers the basics of Express, and how to get started with your first application\.
.
.P
.
.SS "View Partials"
In this screencast we work with partials to display a collection of users using the Jade \fIhttp://jade\-lang\.com\fR template engine, and learn about view path resolution\.
.
.P
.
.SS "Route Specific Middleware"
In the screencast below we learn about the benefits of route\-specific middleware\.
.
.P
.
.SS "Route Param Preconditions"
Learn about route parameter (\fI/user/:id\fR) pre\-conditions, providing automated validation, and loading of data via the named route param segments\.
.
.P

View File

@@ -190,37 +190,34 @@
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>screencasts</code>
</p>
<h3 id="Introduction">Introduction</h3>
<h3>Introduction</h3>
<p>This introduction screencast covers the basics of Express, and how to get started with your first application.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=139583" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=139583" width="560"></embed></object></p>
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=139583' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=139583' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
<h3 id="View-Partials">View Partials</h3>
<h3>View Partials</h3>
<p>In this screencast we work with partials to display a collection of users using the <a href="http://jade-lang.com">Jade</a> template engine, and learn about view path resolution.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=139591" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=139591" width="560"></embed></object></p>
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=139591' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=139591' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
<h3 id="Route-Specific-Middleware">Route Specific Middleware</h3>
<h3>Route Specific Middleware</h3>
<p>In the screencast below we learn about the benefits of route-specific middleware.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=140296" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=140296" width="560"></embed></object></p>
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=140296' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=140296' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
<h3 id="Route-Param-Preconditions">Route Param Preconditions</h3>
<h3>Route Param Preconditions</h3>
<p>Learn about route parameter (<em>/user/:id</em>) pre-conditions, providing automated validation, and loading of data via the named route param segments.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=140300" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=140300" width="560"></embed></object></p>
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=140300' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=140300' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
</div>
</div>
</div>
</body>
</html>
</html>

View File

@@ -7,7 +7,7 @@ require.paths.unshift(__dirname + '/../../support');
*/
var express = require('../../lib/express')
, messages = require('express-contrib/messages');
, messages = require('express-messages');
var app = module.exports = express.createServer();
@@ -22,8 +22,8 @@ app.mounted(function(other){
console.log('ive been mounted!');
});
// Flash message helper provided by express-contrib
// $ npm install express-contrib
// Flash message helper provided by express-messages
// $ npm install express-messages
app.dynamicHelpers({
messages: messages

View File

@@ -10,10 +10,10 @@ h1 Blog
| It looks like you have no posts!
p
| Click
a(href=base + '/post/add') here
a(href=base + '/post/add') here
| to create a post. Login
| as
em "admin"
em "admin"
| and
em "express"
| .

View File

@@ -15,10 +15,11 @@ var pub = __dirname + '/public';
// Auto-compile sass to css with "compiler"
// and then serve with connect's staticProvider
var app = express.createServer(
express.compiler({ src: pub, enable: ['sass'] })
, express.static(pub)
);
var app = express.createServer();
app.use(express.compiler({ src: pub, enable: ['sass'] }));
app.use(app.router);
app.use(express.static(pub));
app.use(express.errorHandler({ dump: true, stack: true }));
// Optional since express defaults to CWD/views
@@ -29,17 +30,30 @@ app.set('views', __dirname + '/views');
// (although you can still mix and match)
app.set('view engine', 'jade');
function User(name, email) {
this.name = name;
this.email = email;
}
// Dummy users
var users = [
{ name: 'tj', email: 'tj@vision-media.ca' }
, { name: 'ciaran', email: 'ciaranj@gmail.com' }
, { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
new User('tj', 'tj@vision-media.ca')
, new User('ciaran', 'ciaranj@gmail.com')
, new User('aaron', 'aaron.heckmann+github@gmail.com')
];
app.get('/', function(req, res){
res.render('users', { users: users });
});
app.get('/users/callback', function(req, res){
// a callback is also accepted
res.partial('users/user', users, function(err, html){
if (err) throw err;
res.send(html);
});
});
app.get('/users', function(req, res){
// we can use res.partial() as if
// we were in a view, utilizing the same api
@@ -48,7 +62,11 @@ app.get('/users', function(req, res){
});
app.get('/users/list', function(req, res){
res.partial('users/list', { object: users });
// use "object" to utilize the name deduced from
// the view filename. The examples below are equivalent
//res.partial('users/list', { object: users });
res.partial('users/list', { list: users });
});
app.get('/user/:id', function(req, res){

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

@@ -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;
},

View File

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

View File

@@ -1,8 +1,12 @@
h1= ninja.name
// file, partial name, and partial object all match ('summary')
// the partial filename prefix '_' is completely optional
#summary!= partial('summary', ninja.summary)
// the partial filename prefix '_' is completely optional.
// In this case we need to specify ninja.summary as the object
// option, since it is a "plain" object Express cannot otherwise
// tell if it is intended to be locals, or THE summary object
#summary!= partial('summary', { object: ninja.summary })
// file, partial name = '_weapon', resolves to 'weapon' object within partial
#weapons

View File

@@ -18,11 +18,10 @@ var app = express.createServer();
// define additional functions exposed to Stylus,
// alter settings, etc
function compile(str, path, fn) {
stylus(str)
function compile(str, path) {
return stylus(str)
.set('filename', path)
.set('compress', true)
.render(fn);
.set('compress', true);
};
// add the stylus middleware, which re-compiles when

View File

@@ -11,7 +11,8 @@
var connect = require('connect')
, HTTPSServer = require('./https')
, HTTPServer = require('./http');
, HTTPServer = require('./http')
, Route = require('./router/route')
/**
* Re-export connect auto-loaders.
@@ -27,7 +28,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.0.0beta2';
exports.version = '2.3.1';
/**
* Shortcut for `new Server(...)`.
@@ -46,10 +47,12 @@ exports.createServer = function(options){
};
/**
* Expose `HTTPServer`.
* Expose constructors.
*/
exports.HTTPServer = HTTPServer;
exports.HTTPSServer = HTTPSServer;
exports.Route = Route;
/**
* View extensions.
@@ -68,3 +71,8 @@ require('./response');
*/
require('./request');
// Error handler title
exports.errorHandler.title = 'Express';

View File

@@ -11,12 +11,24 @@
var qs = require('qs')
, connect = require('connect')
, router = connect.router
, router = require('./router')
, methods = router.methods.concat(['del', 'all'])
, view = require('./view')
, url = require('url')
, utils = connect.utils;
/**
* Expose `HTTPServer`.
*/
exports = module.exports = HTTPServer;
/**
* Server proto.
*/
var app = HTTPServer.prototype;
/**
* Initialize a new `HTTPServer` with optional `middleware`.
*
@@ -24,7 +36,7 @@ var qs = require('qs')
* @api public
*/
var Server = exports = module.exports = function HTTPServer(middleware){
function HTTPServer(middleware){
connect.HTTPServer.call(this, []);
this.init(middleware);
};
@@ -33,7 +45,7 @@ var Server = exports = module.exports = function HTTPServer(middleware){
* Inherit from `connect.HTTPServer`.
*/
Server.prototype.__proto__ = connect.HTTPServer.prototype;
app.__proto__ = connect.HTTPServer.prototype;
/**
* Initialize the server.
@@ -42,19 +54,17 @@ Server.prototype.__proto__ = connect.HTTPServer.prototype;
* @api private
*/
Server.prototype.init = function(middleware){
app.init = function(middleware){
var self = this;
this.cache = {};
this.settings = {};
this.redirects = {};
this.isCallbacks = {};
this.viewHelpers = {};
this._locals = {};
this.dynamicViewHelpers = {};
this.errorHandlers = [];
// default "home" to /
this.set('home', '/');
// set "env" to NODE_ENV, defaulting to "development"
this.set('env', process.env.NODE_ENV || 'development');
// expose objects to each other
@@ -83,21 +93,65 @@ Server.prototype.init = function(middleware){
return fn;
});
// default locals
this.locals({
settings: this.settings
, app: this
});
// default development configuration
this.configure('development', function(){
this.enable('hints');
});
// default production configuration
this.configure('production', function(){
this.enable('cache views');
this.enable('view cache');
});
// register error handlers on "listening"
// so that they disregard definition position.
this.on('listening', this.registerErrorHandlers.bind(this));
// route lookup methods
this.remove = function(url){
return self.remove.all(url);
};
this.match = function(url){
return self.match.all(url);
};
this.lookup = function(url){
return self.lookup.all(url);
};
methods.forEach(function(method){
self.match[method] = function(url){
return self.router.match(url, 'all' == method
? null
: method);
};
self.remove[method] = function(url){
return self.router.remove(url, 'all' == method
? null
: method);
};
self.lookup[method] = function(path){
return self.router.lookup(path, 'all' == method
? null
: method);
};
});
};
/**
* When using the vhost() middleware register error handlers.
*/
Server.prototype.onvhost = function(){
app.onvhost = function(){
this.registerErrorHandlers();
};
@@ -108,7 +162,7 @@ Server.prototype.onvhost = function(){
* @api public
*/
Server.prototype.registerErrorHandlers = function(){
app.registerErrorHandlers = function(){
this.errorHandlers.forEach(function(fn){
this.use(function(err, req, res, next){
fn.apply(this, arguments);
@@ -127,26 +181,37 @@ Server.prototype.registerErrorHandlers = function(){
* @api public
*/
Server.prototype.use = function(route, middleware){
var app, home;
app.use = function(route, middleware){
var app, home, handle;
if ('string' != typeof route) {
middleware = route, route = '/';
}
// express app
if (middleware.handle && middleware.set) app = middleware;
// restore .app property on req and res
if (app) {
app.route = route;
middleware = function(req, res, next) {
var orig = req.app;
app.handle(req, res, function(err){
req.app = res.app = orig;
next(err);
});
};
}
connect.HTTPServer.prototype.use.call(this, route, middleware);
// mounted an app
if (middleware.handle) {
app = middleware;
// express app
if (app.set) {
home = app.set('home');
if ('/' == home) home = '';
app.set('home', app.route + home);
app.parent = this;
}
// mounted hook
// mounted an app, invoke the hook
// and adjust some settings
if (app) {
home = app.set('home');
if ('/' == home) home = '';
app.set('home', app.route + home);
app.parent = this;
if (app.__mounted) app.__mounted.call(app, this);
}
@@ -174,7 +239,7 @@ Server.prototype.use = function(route, middleware){
* @api public
*/
Server.prototype.mounted = function(fn){
app.mounted = function(fn){
this.__mounted = fn;
return this;
};
@@ -186,7 +251,7 @@ Server.prototype.mounted = function(fn){
* @api public
*/
Server.prototype.register = function(){
app.register = function(){
view.register.apply(this, arguments);
return this;
};
@@ -200,9 +265,9 @@ Server.prototype.register = function(){
* @api public
*/
Server.prototype.helpers =
Server.prototype.locals = function(obj){
utils.merge(this.viewHelpers, obj);
app.helpers =
app.locals = function(obj){
utils.merge(this._locals, obj);
return this;
};
@@ -215,7 +280,7 @@ Server.prototype.locals = function(obj){
* @api public
*/
Server.prototype.dynamicHelpers = function(obj){
app.dynamicHelpers = function(obj){
utils.merge(this.dynamicViewHelpers, obj);
return this;
};
@@ -225,7 +290,7 @@ Server.prototype.dynamicHelpers = function(obj){
*
* Param mapping is used to provide pre-conditions to routes
* which us normalized placeholders. For example ":user_id" may
* attempt to load the user from the database, where as ":num" may
* attempt to load the user from the database, where as ":num" may
* pass the value through `parseInt(num, 10)`.
*
* When the callback function accepts only a single argument, the
@@ -234,7 +299,7 @@ Server.prototype.dynamicHelpers = function(obj){
* app.param('page', function(n){ return parseInt(n, 10); });
*
* After which "/users/:page" would automatically provide us with
* an integer for `req.params.page`. If desired we could use the callback
* an integer for `req.params.page`. If desired we could use the callback
* signature shown below, and immediately `next(new Error('invalid page'))`
* when `parseInt` fails.
*
@@ -263,7 +328,7 @@ Server.prototype.dynamicHelpers = function(obj){
* @api public
*/
Server.prototype.param = function(name, fn){
app.param = function(name, fn){
if (Array.isArray(name)) {
name.forEach(function(name){
this.param(name, fn);
@@ -284,7 +349,7 @@ Server.prototype.param = function(name, fn){
* @api public
*/
Server.prototype.error = function(fn){
app.error = function(fn){
this.errorHandlers.push(fn);
return this;
};
@@ -298,7 +363,7 @@ Server.prototype.error = function(fn){
* @api public
*/
Server.prototype.is = function(type, fn){
app.is = function(type, fn){
if (!fn) return this.isCallbacks[type];
this.isCallbacks[type] = fn;
return this;
@@ -314,7 +379,7 @@ Server.prototype.is = function(type, fn){
* @api public
*/
Server.prototype.set = function(setting, val){
app.set = function(setting, val){
if (val === undefined) {
if (this.settings.hasOwnProperty(setting)) {
return this.settings[setting];
@@ -335,7 +400,7 @@ Server.prototype.set = function(setting, val){
* @api public
*/
Server.prototype.enabled = function(setting){
app.enabled = function(setting){
return !!this.set(setting);
};
@@ -347,7 +412,7 @@ Server.prototype.enabled = function(setting){
* @api public
*/
Server.prototype.disabled = function(setting){
app.disabled = function(setting){
return !this.set(setting);
};
@@ -359,7 +424,7 @@ Server.prototype.disabled = function(setting){
* @api public
*/
Server.prototype.enable = function(setting){
app.enable = function(setting){
return this.set(setting, true);
};
@@ -371,7 +436,7 @@ Server.prototype.enable = function(setting){
* @api public
*/
Server.prototype.disable = function(setting){
app.disable = function(setting){
return this.set(setting, false);
};
@@ -384,7 +449,7 @@ Server.prototype.disable = function(setting){
* @api public
*/
Server.prototype.redirect = function(key, url){
app.redirect = function(key, url){
this.redirects[key] = url;
return this;
};
@@ -398,7 +463,7 @@ Server.prototype.redirect = function(key, url){
* @api public
*/
Server.prototype.configure = function(env, fn){
app.configure = function(env, fn){
if ('function' == typeof env) {
fn = env, env = 'all';
}
@@ -411,31 +476,21 @@ Server.prototype.configure = function(env, fn){
// Generate routing methods
function generateMethod(method){
Server.prototype[method] = function(path, fn){
app[method] = function(path){
var self = this;
// Ensure router is mounted
if (!this.__usedRouter) {
this.use(this.router);
// Lookup
if (1 == arguments.length) {
return this.router.lookup(path, 'all' == method
? null
: method);
}
// 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);
}
// Ensure router is mounted
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;
@@ -445,4 +500,4 @@ methods.forEach(generateMethod);
// Alias delete as "del"
Server.prototype.del = Server.prototype.delete;
app.del = app.delete;

View File

@@ -13,6 +13,18 @@ var connect = require('connect')
, HTTPServer = require('./http')
, https = require('https');
/**
* Expose `HTTPSServer`.
*/
exports = module.exports = HTTPSServer;
/**
* Server proto.
*/
var app = HTTPSServer.prototype;
/**
* Initialize a new `HTTPSServer` with the
* given `options`, and optional `middleware`.
@@ -22,7 +34,7 @@ var connect = require('connect')
* @api public
*/
var Server = exports = module.exports = function HTTPSServer(options, middleware){
function HTTPSServer(options, middleware){
connect.HTTPSServer.call(this, options, []);
this.init(middleware);
};
@@ -31,10 +43,10 @@ var Server = exports = module.exports = function HTTPSServer(options, middleware
* Inherit from `connect.HTTPSServer`.
*/
Server.prototype.__proto__ = connect.HTTPSServer.prototype;
app.__proto__ = connect.HTTPSServer.prototype;
// mixin HTTPServer methods
Object.keys(HTTPServer.prototype).forEach(function(method){
Server.prototype[method] = HTTPServer.prototype[method];
app[method] = HTTPServer.prototype[method];
});

View File

@@ -140,7 +140,7 @@ req.accepts = function(type){
req.param = function(name, defaultValue){
// route params like /user/:id
if (this.params.hasOwnProperty(name) && undefined !== this.params[name]) {
if (this.params && this.params.hasOwnProperty(name) && undefined !== this.params[name]) {
return this.params[name];
}
// query string params
@@ -190,13 +190,14 @@ req.param = function(name, defaultValue){
*/
req.flash = function(type, msg){
if (this.session === undefined) throw Error('req.flash() requires sessions');
var msgs = this.session.flash = this.session.flash || {};
if (type && msg) {
var i = 2
, args = arguments
, formatters = this.app.flashFormatters || {};
formatters.__proto__ = flashFormatters;
msg = utils.miniMarkdown(utils.htmlEscape(msg));
msg = utils.miniMarkdown(utils.escape(msg));
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
var formatter = formatters[format];
if (formatter) return formatter(args[i++]);

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) {
@@ -63,20 +63,24 @@ res.send = function(body, headers, status){
break;
case 'string':
if (!this.header('Content-Type')) {
this.charset = this.charset || 'utf-8';
this.contentType('.html');
}
break;
case 'boolean':
case 'object':
if (body instanceof Buffer) {
if (Buffer.isBuffer(body)) {
if (!this.header('Content-Type')) {
this.contentType('.bin');
}
} 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 + ');';
}
@@ -86,7 +90,7 @@ res.send = function(body, headers, status){
// populate Content-Length
if (!this.header('Content-Length')) {
this.header('Content-Length', body instanceof Buffer
this.header('Content-Length', Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body));
}
@@ -128,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
@@ -138,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);
};
/**
@@ -173,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');
@@ -181,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();
}
});
};
@@ -233,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);
};
/**
@@ -315,34 +334,44 @@ res.cookie = function(name, val, options){
res.redirect = function(url, status){
var app = this.app
, req = this.req
, base = app.set('home') || '/'
, status = status || 302
, body;
// Setup redirect map
var map = {
back: this.req.header('Referrer', base)
back: req.header('Referrer', base)
, home: base
};
// Support custom redirect map
map.__proto__ = this.app.redirects;
map.__proto__ = app.redirects;
// Attempt mapped redirect
var mapped = 'function' == typeof map[url]
? map[url](this.req, this)
? map[url](req, this)
: map[url];
// Perform redirect
url = mapped || url;
// Respect mount-point
if (app.route && !~url.indexOf('://')) {
url = join(app.route, url);
}
// Relative
if (!~url.indexOf('://')) {
// Respect mount-point
if (app.route) {
url = join(app.route, url);
}
// Absolute
var host = req.headers.host
, tls = req.connection.encrypted;
url = 'http' + (tls ? 's' : '') + '://' + host + url;
}
// Support text/{plain,html} by default
if (this.req.accepts('html')) {
if (req.accepts('html')) {
body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
this.header('Content-Type', 'text/html');
} else {
@@ -367,8 +396,28 @@ res.redirect = function(url, status){
*/
res.local = function(name, val){
this.locals = this.locals || {};
this._locals = this._locals || {};
return undefined === val
? this.locals[name]
: this.locals[name] = val;
? this._locals[name]
: this._locals[name] = val;
};
/**
* Assign several locals with the given `obj`,
* or return the locals.
*
* @param {Object} obj
* @return {Object|Undefined}
* @api public
*/
res.locals =
res.helpers = function(obj){
if (obj) {
for (var key in obj) {
this.local(key, obj[key]);
}
} else {
return this._locals;
}
};

329
lib/router/index.js Normal file
View File

@@ -0,0 +1,329 @@
/*!
* Express - router
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('../utils')
, parse = require('url').parse
, _methods = require('./methods')
, Route = require('./route');
/**
* Expose router.
*/
exports = module.exports = router;
/**
* Expose methods.
*/
exports.methods = _methods;
/**
* Provides Sinatra-like routing capabilities.
*
* @param {Function} fn
* @return {Function}
* @api private
*/
function router(fn){
var self = this
, methods = {}
, routes = {}
, params = {};
if (!fn) throw new Error('router provider requires a callback function');
// Generate method functions
_methods.forEach(function(method){
methods[method] = generateMethodFunction(method.toUpperCase());
});
// Alias del -> delete
methods.del = methods.delete;
// Apply callback to all methods
methods.all = function(){
var args = arguments;
_methods.forEach(function(name){
methods[name].apply(this, args);
});
return self;
};
// Register param callback
methods.param = function(name, fn){
params[name] = fn;
};
fn.call(this, methods);
function generateMethodFunction(name) {
var localRoutes = routes[name] = routes[name] || [];
return function(path, fn){
var keys = []
, middleware = [];
// slice middleware
if (arguments.length > 2) {
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
fn = middleware.pop();
middleware = utils.flatten(middleware);
}
fn.middleware = middleware;
if (!path) throw new Error(name + ' route requires a path');
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
var route = new Route(name, path, fn);
localRoutes.push(route);
return self;
};
}
function router(req, res, next){
var route
, self = this;
(function pass(i){
if (route = match(req, routes, i)) {
var i = 0
, keys = route.keys;
req.params = route.params;
// Param preconditions
(function param(err) {
try {
var key = keys[i++]
, val = req.params[key]
, fn = params[key];
if ('route' == err) {
pass(req._route_index + 1);
// Error
} else if (err) {
next(err);
// Param has callback
} else if (fn) {
// Return style
if (1 == fn.length) {
req.params[key] = fn(val);
param();
// Middleware style
} else {
fn(req, res, param, val);
}
// Finished processing params
} else if (!key) {
// route middleware
i = 0;
(function nextMiddleware(err){
var fn = route.callback.middleware[i++];
if ('route' == err) {
pass(req._route_index + 1);
} else if (err) {
next(err);
} else if (fn) {
fn(req, res, nextMiddleware);
} else {
route.callback.call(self, req, res, function(err){
if (err) {
next(err);
} else {
pass(req._route_index + 1);
}
});
}
})();
// More params
} else {
param();
}
} catch (err) {
next(err);
}
})();
} else if ('OPTIONS' == req.method) {
options(req, res, routes);
} else {
next();
}
})();
};
router.remove = function(path, method, ret){
var ret = ret || []
, route;
// method specific remove
if (method) {
method = method.toUpperCase();
if (routes[method]) {
for (var i = 0; i < routes[method].length; ++i) {
route = routes[method][i];
if (path == route.path) {
route.index = i;
routes[method].splice(i, 1);
ret.push(route);
--i;
}
}
}
// global remove
} else {
_methods.forEach(function(method){
router.remove(path, method, ret);
});
}
return ret;
};
router.lookup = function(path, method, ret){
ret = ret || [];
// method specific lookup
if (method) {
method = method.toUpperCase();
if (routes[method]) {
routes[method].forEach(function(route, i){
if (path == route.path) {
route.index = i;
ret.push(route);
}
});
}
// global lookup
} else {
_methods.forEach(function(method){
router.lookup(path, method, ret);
});
}
return ret;
};
router.match = function(url, method, ret){
var ret = ret || []
, i = 0
, route
, req;
// method specific matches
if (method) {
method = method.toUpperCase();
req = { url: url, method: method };
while (route = match(req, routes, i)) {
i = req._route_index + 1;
route.index = i;
ret.push(route);
}
// global matches
} else {
_methods.forEach(function(method){
router.match(url, method, ret);
});
}
return ret;
};
return router;
}
/**
* Respond to OPTIONS.
*
* @param {ServerRequest} req
* @param {ServerResponse} req
* @param {Array} routes
* @api private
*/
function options(req, res, routes) {
var pathname = parse(req.url).pathname
, body = optionsFor(pathname, routes).join(',');
res.send(body, { Allow: body });
}
/**
* Return OPTIONS array for the given `path`, matching `routes`.
*
* @param {String} path
* @param {Array} routes
* @return {Array}
* @api private
*/
function optionsFor(path, routes) {
return _methods.filter(function(method){
var arr = routes[method.toUpperCase()];
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i].regexp.test(path)) return true;
}
}).map(function(method){
return method.toUpperCase();
});
}
/**
* Attempt to match the given request to
* one of the routes. When successful
* a route function is returned.
*
* @param {ServerRequest} req
* @param {Object} routes
* @return {Function}
* @api private
*/
function match(req, routes, i) {
var captures
, method = req.method
, i = i || 0;
// pass HEAD to GET routes
if ('HEAD' == method) method = 'GET';
// routes for this method
if (routes = routes[method]) {
var url = parse(req.url)
, pathname = url.pathname;
// matching routes
for (var len = routes.length; i < len; ++i) {
var route = routes[i]
, fn = route.callback
, path = route.regexp
, keys = route.keys;
// match
if (captures = path.exec(pathname)) {
route.params = [];
for (var j = 1, l = captures.length; j < l; ++j) {
var key = keys[j-1],
val = 'string' == typeof captures[j]
? decodeURIComponent(captures[j])
: captures[j];
if (key) {
route.params[key] = val;
} else {
route.params.push(val);
}
}
req._route_index = i;
return route;
}
}
}
}

31
lib/router/methods.js Normal file
View File

@@ -0,0 +1,31 @@
/*!
* Express - router - methods
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Supported HTTP / WebDAV methods.
*/
module.exports = [
'get'
, 'post'
, 'put'
, 'delete'
, 'connect'
, 'options'
, 'trace'
, 'copy'
, 'lock'
, 'mkcol'
, 'move'
, 'propfind'
, 'proppatch'
, 'unlock'
, 'report'
, 'mkactivity'
, 'checkout'
, 'merge'
];

64
lib/router/route.js Normal file
View File

@@ -0,0 +1,64 @@
/*!
* Express - router - Route
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Expose `Route`.
*/
module.exports = Route;
/**
* Initialize `Route` with the given HTTP `method`, `path`,
* and callback `fn`.
*
* @param {String} method
* @param {String} path
* @param {Function} fn
* @api private
*/
function Route(method, path, fn) {
this.callback = fn;
this.path = path;
this.regexp = normalize(path, this.keys = []);
this.method = method;
}
/**
* Normalize the given path string,
* returning a regular expression.
*
* An empty array should be passed,
* which will contain the placeholder
* key names. For example "/user/:id" will
* then contain ["id"].
*
* @param {String|RegExp} path
* @param {Array} keys
* @return {RegExp}
* @api private
*/
function normalize(path, keys) {
if (path instanceof RegExp) return path;
path = path
.concat('/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push(key);
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || '([^/]+?)') + ')'
+ (optional || '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.+)');
return new RegExp('^' + path + '$', 'i');
}

View File

@@ -5,64 +5,6 @@
* MIT Licensed
*/
/**
* Module dependencies.
*/
var path = require('path')
, basename = path.basename
, dirname = path.dirname
, extname = path.extname;
/**
* Memory cache.
*/
var cache = {
basename: {}
, dirname: {}
, extname: {}
};
/**
* Cached basename.
*
* @param {String} path
* @return {String}
* @api private
*/
exports.basename = function(path){
return cache.basename[path]
|| (cache.basename[path] = basename(path));
};
/**
* Cached dirname.
*
* @param {String} path
* @return {String}
* @api private
*/
exports.dirname = function(path){
return cache.dirname[path]
|| (cache.dirname[path] = dirname(path));
};
/**
* Cached extname.
*
* @param {String} path
* @return {String}
* @api private
*/
exports.extname = function(path){
return cache.extname[path]
|| (cache.extname[path] = extname(path));
};
/**
* Merge object `b` with `a` giving precedence to
* values in object `a`.
@@ -88,6 +30,27 @@ exports.union = function(a, b){
return a;
};
/**
* Flatten the given `arr`.
*
* @param {Array} arr
* @return {Array}
* @api private
*/
exports.flatten = function(arr, ret){
var ret = ret || []
, len = arr.length;
for (var i = 0; i < len; ++i) {
if (Array.isArray(arr[i])) {
exports.flatten(arr[i], ret);
} else {
ret.push(arr[i]);
}
}
return ret;
};
/**
* Parse mini markdown implementation.
* The following conversions are supported,
@@ -117,7 +80,7 @@ exports.miniMarkdown = function(str){
* @api private
*/
exports.htmlEscape = function(html) {
exports.escape = function(html) {
return String(html)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')

View File

@@ -15,26 +15,17 @@ var path = require('path')
, basename = path.basename
, utils = require('connect').utils
, View = require('./view/view')
, Partial = require('./view/partial')
, partial = require('./view/partial')
, union = require('./utils').union
, merge = utils.merge
, http = require('http')
, res = http.ServerResponse.prototype;
/**
* Memory cache.
*
* @type Object
*/
var cache = {};
/**
* Expose constructors.
*/
exports = module.exports = View;
exports.Partial = Partial;
/**
* Export template engine registrar.
@@ -48,67 +39,105 @@ exports.register = View.register;
* @api private
*/
function renderPartial(res, view, options, locals, parent){
function renderPartial(res, view, options, parentLocals, parent){
var collection, object, locals;
// Inherit parent view extension when not present
if (parent && !~view.indexOf('.')) {
view += parent.extension;
}
// Allow collection to be passed as second param
if (options) {
if ('length' in options) {
options = { collection: options };
} else if (!options.collection && !options.locals && !options.object) {
options = { object: options };
// collection
if (options.collection) {
collection = options.collection;
delete options.collection;
} else if ('length' in options) {
collection = options;
options = {};
}
// locals
if (options.locals) {
locals = options.locals;
delete options.locals;
}
// object
if ('Object' != options.constructor.name) {
object = options;
options = {};
} else if (undefined != options.object) {
object = options.object;
delete options.object;
}
} else {
options = {};
}
// Inherit locals from parent
union(options, locals);
union(options, parentLocals);
// Merge locals
if (options.locals) {
merge(options, options.locals);
}
if (locals) merge(options, locals);
// Partials dont need layouts
options.renderPartial = true;
options.layout = false;
// Deduce name from view path
var name = options.as || Partial.resolveObjectName(view);
var name = options.as || partial.resolveObjectName(view);
// Render partial
function render(){
if (options.object) {
if (object) {
if ('string' == typeof name) {
options[name] = options.object;
options[name] = object;
} else if (name === global) {
merge(options, options.object);
merge(options, object);
} else {
options.scope = options.object;
options.scope = object;
}
}
return res.render(view, options, null, parent);
return res.render(view, options, null, parent, true);
}
// Collection support
var collection = options.collection;
if (collection) {
var len = collection.length
, buf = '';
delete options.collection;
, 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;
options.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();
@@ -116,7 +145,9 @@ function renderPartial(res, view, options, locals, parent){
};
/**
* Render `view` partial with the given `options`.
* Render `view` partial with the given `options`. Optionally a
* callback `fn(err, str)` may be passed instead of writing to
* the socket.
*
* Options:
*
@@ -131,16 +162,23 @@ function renderPartial(res, view, options, locals, parent){
* For example _video.html_ will have a object _video_ available to it.
*
* @param {String} view
* @param {Object|Array} options, collection, or object
* @param {Object|Array|Function} options, collection, callback, or object
* @param {Function} fn
* @return {String}
* @api public
*/
res.partial = function(view, options){
res.partial = function(view, options, fn){
var app = this.app
, options = options || {}
, parent = {};
// accept callback as second argument
if ('function' == typeof options) {
fn = options;
options = {};
}
// root "views" option
parent.dirname = app.set('views') || process.cwd() + '/views';
@@ -149,8 +187,24 @@ res.partial = function(view, options){
parent.extension = '.' + app.set('view engine');
}
var str = renderPartial(this, view, options, null, parent);
this.send(str);
// render the partial
try {
var str = renderPartial(this, view, options, null, parent);
} catch (err) {
if (fn) {
fn(err);
} else {
this.req.next(err);
}
return;
}
// callback or transfer
if (fn) {
fn(null, str);
} else {
this.send(str);
}
};
/**
@@ -162,6 +216,7 @@ res.partial = function(view, options){
*
* - `scope` Template evaluation context (the value of `this`)
* - `debug` Output debugging information
* - `status` Response status code
*
* @param {String} view
* @param {Object|Function} options or callback function
@@ -169,25 +224,49 @@ res.partial = function(view, options){
* @api public
*/
res.render = function(view, opts, fn, parent){
res.render = function(view, opts, fn, parent, sub){
// support callback function as second arg
if (typeof opts === 'function') {
if ('function' == typeof opts) {
fn = opts, opts = null;
}
try {
return this._render(view, opts, fn, parent, sub);
} catch (err) {
// callback given
if (fn) {
fn(err);
// unwind to root call to prevent
// several next(err) calls
} else if (sub) {
throw err;
// root template, next(err)
} else {
this.req.next(err);
}
}
};
// private render()
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';
// cache id
var cid = view + (parent ? ':' + parent.path : '');
// merge "view options"
if (viewOptions) merge(options, viewOptions);
// merge res.locals
if (this.locals) merge(options, this.locals);
// merge res._locals
if (this._locals) merge(options, this._locals);
// merge render() options
if (opts) merge(options, opts);
@@ -195,10 +274,10 @@ res.render = function(view, opts, fn, parent){
// merge render() .locals
if (opts && opts.locals) merge(options, opts.locals);
// Defaults
var self = this
, root = app.set('views') || process.cwd() + '/views'
, partial = options.renderPartial
// status support
if (options.status) this.statusCode = options.status;
var partial = options.renderPartial
, layout = options.layout;
// Layout support
@@ -218,32 +297,8 @@ res.render = function(view, opts, fn, parent){
// "view engine" setting
options.defaultEngine = app.set('view engine');
// Populate view
// TODO: move this logic into the protos
var orig = view = partial
? new Partial(view, options)
: new View(view, options);
// Ensure view exists, otherwise try _ prefix
if (!view.exists) {
view = partial
? new Partial(view.prefixPath, options)
: new View(view.prefixPath, options);
}
// Ensure view _ prefix exists, otherwise try index
if (!view.exists) {
view = partial
? new Partial(orig.indexPath, options)
: new View(orig.indexPath, options);
}
// Ensure view exists, or try ../index
if (!view.exists && !options.isLayout) {
view = partial
? new Partial(orig.upIndexPath, options)
: new View(orig.upIndexPath, options);
}
// charset option
if (options.charset) this.charset = options.charset;
// Dynamic helper support
if (false !== options.dynamicHelpers) {
@@ -270,37 +325,88 @@ res.render = function(view, opts, fn, parent){
return renderPartial(self, path, opts, options, view);
};
function error(err) {
if (fn) {
fn(err);
} else {
self.req.next(err);
// cached view
if (app.cache[cid]) {
view = app.cache[cid];
options.filename = view.path;
// resolve view
} else {
var orig = view = new View(view, 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[cid] = view;
}
// Attempt render
try {
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);
} catch (err) {
return error(err);
}
// layout helper
options.layout = function(path){
layout = path;
};
// Layout support
// render
var str = view.fn.call(options.scope, options);
// layout expected
if (layout) {
options.isLayout = true;
options.layout = false;
options.body = str;
options.relative = false;
self.render(layout, options, fn, view);
this.render(layout, options, fn, view, true);
// partial return
} else if (partial) {
return str;
// render complete, and
// callback given
} else if (fn) {
fn(null, str);
// respond
} else {
this.send(str);
}
};
}
/**
* Hint at view path resolution, outputting the
* paths that Express has tried.
*
* @api private
*/
function hintAtViewPaths(view, options) {
console.error();
console.error('failed to locate view "' + view.view + '", tried:');
console.error(' - ' + new View(view.path, options).path);
console.error(' - ' + new View(view.prefixPath, options).path);
console.error(' - ' + new View(view.indexPath, options).path);
if (!options.isLayout) console.error(' - ' + new View(view.upIndexPath, options).path);
if (options.isLayout) console.error(' - ' + new View(view.rootPath, options).path);
console.error();
}

View File

@@ -5,38 +5,12 @@
* MIT Licensed
*/
/**
* Module dependencies.
*/
var View = require('./view')
, basename = require('path').basename;
/**
* Memory cache.
*/
var cache = {};
/**
* Initialize a new `Partial` for the given `view` and `options`.
*
* @param {String} view
* @param {Object} options
* @api private
*/
var Partial = exports = module.exports = function Partial(view, options) {
options = options || {};
View.call(this, view, options);
};
/**
* Inherit from `View.prototype`.
*/
Partial.prototype.__proto__ = View.prototype;
/**
* Resolve partial object name from the view path.
*

View File

@@ -9,25 +9,25 @@
* Module dependencies.
*/
var utils = require('../utils')
, extname = utils.extname
, dirname = utils.dirname
, basename = utils.basename
var path = require('path')
, extname = path.extname
, dirname = path.dirname
, basename = path.basename
, fs = require('fs')
, stat = fs.statSync;
/**
* Memory cache.
* Expose `View`.
*/
exports = module.exports = View;
/**
* Require cache.
*/
var cache = {};
/**
* Existance cache.
*/
var exists = {};
/**
* Initialize a new `View` with the given `view` path and `options`.
*
@@ -36,9 +36,8 @@ var exists = {};
* @api private
*/
var View = exports = module.exports = function View(view, options) {
function View(view, options) {
options = options || {};
// TODO: more caching
this.view = view;
this.root = options.root;
this.relative = false !== options.relative;
@@ -47,6 +46,7 @@ var View = exports = module.exports = function View(view, options) {
this.basename = basename(view);
this.engine = this.resolveEngine();
this.extension = '.' + this.engine;
this.name = this.basename.replace(this.extension, '');
this.path = this.resolvePath();
this.dirname = dirname(this.path);
};
@@ -59,16 +59,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;
}
});
@@ -134,6 +129,18 @@ View.prototype.__defineGetter__('templateEngine', function(){
return cache[ext] || (cache[ext] = require(this.engine));
});
/**
* Return root path alternative.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('rootPath', function(){
this.relative = false;
return this.resolvePath();
});
/**
* Return index path alternative.
*
@@ -148,14 +155,14 @@ View.prototype.__defineGetter__('indexPath', function(){
});
/**
* Return ../index path alternative.
* Return ../<name>/index path alternative.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('upIndexPath', function(){
return this.dirname + '/index' + this.extension;
return this.dirname + '/../' + this.name + '/index' + this.extension;
});
/**

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.0.0beta2",
"version": "2.3.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.0.1 < 2.0.0",
"connect": ">= 1.4.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

@@ -4,43 +4,41 @@ var fs = require('fs'),
file = process.argv[2];
if (file) {
var js = fs.readFileSync(file, 'utf8'),
headers = js.match(/<h3 id="(.*?)">(.*?)<\/h3>/g),
toc = ['<ul id="toc">'],
sections = {};
if (js.indexOf('id="toc"') < 0) {
headers.forEach(function(header){
var captures = header.match(/id="(.*?)">(.*?)</),
id = captures[1],
title = captures[2].replace(/\(.*?\)/, '()')
if (~title.indexOf('.')) {
var parts = title.split('.'),
recv = parts.shift(),
method = parts.shift();
if (recv == 'app') recv = 'Server';
if (recv == 'req') recv = 'Request';
if (recv == 'res') recv = 'Response';
sections[recv] = sections[recv] || [];
sections[recv].push('<li><a href="#' + id + '">' + method + '</a></li>');
} else {
toc.push('<li><a href="#' + id + '">' + title + '</a></li>');
}
});
toc.push(renderSections());
toc.push('</ul>');
js = js.replace('<div id="container">', '<div id="container">' + toc.join('\n'));
fs.writeFileSync(file, js);
}
var html = fs.readFileSync(file, 'utf8')
, toc = ['<ul id="toc">']
, sections = {};
html = html.replace(/<h3>(.*?)<\/h3>/g, function(_, title){
var id = title.toLowerCase().replace(' ', '-').replace(/\(.*?\)/, '()');
if (~title.indexOf('.')) {
var parts = title.split('.')
, recv = parts.shift()
, method = parts.shift().replace(/\(.*?\)/, '()');
if (recv == 'app') recv = 'Server';
if (recv == 'req') recv = 'Request';
if (recv == 'res') recv = 'Response';
sections[recv] = sections[recv] || [];
sections[recv].push('<li><a href="#' + id + '">' + method + '</a></li>');
} else {
toc.push('<li><a href="#' + id + '">' + title + '</a></li>');
}
return '<h3 id="' + id + '">' + title + '</h3>';
});
toc.push(renderSections());
toc.push('</ul>');
html = html.replace('<div id="container">', '<div id="container">' + toc.join('\n'));
fs.writeFileSync(file, html);
}
function renderSections() {
var buf = [];
Object.keys(sections).forEach(function(section){
var methods = sections[section],
a = '<a href="#" class="toggle">+</a> <a class="section-title" href="#">' + section + '</a>';
buf.push('<li>' + a + '<ul class="section" id="section-' + section + '">');
buf.push(methods.join('\n'));
buf.push('</ul></li>');
});
return buf.join('\n');
var buf = [];
Object.keys(sections).forEach(function(section){
var methods = sections[section],
a = '<a href="#" class="toggle">+</a> <a class="section-title" href="#">' + section + '</a>';
buf.push('<li>' + a + '<ul class="section" id="section-' + section + '">');
buf.push(methods.join('\n'));
buf.push('</ul></li>');
});
return buf.join('\n');
}

View File

@@ -6,13 +6,20 @@
var express = require('express')
, connect = require('connect')
, assert = require('assert')
, should = require('should');
, should = require('should')
, Route = express.Route;
module.exports = {
'test inheritance': function(){
var server = express.createServer();
server.should.be.an.instanceof(connect.HTTPServer);
},
'test constructor exports': function(){
express.should.have.property('HTTPServer');
express.should.have.property('HTTPSServer');
express.should.have.property('Route');
},
'test connect middleware autoloaders': function(){
express.errorHandler.should.equal(connect.errorHandler);
@@ -183,12 +190,6 @@ module.exports = {
{ body: 'Internal Server Error' });
},
'test error() with route-specific middleware': function(){
var app = express.createServer();
},
'test next()': function(){
var app = express.createServer();
@@ -232,8 +233,8 @@ module.exports = {
'test #configure()': function(beforeExit){
var calls = [];
process.env.NODE_ENV = 'development';
var server = express.createServer();
server.set('env', 'development');
// Config blocks
var ret = server.configure(function(){
@@ -298,29 +299,38 @@ module.exports = {
},
'test #set()': function(){
var app = express.createServer();
var ret = app.set('title', 'My App').set('something', 'else');
ret.should.equal(app);
app.set('title').should.equal('My App');
app.set('something').should.equal('else');
var app = express.createServer();
var ret = app.set('title', 'My App').set('something', 'else');
ret.should.equal(app);
app.set('title').should.equal('My App');
app.set('something').should.equal('else');
},
'test .settings': function(){
var app = express.createServer();
app.set('title', 'My App');
app.settings.title.should.equal('My App');
app.settings.title = 'Something Else';
app.settings.title.should.equal('Something Else');
app.set('title').should.equal('Something Else');
},
'test #enable()': function(){
var app = express.createServer();
var ret = app.enable('some feature');
ret.should.equal(app);
app.set('some feature').should.be.true;
app.enabled('some feature').should.be.true;
app.enabled('something else').should.be.false;
var app = express.createServer();
var ret = app.enable('some feature');
ret.should.equal(app);
app.set('some feature').should.be.true;
app.enabled('some feature').should.be.true;
app.enabled('something else').should.be.false;
},
'test #disable()': function(){
var app = express.createServer();
var ret = app.disable('some feature');
ret.should.equal(app);
app.set('some feature').should.be.false;
app.disabled('some feature').should.be.true;
app.disabled('something else').should.be.true;
var app = express.createServer();
var ret = app.disable('some feature');
ret.should.equal(app);
app.set('some feature').should.be.false;
app.disabled('some feature').should.be.true;
app.disabled('something else').should.be.true;
},
'test middleware precedence': function(){
@@ -401,132 +411,29 @@ module.exports = {
{ url: '/regular' },
{ body: 'hey' });
},
'test route middleware': function(){
var app = express.createServer();
function allow(role) {
return function(req, res, next) {
// this is totally not real, dont use this :)
// for tests only
if (req.headers['x-role'] == role) {
next();
} else {
res.send(401);
}
}
}
function restrictAge(age) {
return function(req, res, next){
if (req.headers['x-age'] >= age) {
next();
} else {
res.send(403);
}
}
}
'test .app property after returning control to parent': function() {
var app = express.createServer()
, blog = express.createServer();
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
res.send(200);
// Mounted servers did not restore `req.app` and `res.app` when
// passing control back to parent via `out()` in `#handle()`.
blog.get('/', function(req, res, next){
req.app.should.equal(blog);
res.app.should.equal(blog);
next();
});
app.get('/booze', [allow('member')], restrictAge(18), function(req, res){
res.send(200);
});
app.use(blog);
app.get('/tobi', [allow('member')], [[restrictAge(18)]], function(req, res){
res.send(200);
});
['xxx', 'booze', 'tobi'].forEach(function(thing){
assert.response(app,
{ url: '/' + thing },
{ body: 'Unauthorized', status: 401 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member' }},
{ body: 'Forbidden', status: 403 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
{ body: 'OK', status: 200 });
});
},
'test named capture groups': function(){
var app = express.createServer();
app.get('/user/:id([0-9]{2,10})', function(req, res){
res.send('user ' + req.params.id);
app.use(function(req, res, next) {
res.send((res.app === app) ? 'restored' : 'not-restored');
});
assert.response(app,
{ url: '/user/12' },
{ body: 'user 12' });
assert.response(app,
{ url: '/user/ab' },
{ body: 'Cannot GET /user/ab' });
},
'test .param()': function(){
var app = express.createServer();
var users = [
{ name: 'tj' }
, { name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
, { name: 'bandit' }
];
function integer(n){ return parseInt(n, 10); };
app.param(['to', 'from'], integer);
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
next();
} else {
next(new Error('failed to find user'));
}
});
app.get('/user/:user', function(req, res, next){
res.send('user ' + req.user.name);
});
app.get('/users/:from-:to', function(req, res, next){
var names = users.slice(req.params.from, req.params.to).map(function(user){
return user.name;
});
res.send('users ' + names.join(', '));
});
assert.response(app,
{ url: '/user/0' },
{ body: 'user tj' });
assert.response(app,
{ url: '/user/1' },
{ body: 'user tobi' });
assert.response(app,
{ url: '/users/0-3' },
{ body: 'users tj, tobi, loki' });
},
'test OPTIONS': function(){
var app = express.createServer();
app.get('/', function(){});
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
assert.response(app,
{ url: '/', method: 'OPTIONS' },
{ headers: { Allow: 'GET' }});
assert.response(app,
{ url: '/user/12', method: 'OPTIONS' },
{ headers: { Allow: 'GET,PUT' }});
{ url: '/' },
{ body: 'restored' }
);
}
};
};

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

@@ -0,0 +1 @@
p two

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

@@ -0,0 +1 @@
p Testing

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

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

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

@@ -0,0 +1 @@
p one

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

@@ -0,0 +1 @@
li #{title} #{item}

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/nested/partial.jade vendored Normal file
View File

@@ -0,0 +1 @@
!= partial('stats', { hits: 15, misses: 1 })

1
test/fixtures/nested/partial2.jade vendored Normal file
View File

@@ -0,0 +1 @@
!= partial('messages')

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

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

2
test/fixtures/stats.jade vendored Normal file
View File

@@ -0,0 +1,2 @@
p Hits #{hits}
p Misses #{misses}

View File

@@ -11,7 +11,7 @@ var express = require('express')
module.exports = {
'test #send()': function(){
var app = express.createServer();
app.get('/html', function(req, res){
res.send('<p>test</p>', { 'Content-Language': 'en' });
});
@@ -30,7 +30,7 @@ module.exports = {
app.get('/text', function(req, res){
res.header('X-Foo', 'bar');
res.contentType('.txt');
res.contentType('txt');
res.send('wahoo');
});
@@ -49,59 +49,72 @@ module.exports = {
app.get('/noargs', function(req, res, next){
res.send();
});
app.get('/undefined', function(req, res, next){
res.send(undefined);
});
app.get('/bool', function(req, res, next){
res.send(true);
});
assert.response(app,
{ url: '/bool' },
{ body: 'true'
, headers: { 'Content-Type': 'application/json; charset=utf-8' }});
assert.response(app,
{ url: '/html' },
{ body: '<p>test</p>'
, headers: {
'Content-Language': 'en'
, 'Content-Type': 'text/html'
, 'Content-Type': 'text/html; charset=utf-8'
}});
assert.response(app,
{ url: '/json' },
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=test' },
{ body: 'test({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ 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'
}});
assert.response(app,
{ url: '/jsonp?callback=invalid()[]' },
{ body: 'invalid({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/json?callback=test' },
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/text' },
{ body: 'wahoo'
@@ -109,22 +122,22 @@ module.exports = {
'Content-Type': 'text/plain'
, 'X-Foo': 'bar'
}});
assert.response(app,
{ url: '/status' },
{ body: 'Not Found'
, status: 404
, headers: { 'Content-Type': 'text/plain' }});
assert.response(app,
{ url: '/error' },
{ body: 'Oh shit!'
, status: 500
, headers: {
'Content-Type': 'text/plain'
'Content-Type': 'text/plain; charset=utf-8'
, 'Content-Length': '8'
}});
assert.response(app,
{ url: '/buffer' },
{ body: 'wahoo!'
@@ -132,13 +145,20 @@ module.exports = {
'Content-Type': 'application/octet-stream'
, 'Content-Length': '6'
}});
assert.response(app,
{ url: '/noargs' },
{ status: 204 }, function(res){
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(){
@@ -154,7 +174,15 @@ module.exports = {
res.contentType('json');
res.send('{"foo":"bar"}');
});
app.get('/literal', function(req, res){
res.contentType('application/json');
res.send('{"foo":"bar"}')
});
assert.response(app,
{ url: '/literal' },
{ headers: { 'Content-Type': 'application/json' }});
assert.response(app,
{ url: '/html' },
{ body: '<p>yay</p>', headers: { 'Content-Type': 'text/html' }});
@@ -179,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(){
@@ -239,58 +269,58 @@ module.exports = {
});
assert.response(app,
{ url: '/html', headers: { Accept: 'text/html,text/plain' }},
{ url: '/html', headers: { Accept: 'text/html,text/plain', Host: 'foo.com' }},
{ body: '<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>' });
assert.response(app,
{ url: '/', headers: { Accept: 'text/plain' }},
{ url: '/', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Permanently. Redirecting to http://google.com'
, status: 301, headers: { Location: 'http://google.com' }});
assert.response(app,
{ url: '/back', headers: { Accept: 'text/plain' }},
{ body: 'Moved Temporarily. Redirecting to /'
, status: 302, headers: { Location: '/', 'Content-Type': 'text/plain' }});
{ url: '/back', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/'
, status: 302, headers: { Location: 'http://foo.com/', 'Content-Type': 'text/plain' }});
assert.response(app,
{ url: '/back', headers: { Referer: '/foo', Accept: 'text/plain' }},
{ body: 'Moved Temporarily. Redirecting to /foo'
, status: 302, headers: { Location: '/foo' }});
{ url: '/back', headers: { Referer: '/foo', Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/foo'
, status: 302, headers: { Location: 'http://foo.com/foo' }});
assert.response(app,
{ url: '/back', headers: { Referrer: '/foo', Accept: 'text/plain' }},
{ body: 'Moved Temporarily. Redirecting to /foo'
, status: 302, headers: { Location: '/foo' }});
{ url: '/back', headers: { Referrer: '/foo', Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/foo'
, status: 302, headers: { Location: 'http://foo.com/foo' }});
assert.response(app,
{ url: '/home', headers: { Accept: 'text/plain' } },
{ body: 'Moved Temporarily. Redirecting to /'
, status: 302, headers: { Location: '/' }});
{ url: '/home', headers: { Accept: 'text/plain', Host: 'foo.com' } },
{ body: 'Moved Temporarily. Redirecting to http://foo.com/'
, status: 302, headers: { Location: 'http://foo.com/' }});
assert.response(app2,
{ url: '/', headers: { Accept: 'text/plain' }},
{ url: '/', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Permanently. Redirecting to http://google.com'
, status: 301, headers: { Location: 'http://google.com' }});
assert.response(app2,
{ url: '/back', headers: { Accept: 'text/plain' }},
{ body: 'Moved Temporarily. Redirecting to /blog'
, status: 302, headers: { Location: '/blog' }});
{ url: '/back', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog'
, status: 302, headers: { Location: 'http://foo.com/blog' }});
assert.response(app2,
{ url: '/home', headers: { Accept: 'text/plain' }},
{ body: 'Moved Temporarily. Redirecting to /blog'
, status: 302, headers: { Location: '/blog' }});
{ url: '/home', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog'
, status: 302, headers: { Location: 'http://foo.com/blog' }});
assert.response(app2,
{ url: '/google', headers: { Accept: 'text/plain' }},
{ url: '/google', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://google.com'
, status: 302, headers: { Location: 'http://google.com' }});
assert.response(app2,
{ url: '/user/12', headers: { Accept: 'text/plain' }},
{ body: 'Moved Temporarily. Redirecting to /user/12/blog'
, status: 302, headers: { Location: '/user/12/blog', 'X-Foo': 'bar' }});
{ url: '/user/12', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/user/12/blog'
, status: 302, headers: { Location: 'http://foo.com/user/12/blog', 'X-Foo': 'bar' }});
},
'test #redirect() when mounted': function(){
@@ -309,9 +339,9 @@ module.exports = {
app.use('/blog', blog);
assert.response(app,
{ url: '/blog/posts' },
{ url: '/blog/posts', headers: { Host: 'foo.com' }},
{ status: 302
, headers: { Location: '/blog/posts/all' }});
, headers: { Location: 'http://foo.com/blog/posts/all' }});
},
'test #sendfile()': function(){
@@ -403,7 +433,7 @@ module.exports = {
{ url: '/large.json' },
{ headers: {
'Accept-Ranges': 'bytes'
, 'Cache-Control': 'public max-age=3600'
, 'Cache-Control': 'public, max-age=3600'
}});
beforeExit(function(){
@@ -529,7 +559,7 @@ module.exports = {
});
assert.response(app,
{ url: '/' },
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=yes; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else']);
@@ -540,7 +570,7 @@ module.exports = {
var app = express.createServer();
app.get('/', function(req, res){
res.clearCookie('rememberme');
res.clearCookie('rememberme', { path: '/foo' });
res.redirect('/');
});
@@ -548,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']);
});
},
@@ -562,5 +592,18 @@ module.exports = {
assert.response(app,
{ url: '/', method: 'HEAD' },
{ body: '', headers: { 'Content-Length': 11 }});
},
'test .charset with res.send()': function(){
var app = express.createServer();
app.get('/', function(req, res){
res.charset = 'ISO-8859-1';
res.send('<p>hey</p>');
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
}
};

245
test/router.test.js Normal file
View File

@@ -0,0 +1,245 @@
/**
* Module dependencies.
*/
var express = require('express')
, connect = require('connect')
, assert = require('assert')
, should = require('should')
, Route = express.Route;
module.exports = {
'test route middleware': function(beforeExit){
var app = express.createServer()
, calls = 0;
function allow(role) {
return function(req, res, next) {
// this is totally not real, dont use this :)
// for tests only
if (req.headers['x-role'] == role) {
next();
} else {
res.send(401);
}
}
}
function restrictAge(age) {
return function(req, res, next){
if (req.headers['x-age'] >= age) {
next();
} else {
res.send(403);
}
}
}
app.param('user', function(req, res, next, user){
++calls;
next();
});
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
res.send(200);
});
app.get('/booze', [allow('member')], restrictAge(18), function(req, res){
res.send(200);
});
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', 'user/tj'].forEach(function(thing){
assert.response(app,
{ url: '/' + thing },
{ body: 'Unauthorized', status: 401 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member' }},
{ body: 'Forbidden', status: 403 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
{ body: 'OK', status: 200 });
});
beforeExit(function(){
calls.should.equal(3);
});
},
'test named capture groups': function(){
var app = express.createServer();
app.get('/user/:id([0-9]{2,10})', function(req, res){
res.send('user ' + req.params.id);
});
assert.response(app,
{ url: '/user/12' },
{ body: 'user 12' });
assert.response(app,
{ url: '/user/ab' },
{ body: 'Cannot GET /user/ab' });
},
'test .param()': function(){
var app = express.createServer();
var users = [
{ name: 'tj' }
, { name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
, { name: 'bandit' }
];
function integer(n){ return parseInt(n, 10); };
app.param(['to', 'from'], integer);
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
next();
} else {
next(new Error('failed to find user'));
}
});
app.get('/user/:user', function(req, res, next){
res.send('user ' + req.user.name);
});
app.get('/users/:from-:to', function(req, res, next){
var names = users.slice(req.params.from, req.params.to).map(function(user){
return user.name;
});
res.send('users ' + names.join(', '));
});
assert.response(app,
{ url: '/user/0' },
{ body: 'user tj' });
assert.response(app,
{ url: '/user/1' },
{ body: 'user tobi' });
assert.response(app,
{ url: '/users/0-3' },
{ body: 'users tj, tobi, loki' });
},
'test OPTIONS': function(){
var app = express.createServer();
app.get('/', function(){});
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
assert.response(app,
{ url: '/', method: 'OPTIONS' },
{ headers: { Allow: 'GET' }});
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(){});
var route = app.get('/user/:id')[0]
route.should.be.an.instanceof(Route);
route.callback.should.be.a('function');
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('GET');
route.index.should.equal(1);
route.keys.should.eql(['id']);
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);
app.lookup('/user/:id').should.have.length(2);
},
'test app.remove': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user', function(){});
app.put('/user', function(){});
app.get('/user').should.have.length(2);
var removed = app.remove.get('/user');
removed.should.have.length(2);
var removed = app.remove.get('/user');
removed.should.have.length(0);
app.get('/user').should.have.length(0);
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.del('/user/:id', function(){});
app.remove.all('/user/:id').should.have.length(3);
app.remove.all('/user/:id').should.have.length(0);
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.del('/user/:id', function(){});
app.remove('/user/:id').should.have.length(3);
},
'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(){});
var route = app.match.get('/user/12')[0];
route.should.be.an.instanceof(Route);
route.callback.should.be.a('function');
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('GET');
route.index.should.equal(2);
route.keys.should.eql(['id']);
route.params.id.should.equal('12');
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);
app.match('/user/123').should.have.length(3);
}
};

View File

@@ -8,7 +8,7 @@ var express = require('express')
, assert = require('assert')
, should = require('should')
, View = require('../lib/view')
, Partial = View.Partial
, partial = require('../lib/view/partial')
/**
* Create a test server with views set to ./fixtures.
@@ -93,8 +93,8 @@ module.exports = {
view.templateEngine.should.equal(require('jade'));
},
'test Partial.resolveObjectName()': function(){
var resolve = Partial.resolveObjectName;
'test partial.resolveObjectName()': function(){
var resolve = partial.resolveObjectName;
resolve('/path/to/user.ejs').should.equal('user');
resolve('/path/to/user-post.ejs').should.equal('userPost');
resolve('/path/to/user post.ejs').should.equal('userPost');
@@ -102,92 +102,80 @@ module.exports = {
resolve('forum thread post.ejs').should.equal('forumThreadPost');
},
'test Partial#path for partials': function(){
var fixtures = __dirname + '/fixtures';
var view = new Partial('user.jade', { root: fixtures });
view.path.should.equal(fixtures + '/user.jade');
var view = new Partial('user', { parentView: view, root: fixtures });
view.path.should.equal(fixtures + '/user.jade');
var view = new Partial('forum/thread', { parentView: view });
view.path.should.equal(fixtures + '/forum/thread.jade');
var view = new Partial('forum/thread.jade', { root: fixtures });
view.path.should.equal(fixtures + '/forum/thread.jade');
var view = new Partial('thread', { parentView: view });
view.path.should.equal(fixtures + '/forum/thread.jade');
},
'test #render()': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res){
res.render('index.jade', { layout: false });
});
app.get('/jade', function(req, res){
res.render('index', { layout: false });
});
app.get('/haml', function(req, res){
res.render('hello.haml', { layout: false });
});
app.get('/callback/layout/no-options', function(req, res){
res.render('hello.jade', function(err, str){
assert.ok(!err);
res.send(str.replace(':(', ':)'));
});
});
app.get('/callback/layout', function(req, res){
res.render('hello.jade', {}, function(err, str){
assert.ok(!err);
res.send(str.replace(':(', ':)'));
});
});
app.get('/callback', function(req, res){
res.render('hello.haml', { layout: false }, function(err, str){
assert.ok(!err);
res.send(str.replace('Hello World', ':)'));
});
});
app.get('/invalid', function(req, res){
res.render('invalid.jade', { layout: false });
});
app.get('/invalid-async', function(req, res){
process.nextTick(function(){
res.render('invalid.jade', { layout: false });
});
});
app.get('/error', function(req, res){
res.render('invalid.jade', { layout: false }, function(err){
res.send(err.arguments[0]);
});
});
app.get('/absolute', function(req, res){
res.render(__dirname + '/fixtures/index.jade', { layout: false });
});
app.get('/ferret', function(req, res){
res.render('ferret', { layout: false, ferret: { name: 'Tobi' }});
});
app.get('/status', function(req, res){
res.render('hello.jade', { status: 500 });
});
assert.response(app,
{ url: '/status' },
{ status: 500 });
assert.response(app,
{ url: '/ferret' },
{ body: '<li class="ferret">Tobi</li>' });
assert.response(app,
{ url: '/' },
{ body: '<p>Welcome</p>', headers: { 'Content-Type': 'text/html' }});
{ body: '<p>Welcome</p>', headers: { 'Content-Type': 'text/html; charset=utf-8' }});
assert.response(app,
{ url: '/jade' },
{ body: '<p>Welcome</p>' });
@@ -226,14 +214,14 @@ module.exports = {
'test #render() layout': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res){
res.render('index.jade');
});
app.get('/jade', function(req, res){
res.render('index');
});
assert.response(app,
{ url: '/' },
{ body: '<html><body><p>Welcome</p></body></html>' });
@@ -242,7 +230,7 @@ module.exports = {
'test #render() specific layout': function(beforeExit){
var app = create()
, called;
app.get('/', function(req, res){
res.render('index.jade', { layout: 'cool-layout.jade' }, function(err, html){
called = true;
@@ -261,7 +249,7 @@ module.exports = {
app.get('/nope', function(req, res){
res.render('index.jade', { layout: 'nope.jade' });
});
assert.response(app,
{ url: '/' },
{ body: '<cool><p>Welcome</p></cool>' });
@@ -277,10 +265,10 @@ module.exports = {
assert.response(app,
{ url: '/nope' },
function(res){
assert.ok(~res.body.indexOf('Error: E'));
assert.ok(~res.body.indexOf('nope/index.jade'));
assert.ok(~res.body.indexOf('Error: failed to locate view'));
assert.ok(~res.body.indexOf('nope'));
});
beforeExit(function(){
assert.ok(called, 'Layout callback never called');
});
@@ -299,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');
@@ -315,17 +316,17 @@ module.exports = {
'test #render() view helpers': function(beforeExit){
var app = create()
, calls = 0;
app.locals({
lastName: 'holowaychuk'
});
app.helpers({
greetings: function(sess, lastName){
return 'Hello ' + sess.name + ' ' + lastName;
}
});
var ret = app.dynamicHelpers({
session: function(req, res){
++calls;
@@ -335,19 +336,19 @@ module.exports = {
return req.session;
}
});
assert.equal(app, ret, 'Server#helpers() is not chainable');
app.get('/', function(req, res){
req.session = { name: 'tj' };
res.render('dynamic-helpers.jade', { layout: false });
});
app.get('/ejs', function(req, res){
req.session = { name: 'tj' };
res.render('dynamic-helpers.ejs', { layout: false });
});
app.get('/precedence', function(req, res){
req.session = { name: 'tj' };
res.render('dynamic-helpers.jade', {
@@ -364,17 +365,30 @@ module.exports = {
assert.response(app,
{ url: '/precedence' },
{ body: '<html><body><p>Hello tj foobar</p></body></html>' });
beforeExit(function(){
assert.equal(3, calls);
});
},
'test #partial()': function(){
'test #partial() collection object': function(){
var app = create();
app.set('view engine', 'jade');
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();
app.set('view engine', 'jade');
// Auto-assigned local w/ collection option
app.get('/', function(req, res){
res.render('items.jade', { items: ['one', 'two'] });
@@ -383,7 +397,7 @@ module.exports = {
assert.response(app,
{ url: '/' },
{ body: '<html><body><ul><li>one</li><li>two</li></ul></body></html>' });
// Auto-assigned local w/ collection array
var movies = [
{ title: 'Nightmare Before Christmas', director: 'Tim Burton' },
@@ -392,7 +406,7 @@ module.exports = {
app.get('/movies', function(req, res){
res.render('movies.jade', { movies: movies });
});
var html = [
'<html>',
'<body>',
@@ -409,11 +423,11 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/movies' },
{ body: html });
// as: str collection option
app.get('/user', function(req, res){
res.partial('user', {
@@ -425,7 +439,7 @@ module.exports = {
assert.response(app,
{ url: '/user' },
{ body: '<p>tj</p>' });
// as: with object collection
app.get('/user/object', function(req, res){
res.partial('user.jade', {
@@ -437,7 +451,7 @@ module.exports = {
assert.response(app,
{ url: '/user' },
{ body: '<p>tj</p>' });
// as: this collection option
app.get('/person', function(req, res){
res.partial('person.jade', {
@@ -450,7 +464,7 @@ module.exports = {
assert.response(app,
{ url: '/person' },
{ body: '<p>name: tj</p>' });
// as: global collection option
app.get('/videos', function(req, res){
res.partial('video.jade', {
@@ -458,7 +472,7 @@ module.exports = {
collection: movies
});
});
assert.response(app,
{ url: '/videos' },
{ body: '<p>Tim Burton</p><p>James Cameron</p>' });
@@ -485,7 +499,7 @@ module.exports = {
assert.response(app,
{ url: '/movie' },
{ body: '<li><div class="title">Nightmare Before Christmas</div><div class="director">Tim Burton</div></li>' });
app.get('/video-global', function(req, res){
res.partial('video.jade', {
object: movies[0],
@@ -497,7 +511,7 @@ module.exports = {
assert.response(app,
{ url: '/video-global' },
{ body: '<p>Tim Burton</p>' });
app.get('/person-this', function(req, res){
res.partial('person.jade', {
object: { name: 'tj' },
@@ -510,7 +524,7 @@ module.exports = {
assert.response(app,
{ url: '/person-this' },
{ body: '<p>User: tj</p>' });
// No options
app.get('/nothing', function(req, res){
res.partial('hello.ejs');
@@ -519,29 +533,143 @@ module.exports = {
assert.response(app,
{ url: '/nothing' },
{ body: 'Hello' });
// Path segments + "as"
app.get('/role/as', function(req, res){
res.partial('user/role.ejs', { as: 'role', collection: ['admin', 'member'] });
});
assert.response(app,
{ url: '/role/as' },
{ body: '<li>Role: admin</li><li>Role: member</li>' });
// Deduce name from last segment
app.get('/role', function(req, res){
res.partial('user/role.ejs', ['admin', 'member']);
});
assert.response(app,
{ url: '/role' },
{ body: '<li>Role: admin</li><li>Role: member</li>' });
// Non-basic object support
function Movie(title, director){
this.title = title;
this.director = director;
}
app.get('/movie/object', function(req, res){
res.partial('movie', new Movie('The TJ', 'tj'));
});
assert.response(app,
{ url: '/movie/object' },
{ body: '<li><div class="title">The TJ</div><div class="director">tj</div></li>' });
// Locals
app.get('/stats', function(req, res){
res.partial('stats', {
hits: 12
, misses: 1
});
});
assert.response(app,
{ url: '/stats' },
{ body: '<p>Hits 12</p><p>Misses 1</p>' });
// Locals
app.get('/stats/locals', function(req, res){
res.partial('stats', {
locals: {
hits: 12
, misses: 1
}
});
});
assert.response(app,
{ url: '/stats/locals' },
{ body: '<p>Hits 12</p><p>Misses 1</p>' });
// Collection + locals
app.get('/items', function(req, res){
res.partial('item-title', {
collection: ['foo', 'bar']
, title: 'test'
, as: 'item'
});
});
assert.response(app,
{ url: '/items' },
{ body: '<li>test foo</li><li>test bar</li>' });
app.get('/stats/callback', function(req, res){
res.partial('stats', { hits: 12, misses: 1 }, function(err, html){
res.send('got: ' + html);
});
});
assert.response(app,
{ url: '/stats/callback' },
{ body: 'got: <p>Hits 12</p><p>Misses 1</p>' });
app.get('/stats/callback/2', function(req, res){
res.locals({ hits: 12, misses: 1 });
res.partial('stats', function(err, html){
res.send('got: ' + html);
});
});
assert.response(app,
{ url: '/stats/callback/2' },
{ body: 'got: <p>Hits 12</p><p>Misses 1</p>' });
// root lookup
app.get('/root', function(req, res){
res.partial('nested/partial');
});
assert.response(app,
{ url: '/root' },
{ body: '<p>Hits 15</p><p>Misses 1</p>' });
// root _* lookup
app.get('/root/underscore', function(req, res){
res.partial('nested/partial2');
});
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 });
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(){
var app = create();
app.get('/', function(req, res, next){
res.render('list.jade', { layout: false });
});
@@ -553,7 +681,7 @@ module.exports = {
'test #partial() with several calls using locals': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('list2.jade', { layout: false });
});
@@ -565,9 +693,9 @@ module.exports = {
'test #partial() locals': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res, next){
res.partial('pet-count', {
locals: {
@@ -577,7 +705,7 @@ module.exports = {
}
});
});
assert.response(app,
{ url: '/' },
{ body: 'We have 5 cool pets\n' });
@@ -585,59 +713,71 @@ module.exports = {
'test #partial() locals precedence': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('greetings.jade', {
name: 'TJ'
, locals: { otherName: 'Overridden' }
});
});
assert.response(app,
{ url: '/' },
{ body: '<html><body><h1>TJ</h1><p>Welcome Overridden</p></body></html>' });
},
'test #partial() index': function(){
var app = create();
app.set('view engine', 'jade');
function Ferret(name){ this.name = name; };
app.get('/ferret', function(req, res){
res.partial('ferret', { name: 'Tobi' });
res.partial('ferret', new Ferret('Tobi'));
});
app.get('/ferret/basic', function(req, res){
res.partial('ferret', { ferret: { name: 'Tobi' }});
});
assert.response(app,
{ url: '/ferret/basic' },
{ body: '<li class="ferret">Tobi</li>' });
assert.response(app,
{ url: '/ferret' },
{ body: '<li class="ferret">Tobi</li>' });
},
'test #partial() relative index': function(){
var app = create();
app.set('view engine', 'jade');
function Ferret(name) { this.name = name; }
app.get('/ferret', function(req, res){
var tobi = { name: 'Tobi' }
, loki = { name: 'Loki' };
var tobi = new Ferret('Tobi')
, loki = new Ferret('Loki');
res.partial('ferret/list', { object: [tobi, loki] });
});
assert.response(app,
{ url: '/ferret' },
{ body: '<ul id="ferrets"><li class="ferret">Tobi</li><li class="ferret">Loki</li></ul>' });
},
'test #partial() object': function(){
var app = create();
app.get('/', function(req, res, next){
res.partial('movie.jade', {
title: 'Foobar'
, director: 'Tim Burton'
});
function Movie(title, director) {
this.title = title;
this.director = director;
}
res.partial('movie.jade', new Movie('Foobar', 'Tim Burton'));
});
assert.response(app,
{ url: '/' },
{ body: '<li><div class="title">Foobar</div><div class="director">Tim Burton</div></li>' });
@@ -645,13 +785,13 @@ module.exports = {
'test #partial() locals with collection': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('pet-land.jade', {
pets: ['Ewald']
});
});
assert.response(app,
{ url: '/' },
{ body: '<html><body><div><li>Ewald is the coolest of Animal land</li></div></body></html>' });
@@ -659,14 +799,14 @@ module.exports = {
'test #partial() inheriting initial locals': function(){
var app = create();
app.get('/pets', function(req, res, next){
res.render('pets.jade', {
site: 'My Cool Pets'
, pets: ['Tobi', 'Jane', 'Bandit']
});
});
var html = [
'<html>',
'<body>',
@@ -680,7 +820,7 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/pets' },
{ body: html });
@@ -688,7 +828,7 @@ module.exports = {
'test #partial() with array-like collection': function(){
var app = create();
var movies = {
0: { title: 'Nightmare Before Christmas', director: 'Tim Burton' },
1: { title: 'Avatar', director: 'James Cameron' },
@@ -714,7 +854,7 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/movies' },
{ body: html });
@@ -723,7 +863,7 @@ module.exports = {
'test "partials" setting': function(){
var app = create();
app.set('partials', __dirname + '/fixtures/sub-templates');
app.get('/', function(req, res){
res.render('items.jade', {
layout: false,
@@ -820,12 +960,12 @@ module.exports = {
, open: '<?'
, title: 'Original'
});
function setTitle(req, res, next) {
res.local('title', 'Wahoo');
next();
}
app.get('/video', setTitle, function(req, res, next){
res.local('close', '?>');
res.render('video.ejs', { layout: false, title: 'keyboard cat' });
@@ -835,20 +975,20 @@ module.exports = {
res.local('close', '?>');
res.render('video.ejs', { layout: false });
});
app.get('/video/3', function(req, res, next){
res.local('close', '?>');
res.render('video.ejs', { layout: false });
});
assert.response(app,
{ url: '/video' },
{ body: '<h1>keyboard cat</h1>' });
assert.response(app,
{ url: '/video/2' },
{ body: '<h1>Wahoo</h1>' });
assert.response(app,
{ url: '/video/3' },
{ body: '<h1>Original</h1>' });
@@ -873,11 +1013,11 @@ module.exports = {
}
next();
});
app.get('/pets', function(req, res, next){
res.render('pets.jade');
});
var html = [
'<html>',
'<body>',
@@ -891,9 +1031,60 @@ module.exports = {
'</body>',
'</html>'
].join('');
assert.response(app,
{ url: '/pets' },
{ body: html });
},
'test .charset with res.render()': function(){
var app = create();
app.get('/', function(req, res){
res.charset = 'ISO-8859-1';
res.render('hello.jade');
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
},
'test charset res.render() option': function(){
var app = create();
app.get('/', function(req, res){
res.render('hello.jade', { charset: 'ISO-8859-1' });
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
},
'test charset option': function(){
var app = create();
app.set('view options', { charset: 'ISO-8859-1' });
app.get('/', function(req, res){
res.render('hello.jade');
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=ISO-8859-1' }});
},
'test charset override': function(){
var app = create();
app.set('view options', { charset: 'ISO-8859-1' });
app.get('/', function(req, res){
res.render('hello.jade', { charset: 'utf8' });
});
assert.response(app,
{ url: '/' },
{ headers: { 'Content-Type': 'text/html; charset=utf8' }});
}
};