├── .editorconfig
├── .gitignore
├── .jshintrc
├── .npmignore
├── LICENSE
├── README.md
├── bower.json
├── examples
├── README.md
├── browser
│ ├── .gitignore
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ ├── server.js
│ ├── views
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ └── webpack.config.js
├── domcache
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ │ ├── App.js
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── isomorphic
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ │ ├── App.js
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── lazyloadviews
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ │ ├── App.js
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── notfound
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ │ ├── App.js
│ │ ├── NotFound.js
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── preloadermiddleware
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ │ ├── App.js
│ │ ├── PetDetail.js
│ │ ├── PetList.js
│ │ └── Preloader.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── react-frozenhead
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ │ ├── App.js
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── react-template
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── templates
│ │ ├── App.js
│ │ ├── PetDetail.js
│ │ └── PetList.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
└── renderinitial
│ ├── .gitignore
│ ├── browser.js
│ ├── package.json
│ ├── router.js
│ ├── server.js
│ ├── views
│ ├── App.js
│ ├── PetDetail.js
│ ├── PetList.js
│ └── Preloader.js
│ ├── webpack.browser.config.js
│ └── webpack.server.config.js
├── gulpfile.js
├── lib
├── LinkHijacker.js
├── Request.js
├── Response.js
├── Route.js
├── Router.js
├── __tests__
│ ├── Request-test.js
│ ├── Route-test.js
│ └── Router-test.js
├── attach.js
├── errors
│ ├── Cancel.js
│ ├── Unhandled.js
│ └── initError.js
├── history
│ ├── BaseHistory.js
│ ├── DummyHistory.js
│ ├── FallbackHistory.js
│ ├── History.js
│ ├── PushStateHistory.js
│ └── getHistory.js
├── index.js
└── utils
│ ├── delayed.js
│ ├── noop.js
│ ├── pathToRegexp.js
│ ├── thunkifyAll.js
│ └── withoutResults.js
├── package.json
└── standalone
└── monorouter.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.{md,markdown}]
11 | indent_style = space
12 | indent_size = 4
13 | trim_trailing_whitespace = false
14 |
15 | [*.js]
16 | indent_style = space
17 | indent_size = 2
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /.tests-built/
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globalstrict": true,
3 | "browser": true,
4 | "node": true,
5 | "es3": true,
6 | "indent": 2,
7 | "eqnull": true,
8 | "strict": false,
9 | "loopfunc": true,
10 | "globals": {
11 | "ActiveXObject": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /test/
3 | /vendor/
4 | /bower.json
5 | /.editorconfig
6 | /.jshintrc
7 | /standalone/
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Matthew Dapena-Tretter
2 | http://matthewwithanm.com
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | monorouter
2 | ==========
3 |
4 | monorouter is an isomorphic JavaScript router by [@matthewwithanm] and
5 | [@lettertwo]. It was designed for use with ReactJS but doesn't have any direct
6 | dependencies on it and should be easily adaptable to other virtual DOM
7 | libraries.
8 |
9 | While it can be used for both browser-only and server-only routing, it was
10 | designed from the ground up to be able to route apps on both sides of the wire.
11 |
12 | **Note: This project is in beta and we consider the API in flux. Let us know if
13 | you have any ideas for improvement!**
14 |
15 |
16 | Usage
17 | -----
18 |
19 | Defining a router looks like this:
20 |
21 | ```javascript
22 | var monorouter = require('monorouter');
23 | var reactRouting = require('monorouter-react');
24 |
25 | monorouter()
26 | .setup(reactRouting())
27 | .route('/', function(req) {
28 | this.render(MyView);
29 | })
30 | .route('/pets/:name/', function(req) {
31 | this.render(PetView, {petName: req.params.name});
32 | });
33 | ```
34 |
35 | Here, we're simply rendering views for two different routes. With monorouter, a
36 | "view" is any function that returns a DOM descriptor.
37 |
38 | The router can be used on the server with express and [connect-monorouter]:
39 |
40 | ```javascript
41 | var express = require('express');
42 | var router = require('./path/to/my/router');
43 | var monorouterMiddleware = require('connect-monorouter');
44 |
45 | var app = express();
46 | app.use(monorouterMiddleware(router));
47 |
48 | var server = app.listen(3000, function() {
49 | console.log('Listening on port %d', server.address().port);
50 | });
51 | ```
52 |
53 | And in the browser:
54 |
55 | ```javascript
56 | router
57 | .attach(document)
58 | .captureClicks(); // This is optional—it uses the router to handle normal links.
59 | ```
60 |
61 | See [the examples][monorouter examples] for a more in-depth look and more
62 | tricks!
63 |
64 |
65 | ### Handling errors
66 |
67 | Error handling with monorouter is similar to [Express]—just add a middleware
68 | with an arity of 3:
69 |
70 | ```javascript
71 | router.use(function(err, req, next) {
72 | if (err.status === 404) {
73 | this.render(My404Template);
74 | }
75 | });
76 | ```
77 |
78 |
79 | API
80 | ---
81 |
82 | In addition to the router itself, monorouter has two important objects. The
83 | first one is the request object, and it's passed as the first argument to your
84 | handler. The second is the context (`this`) of your route handler, and it's used
85 | to interface with your application's state (you can think of this as being
86 | similar to a Response object in server-only routers). This section summarizes
87 | their APIs.
88 |
89 |
90 | ### Request
91 |
92 | - **param(name:String)**: Get the value for one of the dynamic parts of your
93 | route:
94 |
95 | ```javascript
96 | monorouter()
97 | .route('/pets/:name', function(req) {
98 | console.log(req.param('name'));
99 | // snip
100 | });
101 | ```
102 |
103 | - **params:Object**: A hash of the params used in your route.
104 | - **canceled:Boolean**: A boolean that represents whether the request has been
105 | canceled. This is useful for preventing further action in async callbacks:
106 |
107 | ```javascript
108 | monorouter()
109 | .route('/', function(req) {
110 | http('...', function(err, result) {
111 | if (req.canceled) return;
112 | this.render(MyView, {person: result.people[0]});
113 | }.bind(this));
114 | });
115 | ```
116 |
117 | Note that it's not necessary to check this value if all you're doing is
118 | rendering since those operations are no-ops when the request has been
119 | canceled. The request is also an EventEmitter that emits a "cancel" event so,
120 | if you'd like to take action immediately when a request is canceled (and abort
121 | an XHR request, for example), you can do that:
122 |
123 | ```javascript
124 | monorouter()
125 | .route('/', function(req) {
126 | var xhr = http('...', function(err, result) {
127 | if (req.canceled) return;
128 | this.render(MyView, {person: result.people[0]});
129 | }.bind(this));
130 | this.on('cancel', function() {
131 | xhr.abort();
132 | });
133 | });
134 | ```
135 |
136 | - **initialOnly:Boolean**: Indicates whether the request is only for the initial
137 | state of the app. `true` when rendering on the server.
138 | - **location:Object**: A parsed version of the requested URL, in a format based
139 | on the [`document.location` interface][document.location].
140 | - **url:String**: The requested URL.
141 | - **protocol:String**: The protocol of the requested URL, without the colon.
142 | e.g. `"http"`
143 | - **hostname:String**: The hostname of the requested URL, e.g. `'mysite.com'`
144 | - **host:String**: The full host of the requested URL, e.g. `'mysite.com:5000'`
145 | - **search:String**: The search portion of the requested URL, including the
146 | question mark, e.g. `'?hello=5&goodbye=a'`
147 | - **querystring:String**: The search portion of the requested URL, excluding the
148 | question mark, e.g. `'hello=5&goodbye=a'`
149 | - **query:Object**: A version of the query string that's been parsed using
150 | @sindresorhus's [query-string].
151 | - **hash:String**: The hash portion of the requested URL, including the hash
152 | mark. e.g. `'#this-is-the-hash'`
153 | - **fragment:String**: The hash portion of the requested URL, excluding the hash
154 | mark. e.g. `'this-is-the-hash'`
155 | - **first:Boolean**: Is this the first request being handled by this router?
156 | - **from(causes:String...):Boolean**: Check the cause of the request. Causes
157 | that monorouter sends are:
158 |
159 | * `"startup"` - When a request is triggered by the router initialization in
160 | the browser.
161 | * `"popstate"` - When a request is triggered by a popstate event
162 | * `"link"` - When a request is triggered by a link click captured by the link
163 | hijacker
164 |
165 | You may also send custom causes. For example, [connect-monorouter] uses the
166 | string '"httprequest"'. Generally, causes shouldn't be used to affect routing
167 | behavior—they are meant primarily for logging.
168 |
169 |
170 | ### Handler Context
171 |
172 | Within a route handler, you use properties and methods of `this` to define the
173 | application state. Here are some of those:
174 |
175 | - **render(view:Function?, vars:Object?, callback:Function?)**: Render the view
176 | and consider the request complete. The `view` is a function that returns a
177 | virtual DOM instance. It may be omitted if you've previously set one for this
178 | request using `setView` (e.g. in middleware). "vars" are arguments for this
179 | function that will be bound to it for as long as it's rendered.
180 | - **renderIntermediate(view:Function?, vars:Object?, callback:Function?)**: Like
181 | `render`, but doesn't end the request. This is useful if you'd like to render
182 | several different states during the course of handling a single route.
183 | - **renderInitial(view:Function?, vars:Object?, callback:Function?)**: Like
184 | `render`, but only ends "initialOnly" requests.
185 | - **setView(view:Function)**: Sets the view to be rendered for this response.
186 | The application won't actually be updated until/unless one of the `render*`
187 | methods is called.
188 | - **setVars(vars:Object)**: Add vars for any subsequent renders in this request.
189 | "vars" are passed to the view function for rendering.
190 | - **setState(state:Object)**: "state" is similar to vars in that its values are
191 | passed to the view for rendering. Unlike "vars", however, "state" is preserved
192 | between requests. Setting state also triggers a rerender of the current view.
193 | The state and vars are merged and the result passed to the view function.
194 | - **notFound()**: A function that tells the server to send a 404 status code
195 | with this view.
196 | - **doctype:String**: The doctype for the document. Defaults to the HTML5
197 | doctype.
198 | - **contentType:String**: The content type of the document. Defaults to
199 | `'text/html; charset=utf-8'`
200 | - **beforeRender(hook:Function)**: An interface for adding before-render hooks.
201 | All hooks are executed in parallel immediately prior to rendering.
202 | - **ended:Boolean**: Specifies whether the response has ended.
203 | - **initialEnded:Boolean**: Specifies whether the initial state has been rendered.
204 |
205 |
206 | Philosophy
207 | ----------
208 |
209 | If the original idea for [react-nested-router] was "[Ember] got it mostly
210 | right," monorouter's can be said to be "[Express] got it mostly right." (Or
211 | [koa]. Or [Flask]. Or [Django]. Or [Rails]…) Server-side routing is very easy: a
212 | request comes in and, in response, you render a template. Each time this
213 | happens, you render the entire document. Until recently, this approach seemed
214 | incongruent with client-side rendering, but ReactJS and the virtual DOM have
215 | changed that.
216 |
217 | Still, client-side routing is fundamentally different than server-side in some
218 | ways—most notably in that state can be shared between routes. monorouter aims to
219 | expose the functionality related to GUI routing that's common to the server and
220 | browser. Some principles of the project are:
221 |
222 | 1. The same routing that's used in the browser should be used to render the
223 | server response.
224 | 2. Routing is (at least potentially) an asynchronous process while rendering a
225 | view is a synchronous one.
226 | 3. Routes need not map to a single state (or view), but may result in any number
227 | of them throughout their life.
228 | * Two of these states are special: the "initial" state (which will be
229 | serialized and sent by the server and which the browser app must begin in)
230 | and the final state (which the app is in once the handling of a route is
231 | completed).
232 | 4. A view is any JavaScript function that returns a DOM descriptor.
233 | 5. Each view should represent the entire document at a given state—not just a
234 | portion of it.
235 | * Not only does this make reasoning about the application easier, it's very
236 | important when dealing with `
`s
237 | 6. We think monorouter covers all the possible use cases, and we see it as a
238 | foundation on which to build—both via extensions (i.e. middleware) and
239 | additional abstractions (i.e. JSX-friendly interfaces and declarative,
240 | lifecycle-centric route declarations).
241 |
242 |
243 |
244 |
245 | [@matthewwithanm]: http://github.com/matthewwithanm
246 | [@lettertwo]: http://github.com/lettertwo
247 | [react-nested-router]: https://github.com/rpflorence/react-router
248 | [Ember]: https://github.com/emberjs/ember.js
249 | [Express]: https://github.com/visionmedia/express
250 | [koa]: https://github.com/koajs/koa
251 | [Flask]: https://github.com/mitsuhiko/flask
252 | [Django]: https://github.com/django/django
253 | [Rails]: https://github.com/rails/rails
254 | [react-router-component]: https://github.com/andreypopp/react-router-component
255 | [connect-monorouter]: https://github.com/matthewwithanm/connect-monorouter
256 | [monorouter-react]: https://github.com/matthewwithanm/monorouter-react
257 | [monorouter examples]: https://github.com/matthewwithanm/monorouter/tree/master/examples
258 | [document.location]: https://developer.mozilla.org/en-US/docs/Web/API/document.location
259 | [query-string]: https://github.com/sindresorhus/query-string
260 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter",
3 | "version": "0.11.3",
4 | "authors": [
5 | "Matthew Dapena-Tretter "
6 | ],
7 | "description": "A React Application component",
8 | "main": "./standalone/monorouter.js",
9 | "keywords": [
10 | "react-component",
11 | "react",
12 | "application",
13 | "router",
14 | "routing",
15 | "component"
16 | ],
17 | "license": "MIT",
18 | "homepage": "https://github.com/matthewwithanm/monorouter",
19 | "ignore": [
20 | "**/.*",
21 | "node_modules",
22 | "bower_components",
23 | "test",
24 | "tests"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Examples
2 | ========
3 |
4 | This directory contains a few examples of using monorouter on the server and
5 | browser. To see them in action:
6 |
7 | 1. `cd` into the directory
8 | 2. Install the example's dependencies (using `npm install`)
9 | 3. Start the example using `npm start`
10 |
11 | This will start a server on port 5000. Go to `http://localhost:5000` in your
12 | browser and see the example in action!
13 |
--------------------------------------------------------------------------------
/examples/browser/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/browser/app.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 |
6 |
7 | monorouter()
8 | .setup(reactRouting())
9 | .route('index', '/', function(req) {
10 | this.render(PetList);
11 | })
12 | .route('pet', '/pet/:name', function(req) {
13 | this.render(PetDetail, {petName: req.params.name});
14 | })
15 | .attach(document.body)
16 | .captureClicks();
17 |
--------------------------------------------------------------------------------
/examples/browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Browser Example
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/browser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-browser-example",
3 | "version": "1.0.0",
4 | "description": "An example of client-side routing with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.config.js",
8 | "serve": "node ./server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "express": "^4.7.2",
18 | "webpack": "^1.3.2-beta9",
19 | "jsx-loader": "~0.11.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/browser/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 |
4 | port = process.env.PORT || 5000;
5 |
6 | express()
7 | .get('/app.js', function(req, res) {
8 | res.sendfile(path.join(__dirname, 'built', 'app.js'));
9 | })
10 | .get('/', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'index.html'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/browser/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function PetDetail(props) {
5 | return (
6 |
10 | );
11 | }
12 |
13 |
14 | module.exports = PetDetail;
15 |
--------------------------------------------------------------------------------
/examples/browser/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | var pets = [
5 | {name: 'Winston'},
6 | {name: 'Chaplin'},
7 | {name: 'Bennie'}
8 | ];
9 |
10 | function PetList() {
11 | var links = pets.map(function(pet) {
12 | return {pet.name};
13 | });
14 | return (
15 |
18 | );
19 | }
20 |
21 | module.exports = PetList;
22 |
--------------------------------------------------------------------------------
/examples/browser/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | app: './app.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/domcache/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/domcache/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/domcache/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-domcache-example",
3 | "version": "1.0.0",
4 | "description": "An example of caching data in the DOM with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "jsx-loader": "~0.11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/domcache/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 |
6 | var pets = [
7 | {name: 'Winston'},
8 | {name: 'Chaplin'},
9 | {name: 'Bennie'}
10 | ];
11 |
12 | function fetchPetList (cb) {
13 | // Artificially make the response take a while.
14 | setTimeout(function() {
15 | cb(null, pets);
16 | }, Math.round(200 + Math.random() * 800));
17 | }
18 |
19 | function findPetName (petName, data) {
20 | for (var i = 0; i < pets.length; i++) {
21 | var pet = data[i];
22 | if (pet.name.toLowerCase() === petName) return pet.name;
23 | }
24 | }
25 |
26 | module.exports = monorouter()
27 | .setup(reactRouting())
28 | .route('index', '/', function(req) {
29 | this.domCache('petData', fetchPetList, function (err, data) {
30 | this.render(PetList, {pets: data});
31 | });
32 | })
33 | .route('pet', '/pet/:name', function(req) {
34 | this.domCache('petData', fetchPetList, function (err, data) {
35 | this.render(PetDetail, {petName: findPetName(req.params.name, data)});
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/examples/domcache/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/domcache/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function App(props, children) {
5 | return (
6 |
7 |
8 | Domcache Example
9 |
10 |
11 | {children}
12 | {props.domCache()}
13 |
14 |
15 |
16 | );
17 | };
18 |
19 |
20 | module.exports = App;
21 |
--------------------------------------------------------------------------------
/examples/domcache/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = PetDetail;
18 |
--------------------------------------------------------------------------------
/examples/domcache/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetList(props) {
6 | var links = props.pets.map(function(pet) {
7 | return {pet.name};
8 | });
9 | return (
10 |
11 |
14 |
15 | );
16 | }
17 |
18 | module.exports = PetList;
19 |
--------------------------------------------------------------------------------
/examples/domcache/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/domcache/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/isomorphic/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/isomorphic/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/isomorphic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-isomorphic-example",
3 | "version": "1.0.0",
4 | "description": "An example of server- and client-side routing with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "jsx-loader": "~0.11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/isomorphic/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 |
6 |
7 | module.exports = monorouter()
8 | .setup(reactRouting())
9 | .route('index', '/', function(req) {
10 | this.render(PetList);
11 | })
12 | .route('pet', '/pet/:name', function(req) {
13 | this.render(PetDetail, {petName: req.params.name});
14 | });
15 |
--------------------------------------------------------------------------------
/examples/isomorphic/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/isomorphic/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function App(props, children) {
5 | return (
6 |
7 |
8 | Isomorphic Example
9 |
10 |
11 | {children}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 |
19 | module.exports = App;
20 |
--------------------------------------------------------------------------------
/examples/isomorphic/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = PetDetail;
18 |
--------------------------------------------------------------------------------
/examples/isomorphic/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | var pets = [
6 | {name: 'Winston'},
7 | {name: 'Chaplin'},
8 | {name: 'Bennie'}
9 | ];
10 |
11 | function PetList() {
12 | var links = pets.map(function(pet) {
13 | return {pet.name};
14 | });
15 | return (
16 |
17 |
20 |
21 | );
22 | }
23 |
24 | module.exports = PetList;
25 |
--------------------------------------------------------------------------------
/examples/isomorphic/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/isomorphic/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-lazyloadviews-example",
3 | "version": "1.0.0",
4 | "description": "An example of lazily loaded views with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "jsx-loader": "~0.11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 |
4 | var pets = [
5 | {name: 'Winston'},
6 | {name: 'Chaplin'},
7 | {name: 'Bennie'}
8 | ];
9 |
10 | function findPetName (petName, data) {
11 | for (var i = 0; i < pets.length; i++) {
12 | var pet = data[i];
13 | if (pet.name.toLowerCase() === petName) return pet.name;
14 | }
15 | }
16 |
17 | module.exports = monorouter()
18 | .setup(reactRouting())
19 | .route('index', '/', function(req) {
20 | require.ensure(['./views/PetList'], function(require) {
21 | this.render(require('./views/PetList'), {pets: pets});
22 | }.bind(this));
23 | })
24 | .route('pet', '/pet/:name', function(req) {
25 | require.ensure(['./views/PetDetail'], function(require) {
26 | this.render(
27 | require('./views/PetDetail'),
28 | {petName: findPetName(req.params.name, pets)}
29 | );
30 | }.bind(this));
31 | });
32 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use('/assets', express.static(__dirname + '/assets'))
10 | .use(routerMiddleware(Router))
11 | .listen(port, function() {
12 | console.log("Listening on " + port + ".");
13 | console.log("Go to in your browser.");
14 | });
15 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function App(props, children) {
5 | return (
6 |
7 |
8 | Lazy Load Views Example
9 |
10 |
11 | {children}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 |
19 | module.exports = App;
20 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = PetDetail;
18 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetList(props) {
6 | var links = props.pets.map(function(pet) {
7 | return {pet.name};
8 | });
9 | return (
10 |
11 |
14 |
15 | );
16 | }
17 |
18 | module.exports = PetList;
19 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built', 'assets'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '/assets/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/lazyloadviews/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: 'server.js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2'
24 | },
25 |
26 | module: {
27 | loaders: [
28 | {test: /\.js$/, loader: 'jsx'}
29 | ]
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/examples/notfound/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/notfound/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/notfound/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-notfound-example",
3 | "version": "1.0.0",
4 | "description": "An example of 'not found' (404) handling with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "jsx-loader": "~0.11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/notfound/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 | var NotFound = require('./views/NotFound');
6 |
7 | var pets = [
8 | {name: 'Winston'},
9 | {name: 'Chaplin'},
10 | {name: 'Bennie'}
11 | ];
12 |
13 | function findPetName (petName, data) {
14 | for (var i = 0; i < pets.length; i++) {
15 | var pet = data[i];
16 | if (pet.name.toLowerCase() === petName) return pet.name;
17 | }
18 | }
19 |
20 | module.exports = monorouter()
21 | .setup(reactRouting())
22 | .route('index', '/', function(req) {
23 | this.render(PetList, {pets: pets});
24 | })
25 | .route('pet', '/pet/:name', function(req) {
26 | var petName = findPetName(req.params.name, pets);
27 | if (!petName) {
28 | return this
29 | .notFound()
30 | .render(NotFound, {msg: "No pet " + req.params.name + " exists!"});
31 | }
32 | this.render(PetDetail, {petName: petName});
33 | });
34 |
--------------------------------------------------------------------------------
/examples/notfound/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/notfound/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function App(props, children) {
5 | return (
6 |
7 |
8 | NotFound Example
9 |
10 |
11 | {children}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 |
19 | module.exports = App;
20 |
--------------------------------------------------------------------------------
/examples/notfound/views/NotFound.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function NotFound(props) {
6 | return (
7 |
8 |
9 |
Not Found
10 |
{props.msg}
11 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = NotFound;
18 |
--------------------------------------------------------------------------------
/examples/notfound/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = PetDetail;
18 |
--------------------------------------------------------------------------------
/examples/notfound/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetList(props) {
6 | var links = props.pets
7 | .map(function(pet) {
8 | return {pet.name};
9 | })
10 | .concat([fake]);
11 | return (
12 |
13 |
16 |
17 | );
18 | }
19 |
20 | module.exports = PetList;
21 |
--------------------------------------------------------------------------------
/examples/notfound/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/notfound/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-preloadermiddleware-example",
3 | "version": "1.0.0",
4 | "description": "An example of using middleware with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "jsx-loader": "~0.11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 | var Preloader = require('./views/Preloader');
6 |
7 | var pets = [
8 | {name: 'Winston'},
9 | {name: 'Chaplin'},
10 | {name: 'Bennie'}
11 | ];
12 |
13 | function fetchPetList (cb) {
14 | // Artificially make the response take a while.
15 | setTimeout(function() {
16 | cb(null, pets);
17 | }, Math.round(200 + Math.random() * 800));
18 | }
19 |
20 | function findPetName (petName, data) {
21 | for (var i = 0; i < pets.length; i++) {
22 | var pet = data[i];
23 | if (pet.name.toLowerCase() === petName) return pet.name;
24 | }
25 | }
26 |
27 | function preloadData (req, next) {
28 | this.renderInitial(Preloader, function() {
29 | fetchPetList(function(err, data) {
30 | var viewProps = {pets: data};
31 | if (req.params && req.params.name)
32 | viewProps.petName = findPetName(req.params.name, data);
33 | this.setVars(viewProps);
34 | next();
35 | }.bind(this));
36 | }.bind(this));
37 | }
38 |
39 | module.exports = monorouter()
40 | .setup(reactRouting())
41 | .route('index', '/', preloadData, function(req) {
42 | this.render(PetList);
43 | })
44 | .route('pet', '/pet/:name', preloadData, function(req) {
45 | // NOTE: Because renderPreloader renders an initial view, we lose the
46 | // opportunity to have the server send a 404, and the client will have to
47 | // display a 'missing' view. If you want the server to send 404s, you have
48 | // to call `this.unhandled()` before `this.renderInitial()`.
49 | this.render(PetDetail);
50 | });
51 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function App(props, children) {
5 | return (
6 |
7 |
8 | Preloader Middleware Example
9 |
10 |
11 | {children}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 |
19 | module.exports = App;
20 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = PetDetail;
18 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetList(props) {
6 | var links = props.pets.map(function(pet) {
7 | return {pet.name};
8 | });
9 | return (
10 |
11 |
14 |
15 | );
16 | }
17 |
18 | module.exports = PetList;
19 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/views/Preloader.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function Preloader(props) {
6 | return (
7 |
8 |
9 |
Loading...
10 |
11 |
12 | );
13 | }
14 |
15 | module.exports = Preloader;
16 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/preloadermiddleware/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-react-frozenhead-example",
3 | "version": "1.0.0",
4 | "description": "An example of using react-frozenhead with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "react-frozenhead": "^0.1.0",
21 | "jsx-loader": "~0.11.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 |
6 |
7 | module.exports = monorouter()
8 | .setup(reactRouting())
9 | .route('index', '/', function(req) {
10 | this.render(PetList);
11 | })
12 | .route('pet', '/pet/:name', function(req) {
13 | this.render(PetDetail, {petName: req.params.name});
14 | });
15 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var head = require('react-frozenhead');
4 |
5 | function App(props, children) {
6 | return (
7 |
8 |
9 | {props.title + " :: React Frozenhead Example"}
10 | {props.headExtras}
11 |
12 |
13 | {children}
14 |
15 |
16 |
17 | );
18 | };
19 |
20 |
21 | module.exports = App;
22 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | var metaTags = [
7 | ,
8 |
9 | ];
10 | return (
11 |
12 |
16 |
17 | );
18 | }
19 |
20 |
21 | module.exports = PetDetail;
22 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | var pets = [
6 | {name: 'Winston'},
7 | {name: 'Chaplin'},
8 | {name: 'Bennie'}
9 | ];
10 |
11 | function PetList() {
12 | var links = pets.map(function(pet) {
13 | return {pet.name};
14 | });
15 | var metaTags = [
16 | ,
17 |
18 | ];
19 | return (
20 |
21 |
24 |
25 | );
26 | }
27 |
28 | module.exports = PetList;
29 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/react-frozenhead/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/react-template/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/react-template/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/react-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-react-template-example",
3 | "version": "1.0.0",
4 | "description": "An example of using react-template with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "react-template": "^0.3.1",
21 | "jsx-loader": "~0.11.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/react-template/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./templates/PetList');
4 | var PetDetail = require('./templates/PetDetail');
5 |
6 |
7 | module.exports = monorouter()
8 | .setup(reactRouting())
9 | .route('index', '/', function(req) {
10 | this.render(PetList);
11 | })
12 | .route('pet', '/pet/:name', function(req) {
13 | this.render(PetDetail, {petName: req.params.name});
14 | });
15 |
--------------------------------------------------------------------------------
/examples/react-template/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/react-template/templates/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var ReactTemplate = require('react-template');
4 |
5 |
6 | var App = ReactTemplate.create({
7 | render: function() {
8 | return (
9 |
10 |
11 | ReactTemplate Example
12 |
13 |
14 | {this.props.children}
15 |
16 |
17 |
18 | );
19 | }
20 | });
21 |
22 |
23 | module.exports = App;
24 |
--------------------------------------------------------------------------------
/examples/react-template/templates/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 | var ReactTemplate = require('react-template');
5 |
6 |
7 | var PetDetail = ReactTemplate.create({
8 | render: function () {
9 | return (
10 |
11 |
15 |
16 | );
17 | }
18 | });
19 |
20 | module.exports = PetDetail;
21 |
--------------------------------------------------------------------------------
/examples/react-template/templates/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 | var ReactTemplate = require('react-template');
5 |
6 | var pets = [
7 | {name: 'Winston'},
8 | {name: 'Chaplin'},
9 | {name: 'Bennie'}
10 | ];
11 |
12 | var PetList = ReactTemplate.create({
13 | renderLinks: function() {
14 | return pets.map(function(pet) {
15 | return {pet.name};
16 | });
17 | },
18 | render: function() {
19 | return (
20 |
21 |
22 | {this.renderLinks()}
23 |
24 |
25 | );
26 | }
27 | });
28 |
29 | module.exports = PetList;
30 |
--------------------------------------------------------------------------------
/examples/react-template/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/react-template/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/renderinitial/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /built
3 |
--------------------------------------------------------------------------------
/examples/renderinitial/browser.js:
--------------------------------------------------------------------------------
1 | var Router = require('./router');
2 |
3 | Router.attach(document).captureClicks();
4 |
--------------------------------------------------------------------------------
/examples/renderinitial/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter-renderinitial-example",
3 | "version": "1.0.0",
4 | "description": "An example of using renderInitial with monorouter",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.browser.config.js && webpack --config webpack.server.config.js",
8 | "serve": "node ./built/server.js",
9 | "start": "npm run build && npm run serve"
10 | },
11 | "author": "Eric Eldredge ",
12 | "license": "MIT",
13 | "dependencies": {
14 | "monorouter": "^0.7.1",
15 | "react": "^0.11.1",
16 | "monorouter-react": "^0.2.0",
17 | "connect-monorouter": "^0.2.1",
18 | "express": "^4.7.2",
19 | "webpack": "^1.3.2-beta9",
20 | "jsx-loader": "~0.11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/renderinitial/router.js:
--------------------------------------------------------------------------------
1 | var monorouter = require('monorouter');
2 | var reactRouting = require('monorouter-react');
3 | var PetList = require('./views/PetList');
4 | var PetDetail = require('./views/PetDetail');
5 | var Preloader = require('./views/Preloader');
6 |
7 | var pets = [
8 | {name: 'Winston'},
9 | {name: 'Chaplin'},
10 | {name: 'Bennie'}
11 | ];
12 |
13 | function fetchPetList (cb) {
14 | // Artificially make the response take a while.
15 | setTimeout(function() {
16 | cb(null, pets);
17 | }, Math.round(200 + Math.random() * 800));
18 | }
19 |
20 | function findPetName (petName, data) {
21 | for (var i = 0; i < pets.length; i++) {
22 | var pet = data[i];
23 | if (pet.name.toLowerCase() === petName) return pet.name;
24 | }
25 | }
26 |
27 | module.exports = monorouter()
28 | .setup(reactRouting())
29 | .route('index', '/', function(req) {
30 | this.renderInitial(Preloader, function() {
31 | fetchPetList(function(err, data) {
32 | this.render(PetList, {pets: data});
33 | }.bind(this));
34 | });
35 | })
36 | .route('pet', '/pet/:name', function(req) {
37 | // NOTE: Because we're calling `this.renderInitial()`, we lose the
38 | // opportunity to have the server send a 404, and the client will have to
39 | // display a 'missing' view. If you want the server to send 404s, you have
40 | // to call `this.unhandled()` before `this.renderInitial()`.
41 | this.renderInitial(Preloader, function() {
42 | fetchPetList(function(err, data) {
43 | this.render(PetDetail, {petName: findPetName(req.params.name, data)});
44 | }.bind(this));
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/examples/renderinitial/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var routerMiddleware = require('connect-monorouter');
3 | var Router = require('./router');
4 | var path = require('path');
5 |
6 | port = process.env.PORT || 5000;
7 |
8 | express()
9 | .use(routerMiddleware(Router))
10 | .get('/browser.js', function(req, res) {
11 | res.sendfile(path.join(__dirname, 'browser.js'));
12 | })
13 | .listen(port, function() {
14 | console.log("Listening on " + port + ".");
15 | console.log("Go to in your browser.");
16 | });
17 |
--------------------------------------------------------------------------------
/examples/renderinitial/views/App.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 |
4 | function App(props, children) {
5 | return (
6 |
7 |
8 | RenderInitial Example
9 |
10 |
11 | {children}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 |
19 | module.exports = App;
20 |
--------------------------------------------------------------------------------
/examples/renderinitial/views/PetDetail.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetDetail(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 |
17 | module.exports = PetDetail;
18 |
--------------------------------------------------------------------------------
/examples/renderinitial/views/PetList.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function PetList(props) {
6 | var links = props.pets.map(function(pet) {
7 | return {pet.name};
8 | });
9 | return (
10 |
11 |
14 |
15 | );
16 | }
17 |
18 | module.exports = PetList;
19 |
--------------------------------------------------------------------------------
/examples/renderinitial/views/Preloader.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | var React = require('react');
3 | var App = require('./App');
4 |
5 | function Preloader(props) {
6 | return (
7 |
8 |
9 |
Loading...
10 |
11 |
12 | );
13 | }
14 |
15 | module.exports = Preloader;
16 |
--------------------------------------------------------------------------------
/examples/renderinitial/webpack.browser.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | browser: './browser.js'
7 | },
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: path.join(__dirname, 'built'),
12 | devtool: '$@inline-source-map',
13 | libraryTarget: 'umd',
14 | publicPath: '../built/'
15 | },
16 |
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, loader: 'jsx'}
20 | ]
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/examples/renderinitial/webpack.server.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: {
6 | server: './server.js'
7 | },
8 | externals: ['express', 'react'],
9 | target: 'node',
10 | node: {
11 | console: false,
12 | process: false,
13 | global: false,
14 | buffer: false,
15 | __filename: false,
16 | __dirname: false
17 | },
18 |
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, 'built'),
22 | devtool: '$@inline-source-map',
23 | libraryTarget: 'commonjs2',
24 | publicPath: '../built/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx'}
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var
2 | gulp = require('gulp'),
3 | gutil = require('gulp-util'),
4 | browserify = require('gulp-browserify'),
5 | rename = require('gulp-rename'),
6 | gbump = require('gulp-bump'),
7 | jasmine = require('jasmine-runner-node');
8 |
9 | gulp.task('watch', function() {
10 | gulp.watch('./lib/**/*', ['build:browser']);
11 | });
12 |
13 | var bump = function(type) {
14 | gulp.src(['./bower.json', './package.json'])
15 | .pipe(gbump({type: type}))
16 | .pipe(gulp.dest('./'));
17 | };
18 |
19 | gulp.task('bump:major', function() { bump('major'); });
20 | gulp.task('bump:minor', function() { bump('minor'); });
21 | gulp.task('bump:patch', function() { bump('patch'); });
22 |
23 | gulp.task('build:browser', function() {
24 | gulp.src('./lib/index.js')
25 | .pipe(browserify({
26 | standalone: 'monorouter',
27 | transform: ['browserify-shim']
28 | }))
29 | .pipe(rename('monorouter.js'))
30 | .pipe(gulp.dest('./standalone/'));
31 | });
32 |
33 | gulp.task('build:tests', function() {
34 | gulp.src('./lib/**/__tests__/*.js')
35 | .pipe(browserify({
36 | insertGlobals : true,
37 | debug : !gulp.env.production
38 | }))
39 | .pipe(gulp.dest('./.tests-built/'));
40 | });
41 |
42 | gulp.task('watch:tests', function() {
43 | gulp.watch('./lib/**/*', ['build:tests']);
44 | });
45 |
46 | gulp.task('test', ['build:tests'], function() {
47 | jasmine.start({
48 | port: 8888,
49 | files: {
50 | spec: './.tests-built/**/*.js'
51 | }
52 | });
53 | });
54 |
55 | gulp.task('build', ['build:browser']);
56 |
--------------------------------------------------------------------------------
/lib/LinkHijacker.js:
--------------------------------------------------------------------------------
1 | var urllite = require('urllite');
2 | require('urllite/lib/extensions/toString');
3 |
4 |
5 | /**
6 | * A utility for hijacking link clicks and forwarding them to the router.
7 | */
8 | function LinkHijacker(router, el, opts) {
9 | this.router = router;
10 | this.element = el || window;
11 | this.blacklist = opts && opts.blacklist;
12 | this.handleClick = this.handleClick.bind(this);
13 | this.start();
14 | }
15 |
16 | LinkHijacker.prototype.start = function() {
17 | // This handler works by trying to route the URL and then, if it was
18 | // successful, updating the history. If the history object being used doesn't
19 | // support that, don't bother adding the event listener.
20 | if (!this.router.history.push) return;
21 |
22 | this.element.addEventListener('click', this.handleClick);
23 | };
24 |
25 | LinkHijacker.prototype.stop = function() {
26 | this.element.removeEventListener('click', this.handleClick);
27 | };
28 |
29 | LinkHijacker.prototype.handleClick = function(event) {
30 | // Ignore canceled events, modified clicks, and right clicks.
31 | if (event.defaultPrevented) return;
32 | if (event.metaKey || event.ctrlKey || event.shiftKey) return;
33 | if (event.button !== 0) return;
34 |
35 | // Get the element.
36 | var el = event.target;
37 | while (el && el.nodeName !== 'A') {
38 | el = el.parentNode;
39 | }
40 |
41 | // Ignore clicks from non-a elements.
42 | if (!el) return;
43 |
44 | // Ignore the click if the element has a target.
45 | if (el.target && el.target !== '_self') return;
46 |
47 | // Ignore the click if it's a download link. (We use this method of
48 | // detecting the presence of the attribute for old IE versions.)
49 | if (!!el.attributes.download) return;
50 |
51 | // Use a regular expression to parse URLs instead of relying on the browser
52 | // to do it for us (because IE).
53 | var url = urllite(el.href);
54 | var windowURL = urllite(window.location.href);
55 |
56 | // Ignore links that don't share a protocol and host with ours.
57 | if (url.protocol !== windowURL.protocol || url.host !== windowURL.host)
58 | return;
59 |
60 | var fullPath = url.pathname + url.search + url.hash;
61 |
62 | // Ignore URLs that don't share the router's rootUrl
63 | if (fullPath.indexOf(this.router.constructor.rootUrl) !== 0) return;
64 |
65 | // Ignore 'rel="external"' links.
66 | if (el.rel && /(?:^|\s+)external(?:\s+|$)/.test(el.rel)) return;
67 |
68 | // Ignore any blacklisted links.
69 | if (this.blacklist) {
70 | for (var i = 0, len = this.blacklist.length; i < len; i++) {
71 | var blacklisted = this.blacklist[i];
72 |
73 | // The blacklist can contain strings or regexps (or anything else with a
74 | // "test" function).
75 | if (blacklisted === fullPath) return;
76 | if (blacklisted.test && blacklisted.test(fullPath)) return;
77 | }
78 | }
79 |
80 | event.preventDefault();
81 |
82 | // Dispatch the URL.
83 | this.router.history.navigate(fullPath, {cause: 'link'});
84 | };
85 |
86 | module.exports = LinkHijacker;
87 |
--------------------------------------------------------------------------------
/lib/Request.js:
--------------------------------------------------------------------------------
1 | var extend = require('xtend/mutable');
2 | var queryString = require('query-string');
3 | var urllite = require('urllite');
4 | var Cancel = require('./errors/Cancel');
5 | var EventEmitter = require('wolfy87-eventemitter');
6 | var inherits = require('inherits');
7 | require('urllite/lib/extensions/resolve');
8 |
9 |
10 | function getUrl(url, root) {
11 | if (root && root !== '/') {
12 | var parsedRoot = urllite(root);
13 | var hostsMatch = !url.host || !parsedRoot.host ||
14 | (url.host === parsedRoot.host);
15 | var inRoot = hostsMatch && url.pathname.indexOf(parsedRoot.pathname) === 0;
16 | if (inRoot) {
17 | var resolvedPath = url.pathname.slice(parsedRoot.pathname.length);
18 | resolvedPath = resolvedPath.charAt(0) === '/' ? resolvedPath : '/' + resolvedPath;
19 | return resolvedPath + url.search;
20 | }
21 | return null;
22 | }
23 | return url.pathname + url.search;
24 | }
25 |
26 | /**
27 | * An object representing the request to be routed. This object is meant to be
28 | * familiar to users of server libraries like Express and koa.
29 | */
30 | function Request(url, opts) {
31 | var parsed = urllite(url);
32 |
33 | // Make sure we have the host information.
34 | if (!parsed.host) {
35 | if ((typeof document !== 'undefined') && document.location) {
36 | parsed = parsed.resolve(document.location.href);
37 | } else {
38 | throw new Error("You need to dispatch absolute URLs on the server.");
39 | }
40 | }
41 |
42 | extend(this, opts);
43 | this.location = parsed;
44 | this.url = getUrl(parsed, opts && opts.root);
45 | this.originalUrl = parsed.pathname + parsed.search;
46 | this.path = parsed.pathname;
47 | this.protocol = parsed.protocol.replace(/:$/, '');
48 | this.hostname = parsed.hostname;
49 | this.host = parsed.host;
50 | this.search = parsed.search;
51 | this.querystring = parsed.search.replace(/^\?/, '');
52 | this.query = queryString.parse(parsed.search);
53 | this.hash = parsed.hash;
54 | this.fragment = parsed.hash.replace(/^#/, '');
55 | }
56 |
57 | // Make requests event emitters.
58 | inherits(Request, EventEmitter);
59 |
60 | /**
61 | * Get a specific param by name or index.
62 | *
63 | * @param {number|string} The index or name of the param.
64 | * @return {object} The matched variables
65 | */
66 | Request.prototype.param = function(indexOrName) {
67 | return this.params[indexOrName];
68 | };
69 |
70 | /**
71 | * Check whether the request triggered by one of the the specified causes.
72 | */
73 | Request.prototype.from = function() {
74 | var causes;
75 | if (typeof arguments[0] === 'string') causes = arguments;
76 | else causes = arguments[0];
77 |
78 | if (causes && causes.length) {
79 | for (var i = 0, len = causes.length; i < len; i++) {
80 | if (causes[i] === this.cause) return true;
81 | }
82 | }
83 | return false;
84 | };
85 |
86 | Request.prototype.cancel = function() {
87 | if (!this.canceled) {
88 | this.canceled = true;
89 | this.emit('cancel', new Cancel(this));
90 | }
91 | };
92 |
93 | Request.prototype.canceled = false;
94 |
95 | module.exports = Request;
96 |
--------------------------------------------------------------------------------
/lib/Response.js:
--------------------------------------------------------------------------------
1 | var inherits = require('inherits');
2 | var Unhandled = require('./errors/Unhandled');
3 | var EventEmitter = require('wolfy87-eventemitter');
4 | var extend = require('xtend');
5 | var delayed = require('./utils/delayed');
6 | var parallel = require('run-parallel');
7 | var withoutResults = require('./utils/withoutResults');
8 | var thunkifyAll = require('./utils/thunkifyAll');
9 | var noop = require('./utils/noop');
10 |
11 |
12 | /**
13 | * The Response is used as the `this` value for route functions. It hosts
14 | * methods for manipulating the server response and application state.
15 | */
16 | function Response(request, router) {
17 | this.request = request;
18 | this.router = router;
19 | this.state = router.state;
20 | this.vars = {};
21 | this._beforeRenderHooks = [];
22 |
23 | request.on('cancel', this['throw'].bind(this));
24 | }
25 |
26 | inherits(Response, EventEmitter);
27 |
28 | Response.prototype.status = 200;
29 |
30 | //
31 | //
32 | // The main methods for manipulating the application state.
33 | //
34 | //
35 |
36 | /**
37 | * A function decorator used to prevent methods from having any effect if the
38 | * corresponding request has been canceled. This prevents you from having to
39 | * check if the request was cancled every time you do something async in your
40 | * handler (however, if you're doing a lot of work, you should do an explicit
41 | * check and opt out of it).
42 | */
43 | function unlessCanceled(fn) {
44 | return function() {
45 | if (this.request.canceled) return this;
46 | return fn.apply(this, arguments);
47 | };
48 | }
49 |
50 | /**
51 | * Update the view vars for any subsequent views rendered by this response.
52 | */
53 | Response.prototype.setVars = unlessCanceled(function(vars) {
54 | this.vars = extend(this.vars, vars);
55 | return this;
56 | });
57 |
58 | Response.prototype.setState = unlessCanceled(function(state) {
59 | this.router.setState(state);
60 | this.state = this.router.state;
61 | return this;
62 | });
63 |
64 | Response.prototype.setView = unlessCanceled(function(view) {
65 | this.view = view;
66 | return this;
67 | });
68 |
69 | Response.prototype.beforeRender = function(fn) {
70 | this._beforeRenderHooks.push(fn);
71 | return this;
72 | };
73 |
74 | //
75 | //
76 | // Ending
77 | //
78 | //
79 |
80 | /**
81 | * Indicates that a response was ended.
82 | */
83 | Response.prototype.ended = false;
84 | Response.prototype.initialEnded = false;
85 |
86 | /**
87 | * Call to indicate that router is in the "initial" state—the state that the
88 | * server will send and the browser app will begin in.
89 | */
90 | Response.prototype.endInitial = function() {
91 | if (this.initialEnded) {
92 | return;
93 | }
94 | this.initialEnded = true;
95 | this.emit('initialReady');
96 | if (this.request.initialOnly) {
97 | this.end();
98 | }
99 | return this.ended;
100 | };
101 |
102 | Response.prototype.end = function() {
103 | if (!this.ended) {
104 | this.endInitial();
105 | this.ended = true;
106 | this.emit('end');
107 | }
108 | };
109 |
110 | /**
111 | * A method for declaring that the router will not handle the current request.
112 | * Under normal circumstances, you shouldn't use this method but instead simply
113 | * call your handler's `done` callback:
114 | *
115 | * Router.route('users/:username', function(req, done) {
116 | * if (this.params.username === 'Matthew') {
117 | * // Do stuff
118 | * } else {
119 | * done();
120 | * }
121 | * });
122 | *
123 | * This allows other middleware the opportunity to handle the request.
124 | *
125 | * Calling this method is the same as invoking the handler's callback with an
126 | * `Unhandled` error in your route handler:
127 | *
128 | * Router.route('users/:username', function() {
129 | * if (this.params.username === 'Matthew') {
130 | * // Do stuff
131 | * } else {
132 | * throw new Router.Unhandled(this.request);
133 | * }
134 | * });
135 | *
136 | * or (the async version)
137 | *
138 | * Router.route('users/:username', function(done) {
139 | * if (this.params.username === 'Matthew') {
140 | * // Do stuff
141 | * } else {
142 | * done(new Router.Unhandled(this.request));
143 | * }
144 | * });
145 | */
146 | Response.prototype.unhandled = function(msg) {
147 | this['throw'](new Unhandled(this.request, msg));
148 | };
149 |
150 | Response.prototype['throw'] = function(err) {
151 | if (!this.error) {
152 | this.error = err;
153 | this.emit('error', err);
154 | }
155 | };
156 |
157 | //
158 | //
159 | // Metadata methods.
160 | //
161 | //
162 |
163 | /**
164 | * Indicate that no view was found for the corresponding request. This has no
165 | * effect in the browser, but causes a 404 response to be sent on the server.
166 | * Note that this is different from `unhandled` as it indicates that the UI has
167 | * been updated.
168 | */
169 | Response.prototype.notFound = function() {
170 | // TODO: Should this work as a getter too? Or should we make it chainable?
171 | this.status = 404;
172 | return this;
173 | };
174 |
175 | Response.prototype.doctype = '';
176 | Response.prototype.contentType = 'text/html; charset=utf-8';
177 |
178 | //
179 | //
180 | // Rendering shortcuts.
181 | //
182 | //
183 |
184 | function bindVars(view, vars) {
185 | if (vars) {
186 | var oldView = view;
187 | return function(extraVars) {
188 | return oldView.call(this, extend(vars, extraVars));
189 | };
190 | }
191 | return view;
192 | }
193 |
194 | /**
195 | * A function decorator that creates a render function that accepts vars (as a
196 | * second argument) from one that doesn't.
197 | *
198 | * @param {function} fn The function whose arguments to transform.
199 | */
200 | function renderer(fn) {
201 | return function() {
202 | var view, vars, cb, callback;
203 | var args = Array.prototype.slice.call(arguments, 0);
204 | if (typeof args[0] === 'function')
205 | view = args.shift();
206 | if (typeof args[0] === 'object')
207 | vars = args.shift();
208 | cb = args[0];
209 |
210 | var boundRender = function(next) {
211 | // Pass a partially applied version of the view to the original function.
212 | vars = extend(this.vars, vars);
213 | view = bindVars(view || this.view, vars);
214 | fn.call(this, view, next);
215 | };
216 |
217 | var hooks = thunkifyAll(this._beforeRenderHooks, this);
218 | parallel(hooks, function(err) {
219 | boundRender.call(this, cb || noop);
220 | }.bind(this));
221 | };
222 | }
223 |
224 | //
225 | // Shortcut methods for rendering a view with props and (optionally) ending the
226 | // request.
227 | //
228 |
229 | Response.prototype._renderIntermediate = unlessCanceled(function(view, cb) {
230 | delayed(function() {
231 | this.router.setView(view);
232 | cb.call(this);
233 | }.bind(this))();
234 | });
235 |
236 | /**
237 | * Render the provided view and end the request.
238 | */
239 | Response.prototype.render = renderer(function(view, cb) {
240 | this._renderIntermediate(view, function(err) {
241 | if (err) {
242 | cb(err);
243 | return;
244 | }
245 | cb.apply(this, arguments);
246 | this.end();
247 | });
248 | });
249 |
250 | /**
251 | * Render the provided view and mark the current state as the initial one.
252 | */
253 | Response.prototype.renderInitial = renderer(function(view, cb) {
254 | this._renderIntermediate(view, function(err) {
255 | if (err) {
256 | cb(err);
257 | return;
258 | }
259 | this.endInitial();
260 | if (!this.request.initialOnly) cb.apply(this, arguments);
261 | });
262 | });
263 |
264 | /**
265 | * Render the provided view.
266 | */
267 | Response.prototype.renderIntermediate = renderer(function(view, cb) {
268 | this._renderIntermediate(view, cb);
269 | });
270 |
271 | Response.prototype.renderDocumentToString = function() {
272 | var engine = this.router.constructor.engine;
273 | var markup = engine.renderToString(this.router);
274 | return (this.doctype || '') + markup;
275 | };
276 |
277 | module.exports = Response;
278 |
--------------------------------------------------------------------------------
/lib/Route.js:
--------------------------------------------------------------------------------
1 | var pathToRegexp = require('./utils/pathToRegexp');
2 |
3 |
4 | function Route(path) {
5 | this.path = path;
6 | this.keys = [];
7 | this.tokens = [];
8 | this.regexp = pathToRegexp(path, this.keys, this.tokens, {strict: true});
9 | }
10 |
11 | Route.prototype.match = function(url) {
12 | var match = url.match(this.regexp);
13 | if (!match) return;
14 | var matchObj = {};
15 | for (var i = 1, len = match.length; i < len; i++) {
16 | matchObj[i] = matchObj[this.keys[i - 1].name] = match[i];
17 | }
18 | return matchObj;
19 | };
20 |
21 | Route.prototype.url = function(params) {
22 | return this.tokens.map(function(token) {
23 | if (token.literal != null) return token.literal;
24 | if (token.name) {
25 | if (params && params[token.name] != null)
26 | return token.delimiter + params[token.name];
27 | else if (token.optional)
28 | return '';
29 | throw new Error('Missing required param "' + token.name + '"');
30 | }
31 | }).join('');
32 | };
33 |
34 | module.exports = Route;
35 |
--------------------------------------------------------------------------------
/lib/Router.js:
--------------------------------------------------------------------------------
1 | var extend = require('xtend');
2 | var Route = require('./Route');
3 | var Request = require('./Request');
4 | var Response = require('./Response');
5 | var Unhandled = require('./errors/Unhandled');
6 | var delayed = require('./utils/delayed');
7 | var getDefaultHistory = require('./history/getHistory');
8 | var series = require('run-series');
9 | var inherits = require('inherits');
10 | var EventEmitter = require('wolfy87-eventemitter');
11 | var attach = require('./attach');
12 | var LinkHijacker = require('./LinkHijacker');
13 | var withoutResults = require('./utils/withoutResults');
14 | var thunkifyAll = require('./utils/thunkifyAll');
15 |
16 |
17 | function Router(opts) {
18 | if (opts) {
19 | this.state = extend(opts.initialState);
20 | this.history = opts.history;
21 | }
22 | this.url = this.constructor.url.bind(this.constructor);
23 | // TODO: Should we add the properties from the class (i.e. rootUrl) to the instance?
24 | }
25 |
26 | inherits(Router, EventEmitter);
27 |
28 |
29 | /**
30 | * Expose the `Unhandled` error so it can be used in handlers more easily.
31 | */
32 | Router.Unhandled = Unhandled;
33 |
34 | function throwNoEngine() {
35 | throw new Error('You are attempting to render but your router has no rendering engine.');
36 | }
37 |
38 | Router.engine = {
39 | renderInto: throwNoEngine,
40 | renderToString: throwNoEngine
41 | };
42 |
43 | /**
44 | * Add a route to the router class. This API can be used in a manner similar
45 | * to many server-side JS routing libraries—by chainging calls to `route`—
46 | * however, it's more likely that you'll want to pass your routes to the
47 | * router class constructor using JSX.
48 | *
49 | * This function accepts either `(path, handlers...)`, or
50 | * `(name, path, handlers...)`.
51 | */
52 | Router.route = function() {
53 | var RouterClass = this;
54 | var args = Array.prototype.slice.call(arguments, 0);
55 | var name, path, handlers;
56 |
57 | if (typeof args[1] !== 'function') name = args.shift();
58 | path = args.shift();
59 | handlers = args;
60 |
61 | var route = new Route(path);
62 |
63 | if (name) {
64 | if (RouterClass.namedRoutes[name])
65 | throw new Error('Route with name "' + name + '" already exists.');
66 | RouterClass.namedRoutes[name] = route;
67 | }
68 |
69 | // Create and register a middleware to represent this route.
70 | RouterClass.use(function(req, next) {
71 | // If this route doesn't match, skip it.
72 | var match = route.match(req.path);
73 | if (!match) return next();
74 |
75 | req.params = match;
76 |
77 | series(thunkifyAll(handlers, this, [req]), withoutResults(next));
78 | });
79 |
80 | // For chaining!
81 | return this;
82 | };
83 |
84 | Router.prototype.setState = function(state) {
85 | this.state = extend(this.state, state);
86 | this.emit('stateChange');
87 | };
88 |
89 | Router.prototype.setView = function(view) {
90 | this.view = view;
91 | this.emit('viewChange');
92 | };
93 |
94 | Router.prototype.render = function() {
95 | if (!this.view) {
96 | throw new Error('You must set a view before rendering');
97 | }
98 | return this.view(extend(this.state));
99 | };
100 |
101 | Router.finalMiddleware = [
102 | // If we've exhausted the middleware without handling the request, call the
103 | // `unhandled()` method. Going through `unhandled` instead of just creating an
104 | // error in the dispatch callback means we're always going through the same
105 | // process, getting the same events, etc.
106 | function(req, next) {
107 | if (!this.error && !this.ended) return this.unhandled();
108 | next();
109 | }
110 | ];
111 |
112 | /**
113 | *
114 | * @param {string} url
115 | * @param {function?} callback
116 | */
117 | Router.prototype.dispatch = function(url, opts, callback) {
118 | var RouterClass = this.constructor;
119 |
120 | if (typeof arguments[1] === 'function') {
121 | callback = arguments[1];
122 | opts = null;
123 | }
124 |
125 | // Wrap the callback, imposing a delay to force asynchronousness in
126 | // case the user calls it synchronously.
127 | var cb = function(err) {
128 | // Clean up listeners
129 | res.removeAllListeners();
130 | req.removeAllListeners();
131 |
132 | this._currentResponse = null;
133 | if (callback) callback(err, err ? null : res);
134 | }.bind(this);
135 |
136 | var first = (opts && opts.first) || !this._dispatchedFirstRequest;
137 | var req = new Request(url, extend(opts, {
138 | cause: opts && opts.cause,
139 | first: first,
140 | root: RouterClass.rootUrl
141 | }));
142 | this._dispatchedFirstRequest = true;
143 |
144 | var res = new Response(req, this)
145 | .on('error', cb)
146 | .on('end', cb);
147 |
148 | if (req.url == null) {
149 | delayed(function() {
150 | res.unhandled('URL not within router root: ' + url);
151 | })();
152 | return res;
153 | }
154 |
155 | if (this._currentResponse) {
156 | this._currentResponse.request.cancel();
157 | }
158 | this._currentResponse = res;
159 |
160 | // Force async behavior so you have a chance to add listeners to the
161 | // request object.
162 | var middleware = RouterClass.middleware.concat(RouterClass.finalMiddleware);
163 | delayed(function() {
164 | series(thunkifyAll(middleware, res, [req]), function(err) {
165 | if (err) {
166 | // Create a wrapper for each of the error middlewares that receives the
167 | // error from the previous one. This way, error middleware can pass a
168 | // different error than it received, and this new error can be handled
169 | // by subsequent error middleware. If an error middleware does not pass
170 | // an error or end the request, the request will be considered
171 | // unhandled.
172 | var previousError = err;
173 | var errorMiddleware = RouterClass.errorMiddleware.map(function(fn) {
174 | return function(done) {
175 | var newDone = function(err, result) {
176 | if (err) {
177 | previousError = err;
178 | done();
179 | } else {
180 | res.unhandled();
181 | }
182 | };
183 | try {
184 | fn.call(res, previousError, req, newDone);
185 | } catch (err) {
186 | newDone(err);
187 | }
188 | };
189 | });
190 | series(errorMiddleware, function() {
191 | if (previousError) res['throw'](previousError);
192 | });
193 | }
194 | });
195 | })();
196 |
197 | return res;
198 | };
199 |
200 | Router.prototype.captureClicks = function(el, opts) {
201 | return new LinkHijacker(this, el, opts);
202 | };
203 |
204 | Router.use = function(middleware) {
205 | var RouterClass = this,
206 | middlewares = middleware.length === 3 ? RouterClass.errorMiddleware : RouterClass.middleware;
207 | middlewares.push(middleware);
208 | return this;
209 | };
210 |
211 | Router.extend = function(opts) {
212 | var SuperClass = this;
213 | var NewRouter = function(opts) {
214 | if (!(this instanceof NewRouter)) {
215 | return new NewRouter(opts);
216 | }
217 | SuperClass.call(this, opts);
218 | };
219 | inherits(NewRouter, SuperClass);
220 |
221 | // Add "static" props to the new router.
222 | for (var k in SuperClass) {
223 | if (SuperClass.hasOwnProperty(k)) {
224 | if (k === 'super_' || k === 'prototype') continue;
225 | NewRouter[k] = SuperClass[k];
226 | }
227 | }
228 |
229 | NewRouter.engine = opts && opts.engine;
230 | NewRouter.rootUrl = opts && opts.rootUrl || '';
231 | NewRouter.middleware = [];
232 | NewRouter.errorMiddleware = [];
233 | NewRouter.namedRoutes = {};
234 |
235 | return NewRouter;
236 | };
237 |
238 | Router.attach = function(element, opts) {
239 | return attach(this, element, opts);
240 | };
241 |
242 | /**
243 | * Extensions are just functions that mutate the router in any way they want
244 | * and return it. This function is just a prettier way to use them than calling
245 | * them directly.
246 | */
247 | Router.setup = function(extension) {
248 | var router = extension(this);
249 | if (!router)
250 | throw new Error('Invalid extension: extension did not return router.');
251 | return router;
252 | };
253 |
254 | Router.url = function(name, params) {
255 | var route = this.namedRoutes[name];
256 | if (!route)
257 | throw new Error('There is no route named "' + name + '".');
258 | return route.url(params);
259 | };
260 |
261 | module.exports = Router;
262 |
--------------------------------------------------------------------------------
/lib/__tests__/Request-test.js:
--------------------------------------------------------------------------------
1 | var Request = require('../Request');
2 |
3 |
4 | describe('Request', function() {
5 |
6 | it('has no hash in the `url` or `originalUrl` properties', function() {
7 | var req = new Request('http://example.com/a/b/c?search=hi#yup');
8 | expect(req.url).not.toContain('#');
9 | expect(req.originalUrl).not.toContain('#');
10 | });
11 |
12 | it('exposes the hash', function() {
13 | var req = new Request('http://example.com/a/b/c?search=hi#yup');
14 | expect(req.fragment).toBe('yup');
15 | });
16 |
17 | it('contains the domain', function() {
18 | var req = new Request('http://example.com/a/b/c');
19 | expect(req.hostname).toBe('example.com');
20 | });
21 |
22 | it('has the path as its url property', function() {
23 | var req = new Request('http://example.com/a/b/c?search=hi#yup');
24 | expect(req.url).toBe('/a/b/c?search=hi');
25 | });
26 |
27 | it('resolves the url to a root', function() {
28 | var req = new Request('http://example.com/a/b/c?search=hi#yup',
29 | {root: '/a/b'});
30 | expect(req.url).toBe('/c?search=hi');
31 | });
32 |
33 | it('resolves the url to a root with a trailing slash', function() {
34 | var req = new Request('http://example.com/a/b/c?search=hi#yup',
35 | {root: '/a/b/'});
36 | expect(req.url).toBe('/c?search=hi');
37 | });
38 |
39 | it('contains the original, unresolved url', function() {
40 | var req = new Request('http://example.com/a/b/c?search=hi#yup',
41 | {root: '/a/b/'});
42 | expect(req.originalUrl).toBe('/a/b/c?search=hi');
43 | });
44 |
45 | it('has no `url` property when the URL is outside the root', function() {
46 | var req = new Request('http://example.com/a/b/c?search=hi',
47 | {root: '/x/y'});
48 | expect(req.url).toBe(null);
49 | expect(req.originalUrl).toBe('/a/b/c?search=hi');
50 | });
51 |
52 | it('can tell you the cause', function() {
53 | var req = new Request('http://example.com/a/b/c?search=hi',
54 | {cause: 'popstate'});
55 | expect(req.from('popstate')).toBe(true);
56 | expect(req.from('somethingelse')).toBe(false);
57 | expect(req.from('somethingelse', 'popstate')).toBe(true);
58 | expect(req.from(['somethingelse', 'popstate'])).toBe(true);
59 | expect(req.from(['somethingelse'])).toBe(false);
60 | });
61 |
62 | it('should expose all the opts', function() {
63 | var req = new Request('',
64 | {
65 | first: false,
66 | initialOnly: true,
67 | originalReq: {foo: true}
68 | });
69 | expect(req.first).toBe(false);
70 | expect(req.initialOnly).toBe(true);
71 | expect(req.originalReq.foo).toBe(true);
72 | });
73 |
74 | });
75 |
--------------------------------------------------------------------------------
/lib/__tests__/Route-test.js:
--------------------------------------------------------------------------------
1 | var Route = require('../Route');
2 |
3 |
4 | describe('Route', function() {
5 | describe('#url()', function() {
6 |
7 | it('generates a URL', function() {
8 | var route = new Route('/users/');
9 | var url = route.url();
10 | expect(url).toBe('/users/');
11 | });
12 |
13 | it('generates a URL with a named param', function() {
14 | var route = new Route('/users/:username/pets');
15 | var url = route.url({username: 'matthewwithanm'});
16 | expect(url).toBe('/users/matthewwithanm/pets');
17 | });
18 |
19 | it("errors if you don't provide all the params", function() {
20 | var route = new Route('/users/:username/pets');
21 | expect(route.url.bind(route)).toThrowError(/Missing required param/);
22 | });
23 |
24 | it("properly builds URLs with special RegExp chars", function() {
25 | var route = new Route('/weird,^url/:username/pets');
26 | var url = route.url({username: 'matthewwithanm'});
27 | expect(url).toBe('/weird,^url/matthewwithanm/pets');
28 | });
29 |
30 | it("properly builds URLs with optional params", function() {
31 | var route = new Route('/users/:username?');
32 | var url = route.url({username: 'matthewwithanm'});
33 | expect(url).toBe('/users/matthewwithanm');
34 | expect(route.url()).toBe('/users');
35 | });
36 |
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/lib/__tests__/Router-test.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var monorouter = require('../index');
3 | var DummyHistory = require('../history/DummyHistory');
4 | var Route = require('../Route');
5 | var reactRouting = require('monorouter-react');
6 |
7 |
8 | var noop = function() {};
9 | var dummy = function(props) {
10 | return React.DOM.div(null, props.message);
11 | };
12 |
13 | function renderComponent(component, block) {
14 | var div = document.createElement('div');
15 | React.renderComponent(component, div);
16 | if (block)
17 | block();
18 | }
19 |
20 | describe('A Router instance', function() {
21 |
22 | it('creates routes', function() {
23 | var Router = monorouter()
24 | .route('a', '/a', noop)
25 | .route('b', '/b', noop);
26 | expect(Router.middleware.length).toBe(2);
27 | });
28 |
29 | it('matches routes', function(done) {
30 | var Router = monorouter()
31 | .route('/animals/:type', function(req) {
32 | expect(req.params.type).toBe('dog');
33 | done();
34 | });
35 | Router().dispatch('/animals/dog', function(err) {
36 | if (err) done(err);
37 | });
38 | });
39 |
40 | it('throws Unhandled errors for…um, unhandled URLs', function(done) {
41 | var Router = monorouter();
42 | Router()
43 | .dispatch('/animals/dog', function(err) {
44 | expect(err).toBeTruthy();
45 | expect(err.name).toBe('Unhandled');
46 | done();
47 | });
48 | });
49 |
50 | it('throws Unhandled errors when we explicitly choose not to handle', function(done) {
51 | var Router = monorouter()
52 | .route('/animals/:type', function(req) {
53 | this.unhandled();
54 | });
55 | Router().dispatch('/animals/dog', function(err) {
56 | expect(err).toBeTruthy();
57 | expect(err.name).toBe('Unhandled');
58 | done();
59 | });
60 | });
61 |
62 | it('cancels pending requests', function(done) {
63 | var div = document.createElement('div');
64 | var history = new DummyHistory('/async');
65 | var Router = monorouter()
66 | .setup(reactRouting())
67 | .route('/async', function(req) {
68 | req.on('cancel', function(err) {
69 | done();
70 | });
71 | setTimeout(function() {
72 | this.render(dummy);
73 | }.bind(this), 0);
74 | })
75 | .route('/cancelit', function(req) {
76 | this.render(dummy);
77 | });
78 | Router.attach(div, {history: history});
79 | history.push('/cancelit');
80 | });
81 |
82 | it('Updates the router state', function(done){
83 | var Router = monorouter()
84 | .route('/test', function() {
85 | this.success = true;
86 | this.render(dummy);
87 | });
88 | var router = Router();
89 | router.dispatch('/test', function(err, res) {
90 | if (err) return done(err);
91 | expect(res.success).toBeTruthy();
92 | done();
93 | });
94 | });
95 |
96 | it('invokes the beforeRender hooks in turn', function(done){
97 | var order = [];
98 | var Router = monorouter()
99 | .use(function(req, next) {
100 | this
101 | .beforeRender(function(cb) {
102 | setTimeout(function() {
103 | order.push(2);
104 | cb();
105 | }, 0);
106 | })
107 | .beforeRender(function(cb) {
108 | setTimeout(function() {
109 | order.push(3);
110 | cb();
111 | }, 0);
112 | });
113 | next();
114 | })
115 | .route('/test', function() {
116 | order.push(1);
117 | this.render(dummy);
118 | });
119 | var router = Router();
120 | router.dispatch('/test', function(err, res) {
121 | if (err) return done(err);
122 | expect(order).toEqual([1, 2, 3]);
123 | done();
124 | });
125 | });
126 |
127 | it('allows the view to be set without rendering', function(done) {
128 | var view = function(vars) {
129 | expect(vars.value).toBe(5);
130 | done();
131 | return React.DOM.div();
132 | };
133 | var Router = monorouter()
134 | .use(function(req, next) {
135 | this.setView(view);
136 | next();
137 | })
138 | .route('/test', function() {
139 | this.render({value: 5});
140 | });
141 | var router = Router();
142 | router.dispatch('/test', function(err, res) {
143 | router.render();
144 | });
145 | });
146 |
147 | it('allows view and vars to be built up prior to render', function(done) {
148 | var view = function(vars) {
149 | expect(vars.value).toBe(5);
150 | done();
151 | return React.DOM.div();
152 | };
153 | var Router = monorouter()
154 | .use(function(req, next) {
155 | this.setView(view);
156 | next();
157 | })
158 | .use(function(req, next) {
159 | this.setVars({value: 5});
160 | next();
161 | })
162 | .route('/test', function() {
163 | this.render();
164 | });
165 | var router = Router();
166 | router.dispatch('/test', function(err, res) {
167 | router.render();
168 | });
169 | });
170 |
171 | describe('Error middleware', function() {
172 | it('is called if middleware passes an error', function(done) {
173 | var ERR_NAME = 'Test error';
174 | var Router = monorouter()
175 | .route('/animals/:type', function(req, next) {
176 | var err = new Error();
177 | err.name = ERR_NAME;
178 | next(err);
179 | })
180 | .use(function(req, next) {
181 | throw new Error('This should not be called!');
182 | })
183 | .use(function(err, req, next) {
184 | expect(err).toBeTruthy();
185 | expect(err.name).toBe(ERR_NAME);
186 | done();
187 | });
188 | Router().dispatch('/animals/dog', function(err) {
189 | if (err) done(err);
190 | });
191 | });
192 |
193 | it('is called if middleware throws an error', function(done) {
194 | var ERR_NAME = 'Test error';
195 | var Router = monorouter()
196 | .route('/animals/:type', function(req, next) {
197 | var err = new Error();
198 | err.name = ERR_NAME;
199 | throw err;
200 | })
201 | .use(function(req, next) {
202 | throw new Error('This should not be called!');
203 | })
204 | .use(function(err, req, next) {
205 | expect(err).toBeTruthy();
206 | expect(err.name).toBe(ERR_NAME);
207 | done();
208 | });
209 | Router().dispatch('/animals/dog', function(err) {
210 | if (err) done(err);
211 | });
212 | });
213 |
214 | it('treats not passing the error as an unhandled request', function(done) {
215 | var Router = monorouter()
216 | .route('/animals/:type', function(req, next) {
217 | throw new Error();
218 | })
219 | .use(function(err, req, next) {
220 | // Don't pass along the error.
221 | next();
222 | });
223 | Router().dispatch('/animals/dog', function(err) {
224 | expect(err && err.name).toBe('Unhandled');
225 | done();
226 | });
227 | });
228 |
229 | it('passes errors down the chain', function(done) {
230 | var FIRST_ERROR_NAME = 'First';
231 | var SECOND_ERROR_NAME = 'Second';
232 | var Router = monorouter()
233 | .route('/animals/:type', function(req, next) {
234 | var err = new Error();
235 | err.name = FIRST_ERROR_NAME;
236 | throw err;
237 | })
238 | .use(function(err, req, next) {
239 | // Pass along another error.
240 | expect(err && err.name).toBe(FIRST_ERROR_NAME);
241 | var err = new Error();
242 | err.name = SECOND_ERROR_NAME;
243 | next(err);
244 | })
245 | .use(function(err, req, next) {
246 | // Pass along another error.
247 | expect(err && err.name).toBe(SECOND_ERROR_NAME);
248 | done();
249 | });
250 | Router().dispatch('/animals/dog');
251 | });
252 |
253 | it('is passes the error to the dispatch callback', function(done) {
254 | var ERR_NAME = 'Test error';
255 | var Router = monorouter()
256 | .route('/animals/:type', function(req, next) {
257 | throw new Error();
258 | })
259 | .use(function(err, req, next) {
260 | var err = new Error();
261 | err.name = ERR_NAME;
262 | next(err);
263 | });
264 | Router().dispatch('/animals/dog', function(err) {
265 | expect(err && err.name).toBe(ERR_NAME);
266 | done();
267 | });
268 | });
269 | });
270 |
271 | });
272 |
--------------------------------------------------------------------------------
/lib/attach.js:
--------------------------------------------------------------------------------
1 | var getDefaultHistory = require('./history/getHistory');
2 |
3 |
4 | /**
5 | * Bootstraps the app by getting the initial state.
6 | */
7 | function attach(Router, element, opts) {
8 | if (!opts) opts = {};
9 | var history = opts.history || getDefaultHistory();
10 | var router = new Router({history: history});
11 |
12 | var render = function() {
13 | Router.engine.renderInto(router, element);
14 | };
15 |
16 | var onInitialReady = function() {
17 | render();
18 | router
19 | .on('viewChange', render)
20 | .on('stateChange', render);
21 |
22 | // Now that the view has been bootstrapped (i.e. is in its inital state), it
23 | // can be updated.
24 | update();
25 | history.on('update', function(meta) {
26 | update(meta);
27 | });
28 | };
29 |
30 | var previousURL;
31 | var update = function(meta) {
32 | var url = history.currentURL();
33 | if (url === previousURL) return;
34 | previousURL = url;
35 |
36 | var res = router.dispatch(url, meta, function(err) {
37 | if (err && (err.name !== 'Unhandled') && (err.name !== 'Cancel')) {
38 | throw err;
39 | }
40 | });
41 |
42 | if (meta && meta.cause === 'startup') {
43 | res.once('initialReady', onInitialReady);
44 | }
45 | };
46 |
47 | // Start the process.
48 | update({cause: 'startup'});
49 |
50 | return router;
51 | }
52 |
53 | module.exports = attach;
54 |
--------------------------------------------------------------------------------
/lib/errors/Cancel.js:
--------------------------------------------------------------------------------
1 | var initError = require('./initError');
2 |
3 | function Cancel(request) {
4 | initError(this, 'Cancel', 'Request was canceled: ' + request.path);
5 | this.request = request;
6 | }
7 |
8 | Cancel.prototype = Error.prototype;
9 |
10 | module.exports = Cancel;
11 |
--------------------------------------------------------------------------------
/lib/errors/Unhandled.js:
--------------------------------------------------------------------------------
1 | var initError = require('./initError');
2 |
3 | function Unhandled(request, msg) {
4 | if (!msg) msg = 'Path not found: ' + request.path;
5 | initError(this, 'Unhandled', msg);
6 | this.request = request;
7 | }
8 |
9 | Unhandled.prototype = Error.prototype;
10 |
11 | module.exports = Unhandled;
12 |
--------------------------------------------------------------------------------
/lib/errors/initError.js:
--------------------------------------------------------------------------------
1 | function initError(error, name, msg) {
2 | var source = new Error(msg);
3 | error.name = source.name = name;
4 | error.message = source.message;
5 |
6 | if (source.stack) {
7 | error.stack = source.stack;
8 | }
9 |
10 | error.toString = function() {
11 | return this.name + ': ' + this.message;
12 | }
13 | };
14 |
15 | module.exports = initError;
16 |
--------------------------------------------------------------------------------
/lib/history/BaseHistory.js:
--------------------------------------------------------------------------------
1 | var inherits = require('inherits');
2 | var EventEmitter = require('wolfy87-eventemitter');
3 |
4 |
5 | function BaseHistory() {}
6 |
7 | // Make BaseHistory an event emitter.
8 | inherits(BaseHistory, EventEmitter);
9 |
10 | /**
11 | * Navigate to the provided URL without creating a duplicate history entry if
12 | * you're already there.
13 | */
14 | BaseHistory.prototype.navigate = function(url, meta) {
15 | if (url !== this.currentURL()) {
16 | this.push(url, meta);
17 | } else {
18 | this.replace(url, meta);
19 | }
20 | };
21 |
22 | module.exports = BaseHistory;
23 |
--------------------------------------------------------------------------------
/lib/history/DummyHistory.js:
--------------------------------------------------------------------------------
1 | var inherits = require('inherits');
2 | var BaseHistory = require('./BaseHistory');
3 |
4 |
5 | var DummyHistory = function(initialPath) {
6 | this.history = [initialPath];
7 | };
8 |
9 | inherits(DummyHistory, BaseHistory);
10 |
11 | DummyHistory.prototype.currentURL = function() {
12 | return this.history[this.history.length - 1];
13 | };
14 |
15 | DummyHistory.prototype.push = function(path, meta) {
16 | this.history.push(path);
17 | this.emit('update', meta);
18 | };
19 |
20 | DummyHistory.prototype.replace = function(path, meta) {
21 | this.history.pop();
22 | this.history.push(path);
23 | this.emit('update', meta);
24 | };
25 |
26 | DummyHistory.prototype.pop = function() {
27 | throw new Error('Not Implemented');
28 | };
29 |
30 | module.exports = DummyHistory;
31 |
--------------------------------------------------------------------------------
/lib/history/FallbackHistory.js:
--------------------------------------------------------------------------------
1 | var inherits = require('inherits');
2 | var BaseHistory = require('./BaseHistory');
3 | var urllite = require ('urllite');
4 |
5 |
6 | /**
7 | * A history interface for browsers that don't support pushState.
8 | */
9 | function FallbackHistory() {}
10 |
11 | inherits(FallbackHistory, BaseHistory);
12 |
13 | FallbackHistory.prototype.currentURL = function() {
14 | // If we have our own idea of the URL, use that.
15 | if (this._url) return this._url;
16 |
17 | // Use urllite to pave over IE issues with pathname.
18 | var parsed = urllite(document.location.href);
19 | return parsed.pathname + parsed.search + parsed.hash;
20 | };
21 |
22 | FallbackHistory.prototype.push = function(path) {
23 | // No need to update `this._url`—this code is all going to be reloaded.
24 | window.location = path;
25 | };
26 |
27 | FallbackHistory.prototype.replace = function(path, meta) {
28 | // For the fallback history, `replace` won't actually change the browser
29 | // address, but will update its own URL. This is because `replace` usually
30 | // corresponds to "lesser" state changes: having a stale browser URL is
31 | // considered more acceptable than refreshing the entire page.
32 | this._url = path;
33 | this.emit('update', meta);
34 | };
35 |
36 | module.exports = FallbackHistory;
37 |
--------------------------------------------------------------------------------
/lib/history/History.js:
--------------------------------------------------------------------------------
1 | var PushStateHistory = require('./PushStateHistory');
2 | var FallbackHistory = require('./FallbackHistory');
3 |
4 |
5 | var win = typeof window !== 'undefined' ? window : null;
6 | var history = win && win.history;
7 |
8 | module.exports = history && history.pushState ? PushStateHistory : FallbackHistory;
9 |
--------------------------------------------------------------------------------
/lib/history/PushStateHistory.js:
--------------------------------------------------------------------------------
1 | var inherits = require('inherits');
2 | var BaseHistory = require('./BaseHistory');
3 | var urllite = require ('urllite');
4 |
5 |
6 | /**
7 | * A history implementation that uses `pushState`.
8 | */
9 | function PushStateHistory() {
10 | window.addEventListener('popstate', function(event) {
11 | this.emit('update', {cause: 'popstate'});
12 | }.bind(this));
13 | }
14 |
15 | inherits(PushStateHistory, BaseHistory);
16 |
17 | PushStateHistory.prototype.currentURL = function() {
18 | // Use urllite to pave over IE issues with pathname.
19 | var parsed = urllite(document.location.href);
20 | return parsed.pathname + parsed.search + parsed.hash;
21 | };
22 |
23 | PushStateHistory.prototype.push = function(path, meta) {
24 | window.history.pushState({}, '', path);
25 | this.emit('update', meta);
26 | };
27 |
28 | PushStateHistory.prototype.replace = function(path, meta) {
29 | window.history.replaceState({}, '', path);
30 | this.emit('update', meta);
31 | };
32 |
33 | module.exports = PushStateHistory;
34 |
--------------------------------------------------------------------------------
/lib/history/getHistory.js:
--------------------------------------------------------------------------------
1 | var History = require('./History');
2 |
3 | var singleton;
4 |
5 | function getHistory() {
6 | if (!singleton)
7 | singleton = new History();
8 | return singleton;
9 | }
10 |
11 | module.exports = getHistory;
12 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | var Router = require('./Router');
2 |
3 | function monorouter(opts) {
4 | return Router.extend(opts);
5 | }
6 |
7 | module.exports = monorouter;
8 |
--------------------------------------------------------------------------------
/lib/utils/delayed.js:
--------------------------------------------------------------------------------
1 | var delay = typeof setImmediate === 'function' ? setImmediate : function(fn) {
2 | setTimeout(fn, 0);
3 | };
4 |
5 | /**
6 | * Creates a delayed version of the provided function. This is used to guarantee
7 | * ansynchronous behavior for potentially synchronous operations.
8 | */
9 | function delayed(fn) {
10 | return function() {
11 | var args = arguments;
12 | var self = this;
13 | var fnWithArgs = function() {
14 | fn.apply(self, args);
15 | };
16 | delay(fnWithArgs);
17 | };
18 | }
19 |
20 | module.exports = delayed;
21 |
--------------------------------------------------------------------------------
/lib/utils/noop.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {};
2 |
--------------------------------------------------------------------------------
/lib/utils/pathToRegexp.js:
--------------------------------------------------------------------------------
1 | // A custom version of Blake Embrey's path-to-regexp—modified in order to
2 | // support URL reversal.
3 |
4 | /*
5 | * The MIT License (MIT)
6 | *
7 | * Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
8 | *
9 | * Permission is hereby granted, free of charge, to any person obtaining a copy
10 | * of this software and associated documentation files (the "Software"), to deal
11 | * in the Software without restriction, including without limitation the rights
12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | * copies of the Software, and to permit persons to whom the Software is
14 | * furnished to do so, subject to the following conditions:
15 | *
16 | * The above copyright notice and this permission notice shall be included in
17 | * all copies or substantial portions of the Software.
18 | *
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | * THE SOFTWARE.
26 | */
27 |
28 | /**
29 | * Expose `pathtoRegexp`.
30 | */
31 | module.exports = pathtoRegexp;
32 |
33 | var PATH_REGEXP = new RegExp([
34 | // Match already escaped characters that would otherwise incorrectly appear
35 | // in future matches. This allows the user to escape special characters that
36 | // shouldn't be transformed.
37 | '(\\\\.)',
38 | // Match Express-style parameters and un-named parameters with a prefix
39 | // and optional suffixes. Matches appear as:
40 | //
41 | // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
42 | // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
43 | '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
44 | // Match regexp special characters that should always be escaped.
45 | '([.+*?=^!:${}()[\\]|\\/])'
46 | ].join('|'), 'g');
47 |
48 | /**
49 | * Escape the capturing group by escaping special characters and meaning.
50 | *
51 | * @param {String} group
52 | * @return {String}
53 | */
54 | function escapeGroup (group) {
55 | return group.replace(/([=!:$\/()])/g, '\\$1');
56 | }
57 |
58 | /**
59 | * Normalize the given path string, returning a regular expression.
60 | *
61 | * An empty array should be passed in, which will contain the placeholder key
62 | * names. For example `/user/:id` will then contain `["id"]`.
63 | *
64 | * @param {(String|RegExp|Array)} path
65 | * @param {Array} keys
66 | * @param {Array} tokens
67 | * @param {Object} options
68 | * @return {RegExp}
69 | */
70 | function pathtoRegexp (path, keys, tokens, options) {
71 | keys = keys || [];
72 | tokens = tokens || [];
73 | options = options || {};
74 |
75 | var strict = options.strict;
76 | var end = options.end !== false;
77 | var flags = options.sensitive ? '' : 'i';
78 | var index = 0;
79 |
80 | var charIndex = 0;
81 | var originalPath = path;
82 |
83 | // Alter the path string into a usable regexp.
84 | path = path.replace(PATH_REGEXP, function (match, escaped, prefix, key, capture, group, suffix, escape, offset) {
85 |
86 | if (offset !== charIndex) {
87 | tokens.push({literal: path.slice(charIndex, offset)});
88 | }
89 | charIndex = offset + match.length;
90 |
91 | // Avoiding re-escaping escaped characters.
92 | if (escaped) {
93 | return escaped;
94 | }
95 |
96 | // Escape regexp special characters.
97 | if (escape) {
98 | tokens.push({literal: escape});
99 | return '\\' + escape;
100 | }
101 |
102 | var repeat = suffix === '+' || suffix === '*';
103 | var optional = suffix === '?' || suffix === '*';
104 |
105 | keys.push({
106 | name: key || index++,
107 | delimiter: prefix || '/',
108 | optional: optional,
109 | repeat: repeat
110 | });
111 | tokens.push(keys[keys.length - 1]);
112 |
113 | // Escape the prefix character.
114 | prefix = prefix ? '\\' + prefix : '';
115 |
116 | // Match using the custom capturing group, or fallback to capturing
117 | // everything up to the next slash (or next period if the param was
118 | // prefixed with a period).
119 | capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?');
120 |
121 | // Allow parameters to be repeated more than once.
122 | if (repeat) {
123 | capture = capture + '(?:' + prefix + capture + ')*';
124 | }
125 |
126 | // Allow a parameter to be optional.
127 | if (optional) {
128 | return '(?:' + prefix + '(' + capture + '))?';
129 | }
130 |
131 | // Basic parameter support.
132 | return prefix + '(' + capture + ')';
133 | });
134 |
135 | // Add any unconsumed part of the string to our token list.
136 | if (charIndex !== originalPath.length) {
137 | tokens.push({literal: originalPath.slice(charIndex, originalPath.length)});
138 | }
139 |
140 | // Check whether the path ends in a slash as it alters some match behaviour.
141 | var endsWithSlash = path[path.length - 1] === '/';
142 |
143 | // In non-strict mode we allow an optional trailing slash in the match. If
144 | // the path to match already ended with a slash, we need to remove it for
145 | // consistency. The slash is only valid at the very end of a path match, not
146 | // anywhere in the middle. This is important for non-ending mode, otherwise
147 | // "/test/" will match "/test//route".
148 | if (!strict) {
149 | path = (endsWithSlash ? path.slice(0, -2) : path) + '(?:\\/(?=$))?';
150 | }
151 |
152 | // In non-ending mode, we need prompt the capturing groups to match as much
153 | // as possible by using a positive lookahead for the end or next path segment.
154 | if (!end) {
155 | path += strict && endsWithSlash ? '' : '(?=\\/|$)';
156 | }
157 |
158 | return new RegExp('^' + path + (end ? '$' : ''), flags);
159 | }
160 |
--------------------------------------------------------------------------------
/lib/utils/thunkifyAll.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Return thunkified versions of the provided functions bound to the specified
3 | * context and with the provided initial args.
4 | */
5 | function thunkifyAll(fns, thisArg, args) {
6 | fns = fns || [];
7 | return fns.map(function(fn) {
8 | return function(done) {
9 | var newArgs = args ? Array.prototype.slice.call(args, 0) : [];
10 | newArgs.push(done);
11 | try {
12 | fn.apply(thisArg, newArgs);
13 | } catch (err) {
14 | done(err);
15 | }
16 | };
17 | });
18 | }
19 |
20 | module.exports = thunkifyAll;
21 |
--------------------------------------------------------------------------------
/lib/utils/withoutResults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a new version of a callback that doesn't get the results. This is
3 | * used so that we don't forward collected results from run-series.
4 | */
5 | function withoutResults(callback, thisArg) {
6 | if (callback) {
7 | return function(err, results) {
8 | return callback.call(thisArg, err);
9 | };
10 | }
11 | }
12 |
13 | module.exports = withoutResults;
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorouter",
3 | "version": "0.11.3",
4 | "description": "An isomorphic JS router for the virtual DOM",
5 | "main": "./lib/index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "dependencies": {
10 | "urllite": "~0.4.2",
11 | "query-string": "~0.4.1",
12 | "wolfy87-eventemitter": "~4.2.6",
13 | "inherits": "~2.0.1",
14 | "xtend": "~3.0.0",
15 | "run-series": "~1.0.2",
16 | "run-parallel": "~1.0.0"
17 | },
18 | "devDependencies": {
19 | "browserify": "~3.33.0",
20 | "browserify-shim": "~3.3.2",
21 | "gulp": "~3.5.6",
22 | "gulp-bump": "~0.1.6",
23 | "gulp-util": "~2.2.14",
24 | "gulp-browserify": "~0.5.0",
25 | "gulp-rename": "~1.2.0",
26 | "webpack": "^1.3.1-beta4",
27 | "jsx-loader": "^0.10.2",
28 | "imports-loader": "^0.6.3",
29 | "express": "^4.5.1",
30 | "jasmine-runner-node": "0.0.1",
31 | "react": "*",
32 | "monorouter-react": "^0.3.0"
33 | },
34 | "scripts": {
35 | "prepublish": "gulp build",
36 | "test": "gulp test"
37 | },
38 | "repository": {
39 | "type": "git",
40 | "url": "git://github.com/matthewwithanm/monorouter.git"
41 | },
42 | "keywords": [
43 | "react-component",
44 | "react",
45 | "application",
46 | "router",
47 | "routing",
48 | "component"
49 | ],
50 | "author": "Matthew Dapena-Tretter ",
51 | "license": "MIT",
52 | "bugs": {
53 | "url": "https://github.com/matthewwithanm/monorouter/issues"
54 | },
55 | "browserify-shim": {
56 | "react": "global:React"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/standalone/monorouter.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),o.monorouter=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o element.
37 | var el = event.target;
38 | while (el && el.nodeName !== 'A') {
39 | el = el.parentNode;
40 | }
41 |
42 | // Ignore clicks from non-a elements.
43 | if (!el) return;
44 |
45 | // Ignore the click if the element has a target.
46 | if (el.target && el.target !== '_self') return;
47 |
48 | // Ignore the click if it's a download link. (We use this method of
49 | // detecting the presence of the attribute for old IE versions.)
50 | if (!!el.attributes.download) return;
51 |
52 | // Use a regular expression to parse URLs instead of relying on the browser
53 | // to do it for us (because IE).
54 | var url = urllite(el.href);
55 | var windowURL = urllite(window.location.href);
56 |
57 | // Ignore links that don't share a protocol and host with ours.
58 | if (url.protocol !== windowURL.protocol || url.host !== windowURL.host)
59 | return;
60 |
61 | var fullPath = url.pathname + url.search + url.hash;
62 |
63 | // Ignore URLs that don't share the router's rootUrl
64 | if (fullPath.indexOf(this.router.constructor.rootUrl) !== 0) return;
65 |
66 | // Ignore 'rel="external"' links.
67 | if (el.rel && /(?:^|\s+)external(?:\s+|$)/.test(el.rel)) return;
68 |
69 | // Ignore any blacklisted links.
70 | if (this.blacklist) {
71 | for (var i = 0, len = this.blacklist.length; i < len; i++) {
72 | var blacklisted = this.blacklist[i];
73 |
74 | // The blacklist can contain strings or regexps (or anything else with a
75 | // "test" function).
76 | if (blacklisted === fullPath) return;
77 | if (blacklisted.test && blacklisted.test(fullPath)) return;
78 | }
79 | }
80 |
81 | event.preventDefault();
82 |
83 | // Dispatch the URL.
84 | this.router.history.navigate(fullPath, {cause: 'link'});
85 | };
86 |
87 | module.exports = LinkHijacker;
88 |
89 | },{"urllite":25,"urllite/lib/extensions/toString":30}],2:[function(_dereq_,module,exports){
90 | var extend = _dereq_('xtend/mutable');
91 | var queryString = _dereq_('query-string');
92 | var urllite = _dereq_('urllite');
93 | var Cancel = _dereq_('./errors/Cancel');
94 | var EventEmitter = _dereq_('wolfy87-eventemitter');
95 | var inherits = _dereq_('inherits');
96 | _dereq_('urllite/lib/extensions/resolve');
97 |
98 |
99 | function getUrl(url, root) {
100 | if (root && root !== '/') {
101 | var parsedRoot = urllite(root);
102 | var hostsMatch = !url.host || !parsedRoot.host ||
103 | (url.host === parsedRoot.host);
104 | var inRoot = hostsMatch && url.pathname.indexOf(parsedRoot.pathname) === 0;
105 | if (inRoot) {
106 | var resolvedPath = url.pathname.slice(parsedRoot.pathname.length);
107 | resolvedPath = resolvedPath.charAt(0) === '/' ? resolvedPath : '/' + resolvedPath;
108 | return resolvedPath + url.search;
109 | }
110 | return null;
111 | }
112 | return url.pathname + url.search;
113 | }
114 |
115 | /**
116 | * An object representing the request to be routed. This object is meant to be
117 | * familiar to users of server libraries like Express and koa.
118 | */
119 | function Request(url, opts) {
120 | var parsed = urllite(url);
121 |
122 | // Make sure we have the host information.
123 | if (!parsed.host) {
124 | if ((typeof document !== 'undefined') && document.location) {
125 | parsed = parsed.resolve(document.location.href);
126 | } else {
127 | throw new Error("You need to dispatch absolute URLs on the server.");
128 | }
129 | }
130 |
131 | extend(this, opts);
132 | this.location = parsed;
133 | this.url = getUrl(parsed, opts && opts.root);
134 | this.originalUrl = parsed.pathname + parsed.search;
135 | this.path = parsed.pathname;
136 | this.protocol = parsed.protocol.replace(/:$/, '');
137 | this.hostname = parsed.hostname;
138 | this.host = parsed.host;
139 | this.search = parsed.search;
140 | this.querystring = parsed.search.replace(/^\?/, '');
141 | this.query = queryString.parse(parsed.search);
142 | this.hash = parsed.hash;
143 | this.fragment = parsed.hash.replace(/^#/, '');
144 | }
145 |
146 | // Make requests event emitters.
147 | inherits(Request, EventEmitter);
148 |
149 | /**
150 | * Get a specific param by name or index.
151 | *
152 | * @param {number|string} The index or name of the param.
153 | * @return {object} The matched variables
154 | */
155 | Request.prototype.param = function(indexOrName) {
156 | return this.params[indexOrName];
157 | };
158 |
159 | /**
160 | * Check whether the request triggered by one of the the specified causes.
161 | */
162 | Request.prototype.from = function() {
163 | var causes;
164 | if (typeof arguments[0] === 'string') causes = arguments;
165 | else causes = arguments[0];
166 |
167 | if (causes && causes.length) {
168 | for (var i = 0, len = causes.length; i < len; i++) {
169 | if (causes[i] === this.cause) return true;
170 | }
171 | }
172 | return false;
173 | };
174 |
175 | Request.prototype.cancel = function() {
176 | if (!this.canceled) {
177 | this.canceled = true;
178 | this.emit('cancel', new Cancel(this));
179 | }
180 | };
181 |
182 | Request.prototype.canceled = false;
183 |
184 | module.exports = Request;
185 |
186 | },{"./errors/Cancel":7,"inherits":21,"query-string":22,"urllite":25,"urllite/lib/extensions/resolve":29,"wolfy87-eventemitter":31,"xtend/mutable":33}],3:[function(_dereq_,module,exports){
187 | var inherits = _dereq_('inherits');
188 | var Unhandled = _dereq_('./errors/Unhandled');
189 | var EventEmitter = _dereq_('wolfy87-eventemitter');
190 | var extend = _dereq_('xtend');
191 | var delayed = _dereq_('./utils/delayed');
192 | var parallel = _dereq_('run-parallel');
193 | var withoutResults = _dereq_('./utils/withoutResults');
194 | var thunkifyAll = _dereq_('./utils/thunkifyAll');
195 | var noop = _dereq_('./utils/noop');
196 |
197 |
198 | /**
199 | * The Response is used as the `this` value for route functions. It hosts
200 | * methods for manipulating the server response and application state.
201 | */
202 | function Response(request, router) {
203 | this.request = request;
204 | this.router = router;
205 | this.state = router.state;
206 | this.vars = {};
207 | this._beforeRenderHooks = [];
208 |
209 | request.on('cancel', this['throw'].bind(this));
210 | }
211 |
212 | inherits(Response, EventEmitter);
213 |
214 | Response.prototype.status = 200;
215 |
216 | //
217 | //
218 | // The main methods for manipulating the application state.
219 | //
220 | //
221 |
222 | /**
223 | * A function decorator used to prevent methods from having any effect if the
224 | * corresponding request has been canceled. This prevents you from having to
225 | * check if the request was cancled every time you do something async in your
226 | * handler (however, if you're doing a lot of work, you should do an explicit
227 | * check and opt out of it).
228 | */
229 | function unlessCanceled(fn) {
230 | return function() {
231 | if (this.request.canceled) return this;
232 | return fn.apply(this, arguments);
233 | };
234 | }
235 |
236 | /**
237 | * Update the view vars for any subsequent views rendered by this response.
238 | */
239 | Response.prototype.setVars = unlessCanceled(function(vars) {
240 | this.vars = extend(this.vars, vars);
241 | return this;
242 | });
243 |
244 | Response.prototype.setState = unlessCanceled(function(state) {
245 | this.router.setState(state);
246 | this.state = this.router.state;
247 | return this;
248 | });
249 |
250 | Response.prototype.setView = unlessCanceled(function(view) {
251 | this.view = view;
252 | return this;
253 | });
254 |
255 | Response.prototype.beforeRender = function(fn) {
256 | this._beforeRenderHooks.push(fn);
257 | return this;
258 | };
259 |
260 | //
261 | //
262 | // Ending
263 | //
264 | //
265 |
266 | /**
267 | * Indicates that a response was ended.
268 | */
269 | Response.prototype.ended = false;
270 | Response.prototype.initialEnded = false;
271 |
272 | /**
273 | * Call to indicate that router is in the "initial" state—the state that the
274 | * server will send and the browser app will begin in.
275 | */
276 | Response.prototype.endInitial = function() {
277 | if (this.initialEnded) {
278 | return;
279 | }
280 | this.initialEnded = true;
281 | this.emit('initialReady');
282 | if (this.request.initialOnly) {
283 | this.end();
284 | }
285 | return this.ended;
286 | };
287 |
288 | Response.prototype.end = function() {
289 | if (!this.ended) {
290 | this.endInitial();
291 | this.ended = true;
292 | this.emit('end');
293 | }
294 | };
295 |
296 | /**
297 | * A method for declaring that the router will not handle the current request.
298 | * Under normal circumstances, you shouldn't use this method but instead simply
299 | * call your handler's `done` callback:
300 | *
301 | * Router.route('users/:username', function(req, done) {
302 | * if (this.params.username === 'Matthew') {
303 | * // Do stuff
304 | * } else {
305 | * done();
306 | * }
307 | * });
308 | *
309 | * This allows other middleware the opportunity to handle the request.
310 | *
311 | * Calling this method is the same as invoking the handler's callback with an
312 | * `Unhandled` error in your route handler:
313 | *
314 | * Router.route('users/:username', function() {
315 | * if (this.params.username === 'Matthew') {
316 | * // Do stuff
317 | * } else {
318 | * throw new Router.Unhandled(this.request);
319 | * }
320 | * });
321 | *
322 | * or (the async version)
323 | *
324 | * Router.route('users/:username', function(done) {
325 | * if (this.params.username === 'Matthew') {
326 | * // Do stuff
327 | * } else {
328 | * done(new Router.Unhandled(this.request));
329 | * }
330 | * });
331 | */
332 | Response.prototype.unhandled = function(msg) {
333 | this['throw'](new Unhandled(this.request, msg));
334 | };
335 |
336 | Response.prototype['throw'] = function(err) {
337 | if (!this.error) {
338 | this.error = err;
339 | this.emit('error', err);
340 | }
341 | };
342 |
343 | //
344 | //
345 | // Metadata methods.
346 | //
347 | //
348 |
349 | /**
350 | * Indicate that no view was found for the corresponding request. This has no
351 | * effect in the browser, but causes a 404 response to be sent on the server.
352 | * Note that this is different from `unhandled` as it indicates that the UI has
353 | * been updated.
354 | */
355 | Response.prototype.notFound = function() {
356 | // TODO: Should this work as a getter too? Or should we make it chainable?
357 | this.status = 404;
358 | return this;
359 | };
360 |
361 | Response.prototype.doctype = '';
362 | Response.prototype.contentType = 'text/html; charset=utf-8';
363 |
364 | //
365 | //
366 | // Rendering shortcuts.
367 | //
368 | //
369 |
370 | function bindVars(view, vars) {
371 | if (vars) {
372 | var oldView = view;
373 | return function(extraVars) {
374 | return oldView.call(this, extend(vars, extraVars));
375 | };
376 | }
377 | return view;
378 | }
379 |
380 | /**
381 | * A function decorator that creates a render function that accepts vars (as a
382 | * second argument) from one that doesn't.
383 | *
384 | * @param {function} fn The function whose arguments to transform.
385 | */
386 | function renderer(fn) {
387 | return function() {
388 | var view, vars, cb, callback;
389 | var args = Array.prototype.slice.call(arguments, 0);
390 | if (typeof args[0] === 'function')
391 | view = args.shift();
392 | if (typeof args[0] === 'object')
393 | vars = args.shift();
394 | cb = args[0];
395 |
396 | var boundRender = function(next) {
397 | // Pass a partially applied version of the view to the original function.
398 | vars = extend(this.vars, vars);
399 | view = bindVars(view || this.view, vars);
400 | fn.call(this, view, next);
401 | };
402 |
403 | var hooks = thunkifyAll(this._beforeRenderHooks, this);
404 | parallel(hooks, function(err) {
405 | boundRender.call(this, cb || noop);
406 | }.bind(this));
407 | };
408 | }
409 |
410 | //
411 | // Shortcut methods for rendering a view with props and (optionally) ending the
412 | // request.
413 | //
414 |
415 | Response.prototype._renderIntermediate = unlessCanceled(function(view, cb) {
416 | delayed(function() {
417 | this.router.setView(view);
418 | cb.call(this);
419 | }.bind(this))();
420 | });
421 |
422 | /**
423 | * Render the provided view and end the request.
424 | */
425 | Response.prototype.render = renderer(function(view, cb) {
426 | this._renderIntermediate(view, function(err) {
427 | if (err) {
428 | cb(err);
429 | return;
430 | }
431 | cb.apply(this, arguments);
432 | this.end();
433 | });
434 | });
435 |
436 | /**
437 | * Render the provided view and mark the current state as the initial one.
438 | */
439 | Response.prototype.renderInitial = renderer(function(view, cb) {
440 | this._renderIntermediate(view, function(err) {
441 | if (err) {
442 | cb(err);
443 | return;
444 | }
445 | this.endInitial();
446 | if (!this.request.initialOnly) cb.apply(this, arguments);
447 | });
448 | });
449 |
450 | /**
451 | * Render the provided view.
452 | */
453 | Response.prototype.renderIntermediate = renderer(function(view, cb) {
454 | this._renderIntermediate(view, cb);
455 | });
456 |
457 | Response.prototype.renderDocumentToString = function() {
458 | var engine = this.router.constructor.engine;
459 | var markup = engine.renderToString(this.router);
460 | return (this.doctype || '') + markup;
461 | };
462 |
463 | module.exports = Response;
464 |
465 | },{"./errors/Unhandled":8,"./utils/delayed":16,"./utils/noop":17,"./utils/thunkifyAll":19,"./utils/withoutResults":20,"inherits":21,"run-parallel":23,"wolfy87-eventemitter":31,"xtend":32}],4:[function(_dereq_,module,exports){
466 | var pathToRegexp = _dereq_('./utils/pathToRegexp');
467 |
468 |
469 | function Route(path) {
470 | this.path = path;
471 | this.keys = [];
472 | this.tokens = [];
473 | this.regexp = pathToRegexp(path, this.keys, this.tokens, {strict: true});
474 | }
475 |
476 | Route.prototype.match = function(url) {
477 | var match = url.match(this.regexp);
478 | if (!match) return;
479 | var matchObj = {};
480 | for (var i = 1, len = match.length; i < len; i++) {
481 | matchObj[i] = matchObj[this.keys[i - 1].name] = match[i];
482 | }
483 | return matchObj;
484 | };
485 |
486 | Route.prototype.url = function(params) {
487 | return this.tokens.map(function(token) {
488 | if (token.literal != null) return token.literal;
489 | if (token.name) {
490 | if (params && params[token.name] != null)
491 | return token.delimiter + params[token.name];
492 | else if (token.optional)
493 | return '';
494 | throw new Error('Missing required param "' + token.name + '"');
495 | }
496 | }).join('');
497 | };
498 |
499 | module.exports = Route;
500 |
501 | },{"./utils/pathToRegexp":18}],5:[function(_dereq_,module,exports){
502 | var extend = _dereq_('xtend');
503 | var Route = _dereq_('./Route');
504 | var Request = _dereq_('./Request');
505 | var Response = _dereq_('./Response');
506 | var Unhandled = _dereq_('./errors/Unhandled');
507 | var delayed = _dereq_('./utils/delayed');
508 | var getDefaultHistory = _dereq_('./history/getHistory');
509 | var series = _dereq_('run-series');
510 | var inherits = _dereq_('inherits');
511 | var EventEmitter = _dereq_('wolfy87-eventemitter');
512 | var attach = _dereq_('./attach');
513 | var LinkHijacker = _dereq_('./LinkHijacker');
514 | var withoutResults = _dereq_('./utils/withoutResults');
515 | var thunkifyAll = _dereq_('./utils/thunkifyAll');
516 |
517 |
518 | function Router(opts) {
519 | if (opts) {
520 | this.state = extend(opts.initialState);
521 | this.history = opts.history;
522 | }
523 | this.url = this.constructor.url.bind(this.constructor);
524 | // TODO: Should we add the properties from the class (i.e. rootUrl) to the instance?
525 | }
526 |
527 | inherits(Router, EventEmitter);
528 |
529 |
530 | /**
531 | * Expose the `Unhandled` error so it can be used in handlers more easily.
532 | */
533 | Router.Unhandled = Unhandled;
534 |
535 | function throwNoEngine() {
536 | throw new Error('You are attempting to render but your router has no rendering engine.');
537 | }
538 |
539 | Router.engine = {
540 | renderInto: throwNoEngine,
541 | renderToString: throwNoEngine
542 | };
543 |
544 | /**
545 | * Add a route to the router class. This API can be used in a manner similar
546 | * to many server-side JS routing libraries—by chainging calls to `route`—
547 | * however, it's more likely that you'll want to pass your routes to the
548 | * router class constructor using JSX.
549 | *
550 | * This function accepts either `(path, handlers...)`, or
551 | * `(name, path, handlers...)`.
552 | */
553 | Router.route = function() {
554 | var RouterClass = this;
555 | var args = Array.prototype.slice.call(arguments, 0);
556 | var name, path, handlers;
557 |
558 | if (typeof args[1] !== 'function') name = args.shift();
559 | path = args.shift();
560 | handlers = args;
561 |
562 | var route = new Route(path);
563 |
564 | if (name) {
565 | if (RouterClass.namedRoutes[name])
566 | throw new Error('Route with name "' + name + '" already exists.');
567 | RouterClass.namedRoutes[name] = route;
568 | }
569 |
570 | // Create and register a middleware to represent this route.
571 | RouterClass.use(function(req, next) {
572 | // If this route doesn't match, skip it.
573 | var match = route.match(req.path);
574 | if (!match) return next();
575 |
576 | req.params = match;
577 |
578 | series(thunkifyAll(handlers, this, [req]), withoutResults(next));
579 | });
580 |
581 | // For chaining!
582 | return this;
583 | };
584 |
585 | Router.prototype.setState = function(state) {
586 | this.state = extend(this.state, state);
587 | this.emit('stateChange');
588 | };
589 |
590 | Router.prototype.setView = function(view) {
591 | this.view = view;
592 | this.emit('viewChange');
593 | };
594 |
595 | Router.prototype.render = function() {
596 | if (!this.view) {
597 | throw new Error('You must set a view before rendering');
598 | }
599 | return this.view(extend(this.state));
600 | };
601 |
602 | Router.finalMiddleware = [
603 | // If we've exhausted the middleware without handling the request, call the
604 | // `unhandled()` method. Going through `unhandled` instead of just creating an
605 | // error in the dispatch callback means we're always going through the same
606 | // process, getting the same events, etc.
607 | function(req, next) {
608 | if (!this.error && !this.ended) return this.unhandled();
609 | next();
610 | }
611 | ];
612 |
613 | /**
614 | *
615 | * @param {string} url
616 | * @param {function?} callback
617 | */
618 | Router.prototype.dispatch = function(url, opts, callback) {
619 | var RouterClass = this.constructor;
620 |
621 | if (typeof arguments[1] === 'function') {
622 | callback = arguments[1];
623 | opts = null;
624 | }
625 |
626 | // Wrap the callback, imposing a delay to force asynchronousness in
627 | // case the user calls it synchronously.
628 | var cb = function(err) {
629 | // Clean up listeners
630 | res.removeAllListeners();
631 | req.removeAllListeners();
632 |
633 | this._currentResponse = null;
634 | if (callback) callback(err, err ? null : res);
635 | }.bind(this);
636 |
637 | var first = (opts && opts.first) || !this._dispatchedFirstRequest;
638 | var req = new Request(url, extend(opts, {
639 | cause: opts && opts.cause,
640 | first: first,
641 | root: RouterClass.rootUrl
642 | }));
643 | this._dispatchedFirstRequest = true;
644 |
645 | var res = new Response(req, this)
646 | .on('error', cb)
647 | .on('end', cb);
648 |
649 | if (req.url == null) {
650 | delayed(function() {
651 | res.unhandled('URL not within router root: ' + url);
652 | })();
653 | return res;
654 | }
655 |
656 | if (this._currentResponse) {
657 | this._currentResponse.request.cancel();
658 | }
659 | this._currentResponse = res;
660 |
661 | // Force async behavior so you have a chance to add listeners to the
662 | // request object.
663 | var middleware = RouterClass.middleware.concat(RouterClass.finalMiddleware);
664 | delayed(function() {
665 | series(thunkifyAll(middleware, res, [req]), function(err) {
666 | if (err) {
667 | // Create a wrapper for each of the error middlewares that receives the
668 | // error from the previous one. This way, error middleware can pass a
669 | // different error than it received, and this new error can be handled
670 | // by subsequent error middleware. If an error middleware does not pass
671 | // an error or end the request, the request will be considered
672 | // unhandled.
673 | var previousError = err;
674 | var errorMiddleware = RouterClass.errorMiddleware.map(function(fn) {
675 | return function(done) {
676 | var newDone = function(err, result) {
677 | if (err) {
678 | previousError = err;
679 | done();
680 | } else {
681 | res.unhandled();
682 | }
683 | };
684 | try {
685 | fn.call(res, previousError, req, newDone);
686 | } catch (err) {
687 | newDone(err);
688 | }
689 | };
690 | });
691 | series(errorMiddleware, function() {
692 | if (previousError) res['throw'](previousError);
693 | });
694 | }
695 | });
696 | })();
697 |
698 | return res;
699 | };
700 |
701 | Router.prototype.captureClicks = function(el, opts) {
702 | return new LinkHijacker(this, el, opts);
703 | };
704 |
705 | Router.use = function(middleware) {
706 | var RouterClass = this,
707 | middlewares = middleware.length === 3 ? RouterClass.errorMiddleware : RouterClass.middleware;
708 | middlewares.push(middleware);
709 | return this;
710 | };
711 |
712 | Router.extend = function(opts) {
713 | var SuperClass = this;
714 | var NewRouter = function(opts) {
715 | if (!(this instanceof NewRouter)) {
716 | return new NewRouter(opts);
717 | }
718 | SuperClass.call(this, opts);
719 | };
720 | inherits(NewRouter, SuperClass);
721 |
722 | // Add "static" props to the new router.
723 | for (var k in SuperClass) {
724 | if (SuperClass.hasOwnProperty(k)) {
725 | if (k === 'super_' || k === 'prototype') continue;
726 | NewRouter[k] = SuperClass[k];
727 | }
728 | }
729 |
730 | NewRouter.engine = opts && opts.engine;
731 | NewRouter.rootUrl = opts && opts.rootUrl || '';
732 | NewRouter.middleware = [];
733 | NewRouter.errorMiddleware = [];
734 | NewRouter.namedRoutes = {};
735 |
736 | return NewRouter;
737 | };
738 |
739 | Router.attach = function(element, opts) {
740 | return attach(this, element, opts);
741 | };
742 |
743 | /**
744 | * Extensions are just functions that mutate the router in any way they want
745 | * and return it. This function is just a prettier way to use them than calling
746 | * them directly.
747 | */
748 | Router.setup = function(extension) {
749 | var router = extension(this);
750 | if (!router)
751 | throw new Error('Invalid extension: extension did not return router.');
752 | return router;
753 | };
754 |
755 | Router.url = function(name, params) {
756 | var route = this.namedRoutes[name];
757 | if (!route)
758 | throw new Error('There is no route named "' + name + '".');
759 | return route.url(params);
760 | };
761 |
762 | module.exports = Router;
763 |
764 | },{"./LinkHijacker":1,"./Request":2,"./Response":3,"./Route":4,"./attach":6,"./errors/Unhandled":8,"./history/getHistory":15,"./utils/delayed":16,"./utils/thunkifyAll":19,"./utils/withoutResults":20,"inherits":21,"run-series":24,"wolfy87-eventemitter":31,"xtend":32}],6:[function(_dereq_,module,exports){
765 | var getDefaultHistory = _dereq_('./history/getHistory');
766 |
767 |
768 | /**
769 | * Bootstraps the app by getting the initial state.
770 | */
771 | function attach(Router, element, opts) {
772 | if (!opts) opts = {};
773 | var history = opts.history || getDefaultHistory();
774 | var router = new Router({history: history});
775 |
776 | var render = function() {
777 | Router.engine.renderInto(router, element);
778 | };
779 |
780 | var onInitialReady = function() {
781 | render();
782 | router
783 | .on('viewChange', render)
784 | .on('stateChange', render);
785 |
786 | // Now that the view has been bootstrapped (i.e. is in its inital state), it
787 | // can be updated.
788 | update();
789 | history.on('update', function(meta) {
790 | update(meta);
791 | });
792 | };
793 |
794 | var previousURL;
795 | var update = function(meta) {
796 | var url = history.currentURL();
797 | if (url === previousURL) return;
798 | previousURL = url;
799 |
800 | var res = router.dispatch(url, meta, function(err) {
801 | if (err && (err.name !== 'Unhandled') && (err.name !== 'Cancel')) {
802 | throw err;
803 | }
804 | });
805 |
806 | if (meta && meta.cause === 'startup') {
807 | res.once('initialReady', onInitialReady);
808 | }
809 | };
810 |
811 | // Start the process.
812 | update({cause: 'startup'});
813 |
814 | return router;
815 | }
816 |
817 | module.exports = attach;
818 |
819 | },{"./history/getHistory":15}],7:[function(_dereq_,module,exports){
820 | var initError = _dereq_('./initError');
821 |
822 | function Cancel(request) {
823 | initError(this, 'Cancel', 'Request was canceled: ' + request.path);
824 | this.request = request;
825 | }
826 |
827 | Cancel.prototype = Error.prototype;
828 |
829 | module.exports = Cancel;
830 |
831 | },{"./initError":9}],8:[function(_dereq_,module,exports){
832 | var initError = _dereq_('./initError');
833 |
834 | function Unhandled(request, msg) {
835 | if (!msg) msg = 'Path not found: ' + request.path;
836 | initError(this, 'Unhandled', msg);
837 | this.request = request;
838 | }
839 |
840 | Unhandled.prototype = Error.prototype;
841 |
842 | module.exports = Unhandled;
843 |
844 | },{"./initError":9}],9:[function(_dereq_,module,exports){
845 | function initError(error, name, msg) {
846 | var source = new Error(msg);
847 | error.name = source.name = name;
848 | error.message = source.message;
849 |
850 | if (source.stack) {
851 | error.stack = source.stack;
852 | }
853 |
854 | error.toString = function() {
855 | return this.name + ': ' + this.message;
856 | }
857 | };
858 |
859 | module.exports = initError;
860 |
861 | },{}],10:[function(_dereq_,module,exports){
862 | var Router = _dereq_('./Router');
863 |
864 | function monorouter(opts) {
865 | return Router.extend(opts);
866 | }
867 |
868 | module.exports = monorouter;
869 |
870 | },{"./Router":5}],11:[function(_dereq_,module,exports){
871 | var inherits = _dereq_('inherits');
872 | var EventEmitter = _dereq_('wolfy87-eventemitter');
873 |
874 |
875 | function BaseHistory() {}
876 |
877 | // Make BaseHistory an event emitter.
878 | inherits(BaseHistory, EventEmitter);
879 |
880 | /**
881 | * Navigate to the provided URL without creating a duplicate history entry if
882 | * you're already there.
883 | */
884 | BaseHistory.prototype.navigate = function(url, meta) {
885 | if (url !== this.currentURL()) {
886 | this.push(url, meta);
887 | } else {
888 | this.replace(url, meta);
889 | }
890 | };
891 |
892 | module.exports = BaseHistory;
893 |
894 | },{"inherits":21,"wolfy87-eventemitter":31}],12:[function(_dereq_,module,exports){
895 | var inherits = _dereq_('inherits');
896 | var BaseHistory = _dereq_('./BaseHistory');
897 | var urllite = _dereq_ ('urllite');
898 |
899 |
900 | /**
901 | * A history interface for browsers that don't support pushState.
902 | */
903 | function FallbackHistory() {}
904 |
905 | inherits(FallbackHistory, BaseHistory);
906 |
907 | FallbackHistory.prototype.currentURL = function() {
908 | // If we have our own idea of the URL, use that.
909 | if (this._url) return this._url;
910 |
911 | // Use urllite to pave over IE issues with pathname.
912 | var parsed = urllite(document.location.href);
913 | return parsed.pathname + parsed.search + parsed.hash;
914 | };
915 |
916 | FallbackHistory.prototype.push = function(path) {
917 | // No need to update `this._url`—this code is all going to be reloaded.
918 | window.location = path;
919 | };
920 |
921 | FallbackHistory.prototype.replace = function(path, meta) {
922 | // For the fallback history, `replace` won't actually change the browser
923 | // address, but will update its own URL. This is because `replace` usually
924 | // corresponds to "lesser" state changes: having a stale browser URL is
925 | // considered more acceptable than refreshing the entire page.
926 | this._url = path;
927 | this.emit('update', meta);
928 | };
929 |
930 | module.exports = FallbackHistory;
931 |
932 | },{"./BaseHistory":11,"inherits":21,"urllite":25}],13:[function(_dereq_,module,exports){
933 | var PushStateHistory = _dereq_('./PushStateHistory');
934 | var FallbackHistory = _dereq_('./FallbackHistory');
935 |
936 |
937 | var win = typeof window !== 'undefined' ? window : null;
938 | var history = win && win.history;
939 |
940 | module.exports = history && history.pushState ? PushStateHistory : FallbackHistory;
941 |
942 | },{"./FallbackHistory":12,"./PushStateHistory":14}],14:[function(_dereq_,module,exports){
943 | var inherits = _dereq_('inherits');
944 | var BaseHistory = _dereq_('./BaseHistory');
945 | var urllite = _dereq_ ('urllite');
946 |
947 |
948 | /**
949 | * A history implementation that uses `pushState`.
950 | */
951 | function PushStateHistory() {
952 | window.addEventListener('popstate', function(event) {
953 | this.emit('update', {cause: 'popstate'});
954 | }.bind(this));
955 | }
956 |
957 | inherits(PushStateHistory, BaseHistory);
958 |
959 | PushStateHistory.prototype.currentURL = function() {
960 | // Use urllite to pave over IE issues with pathname.
961 | var parsed = urllite(document.location.href);
962 | return parsed.pathname + parsed.search + parsed.hash;
963 | };
964 |
965 | PushStateHistory.prototype.push = function(path, meta) {
966 | window.history.pushState({}, '', path);
967 | this.emit('update', meta);
968 | };
969 |
970 | PushStateHistory.prototype.replace = function(path, meta) {
971 | window.history.replaceState({}, '', path);
972 | this.emit('update', meta);
973 | };
974 |
975 | module.exports = PushStateHistory;
976 |
977 | },{"./BaseHistory":11,"inherits":21,"urllite":25}],15:[function(_dereq_,module,exports){
978 | var History = _dereq_('./History');
979 |
980 | var singleton;
981 |
982 | function getHistory() {
983 | if (!singleton)
984 | singleton = new History();
985 | return singleton;
986 | }
987 |
988 | module.exports = getHistory;
989 |
990 | },{"./History":13}],16:[function(_dereq_,module,exports){
991 | var delay = typeof setImmediate === 'function' ? setImmediate : function(fn) {
992 | setTimeout(fn, 0);
993 | };
994 |
995 | /**
996 | * Creates a delayed version of the provided function. This is used to guarantee
997 | * ansynchronous behavior for potentially synchronous operations.
998 | */
999 | function delayed(fn) {
1000 | return function() {
1001 | var args = arguments;
1002 | var self = this;
1003 | var fnWithArgs = function() {
1004 | fn.apply(self, args);
1005 | };
1006 | delay(fnWithArgs);
1007 | };
1008 | }
1009 |
1010 | module.exports = delayed;
1011 |
1012 | },{}],17:[function(_dereq_,module,exports){
1013 | module.exports = function() {};
1014 |
1015 | },{}],18:[function(_dereq_,module,exports){
1016 | // A custom version of Blake Embrey's path-to-regexp—modified in order to
1017 | // support URL reversal.
1018 |
1019 | /*
1020 | * The MIT License (MIT)
1021 | *
1022 | * Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
1023 | *
1024 | * Permission is hereby granted, free of charge, to any person obtaining a copy
1025 | * of this software and associated documentation files (the "Software"), to deal
1026 | * in the Software without restriction, including without limitation the rights
1027 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1028 | * copies of the Software, and to permit persons to whom the Software is
1029 | * furnished to do so, subject to the following conditions:
1030 | *
1031 | * The above copyright notice and this permission notice shall be included in
1032 | * all copies or substantial portions of the Software.
1033 | *
1034 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1035 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1036 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1037 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1038 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1039 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1040 | * THE SOFTWARE.
1041 | */
1042 |
1043 | /**
1044 | * Expose `pathtoRegexp`.
1045 | */
1046 | module.exports = pathtoRegexp;
1047 |
1048 | var PATH_REGEXP = new RegExp([
1049 | // Match already escaped characters that would otherwise incorrectly appear
1050 | // in future matches. This allows the user to escape special characters that
1051 | // shouldn't be transformed.
1052 | '(\\\\.)',
1053 | // Match Express-style parameters and un-named parameters with a prefix
1054 | // and optional suffixes. Matches appear as:
1055 | //
1056 | // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
1057 | // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
1058 | '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
1059 | // Match regexp special characters that should always be escaped.
1060 | '([.+*?=^!:${}()[\\]|\\/])'
1061 | ].join('|'), 'g');
1062 |
1063 | /**
1064 | * Escape the capturing group by escaping special characters and meaning.
1065 | *
1066 | * @param {String} group
1067 | * @return {String}
1068 | */
1069 | function escapeGroup (group) {
1070 | return group.replace(/([=!:$\/()])/g, '\\$1');
1071 | }
1072 |
1073 | /**
1074 | * Normalize the given path string, returning a regular expression.
1075 | *
1076 | * An empty array should be passed in, which will contain the placeholder key
1077 | * names. For example `/user/:id` will then contain `["id"]`.
1078 | *
1079 | * @param {(String|RegExp|Array)} path
1080 | * @param {Array} keys
1081 | * @param {Array} tokens
1082 | * @param {Object} options
1083 | * @return {RegExp}
1084 | */
1085 | function pathtoRegexp (path, keys, tokens, options) {
1086 | keys = keys || [];
1087 | tokens = tokens || [];
1088 | options = options || {};
1089 |
1090 | var strict = options.strict;
1091 | var end = options.end !== false;
1092 | var flags = options.sensitive ? '' : 'i';
1093 | var index = 0;
1094 |
1095 | var charIndex = 0;
1096 | var originalPath = path;
1097 |
1098 | // Alter the path string into a usable regexp.
1099 | path = path.replace(PATH_REGEXP, function (match, escaped, prefix, key, capture, group, suffix, escape, offset) {
1100 |
1101 | if (offset !== charIndex) {
1102 | tokens.push({literal: path.slice(charIndex, offset)});
1103 | }
1104 | charIndex = offset + match.length;
1105 |
1106 | // Avoiding re-escaping escaped characters.
1107 | if (escaped) {
1108 | return escaped;
1109 | }
1110 |
1111 | // Escape regexp special characters.
1112 | if (escape) {
1113 | tokens.push({literal: escape});
1114 | return '\\' + escape;
1115 | }
1116 |
1117 | var repeat = suffix === '+' || suffix === '*';
1118 | var optional = suffix === '?' || suffix === '*';
1119 |
1120 | keys.push({
1121 | name: key || index++,
1122 | delimiter: prefix || '/',
1123 | optional: optional,
1124 | repeat: repeat
1125 | });
1126 | tokens.push(keys[keys.length - 1]);
1127 |
1128 | // Escape the prefix character.
1129 | prefix = prefix ? '\\' + prefix : '';
1130 |
1131 | // Match using the custom capturing group, or fallback to capturing
1132 | // everything up to the next slash (or next period if the param was
1133 | // prefixed with a period).
1134 | capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?');
1135 |
1136 | // Allow parameters to be repeated more than once.
1137 | if (repeat) {
1138 | capture = capture + '(?:' + prefix + capture + ')*';
1139 | }
1140 |
1141 | // Allow a parameter to be optional.
1142 | if (optional) {
1143 | return '(?:' + prefix + '(' + capture + '))?';
1144 | }
1145 |
1146 | // Basic parameter support.
1147 | return prefix + '(' + capture + ')';
1148 | });
1149 |
1150 | // Add any unconsumed part of the string to our token list.
1151 | if (charIndex !== originalPath.length) {
1152 | tokens.push({literal: originalPath.slice(charIndex, originalPath.length)});
1153 | }
1154 |
1155 | // Check whether the path ends in a slash as it alters some match behaviour.
1156 | var endsWithSlash = path[path.length - 1] === '/';
1157 |
1158 | // In non-strict mode we allow an optional trailing slash in the match. If
1159 | // the path to match already ended with a slash, we need to remove it for
1160 | // consistency. The slash is only valid at the very end of a path match, not
1161 | // anywhere in the middle. This is important for non-ending mode, otherwise
1162 | // "/test/" will match "/test//route".
1163 | if (!strict) {
1164 | path = (endsWithSlash ? path.slice(0, -2) : path) + '(?:\\/(?=$))?';
1165 | }
1166 |
1167 | // In non-ending mode, we need prompt the capturing groups to match as much
1168 | // as possible by using a positive lookahead for the end or next path segment.
1169 | if (!end) {
1170 | path += strict && endsWithSlash ? '' : '(?=\\/|$)';
1171 | }
1172 |
1173 | return new RegExp('^' + path + (end ? '$' : ''), flags);
1174 | }
1175 |
1176 | },{}],19:[function(_dereq_,module,exports){
1177 | /**
1178 | * Return thunkified versions of the provided functions bound to the specified
1179 | * context and with the provided initial args.
1180 | */
1181 | function thunkifyAll(fns, thisArg, args) {
1182 | fns = fns || [];
1183 | return fns.map(function(fn) {
1184 | return function(done) {
1185 | var newArgs = args ? Array.prototype.slice.call(args, 0) : [];
1186 | newArgs.push(done);
1187 | try {
1188 | fn.apply(thisArg, newArgs);
1189 | } catch (err) {
1190 | done(err);
1191 | }
1192 | };
1193 | });
1194 | }
1195 |
1196 | module.exports = thunkifyAll;
1197 |
1198 | },{}],20:[function(_dereq_,module,exports){
1199 | /**
1200 | * Creates a new version of a callback that doesn't get the results. This is
1201 | * used so that we don't forward collected results from run-series.
1202 | */
1203 | function withoutResults(callback, thisArg) {
1204 | if (callback) {
1205 | return function(err, results) {
1206 | return callback.call(thisArg, err);
1207 | };
1208 | }
1209 | }
1210 |
1211 | module.exports = withoutResults;
1212 |
1213 | },{}],21:[function(_dereq_,module,exports){
1214 | if (typeof Object.create === 'function') {
1215 | // implementation from standard node.js 'util' module
1216 | module.exports = function inherits(ctor, superCtor) {
1217 | ctor.super_ = superCtor
1218 | ctor.prototype = Object.create(superCtor.prototype, {
1219 | constructor: {
1220 | value: ctor,
1221 | enumerable: false,
1222 | writable: true,
1223 | configurable: true
1224 | }
1225 | });
1226 | };
1227 | } else {
1228 | // old school shim for old browsers
1229 | module.exports = function inherits(ctor, superCtor) {
1230 | ctor.super_ = superCtor
1231 | var TempCtor = function () {}
1232 | TempCtor.prototype = superCtor.prototype
1233 | ctor.prototype = new TempCtor()
1234 | ctor.prototype.constructor = ctor
1235 | }
1236 | }
1237 |
1238 | },{}],22:[function(_dereq_,module,exports){
1239 | /*!
1240 | query-string
1241 | Parse and stringify URL query strings
1242 | https://github.com/sindresorhus/query-string
1243 | by Sindre Sorhus
1244 | MIT License
1245 | */
1246 | (function () {
1247 | 'use strict';
1248 | var queryString = {};
1249 |
1250 | queryString.parse = function (str) {
1251 | if (typeof str !== 'string') {
1252 | return {};
1253 | }
1254 |
1255 | str = str.trim().replace(/^(\?|#)/, '');
1256 |
1257 | if (!str) {
1258 | return {};
1259 | }
1260 |
1261 | return str.trim().split('&').reduce(function (ret, param) {
1262 | var parts = param.replace(/\+/g, ' ').split('=');
1263 | var key = parts[0];
1264 | var val = parts[1];
1265 |
1266 | key = decodeURIComponent(key);
1267 | // missing `=` should be `null`:
1268 | // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
1269 | val = val === undefined ? null : decodeURIComponent(val);
1270 |
1271 | if (!ret.hasOwnProperty(key)) {
1272 | ret[key] = val;
1273 | } else if (Array.isArray(ret[key])) {
1274 | ret[key].push(val);
1275 | } else {
1276 | ret[key] = [ret[key], val];
1277 | }
1278 |
1279 | return ret;
1280 | }, {});
1281 | };
1282 |
1283 | queryString.stringify = function (obj) {
1284 | return obj ? Object.keys(obj).map(function (key) {
1285 | var val = obj[key];
1286 |
1287 | if (Array.isArray(val)) {
1288 | return val.map(function (val2) {
1289 | return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
1290 | }).join('&');
1291 | }
1292 |
1293 | return encodeURIComponent(key) + '=' + encodeURIComponent(val);
1294 | }).join('&') : '';
1295 | };
1296 |
1297 | if (typeof define === 'function' && define.amd) {
1298 | define(function() { return queryString; });
1299 | } else if (typeof module !== 'undefined' && module.exports) {
1300 | module.exports = queryString;
1301 | } else {
1302 | window.queryString = queryString;
1303 | }
1304 | })();
1305 |
1306 | },{}],23:[function(_dereq_,module,exports){
1307 | module.exports = function (tasks, cb) {
1308 | var results, pending, keys
1309 | if (Array.isArray(tasks)) {
1310 | results = []
1311 | pending = tasks.length
1312 | } else {
1313 | keys = Object.keys(tasks)
1314 | results = {}
1315 | pending = keys.length
1316 | }
1317 |
1318 | function done (i, err, result) {
1319 | results[i] = result
1320 | if (--pending === 0 || err) {
1321 | cb && cb(err, results)
1322 | cb = null
1323 | }
1324 | }
1325 |
1326 | if (!pending) {
1327 | // empty
1328 | cb && cb(null, results)
1329 | cb = null
1330 | } else if (keys) {
1331 | // object
1332 | keys.forEach(function (key) {
1333 | tasks[key](done.bind(undefined, key))
1334 | })
1335 | } else {
1336 | // array
1337 | tasks.forEach(function (task, i) {
1338 | task(done.bind(undefined, i))
1339 | })
1340 | }
1341 | }
1342 |
1343 | },{}],24:[function(_dereq_,module,exports){
1344 | module.exports = function (tasks, cb) {
1345 | var current = 0
1346 | var results = []
1347 | cb = cb || function () {}
1348 |
1349 | function done (err, result) {
1350 | if (err) return cb(err, results)
1351 | results.push(result)
1352 |
1353 | if (++current >= tasks.length) {
1354 | cb(null, results)
1355 | } else {
1356 | tasks[current](done)
1357 | }
1358 | }
1359 |
1360 | if (tasks.length) {
1361 | tasks[0](done)
1362 | } else {
1363 | cb(null, [])
1364 | }
1365 | }
1366 |
1367 | },{}],25:[function(_dereq_,module,exports){
1368 | (function() {
1369 | var urllite;
1370 |
1371 | urllite = _dereq_('./core');
1372 |
1373 | _dereq_('./extensions/resolve');
1374 |
1375 | _dereq_('./extensions/relativize');
1376 |
1377 | _dereq_('./extensions/normalize');
1378 |
1379 | _dereq_('./extensions/toString');
1380 |
1381 | module.exports = urllite;
1382 |
1383 | }).call(this);
1384 |
1385 | },{"./core":26,"./extensions/normalize":27,"./extensions/relativize":28,"./extensions/resolve":29,"./extensions/toString":30}],26:[function(_dereq_,module,exports){
1386 | (function() {
1387 | var URL, URL_PATTERN, defaults, urllite,
1388 | __hasProp = {}.hasOwnProperty,
1389 | __slice = [].slice;
1390 |
1391 | URL_PATTERN = /^(?:(?:([^:\/?\#]+:)\/+|(\/\/))(?:([a-z0-9-\._~%]+)(?::([a-z0-9-\._~%]+))?@)?(([a-z0-9-\._~%!$&'()*+,;=]+)(?::([0-9]+))?)?)?([^?\#]*?)(\?[^\#]*)?(\#.*)?$/;
1392 |
1393 | urllite = function(raw, opts) {
1394 | return urllite.URL.parse(raw, opts);
1395 | };
1396 |
1397 | urllite.URL = URL = (function() {
1398 | function URL(props) {
1399 | var k, v;
1400 | for (k in props) {
1401 | if (!__hasProp.call(props, k)) continue;
1402 | v = props[k];
1403 | this[k] = v;
1404 | }
1405 | }
1406 |
1407 | URL.parse = function(raw) {
1408 | var m, pathname, protocol;
1409 | m = raw.toString().match(URL_PATTERN);
1410 | pathname = m[8] || '';
1411 | protocol = m[1];
1412 | return urllite._createURL({
1413 | protocol: protocol,
1414 | username: m[3],
1415 | password: m[4],
1416 | hostname: m[6],
1417 | port: m[7],
1418 | pathname: protocol && pathname.charAt(0) !== '/' ? "/" + pathname : pathname,
1419 | search: m[9],
1420 | hash: m[10],
1421 | isSchemeRelative: m[2] != null
1422 | });
1423 | };
1424 |
1425 | return URL;
1426 |
1427 | })();
1428 |
1429 | defaults = {
1430 | protocol: '',
1431 | username: '',
1432 | password: '',
1433 | host: '',
1434 | hostname: '',
1435 | port: '',
1436 | pathname: '',
1437 | search: '',
1438 | hash: '',
1439 | origin: '',
1440 | isSchemeRelative: false
1441 | };
1442 |
1443 | urllite._createURL = function() {
1444 | var base, bases, k, props, v, _i, _len, _ref, _ref1;
1445 | bases = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1446 | props = {};
1447 | for (_i = 0, _len = bases.length; _i < _len; _i++) {
1448 | base = bases[_i];
1449 | for (k in defaults) {
1450 | if (!__hasProp.call(defaults, k)) continue;
1451 | v = defaults[k];
1452 | props[k] = (_ref = (_ref1 = base[k]) != null ? _ref1 : props[k]) != null ? _ref : v;
1453 | }
1454 | }
1455 | props.host = props.hostname && props.port ? "" + props.hostname + ":" + props.port : props.hostname ? props.hostname : '';
1456 | props.origin = props.protocol ? "" + props.protocol + "//" + props.host : '';
1457 | props.isAbsolutePathRelative = !props.host && props.pathname.charAt(0) === '/';
1458 | props.isPathRelative = !props.host && props.pathname.charAt(0) !== '/';
1459 | props.isRelative = props.isSchemeRelative || props.isAbsolutePathRelative || props.isPathRelative;
1460 | props.isAbsolute = !props.isRelative;
1461 | return new urllite.URL(props);
1462 | };
1463 |
1464 | module.exports = urllite;
1465 |
1466 | }).call(this);
1467 |
1468 | },{}],27:[function(_dereq_,module,exports){
1469 | (function() {
1470 | var URL, urllite;
1471 |
1472 | urllite = _dereq_('../core');
1473 |
1474 | URL = urllite.URL;
1475 |
1476 | URL.prototype.normalize = function() {
1477 | var m, pathname;
1478 | pathname = this.pathname;
1479 | while (m = /^(.*?)[^\/]+\/\.\.\/*(.*)$/.exec(pathname)) {
1480 | pathname = "" + m[1] + m[2];
1481 | }
1482 | if (this.host && pathname.indexOf('..') !== -1) {
1483 | throw new Error('Path is behind root.');
1484 | }
1485 | return urllite._createURL(this, {
1486 | pathname: pathname
1487 | });
1488 | };
1489 |
1490 | }).call(this);
1491 |
1492 | },{"../core":26}],28:[function(_dereq_,module,exports){
1493 | (function() {
1494 | var URL, urllite;
1495 |
1496 | urllite = _dereq_('../core');
1497 |
1498 | _dereq_('./resolve');
1499 |
1500 | URL = urllite.URL;
1501 |
1502 | URL.prototype.relativize = function(other) {
1503 | var c, i, newSegments, otherSegments, url, urlSegments, _i, _len, _ref;
1504 | if (this.isPathRelative) {
1505 | return new urllite.URL(this);
1506 | }
1507 | if (typeof other === 'string') {
1508 | other = urllite(other);
1509 | }
1510 | url = this.resolve(other);
1511 | if (url.origin && url.origin !== other.origin) {
1512 | throw new Error("Origins don't match (" + url.origin + " and " + other.origin + ")");
1513 | } else if (!other.isAbsolute && !other.isAbsolutePathRelative) {
1514 | throw new Error("Other URL (<" + other + ">) is neither absolute nor absolute path relative.");
1515 | }
1516 | otherSegments = other.pathname.split('/').slice(1);
1517 | urlSegments = url.pathname.split('/').slice(1);
1518 | for (i = _i = 0, _len = urlSegments.length; _i < _len; i = ++_i) {
1519 | c = urlSegments[i];
1520 | if (!(c === otherSegments[i] && (urlSegments.length > (_ref = i + 1) && _ref < otherSegments.length))) {
1521 | break;
1522 | }
1523 | }
1524 | newSegments = urlSegments.slice(i);
1525 | while (i < otherSegments.length - 1) {
1526 | if (otherSegments[i]) {
1527 | newSegments.unshift('..');
1528 | }
1529 | i++;
1530 | }
1531 | if (newSegments.length === 1) {
1532 | newSegments = newSegments[0] === otherSegments[i] ? [''] : newSegments[0] === '' ? ['.'] : newSegments;
1533 | }
1534 | return urllite._createURL({
1535 | pathname: newSegments.join('/'),
1536 | search: url.search,
1537 | hash: url.hash
1538 | });
1539 | };
1540 |
1541 | }).call(this);
1542 |
1543 | },{"../core":26,"./resolve":29}],29:[function(_dereq_,module,exports){
1544 | (function() {
1545 | var URL, copyProps, oldParse, urllite,
1546 | __slice = [].slice;
1547 |
1548 | urllite = _dereq_('../core');
1549 |
1550 | _dereq_('./normalize');
1551 |
1552 | URL = urllite.URL;
1553 |
1554 | oldParse = URL.parse;
1555 |
1556 | copyProps = function() {
1557 | var prop, props, source, target, _i, _len;
1558 | target = arguments[0], source = arguments[1], props = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
1559 | for (_i = 0, _len = props.length; _i < _len; _i++) {
1560 | prop = props[_i];
1561 | target[prop] = source[prop];
1562 | }
1563 | return target;
1564 | };
1565 |
1566 | URL.parse = function(raw, opts) {
1567 | var base, url;
1568 | if (base = opts != null ? opts.base : void 0) {
1569 | delete opts.base;
1570 | }
1571 | url = oldParse(raw, opts);
1572 | if (base) {
1573 | return url.resolve(base);
1574 | } else {
1575 | return url;
1576 | }
1577 | };
1578 |
1579 | URL.prototype.resolve = function(base) {
1580 | var p, prefix;
1581 | if (this.isAbsolute) {
1582 | return new urllite.URL(this);
1583 | }
1584 | if (typeof base === 'string') {
1585 | base = urllite(base);
1586 | }
1587 | p = {};
1588 | if (this.isSchemeRelative) {
1589 | copyProps(p, this, 'username', 'password', 'host', 'hostname', 'port', 'pathname', 'search', 'hash');
1590 | p.isSchemeRelative = !(p.protocol = base.protocol);
1591 | } else if (this.isAbsolutePathRelative || this.isPathRelative) {
1592 | copyProps(p, this, 'search', 'hash');
1593 | copyProps(p, base, 'protocol', 'username', 'password', 'host', 'hostname', 'port');
1594 | p.pathname = this.isPathRelative ? base.pathname.slice(0, -1) === '/' ? "" + base.pathname + "/" + this.pathname : (prefix = base.pathname.split('/').slice(0, -1).join('/'), prefix ? "" + prefix + "/" + this.pathname : this.pathname) : this.pathname;
1595 | }
1596 | return urllite._createURL(p).normalize();
1597 | };
1598 |
1599 | }).call(this);
1600 |
1601 | },{"../core":26,"./normalize":27}],30:[function(_dereq_,module,exports){
1602 | (function() {
1603 | var URL, urllite;
1604 |
1605 | urllite = _dereq_('../core');
1606 |
1607 | URL = urllite.URL;
1608 |
1609 | URL.prototype.toString = function() {
1610 | var authority, prefix, userinfo;
1611 | prefix = this.isSchemeRelative ? '//' : this.protocol === 'file:' ? "" + this.protocol + "///" : this.protocol ? "" + this.protocol + "//" : '';
1612 | userinfo = this.password ? "" + this.username + ":" + this.password : this.username ? "" + this.username : '';
1613 | authority = userinfo ? "" + userinfo + "@" + this.host : this.host ? "" + this.host : '';
1614 | return "" + prefix + authority + this.pathname + this.search + this.hash;
1615 | };
1616 |
1617 | }).call(this);
1618 |
1619 | },{"../core":26}],31:[function(_dereq_,module,exports){
1620 | /*!
1621 | * EventEmitter v4.2.6 - git.io/ee
1622 | * Oliver Caldwell
1623 | * MIT license
1624 | * @preserve
1625 | */
1626 |
1627 | (function () {
1628 | 'use strict';
1629 |
1630 | /**
1631 | * Class for managing events.
1632 | * Can be extended to provide event functionality in other classes.
1633 | *
1634 | * @class EventEmitter Manages event registering and emitting.
1635 | */
1636 | function EventEmitter() {}
1637 |
1638 | // Shortcuts to improve speed and size
1639 | var proto = EventEmitter.prototype;
1640 | var exports = this;
1641 | var originalGlobalValue = exports.EventEmitter;
1642 |
1643 | /**
1644 | * Finds the index of the listener for the event in it's storage array.
1645 | *
1646 | * @param {Function[]} listeners Array of listeners to search through.
1647 | * @param {Function} listener Method to look for.
1648 | * @return {Number} Index of the specified listener, -1 if not found
1649 | * @api private
1650 | */
1651 | function indexOfListener(listeners, listener) {
1652 | var i = listeners.length;
1653 | while (i--) {
1654 | if (listeners[i].listener === listener) {
1655 | return i;
1656 | }
1657 | }
1658 |
1659 | return -1;
1660 | }
1661 |
1662 | /**
1663 | * Alias a method while keeping the context correct, to allow for overwriting of target method.
1664 | *
1665 | * @param {String} name The name of the target method.
1666 | * @return {Function} The aliased method
1667 | * @api private
1668 | */
1669 | function alias(name) {
1670 | return function aliasClosure() {
1671 | return this[name].apply(this, arguments);
1672 | };
1673 | }
1674 |
1675 | /**
1676 | * Returns the listener array for the specified event.
1677 | * Will initialise the event object and listener arrays if required.
1678 | * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
1679 | * Each property in the object response is an array of listener functions.
1680 | *
1681 | * @param {String|RegExp} evt Name of the event to return the listeners from.
1682 | * @return {Function[]|Object} All listener functions for the event.
1683 | */
1684 | proto.getListeners = function getListeners(evt) {
1685 | var events = this._getEvents();
1686 | var response;
1687 | var key;
1688 |
1689 | // Return a concatenated array of all matching events if
1690 | // the selector is a regular expression.
1691 | if (typeof evt === 'object') {
1692 | response = {};
1693 | for (key in events) {
1694 | if (events.hasOwnProperty(key) && evt.test(key)) {
1695 | response[key] = events[key];
1696 | }
1697 | }
1698 | }
1699 | else {
1700 | response = events[evt] || (events[evt] = []);
1701 | }
1702 |
1703 | return response;
1704 | };
1705 |
1706 | /**
1707 | * Takes a list of listener objects and flattens it into a list of listener functions.
1708 | *
1709 | * @param {Object[]} listeners Raw listener objects.
1710 | * @return {Function[]} Just the listener functions.
1711 | */
1712 | proto.flattenListeners = function flattenListeners(listeners) {
1713 | var flatListeners = [];
1714 | var i;
1715 |
1716 | for (i = 0; i < listeners.length; i += 1) {
1717 | flatListeners.push(listeners[i].listener);
1718 | }
1719 |
1720 | return flatListeners;
1721 | };
1722 |
1723 | /**
1724 | * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
1725 | *
1726 | * @param {String|RegExp} evt Name of the event to return the listeners from.
1727 | * @return {Object} All listener functions for an event in an object.
1728 | */
1729 | proto.getListenersAsObject = function getListenersAsObject(evt) {
1730 | var listeners = this.getListeners(evt);
1731 | var response;
1732 |
1733 | if (listeners instanceof Array) {
1734 | response = {};
1735 | response[evt] = listeners;
1736 | }
1737 |
1738 | return response || listeners;
1739 | };
1740 |
1741 | /**
1742 | * Adds a listener function to the specified event.
1743 | * The listener will not be added if it is a duplicate.
1744 | * If the listener returns true then it will be removed after it is called.
1745 | * If you pass a regular expression as the event name then the listener will be added to all events that match it.
1746 | *
1747 | * @param {String|RegExp} evt Name of the event to attach the listener to.
1748 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1749 | * @return {Object} Current instance of EventEmitter for chaining.
1750 | */
1751 | proto.addListener = function addListener(evt, listener) {
1752 | var listeners = this.getListenersAsObject(evt);
1753 | var listenerIsWrapped = typeof listener === 'object';
1754 | var key;
1755 |
1756 | for (key in listeners) {
1757 | if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
1758 | listeners[key].push(listenerIsWrapped ? listener : {
1759 | listener: listener,
1760 | once: false
1761 | });
1762 | }
1763 | }
1764 |
1765 | return this;
1766 | };
1767 |
1768 | /**
1769 | * Alias of addListener
1770 | */
1771 | proto.on = alias('addListener');
1772 |
1773 | /**
1774 | * Semi-alias of addListener. It will add a listener that will be
1775 | * automatically removed after it's first execution.
1776 | *
1777 | * @param {String|RegExp} evt Name of the event to attach the listener to.
1778 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1779 | * @return {Object} Current instance of EventEmitter for chaining.
1780 | */
1781 | proto.addOnceListener = function addOnceListener(evt, listener) {
1782 | return this.addListener(evt, {
1783 | listener: listener,
1784 | once: true
1785 | });
1786 | };
1787 |
1788 | /**
1789 | * Alias of addOnceListener.
1790 | */
1791 | proto.once = alias('addOnceListener');
1792 |
1793 | /**
1794 | * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
1795 | * You need to tell it what event names should be matched by a regex.
1796 | *
1797 | * @param {String} evt Name of the event to create.
1798 | * @return {Object} Current instance of EventEmitter for chaining.
1799 | */
1800 | proto.defineEvent = function defineEvent(evt) {
1801 | this.getListeners(evt);
1802 | return this;
1803 | };
1804 |
1805 | /**
1806 | * Uses defineEvent to define multiple events.
1807 | *
1808 | * @param {String[]} evts An array of event names to define.
1809 | * @return {Object} Current instance of EventEmitter for chaining.
1810 | */
1811 | proto.defineEvents = function defineEvents(evts) {
1812 | for (var i = 0; i < evts.length; i += 1) {
1813 | this.defineEvent(evts[i]);
1814 | }
1815 | return this;
1816 | };
1817 |
1818 | /**
1819 | * Removes a listener function from the specified event.
1820 | * When passed a regular expression as the event name, it will remove the listener from all events that match it.
1821 | *
1822 | * @param {String|RegExp} evt Name of the event to remove the listener from.
1823 | * @param {Function} listener Method to remove from the event.
1824 | * @return {Object} Current instance of EventEmitter for chaining.
1825 | */
1826 | proto.removeListener = function removeListener(evt, listener) {
1827 | var listeners = this.getListenersAsObject(evt);
1828 | var index;
1829 | var key;
1830 |
1831 | for (key in listeners) {
1832 | if (listeners.hasOwnProperty(key)) {
1833 | index = indexOfListener(listeners[key], listener);
1834 |
1835 | if (index !== -1) {
1836 | listeners[key].splice(index, 1);
1837 | }
1838 | }
1839 | }
1840 |
1841 | return this;
1842 | };
1843 |
1844 | /**
1845 | * Alias of removeListener
1846 | */
1847 | proto.off = alias('removeListener');
1848 |
1849 | /**
1850 | * Adds listeners in bulk using the manipulateListeners method.
1851 | * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
1852 | * You can also pass it a regular expression to add the array of listeners to all events that match it.
1853 | * Yeah, this function does quite a bit. That's probably a bad thing.
1854 | *
1855 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
1856 | * @param {Function[]} [listeners] An optional array of listener functions to add.
1857 | * @return {Object} Current instance of EventEmitter for chaining.
1858 | */
1859 | proto.addListeners = function addListeners(evt, listeners) {
1860 | // Pass through to manipulateListeners
1861 | return this.manipulateListeners(false, evt, listeners);
1862 | };
1863 |
1864 | /**
1865 | * Removes listeners in bulk using the manipulateListeners method.
1866 | * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
1867 | * You can also pass it an event name and an array of listeners to be removed.
1868 | * You can also pass it a regular expression to remove the listeners from all events that match it.
1869 | *
1870 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
1871 | * @param {Function[]} [listeners] An optional array of listener functions to remove.
1872 | * @return {Object} Current instance of EventEmitter for chaining.
1873 | */
1874 | proto.removeListeners = function removeListeners(evt, listeners) {
1875 | // Pass through to manipulateListeners
1876 | return this.manipulateListeners(true, evt, listeners);
1877 | };
1878 |
1879 | /**
1880 | * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
1881 | * The first argument will determine if the listeners are removed (true) or added (false).
1882 | * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
1883 | * You can also pass it an event name and an array of listeners to be added/removed.
1884 | * You can also pass it a regular expression to manipulate the listeners of all events that match it.
1885 | *
1886 | * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
1887 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
1888 | * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
1889 | * @return {Object} Current instance of EventEmitter for chaining.
1890 | */
1891 | proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
1892 | var i;
1893 | var value;
1894 | var single = remove ? this.removeListener : this.addListener;
1895 | var multiple = remove ? this.removeListeners : this.addListeners;
1896 |
1897 | // If evt is an object then pass each of it's properties to this method
1898 | if (typeof evt === 'object' && !(evt instanceof RegExp)) {
1899 | for (i in evt) {
1900 | if (evt.hasOwnProperty(i) && (value = evt[i])) {
1901 | // Pass the single listener straight through to the singular method
1902 | if (typeof value === 'function') {
1903 | single.call(this, i, value);
1904 | }
1905 | else {
1906 | // Otherwise pass back to the multiple function
1907 | multiple.call(this, i, value);
1908 | }
1909 | }
1910 | }
1911 | }
1912 | else {
1913 | // So evt must be a string
1914 | // And listeners must be an array of listeners
1915 | // Loop over it and pass each one to the multiple method
1916 | i = listeners.length;
1917 | while (i--) {
1918 | single.call(this, evt, listeners[i]);
1919 | }
1920 | }
1921 |
1922 | return this;
1923 | };
1924 |
1925 | /**
1926 | * Removes all listeners from a specified event.
1927 | * If you do not specify an event then all listeners will be removed.
1928 | * That means every event will be emptied.
1929 | * You can also pass a regex to remove all events that match it.
1930 | *
1931 | * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1932 | * @return {Object} Current instance of EventEmitter for chaining.
1933 | */
1934 | proto.removeEvent = function removeEvent(evt) {
1935 | var type = typeof evt;
1936 | var events = this._getEvents();
1937 | var key;
1938 |
1939 | // Remove different things depending on the state of evt
1940 | if (type === 'string') {
1941 | // Remove all listeners for the specified event
1942 | delete events[evt];
1943 | }
1944 | else if (type === 'object') {
1945 | // Remove all events matching the regex.
1946 | for (key in events) {
1947 | if (events.hasOwnProperty(key) && evt.test(key)) {
1948 | delete events[key];
1949 | }
1950 | }
1951 | }
1952 | else {
1953 | // Remove all listeners in all events
1954 | delete this._events;
1955 | }
1956 |
1957 | return this;
1958 | };
1959 |
1960 | /**
1961 | * Alias of removeEvent.
1962 | *
1963 | * Added to mirror the node API.
1964 | */
1965 | proto.removeAllListeners = alias('removeEvent');
1966 |
1967 | /**
1968 | * Emits an event of your choice.
1969 | * When emitted, every listener attached to that event will be executed.
1970 | * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1971 | * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1972 | * So they will not arrive within the array on the other side, they will be separate.
1973 | * You can also pass a regular expression to emit to all events that match it.
1974 | *
1975 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1976 | * @param {Array} [args] Optional array of arguments to be passed to each listener.
1977 | * @return {Object} Current instance of EventEmitter for chaining.
1978 | */
1979 | proto.emitEvent = function emitEvent(evt, args) {
1980 | var listeners = this.getListenersAsObject(evt);
1981 | var listener;
1982 | var i;
1983 | var key;
1984 | var response;
1985 |
1986 | for (key in listeners) {
1987 | if (listeners.hasOwnProperty(key)) {
1988 | i = listeners[key].length;
1989 |
1990 | while (i--) {
1991 | // If the listener returns true then it shall be removed from the event
1992 | // The function is executed either with a basic call or an apply if there is an args array
1993 | listener = listeners[key][i];
1994 |
1995 | if (listener.once === true) {
1996 | this.removeListener(evt, listener.listener);
1997 | }
1998 |
1999 | response = listener.listener.apply(this, args || []);
2000 |
2001 | if (response === this._getOnceReturnValue()) {
2002 | this.removeListener(evt, listener.listener);
2003 | }
2004 | }
2005 | }
2006 | }
2007 |
2008 | return this;
2009 | };
2010 |
2011 | /**
2012 | * Alias of emitEvent
2013 | */
2014 | proto.trigger = alias('emitEvent');
2015 |
2016 | /**
2017 | * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
2018 | * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
2019 | *
2020 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
2021 | * @param {...*} Optional additional arguments to be passed to each listener.
2022 | * @return {Object} Current instance of EventEmitter for chaining.
2023 | */
2024 | proto.emit = function emit(evt) {
2025 | var args = Array.prototype.slice.call(arguments, 1);
2026 | return this.emitEvent(evt, args);
2027 | };
2028 |
2029 | /**
2030 | * Sets the current value to check against when executing listeners. If a
2031 | * listeners return value matches the one set here then it will be removed
2032 | * after execution. This value defaults to true.
2033 | *
2034 | * @param {*} value The new value to check for when executing listeners.
2035 | * @return {Object} Current instance of EventEmitter for chaining.
2036 | */
2037 | proto.setOnceReturnValue = function setOnceReturnValue(value) {
2038 | this._onceReturnValue = value;
2039 | return this;
2040 | };
2041 |
2042 | /**
2043 | * Fetches the current value to check against when executing listeners. If
2044 | * the listeners return value matches this one then it should be removed
2045 | * automatically. It will return true by default.
2046 | *
2047 | * @return {*|Boolean} The current value to check for or the default, true.
2048 | * @api private
2049 | */
2050 | proto._getOnceReturnValue = function _getOnceReturnValue() {
2051 | if (this.hasOwnProperty('_onceReturnValue')) {
2052 | return this._onceReturnValue;
2053 | }
2054 | else {
2055 | return true;
2056 | }
2057 | };
2058 |
2059 | /**
2060 | * Fetches the events object and creates one if required.
2061 | *
2062 | * @return {Object} The events storage object.
2063 | * @api private
2064 | */
2065 | proto._getEvents = function _getEvents() {
2066 | return this._events || (this._events = {});
2067 | };
2068 |
2069 | /**
2070 | * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
2071 | *
2072 | * @return {Function} Non conflicting EventEmitter class.
2073 | */
2074 | EventEmitter.noConflict = function noConflict() {
2075 | exports.EventEmitter = originalGlobalValue;
2076 | return EventEmitter;
2077 | };
2078 |
2079 | // Expose the class either via AMD, CommonJS or the global object
2080 | if (typeof define === 'function' && define.amd) {
2081 | define(function () {
2082 | return EventEmitter;
2083 | });
2084 | }
2085 | else if (typeof module === 'object' && module.exports){
2086 | module.exports = EventEmitter;
2087 | }
2088 | else {
2089 | this.EventEmitter = EventEmitter;
2090 | }
2091 | }.call(this));
2092 |
2093 | },{}],32:[function(_dereq_,module,exports){
2094 | module.exports = extend
2095 |
2096 | function extend() {
2097 | var target = {}
2098 |
2099 | for (var i = 0; i < arguments.length; i++) {
2100 | var source = arguments[i]
2101 |
2102 | for (var key in source) {
2103 | if (source.hasOwnProperty(key)) {
2104 | target[key] = source[key]
2105 | }
2106 | }
2107 | }
2108 |
2109 | return target
2110 | }
2111 |
2112 | },{}],33:[function(_dereq_,module,exports){
2113 | module.exports = extend
2114 |
2115 | function extend(target) {
2116 | for (var i = 1; i < arguments.length; i++) {
2117 | var source = arguments[i]
2118 |
2119 | for (var key in source) {
2120 | if (source.hasOwnProperty(key)) {
2121 | target[key] = source[key]
2122 | }
2123 | }
2124 | }
2125 |
2126 | return target
2127 | }
2128 |
2129 | },{}]},{},[10])
2130 | (10)
2131 | });
--------------------------------------------------------------------------------